tegrakernel/kernel/nvidia/drivers/firmware/tegra/mail.c

543 lines
11 KiB
C
Raw Permalink Normal View History

2022-02-16 09:13:02 -06:00
/*
* Copyright (c) 2013-2020, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/semaphore.h>
#include <linux/slab.h>
#include <soc/tegra/bpmp_abi.h>
#include <soc/tegra/fuse.h>
#include <soc/tegra/tegra_bpmp.h>
#include "bpmp.h"
#define CHANNEL_TIMEOUT (timeout_mul * USEC_PER_SEC)
#define THREAD_CH_TIMEOUT (timeout_mul * USEC_PER_SEC)
static unsigned int timeout_mul = 4;
struct channel_data channel_area[NR_MAX_CHANNELS];
static struct completion *completion;
static DEFINE_SPINLOCK(lock);
static DEFINE_RAW_SPINLOCK(ach_lock);
static const struct channel_cfg *channel_cfg;
static const struct mail_ops *mail_ops;
uint32_t tegra_bpmp_mail_readl(int ch, int offset)
{
u32 *data = (u32 *)(channel_area[ch].ib->data + offset);
return *data;
}
EXPORT_SYMBOL(tegra_bpmp_mail_readl);
int tegra_bpmp_read_data(unsigned int ch, void *data, size_t sz)
{
void __iomem *addr;
unsigned int m;
if (!data || sz > MSG_DATA_MIN_SZ)
return -EINVAL;
m = (1u << ch) & channel_cfg->channel_mask;
if (!m)
return -EINVAL;
addr = (void __iomem*)channel_area[ch].ib->data;
memcpy_fromio(data, addr, sz);
return 0;
}
EXPORT_SYMBOL(tegra_bpmp_read_data);
void tegra_bpmp_mail_return_data(int ch, int code, void *data, int sz)
{
if (mail_ops && mail_ops->return_data)
mail_ops->return_data(mail_ops, ch, code, data, sz);
}
EXPORT_SYMBOL(tegra_bpmp_mail_return_data);
void tegra_bpmp_mail_return(int ch, int code, int v)
{
tegra_bpmp_mail_return_data(ch, code, &v, sizeof(v));
}
EXPORT_SYMBOL(tegra_bpmp_mail_return);
static unsigned int to_reclaim;
static unsigned int to_complete;
static unsigned int tch_free;
static struct semaphore tch_sem;
static struct completion *bpmp_completion_obj(int ch)
{
unsigned int i;
i = ch - channel_cfg->thread_ch_0;
if (i >= channel_cfg->thread_ch_cnt)
return NULL;
return completion + i;
}
static void bpmp_signal_thread(int ch)
{
struct mb_data *p = channel_area[ch].ob;
struct completion *w;
unsigned int m = 1u << ch;
if (!(p->flags & RING_DOORBELL))
return;
if ((to_reclaim & m) != 0) {
mail_ops->free_master(mail_ops, ch);
to_reclaim &= ~m;
tch_free |= m;
WARN_ON(tch_sem.count >= channel_cfg->thread_ch_cnt);
up(&tch_sem);
return;
}
w = bpmp_completion_obj(ch);
if (!w) {
WARN_ON(1);
return;
}
complete(w);
}
void bpmp_handle_irq(unsigned int chidx)
{
unsigned int m;
int ch;
int i;
if (!mail_ops)
return;
if (WARN_ON_ONCE(chidx >= channel_cfg->ib_ch_cnt))
return;
ch = channel_cfg->ib_ch_0 + chidx;
if (mail_ops->slave_signalled(mail_ops, ch))
bpmp_handle_mail(channel_area[ch].ib->code, ch);
spin_lock(&lock);
for (i = 0; i < channel_cfg->thread_ch_cnt && to_complete; i++) {
ch = channel_cfg->thread_ch_0 + i;
m = 1u << ch;
if (mail_ops->master_acked(mail_ops, ch) && (to_complete & m)) {
to_complete &= ~m;
bpmp_signal_thread(ch);
}
}
spin_unlock(&lock);
}
static int bpmp_wait_master_free(int ch)
{
ktime_t start;
if (mail_ops->master_free(mail_ops, ch))
return 0;
start = ktime_get();
while (ktime_us_delta(ktime_get(), start) < CHANNEL_TIMEOUT) {
if (mail_ops->master_free(mail_ops, ch))
return 0;
}
return -ETIMEDOUT;
}
static void __bpmp_write_ch(int ch, int mrq, int flags, void *data, int sz)
{
struct mb_data *p = channel_area[ch].ob;
void __iomem *addr;
p->code = mrq;
p->flags = flags;
if (data) {
addr = (void __iomem *)p->data;
memcpy_toio(addr, data, sz);
}
mail_ops->signal_slave(mail_ops, ch);
}
static int bpmp_write_ch(int ch, int mrq, int flags, void *data, int sz)
{
int r;
r = bpmp_wait_master_free(ch);
if (r)
return r;
__bpmp_write_ch(ch, mrq, flags, data, sz);
return 0;
}
static int bpmp_write_threaded_ch(int *ch, int mrq, void *data, int sz)
{
unsigned long flags;
unsigned int m;
int ret;
spin_lock_irqsave(&lock, flags);
*ch = __ffs(tch_free);
ret = mail_ops->master_free(mail_ops, *ch) ? 0 : -EFAULT;
if (ret) {
pr_err("bpmp: channel %d is not free to write\n", *ch);
} else {
m = 1u << *ch;
tch_free &= ~m;
__bpmp_write_ch(*ch, mrq, DO_ACK | RING_DOORBELL, data, sz);
to_complete |= m;
}
spin_unlock_irqrestore(&lock, flags);
return ret;
}
static int __bpmp_read_ch(int ch, void *data, int sz)
{
struct mb_data *p = channel_area[ch].ib;
void __iomem *addr;
if (data) {
addr = (void __iomem*) p->data;
memcpy_fromio(data, addr, sz);
}
mail_ops->free_master(mail_ops, ch);
return p->code;
}
static int bpmp_read_ch(int ch, void *data, int sz)
{
unsigned long flags;
int r;
spin_lock_irqsave(&lock, flags);
r = __bpmp_read_ch(ch, data, sz);
tch_free |= (1u << ch);
spin_unlock_irqrestore(&lock, flags);
return r;
}
static int bpmp_wait_ack(int ch)
{
ktime_t start;
if (mail_ops->master_acked(mail_ops, ch))
return 0;
start = ktime_get();
while (ktime_us_delta(ktime_get(), start) < CHANNEL_TIMEOUT) {
if (mail_ops->master_acked(mail_ops, ch))
return 0;
}
return -ETIMEDOUT;
}
static int bpmp_valid_txfer(void *ob_data, int ob_sz, void *ib_data, int ib_sz)
{
return ob_sz >= 0 &&
ob_sz <= MSG_DATA_MIN_SZ &&
ib_sz >= 0 &&
ib_sz <= MSG_DATA_MIN_SZ &&
(!ob_sz || ob_data) &&
(!ib_sz || ib_data);
}
static void bpmp_format_req(char *fmt, size_t len, uint8_t *data, size_t sz)
{
size_t ne;
size_t i;
int n;
for (i = 0; i < sz; i++) {
ne = sz - i - 1;
n = snprintf(fmt, len, ne ? "0x%02x " : "0x%02x", data[i]);
if (n >= len)
break;
fmt += n;
len -= n;
}
}
static void bpmp_dump_req(int ch)
{
char fmt[MSG_DATA_MIN_SZ * 5] = "";
struct mb_data d;
struct mb_data *p = channel_area[ch].ob;
memcpy_fromio(&d, p, sizeof(d));
bpmp_format_req(fmt, sizeof(fmt), d.data, sizeof(d.data));
pr_err("ch %d mrq %d data <%s>\n", ch, d.code, fmt);
}
static int bpmp_send_receive_atomic(int ch, int mrq, void *ob_data, int ob_sz,
void *ib_data, int ib_sz)
{
char fmt[MSG_DATA_MIN_SZ * 5] = "";
int r;
r = bpmp_write_ch(ch, mrq, DO_ACK, ob_data, ob_sz);
if (r)
return r;
if (mail_ops->ring_doorbell)
mail_ops->ring_doorbell(ch);
r = bpmp_wait_ack(ch);
if (r) {
bpmp_format_req(fmt, sizeof(fmt), ob_data, ob_sz);
pr_err("bpmp_wait_ack() returned %d (ch %d mrq %d data <%s>)\n",
r, mrq, ch, fmt);
WARN_ON(1);
return r;
}
return __bpmp_read_ch(ch, ib_data, ib_sz);
}
/* should be called with local irqs disabled */
int tegra_bpmp_send_receive_atomic(int mrq, void *ob_data, int ob_sz,
void *ib_data, int ib_sz)
{
unsigned int cpu;
int ch;
int r;
if (WARN_ON(!irqs_disabled()))
return -EPERM;
if (!bpmp_valid_txfer(ob_data, ob_sz, ib_data, ib_sz))
return -EINVAL;
if (!mail_ops)
return -ENODEV;
if (channel_cfg->per_cpu_ch_cnt == 1) {
raw_spin_lock(&ach_lock);
ch = channel_cfg->per_cpu_ch_0;
} else {
cpu = smp_processor_id();
if (cpu >= channel_cfg->per_cpu_ch_cnt)
return -ENODEV;
ch = channel_cfg->per_cpu_ch_0 + cpu;
}
r = bpmp_send_receive_atomic(ch, mrq, ob_data, ob_sz, ib_data, ib_sz);
if (channel_cfg->per_cpu_ch_cnt == 1)
raw_spin_unlock(&ach_lock);
return r;
}
EXPORT_SYMBOL(tegra_bpmp_send_receive_atomic);
static int bpmp_trywait(int ch, int mrq, void *ob_data, int ob_sz)
{
char fmt[MSG_DATA_MIN_SZ * 5] = "";
struct completion *w;
unsigned long timeout;
unsigned long rt;
unsigned long et;
unsigned long flags;
w = bpmp_completion_obj(ch);
timeout = usecs_to_jiffies(THREAD_CH_TIMEOUT);
rt = wait_for_completion_timeout(w, timeout);
if (rt) {
et = timeout - rt;
if (et > timeout / 4)
pr_warn("bpmp: mrq %d took %u us\n",
mrq, jiffies_to_usecs(et));
return 0;
}
spin_lock_irqsave(&lock, flags);
if (mail_ops->master_acked(mail_ops, ch)) {
spin_unlock_irqrestore(&lock, flags);
return 0;
}
to_reclaim |= 1u << ch;
spin_unlock_irqrestore(&lock, flags);
bpmp_format_req(fmt, sizeof(fmt), ob_data, ob_sz);
pr_err("%s() timed out (ch %d mrq %d data <%s>)\n",
__func__, ch, mrq, fmt);
WARN_ON(1);
return -ETIMEDOUT;
}
int tegra_bpmp_send_receive(int mrq, void *ob_data, int ob_sz,
void *ib_data, int ib_sz)
{
int ch;
int r;
if (WARN_ON(irqs_disabled()))
return -EPERM;
if (!bpmp_valid_txfer(ob_data, ob_sz, ib_data, ib_sz))
return -EINVAL;
if (!mail_ops)
return -ENODEV;
r = down_timeout(&tch_sem, usecs_to_jiffies(THREAD_CH_TIMEOUT));
if (r) {
pr_err("%s() down_timeout return %d\n", __func__, r);
pr_err("tch_free 0x%x to_complete 0x%x\n",
tch_free, to_complete);
return r;
}
r = bpmp_write_threaded_ch(&ch, mrq, ob_data, ob_sz);
if (r)
goto out;
if (mail_ops->ring_doorbell)
mail_ops->ring_doorbell(ch);
r = bpmp_trywait(ch, mrq, ob_data, ob_sz);
if (r)
return r;
r = bpmp_read_ch(ch, ib_data, ib_sz);
out:
up(&tch_sem);
return r;
}
EXPORT_SYMBOL(tegra_bpmp_send_receive);
int tegra_bpmp_running(void)
{
return mail_ops ? 1 : 0;
}
EXPORT_SYMBOL(tegra_bpmp_running);
void tegra_bpmp_resume(void)
{
if (mail_ops->resume)
mail_ops->resume();
}
int tegra_bpmp_suspend(void)
{
if (to_complete) {
unsigned int i;
pr_err("%s() channels waiting (to_complete 0x%x)\n",
__func__, to_complete);
for (i = 0; i < NR_MAX_CHANNELS; ++i) {
if (to_complete & (1 << i)) {
bpmp_dump_req(i);
}
}
return -EBUSY;
}
if (mail_ops->suspend)
mail_ops->suspend();
return 0;
}
static int bpmp_init_completion(int cnt)
{
int i;
completion = kcalloc(cnt, sizeof(*completion), GFP_KERNEL);
if (!completion)
return -ENOMEM;
for (i = 0; i < cnt; i++)
init_completion(completion + i);
return 0;
}
int bpmp_mail_init(const struct channel_cfg *cfg, const struct mail_ops *ops,
struct device_node *of_node)
{
int r;
r = ops->init_prepare ? ops->init_prepare() : 0;
if (r) {
pr_err("bpmp: mail init prepare failed (%d)\n", r);
return r;
}
tch_free = (1 << cfg->thread_ch_cnt) - 1;
tch_free <<= cfg->thread_ch_0;
sema_init(&tch_sem, cfg->thread_ch_cnt);
r = bpmp_init_completion(cfg->thread_ch_cnt);
if (r)
return r;
r = ops->init_irq ? ops->init_irq(cfg->ib_ch_cnt) : 0;
if (r) {
pr_err("bpmp: irq init failed (%d)\n", r);
return r;
}
r = bpmp_mailman_init();
if (r) {
pr_err("bpmp: mailman init failed (%d)\n", r);
return r;
}
r = ops->connect(cfg, ops, of_node);
if (r) {
pr_err("bpmp: connect failed (%d)\n", r);
return r;
}
if (!tegra_platform_is_silicon())
timeout_mul = 600;
channel_cfg = cfg;
mail_ops = ops;
pr_info("bpmp: mail init ok\n");
return 0;
}