Files

930 lines
27 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 MediaTek Inc.
*/
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/mailbox_client.h>
#include <linux/mailbox_controller.h>
#include <linux/of_platform.h>
#include <linux/sched/clock.h>
#include <linux/timer.h>
#include <linux/mailbox/mtk-cmdq-mailbox.h>
#include <linux/mailbox/mtk-cmdq-sec-mailbox.h>
#define CMDQ_THR_EXEC_CNT_PA (0x28)
#define CMDQ_TIMEOUT_DEFAULT (1000)
#define CMDQ_WFE_CMD(event) (0x2000000080008001ULL | ((u64)(event) << 32))
#define CMDQ_EOC_CMD (0x4000000000000001ULL)
#define CMDQ_JUMP_CMD(addr, shift) (0x1000000100000000ULL | ((addr) >> (shift)))
struct cmdq_sec_task {
struct cmdq_task task;
/* secure CMDQ */
bool reset_exec;
u32 wait_cookie;
u64 trigger;
u64 exec_time;
struct work_struct exec_work;
};
struct cmdq_sec_thread {
struct cmdq_thread thread;
/* secure CMDQ */
struct device *dev;
u32 idx;
struct timer_list timeout;
u32 timeout_ms;
struct work_struct timeout_work;
u32 wait_cookie;
u32 next_cookie;
u32 task_cnt;
struct workqueue_struct *task_exec_wq;
};
/**
* struct cmdq_sec_context - CMDQ secure context structure.
* @tgid: tgid of process context.
* @state: state of inter-world communicatiom.
* @iwc_msg: buffer for inter-world communicatiom message.
* @tee_ctx: context structure for tee vendor.
*
* Note it is not global data, each process has its own cmdq_sec_context.
*/
struct cmdq_sec_context {
u32 tgid;
enum cmdq_iwc_state_enum state;
void *iwc_msg;
struct cmdq_sec_tee_context tee_ctx;
};
/**
* struct cmdq_sec_shared_mem - shared memory between normal and secure world
* @va: virtual address of share memory.
* @pa: physical address of share memory.
* @size: size of share memory.
*
*/
struct cmdq_sec_shared_mem {
void *va;
dma_addr_t pa;
u32 size;
};
struct cmdq_sec {
struct device dev;
const struct gce_sec_plat *pdata;
void __iomem *base;
phys_addr_t base_pa;
struct cmdq_sec_thread *sec_thread;
struct cmdq_pkt clt_pkt;
atomic_t path_res;
struct cmdq_sec_shared_mem *shared_mem;
struct cmdq_sec_context *context;
struct workqueue_struct *timeout_wq;
u64 sec_invoke;
u64 sec_done;
struct mbox_client notify_clt;
struct mbox_chan *notify_chan;
bool notify_run;
struct work_struct irq_notify_work;
struct workqueue_struct *notify_wq;
/* mutex for cmdq_sec_thread excuting cmdq_sec_task */
struct mutex exec_lock;
};
static atomic_t cmdq_path_res = ATOMIC_INIT(0);
static int cmdq_sec_task_submit(struct cmdq_sec *cmdq, struct cmdq_sec_task *sec_task,
const u32 iwc_cmd, const u32 thrd_idx);
u16 cmdq_sec_get_eof_event_id(struct mbox_chan *chan)
{
struct cmdq_thread *thread = chan->con_priv;
struct cmdq_sec_thread *sec_thread = container_of(thread, struct cmdq_sec_thread, thread);
struct cmdq_sec *cmdq = container_of(sec_thread->dev, struct cmdq_sec, dev);
return (u16)cmdq->pdata->cmdq_event;
}
EXPORT_SYMBOL_GPL(cmdq_sec_get_eof_event_id);
dma_addr_t cmdq_sec_get_exec_cnt_addr(struct mbox_chan *chan)
{
struct cmdq_thread *thread = chan->con_priv;
struct cmdq_sec_thread *sec_thread = container_of(thread, struct cmdq_sec_thread, thread);
struct cmdq_sec *cmdq = container_of(sec_thread->dev, struct cmdq_sec, dev);
if (!cmdq->shared_mem) {
dev_err(&cmdq->dev, "%s share memory not ready!", __func__);
return 0;
}
dev_dbg(&cmdq->dev, "%s %d: thread:%u gce:%#lx",
__func__, __LINE__, sec_thread->idx,
(unsigned long)cmdq->base_pa);
return cmdq->base_pa + CMDQ_THR_BASE +
CMDQ_THR_SIZE * sec_thread->idx + CMDQ_THR_EXEC_CNT_PA;
}
EXPORT_SYMBOL_GPL(cmdq_sec_get_exec_cnt_addr);
dma_addr_t cmdq_sec_get_cookie_addr(struct mbox_chan *chan)
{
struct cmdq_thread *thread = chan->con_priv;
struct cmdq_sec_thread *sec_thread = container_of(thread, struct cmdq_sec_thread, thread);
struct cmdq_sec *cmdq = container_of(sec_thread->dev, struct cmdq_sec, dev);
if (!cmdq->shared_mem) {
dev_err(&cmdq->dev, "%s share memory not ready!", __func__);
return 0;
}
dev_dbg(&cmdq->dev, "%s %d: thread:%u gce:%#lx",
__func__, __LINE__, sec_thread->idx,
(unsigned long)cmdq->base_pa);
return cmdq->shared_mem->pa +
CMDQ_SEC_SHARED_THR_CNT_OFFSET + sec_thread->idx * sizeof(u32);
}
EXPORT_SYMBOL_GPL(cmdq_sec_get_cookie_addr);
static u32 cmdq_sec_get_cookie(struct cmdq_sec *cmdq, u32 idx)
{
return *(u32 *)(cmdq->shared_mem->va +
CMDQ_SEC_SHARED_THR_CNT_OFFSET + idx * sizeof(u32));
}
static void cmdq_sec_task_done(struct cmdq_sec_task *sec_task, int sta)
{
struct cmdq_cb_data data;
data.sta = sta;
data.pkt = sec_task->task.pkt;
pr_debug("%s sec_task:%p pkt:%p err:%d",
__func__, sec_task, sec_task->task.pkt, sta);
mbox_chan_received_data(sec_task->task.thread->chan, &data);
list_del_init(&sec_task->task.list_entry);
kfree(sec_task);
}
static bool cmdq_sec_irq_handler(struct cmdq_sec_thread *sec_thread,
const u32 cookie, const int err)
{
struct cmdq_sec_task *sec_task;
struct cmdq_task *task, *temp, *cur_task = NULL;
struct cmdq_sec *cmdq = container_of(sec_thread->dev, struct cmdq_sec, dev);
unsigned long flags;
int done;
spin_lock_irqsave(&sec_thread->thread.chan->lock, flags);
if (sec_thread->wait_cookie <= cookie)
done = cookie - sec_thread->wait_cookie + 1;
else if (sec_thread->wait_cookie == (cookie + 1) % CMDQ_MAX_COOKIE_VALUE)
done = 0;
else
done = CMDQ_MAX_COOKIE_VALUE - sec_thread->wait_cookie + 1 + cookie + 1;
list_for_each_entry_safe(task, temp, &sec_thread->thread.task_busy_list, list_entry) {
if (!done)
break;
sec_task = container_of(task, struct cmdq_sec_task, task);
cmdq_sec_task_done(sec_task, err);
if (sec_thread->task_cnt)
sec_thread->task_cnt -= 1;
done--;
}
cur_task = list_first_entry_or_null(&sec_thread->thread.task_busy_list,
struct cmdq_task, list_entry);
if (err && cur_task) {
spin_unlock_irqrestore(&sec_thread->thread.chan->lock, flags);
sec_task = container_of(cur_task, struct cmdq_sec_task, task);
/* for error task, cancel, callback and done */
cmdq_sec_task_submit(cmdq, sec_task, CMD_CMDQ_IWC_CANCEL_TASK,
sec_thread->idx);
cmdq_sec_task_done(sec_task, err);
spin_lock_irqsave(&sec_thread->thread.chan->lock, flags);
task = list_first_entry_or_null(&sec_thread->thread.task_busy_list,
struct cmdq_task, list_entry);
if (cur_task == task)
cmdq_sec_task_done(sec_task, err);
else
dev_err(&cmdq->dev, "task list changed");
/*
* error case stop all task for secure,
* since secure tdrv always remove all when cancel
*/
while (!list_empty(&sec_thread->thread.task_busy_list)) {
cur_task = list_first_entry(&sec_thread->thread.task_busy_list,
struct cmdq_task, list_entry);
sec_task = container_of(cur_task, struct cmdq_sec_task, task);
cmdq_sec_task_done(sec_task, -ECONNABORTED);
}
} else if (err) {
dev_dbg(&cmdq->dev, "error but all task done, check notify callback");
}
if (list_empty(&sec_thread->thread.task_busy_list)) {
sec_thread->wait_cookie = 0;
sec_thread->next_cookie = 0;
sec_thread->task_cnt = 0;
__raw_writel(0, (void __iomem *)cmdq->shared_mem->va +
CMDQ_SEC_SHARED_THR_CNT_OFFSET +
sec_thread->idx * sizeof(u32));
spin_unlock_irqrestore(&sec_thread->thread.chan->lock, flags);
del_timer(&sec_thread->timeout);
return true;
}
sec_thread->wait_cookie = cookie % CMDQ_MAX_COOKIE_VALUE + 1;
mod_timer(&sec_thread->timeout, jiffies + msecs_to_jiffies(sec_thread->timeout_ms));
spin_unlock_irqrestore(&sec_thread->thread.chan->lock, flags);
return false;
}
static void cmdq_sec_irq_notify_work(struct work_struct *work_item)
{
struct cmdq_sec *cmdq = container_of(work_item, struct cmdq_sec, irq_notify_work);
int i;
mutex_lock(&cmdq->exec_lock);
for (i = 0; i <= cmdq->pdata->secure_thread_nr; i++) {
struct cmdq_sec_thread *sec_thread = &cmdq->sec_thread[i];
u32 cookie = cmdq_sec_get_cookie(cmdq, sec_thread->idx);
if (cookie < sec_thread->wait_cookie || !sec_thread->task_cnt)
continue;
cmdq_sec_irq_handler(sec_thread, cookie, 0);
}
mutex_unlock(&cmdq->exec_lock);
}
static void cmdq_sec_irq_notify_callback(struct mbox_client *cl, void *mssg)
{
struct cmdq_cb_data *data = (struct cmdq_cb_data *)mssg;
struct cmdq_sec *cmdq = container_of(data->pkt, struct cmdq_sec, clt_pkt);
if (work_pending(&cmdq->irq_notify_work)) {
dev_dbg(&cmdq->dev, "%s last notify callback working", __func__);
return;
}
queue_work(cmdq->notify_wq, &cmdq->irq_notify_work);
}
static int cmdq_sec_irq_notify_start(struct cmdq_sec *cmdq)
{
int err;
dma_addr_t dma_addr;
u64 *inst = NULL;
if (cmdq->notify_run)
return 0;
cmdq->notify_clt.dev = cmdq->pdata->mbox->dev;
cmdq->notify_clt.rx_callback = cmdq_sec_irq_notify_callback;
cmdq->notify_clt.tx_block = false;
cmdq->notify_clt.knows_txdone = true;
cmdq->notify_chan = mbox_request_channel(&cmdq->notify_clt, 0);
if (IS_ERR(cmdq->notify_chan)) {
dev_err(&cmdq->dev, "failed to request channel\n");
return -ENODEV;
}
cmdq->clt_pkt.va_base = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!cmdq->clt_pkt.va_base)
return -ENOMEM;
cmdq->clt_pkt.buf_size = PAGE_SIZE;
dma_addr = dma_map_single(cmdq->pdata->mbox->dev, cmdq->clt_pkt.va_base,
cmdq->clt_pkt.buf_size, DMA_TO_DEVICE);
if (dma_mapping_error(cmdq->pdata->mbox->dev, dma_addr)) {
dev_err(cmdq->pdata->mbox->dev, "dma map failed, size=%lu\n", PAGE_SIZE);
kfree(cmdq->clt_pkt.va_base);
return -ENOMEM;
}
cmdq->clt_pkt.pa_base = dma_addr;
INIT_WORK(&cmdq->irq_notify_work, cmdq_sec_irq_notify_work);
/* generate irq notify loop command */
inst = (u64 *)cmdq->clt_pkt.va_base;
*inst = CMDQ_WFE_CMD(cmdq->pdata->cmdq_event);
inst++;
*inst = CMDQ_EOC_CMD;
inst++;
*inst = CMDQ_JUMP_CMD(cmdq->clt_pkt.pa_base, cmdq->pdata->shift);
inst++;
cmdq->clt_pkt.cmd_buf_size += CMDQ_INST_SIZE * 3;
cmdq->clt_pkt.loop = true;
dma_sync_single_for_device(cmdq->pdata->mbox->dev,
cmdq->clt_pkt.pa_base,
cmdq->clt_pkt.cmd_buf_size,
DMA_TO_DEVICE);
err = mbox_send_message(cmdq->notify_chan, &cmdq->clt_pkt);
mbox_client_txdone(cmdq->notify_chan, 0);
if (err < 0) {
dev_err(&cmdq->dev, "%s failed:%d", __func__, err);
dma_unmap_single(cmdq->pdata->mbox->dev, cmdq->clt_pkt.pa_base,
cmdq->clt_pkt.buf_size, DMA_TO_DEVICE);
kfree(cmdq->clt_pkt.va_base);
mbox_free_channel(cmdq->notify_chan);
return err;
}
cmdq->notify_run = true;
dev_dbg(&cmdq->dev, "%s success!", __func__);
return 0;
}
static int cmdq_sec_session_init(struct cmdq_sec_context *context)
{
int err = 0;
if (context->state >= IWC_SES_OPENED) {
pr_debug("session opened:%u", context->state);
return 0;
}
if (context->state == IWC_INIT) {
err = cmdq_sec_init_context(&context->tee_ctx);
if (err)
return err;
context->state = IWC_CONTEXT_INITED;
}
if (context->state == IWC_CONTEXT_INITED) {
if (context->iwc_msg) {
pr_err("iwcMessage not NULL:%p", context->iwc_msg);
return -EINVAL;
}
err = cmdq_sec_allocate_wsm(&context->tee_ctx, &context->iwc_msg,
sizeof(struct iwc_cmdq_message_t));
if (err)
return err;
context->state = IWC_WSM_ALLOCATED;
}
if (context->state == IWC_WSM_ALLOCATED) {
err = cmdq_sec_open_session(&context->tee_ctx, context->iwc_msg);
if (err)
return err;
context->state = IWC_SES_OPENED;
}
return 0;
}
static int cmdq_sec_fill_iwc_msg(struct cmdq_sec_context *context,
struct cmdq_sec_task *sec_task, u32 thrd_idx)
{
struct iwc_cmdq_message_t *iwc_msg = NULL;
struct cmdq_sec_data *data = (struct cmdq_sec_data *)sec_task->task.pkt->sec_data;
u32 *instr;
iwc_msg = (struct iwc_cmdq_message_t *)context->iwc_msg;
if (sec_task->task.pkt->cmd_buf_size + 4 * CMDQ_INST_SIZE > CMDQ_TZ_CMD_BLOCK_SIZE) {
pr_err("sec_task:%p size:%zu > %u",
sec_task, sec_task->task.pkt->cmd_buf_size, CMDQ_TZ_CMD_BLOCK_SIZE);
return -EFAULT;
}
if (thrd_idx == CMDQ_INVALID_THREAD) {
iwc_msg->command.cmd_size = 0;
iwc_msg->command.metadata.addr_list_length = 0;
return -EINVAL;
}
iwc_msg->command.thread = thrd_idx;
iwc_msg->command.cmd_size = sec_task->task.pkt->cmd_buf_size;
memcpy(iwc_msg->command.va_base, sec_task->task.pkt->va_base, iwc_msg->command.cmd_size);
instr = &iwc_msg->command.va_base[iwc_msg->command.cmd_size / 4 - 4];
/* Remove IRQ_EN in EOC */
if (*(u64 *)instr == CMDQ_EOC_CMD)
instr[0] = 0;
else
pr_err("%s %d: find EOC failed: %#x %#x",
__func__, __LINE__, instr[1], instr[0]);
iwc_msg->command.wait_cookie = sec_task->wait_cookie;
iwc_msg->command.reset_exec = sec_task->reset_exec;
if (data->meta_cnt > 0) {
iwc_msg->command.metadata.addr_list_length = data->meta_cnt;
memcpy(iwc_msg->command.metadata.addr_list, data->meta_list,
data->meta_cnt * sizeof(struct iwc_cmdq_addr_metadata_t));
}
iwc_msg->command.normal_task_handle = (unsigned long)sec_task->task.pkt;
return 0;
}
static int cmdq_sec_session_send(struct cmdq_sec_context *context,
struct cmdq_sec_task *sec_task, const u32 iwc_cmd,
const u32 thrd_idx, struct cmdq_sec *cmdq)
{
int err = 0;
u64 cost;
struct iwc_cmdq_message_t *iwc_msg = NULL;
iwc_msg = (struct iwc_cmdq_message_t *)context->iwc_msg;
memset(iwc_msg, 0, sizeof(*iwc_msg));
iwc_msg->cmd = iwc_cmd;
iwc_msg->cmdq_id = cmdq->pdata->hwid;
iwc_msg->command.thread = thrd_idx;
switch (iwc_cmd) {
case CMD_CMDQ_IWC_SUBMIT_TASK:
err = cmdq_sec_fill_iwc_msg(context, sec_task, thrd_idx);
if (err)
return err;
break;
case CMD_CMDQ_IWC_CANCEL_TASK:
iwc_msg->cancel_task.wait_cookie = sec_task->wait_cookie;
iwc_msg->cancel_task.thread = thrd_idx;
break;
case CMD_CMDQ_IWC_PATH_RES_ALLOCATE:
if (!cmdq->shared_mem || !cmdq->shared_mem->va) {
dev_err(&cmdq->dev, "%s %d: shared_mem is NULL", __func__, __LINE__);
return -EFAULT;
}
iwc_msg->path_resource.size = cmdq->shared_mem->size;
iwc_msg->path_resource.share_memoy_pa = cmdq->shared_mem->pa;
iwc_msg->path_resource.use_normal_irq = 1;
break;
default:
break;
}
cmdq->sec_invoke = sched_clock();
dev_dbg(&cmdq->dev, "%s execute cmdq:%p sec_task:%p command:%u thread:%u cookie:%d",
__func__, cmdq, sec_task, iwc_cmd, thrd_idx,
sec_task ? sec_task->wait_cookie : -1);
/* send message */
err = cmdq_sec_execute_session(&context->tee_ctx, iwc_cmd, CMDQ_TIMEOUT_DEFAULT);
cmdq->sec_done = sched_clock();
cost = div_u64(cmdq->sec_done - cmdq->sec_invoke, 1000000);
if (cost >= CMDQ_TIMEOUT_DEFAULT)
dev_err(&cmdq->dev, "%s execute timeout cmdq:%p sec_task:%p cost:%lluus",
__func__, cmdq, sec_task, cost);
else
dev_dbg(&cmdq->dev, "%s execute done cmdq:%p sec_task:%p cost:%lluus",
__func__, cmdq, sec_task, cost);
if (err)
return err;
context->state = IWC_SES_ON_TRANSACTED;
return 0;
}
static int cmdq_sec_session_reply(const u32 iwc_cmd, struct iwc_cmdq_message_t *iwc_msg,
struct cmdq_sec_task *sec_task)
{
if (iwc_msg->rsp >= 0)
return iwc_msg->rsp;
if (iwc_cmd == CMD_CMDQ_IWC_SUBMIT_TASK) {
struct iwc_cmdq_sec_status_t *sec_status = &iwc_msg->sec_status;
int i;
/* print submit fail case status */
pr_err("last sec status: step:%u status:%d args:%#x %#x %#x %#x dispatch:%s\n",
sec_status->step, sec_status->status, sec_status->args[0],
sec_status->args[1], sec_status->args[2], sec_status->args[3],
sec_status->dispatch);
for (i = 0; i < sec_status->inst_index; i += 2)
pr_err("instr %d: %08x %08x\n", i / 2,
sec_status->sec_inst[i], sec_status->sec_inst[i + 1]);
} else if (iwc_cmd == CMD_CMDQ_IWC_CANCEL_TASK) {
struct iwc_cmdq_cancel_task_t *cancel = &iwc_msg->cancel_task;
/* print cancel task fail case status */
if ((cancel->err_instr[1] >> 24) == CMDQ_CODE_WFE)
pr_err("secure error inst event:%u value:%d\n",
cancel->err_instr[1], cancel->reg_value);
pr_err("cancel_task inst:%08x %08x aee:%d reset:%d pc:0x%08x\n",
cancel->err_instr[1], cancel->err_instr[0],
cancel->throw_aee, cancel->has_reset, cancel->pc);
}
return iwc_msg->rsp;
}
static int cmdq_sec_task_submit(struct cmdq_sec *cmdq, struct cmdq_sec_task *sec_task,
const u32 iwc_cmd, const u32 thrd_idx)
{
struct cmdq_sec_context *context;
int err = 0;
if (!cmdq->context) {
context = kzalloc(sizeof(*cmdq->context), GFP_ATOMIC);
if (!context)
return -ENOMEM;
cmdq->context = context;
cmdq->context->state = IWC_INIT;
cmdq->context->tgid = current->tgid;
}
if (cmdq->context->state == IWC_INIT)
cmdq_sec_setup_tee_context(&cmdq->context->tee_ctx);
err = cmdq_sec_session_init(cmdq->context);
if (err) {
dev_err(&cmdq->dev, "%s %d: cmdq_sec_session_init fail: %d",
__func__, __LINE__, err);
return err;
}
err = cmdq_sec_irq_notify_start(cmdq);
if (err) {
dev_err(&cmdq->dev, "%s %d: cmdq_sec_irq_notify_start fail: %d",
__func__, __LINE__, err);
return err;
}
err = cmdq_sec_session_send(cmdq->context, sec_task, iwc_cmd, thrd_idx, cmdq);
if (err) {
dev_err(&cmdq->dev, "%s %d: iwc_cmd:%d err:%d sec_task:%p thread:%u gce:%#lx",
__func__, __LINE__, iwc_cmd, err, sec_task, thrd_idx,
(unsigned long)cmdq->base_pa);
return err;
}
err = cmdq_sec_session_reply(iwc_cmd, cmdq->context->iwc_msg, sec_task);
if (err) {
dev_err(&cmdq->dev, "%s %d: cmdq_sec_session_reply fail: %d",
__func__, __LINE__, err);
return err;
}
return 0;
}
static void cmdq_sec_task_exec_work(struct work_struct *work_item)
{
struct cmdq_sec_task *sec_task = container_of(work_item,
struct cmdq_sec_task, exec_work);
struct cmdq_sec_thread *sec_thread = container_of(sec_task->task.thread,
struct cmdq_sec_thread, thread);
struct cmdq_sec *cmdq = container_of(sec_thread->dev, struct cmdq_sec, dev);
unsigned long flags;
int err;
dev_dbg(&cmdq->dev, "%s gce:%#lx sec_task:%p pkt:%p thread:%u",
__func__, (unsigned long)cmdq->base_pa,
sec_task, sec_task->task.pkt, sec_thread->idx);
if (!sec_task->task.pkt->sec_data) {
dev_err(&cmdq->dev, "pkt:%p without sec_data", sec_task->task.pkt);
return;
}
if (sec_thread->task_cnt > CMDQ_MAX_TASK_IN_SECURE_THREAD) {
struct cmdq_cb_data cb_data;
dev_dbg(&cmdq->dev, "task_cnt:%u cannot more than %u sec_task:%p thread:%u",
sec_thread->task_cnt, CMDQ_MAX_TASK_IN_SECURE_THREAD,
sec_task, sec_thread->idx);
cb_data.sta = -EMSGSIZE;
cb_data.pkt = sec_task->task.pkt;
mbox_chan_received_data(sec_thread->thread.chan, &cb_data);
kfree(sec_task);
return;
}
mutex_lock(&cmdq->exec_lock);
spin_lock_irqsave(&sec_thread->thread.chan->lock, flags);
if (!sec_thread->task_cnt) {
mod_timer(&sec_thread->timeout, jiffies +
msecs_to_jiffies(sec_thread->timeout_ms));
sec_thread->wait_cookie = 1;
sec_thread->next_cookie = 1;
sec_thread->task_cnt = 0;
__raw_writel(0, (void __iomem *)cmdq->shared_mem->va +
CMDQ_SEC_SHARED_THR_CNT_OFFSET + sec_thread->idx * sizeof(u32));
}
sec_task->reset_exec = sec_thread->task_cnt ? false : true;
sec_task->wait_cookie = sec_thread->next_cookie;
sec_thread->next_cookie = (sec_thread->next_cookie + 1) % CMDQ_MAX_COOKIE_VALUE;
list_add_tail(&sec_task->task.list_entry, &sec_thread->thread.task_busy_list);
sec_thread->task_cnt += 1;
spin_unlock_irqrestore(&sec_thread->thread.chan->lock, flags);
sec_task->trigger = sched_clock();
if (!atomic_cmpxchg(&cmdq_path_res, 0, 1)) {
err = cmdq_sec_task_submit(cmdq, NULL, CMD_CMDQ_IWC_PATH_RES_ALLOCATE,
CMDQ_INVALID_THREAD);
if (err) {
atomic_set(&cmdq_path_res, 0);
goto task_end;
}
}
err = cmdq_sec_task_submit(cmdq, sec_task, CMD_CMDQ_IWC_SUBMIT_TASK,
sec_thread->idx);
if (err)
dev_err(&cmdq->dev, "cmdq_sec_task_submit err:%d sec_task:%p thread:%u",
err, sec_task, sec_thread->idx);
task_end:
if (err) {
struct cmdq_cb_data cb_data;
cb_data.sta = err;
cb_data.pkt = sec_task->task.pkt;
mbox_chan_received_data(sec_thread->thread.chan, &cb_data);
spin_lock_irqsave(&sec_thread->thread.chan->lock, flags);
if (!sec_thread->task_cnt)
dev_err(&cmdq->dev, "thread:%u task_cnt:%u cannot below zero",
sec_thread->idx, sec_thread->task_cnt);
else
sec_thread->task_cnt -= 1;
sec_thread->next_cookie = (sec_thread->next_cookie - 1 +
CMDQ_MAX_COOKIE_VALUE) % CMDQ_MAX_COOKIE_VALUE;
list_del(&sec_task->task.list_entry);
dev_dbg(&cmdq->dev, "gce:%#lx err:%d sec_task:%p pkt:%p",
(unsigned long)cmdq->base_pa, err, sec_task, sec_task->task.pkt);
dev_dbg(&cmdq->dev, "thread:%u task_cnt:%u wait_cookie:%u next_cookie:%u",
sec_thread->idx, sec_thread->task_cnt,
sec_thread->wait_cookie, sec_thread->next_cookie);
spin_unlock_irqrestore(&sec_thread->thread.chan->lock, flags);
kfree(sec_task);
}
mutex_unlock(&cmdq->exec_lock);
}
static int cmdq_sec_mbox_send_data(struct mbox_chan *chan, void *data)
{
struct cmdq_pkt *pkt = (struct cmdq_pkt *)data;
struct cmdq_sec_data *sec_data = (struct cmdq_sec_data *)pkt->sec_data;
struct cmdq_thread *thread = (struct cmdq_thread *)chan->con_priv;
struct cmdq_sec_thread *sec_thread = container_of(thread, struct cmdq_sec_thread, thread);
struct cmdq_sec_task *sec_task;
if (!sec_data)
return -EINVAL;
sec_task = kzalloc(sizeof(*sec_task), GFP_ATOMIC);
if (!sec_task)
return -ENOMEM;
sec_task->task.pkt = pkt;
sec_task->task.thread = thread;
INIT_WORK(&sec_task->exec_work, cmdq_sec_task_exec_work);
queue_work(sec_thread->task_exec_wq, &sec_task->exec_work);
return 0;
}
static void cmdq_sec_thread_timeout(struct timer_list *t)
{
struct cmdq_sec_thread *sec_thread = from_timer(sec_thread, t, timeout);
struct cmdq_sec *cmdq = container_of(sec_thread->dev, struct cmdq_sec, dev);
if (!work_pending(&sec_thread->timeout_work))
queue_work(cmdq->timeout_wq, &sec_thread->timeout_work);
}
static void cmdq_sec_task_timeout_work(struct work_struct *work_item)
{
struct cmdq_sec_thread *sec_thread = container_of(work_item,
struct cmdq_sec_thread, timeout_work);
struct cmdq_sec *cmdq = container_of(sec_thread->dev, struct cmdq_sec, dev);
struct cmdq_task *task;
struct cmdq_sec_task *sec_task;
unsigned long flags;
u64 duration;
u32 cookie;
mutex_lock(&cmdq->exec_lock);
spin_lock_irqsave(&sec_thread->thread.chan->lock, flags);
if (list_empty(&sec_thread->thread.task_busy_list)) {
dev_err(&cmdq->dev, "thread:%u task_list is empty", sec_thread->idx);
spin_unlock_irqrestore(&sec_thread->thread.chan->lock, flags);
goto done;
}
task = list_first_entry(&sec_thread->thread.task_busy_list,
struct cmdq_task, list_entry);
sec_task = container_of(task, struct cmdq_sec_task, task);
duration = div_u64(sched_clock() - sec_task->trigger, 1000000);
if (duration < sec_thread->timeout_ms) {
mod_timer(&sec_thread->timeout, jiffies +
msecs_to_jiffies(sec_thread->timeout_ms - duration));
spin_unlock_irqrestore(&sec_thread->thread.chan->lock, flags);
goto done;
}
cookie = cmdq_sec_get_cookie(cmdq, sec_thread->idx);
spin_unlock_irqrestore(&sec_thread->thread.chan->lock, flags);
dev_err(&cmdq->dev, "%s duration:%llu cookie:%u thread:%u",
__func__, duration, cookie, sec_thread->idx);
cmdq_sec_irq_handler(sec_thread, cookie, -ETIMEDOUT);
done:
mutex_unlock(&cmdq->exec_lock);
}
static int cmdq_sec_mbox_startup(struct mbox_chan *chan)
{
struct cmdq_thread *thread = (struct cmdq_thread *)chan->con_priv;
struct cmdq_sec_thread *sec_thread = container_of(thread,
struct cmdq_sec_thread, thread);
char name[20];
snprintf(name, sizeof(name), "task_exec_wq_%u", sec_thread->idx);
sec_thread->task_exec_wq = create_singlethread_workqueue(name);
return 0;
}
static int cmdq_sec_mbox_flush(struct mbox_chan *chan, unsigned long timeout)
{
struct cmdq_thread *thread = (struct cmdq_thread *)chan->con_priv;
struct cmdq_sec_thread *sec_thread = container_of(thread,
struct cmdq_sec_thread, thread);
struct cmdq_sec *cmdq = container_of(sec_thread->dev, struct cmdq_sec, dev);
u32 cookie = 0;
mutex_lock(&cmdq->exec_lock);
if (list_empty(&thread->task_busy_list)) {
mutex_unlock(&cmdq->exec_lock);
return 0;
}
cookie = cmdq_sec_get_cookie(cmdq, sec_thread->idx);
if (cookie >= sec_thread->wait_cookie && sec_thread->task_cnt > 0)
cmdq_sec_irq_handler(sec_thread, cookie, -ECONNABORTED);
mutex_unlock(&cmdq->exec_lock);
return 0;
}
static void cmdq_sec_mbox_shutdown(struct mbox_chan *chan)
{
cmdq_sec_mbox_flush(chan, 0);
}
static const struct mbox_chan_ops cmdq_sec_mbox_chan_ops = {
.send_data = cmdq_sec_mbox_send_data,
.startup = cmdq_sec_mbox_startup,
.shutdown = cmdq_sec_mbox_shutdown,
.flush = cmdq_sec_mbox_flush,
};
struct cmdq_sec_mailbox cmdq_sec_mbox = {
.ops = &cmdq_sec_mbox_chan_ops,
};
EXPORT_SYMBOL_GPL(cmdq_sec_mbox);
static int cmdq_sec_probe(struct platform_device *pdev)
{
int i;
struct cmdq_sec *cmdq;
struct device *dev = &pdev->dev;
struct resource *res;
cmdq = devm_kzalloc(dev, sizeof(*cmdq), GFP_KERNEL);
if (!cmdq)
return -ENOMEM;
cmdq->dev = pdev->dev;
cmdq->pdata = (struct gce_sec_plat *)pdev->dev.platform_data;
if (!cmdq->pdata) {
dev_err(dev, "no valid gce platform data!\n");
return -EINVAL;
}
cmdq->base = cmdq->pdata->base;
res = platform_get_resource(to_platform_device(cmdq->pdata->mbox->dev),
IORESOURCE_MEM, 0);
if (IS_ERR(cmdq->base)) {
dev_err(dev, "devm_platform_get_and_ioremap_resource failed!\n");
return PTR_ERR(cmdq->base);
}
cmdq->base_pa = res->start;
cmdq->sec_thread = devm_kcalloc(dev, cmdq->pdata->secure_thread_nr,
sizeof(*cmdq->sec_thread), GFP_KERNEL);
if (!cmdq->sec_thread)
return -ENOMEM;
mutex_init(&cmdq->exec_lock);
for (i = 0; i < cmdq->pdata->secure_thread_nr; i++) {
u32 idx = i + cmdq->pdata->secure_thread_min;
cmdq->sec_thread[i].dev = &cmdq->dev;
cmdq->sec_thread[i].idx = idx;
cmdq->sec_thread[i].thread.base = cmdq->base + CMDQ_THR_BASE + CMDQ_THR_SIZE * idx;
cmdq->sec_thread[i].timeout_ms = CMDQ_TIMEOUT_DEFAULT;
INIT_LIST_HEAD(&cmdq->sec_thread[i].thread.task_busy_list);
cmdq->pdata->mbox->chans[idx].con_priv = (void *)&cmdq->sec_thread[i].thread;
dev_dbg(dev, "re-assign chans[%d] as secure thread\n", idx);
timer_setup(&cmdq->sec_thread[i].timeout, cmdq_sec_thread_timeout, 0);
INIT_WORK(&cmdq->sec_thread[i].timeout_work, cmdq_sec_task_timeout_work);
}
cmdq->notify_wq = create_singlethread_workqueue("mtk_cmdq_sec_notify_wq");
cmdq->timeout_wq = create_singlethread_workqueue("mtk_cmdq_sec_timeout_wq");
cmdq->shared_mem = devm_kzalloc(dev, sizeof(*cmdq->shared_mem), GFP_KERNEL);
if (!cmdq->shared_mem)
return -ENOMEM;
cmdq->shared_mem->va = dma_alloc_coherent(dev, PAGE_SIZE,
&cmdq->shared_mem->pa, GFP_KERNEL);
cmdq->shared_mem->size = PAGE_SIZE;
platform_set_drvdata(pdev, cmdq);
return 0;
}
static int cmdq_sec_remove(struct platform_device *pdev)
{
struct cmdq_sec *cmdq = platform_get_drvdata(pdev);
if (cmdq->context)
cmdq_sec_free_wsm(&cmdq->context->tee_ctx, &cmdq->context->iwc_msg);
return 0;
}
static struct platform_driver cmdq_sec_drv = {
.probe = cmdq_sec_probe,
.remove = cmdq_sec_remove,
.driver = {
.name = "mtk-cmdq-sec",
},
};
static int __init cmdq_sec_init(void)
{
return platform_driver_register(&cmdq_sec_drv);
}
static void __exit cmdq_sec_exit(void)
{
platform_driver_unregister(&cmdq_sec_drv);
}
module_init(cmdq_sec_init);
module_exit(cmdq_sec_exit);
MODULE_LICENSE("GPL");