kernel-brax3-ubuntu-touch/drivers/misc/mediatek/apusys/midware/2.0/mdw_cmd.c
erascape f319b992b1 kernel-5.15: Initial import brax3 UT kernel
* halium configs enabled

Signed-off-by: erascape <erascape@proton.me>
2025-09-23 15:17:10 +00:00

1155 lines
29 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2021 MediaTek Inc.
*/
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/sync_file.h>
#include <linux/sched/clock.h>
#include "mdw_trace.h"
#include "mdw_cmn.h"
#include "mdw_mem.h"
#include "mdw_mem_pool.h"
#include "rv/mdw_rv_tag.h"
#define mdw_cmd_show(c, f) \
f("cmd(0x%llx/0x%llx/0x%llx/0x%llx/%d/%u)param(%u/%u/%u/%u/"\
"%u/%u/%u)subcmds(%u/%p/%u/%u)pid(%d/%d)(%d)\n", \
(uint64_t) c->mpriv, c->uid, c->kid, c->rvid, c->id, kref_read(&c->ref), \
c->priority, c->hardlimit, c->softlimit, \
c->power_save, c->power_plcy, c->power_dtime, \
c->app_type, c->num_subcmds, c->cmdbufs, \
c->num_cmdbufs, c->size_cmdbufs, \
c->pid, c->tgid, task_pid_nr(current))
static void mdw_cmd_cmdbuf_out(struct mdw_fpriv *mpriv, struct mdw_cmd *c)
{
struct mdw_subcmd_kinfo *ksubcmd = NULL;
unsigned int i = 0, j = 0;
if (!c->cmdbufs)
return;
/* flush cmdbufs and execinfos */
if (mdw_mem_invalidate(mpriv, c->cmdbufs))
mdw_drv_warn("s(0x%llx)c(0x%llx) invalidate cmdbufs(%u) fail\n",
(uint64_t)mpriv, c->kid, c->cmdbufs->size);
for (i = 0; i < c->num_subcmds; i++) {
ksubcmd = &c->ksubcmds[i];
for (j = 0; j < ksubcmd->info->num_cmdbufs; j++) {
if (!ksubcmd->ori_cbs[j]) {
mdw_drv_warn("no ori mems(%d-%d)\n", i, j);
continue;
}
/* cmdbuf copy out */
if (ksubcmd->cmdbufs[j].direction != MDW_CB_IN) {
mdw_trace_begin("apumdw:cbs_copy_out|cb:%u-%u size:%u type:%u",
i, j, ksubcmd->ori_cbs[j]->size, ksubcmd->info->type);
memcpy(ksubcmd->ori_cbs[j]->vaddr,
(void *)ksubcmd->kvaddrs[j],
ksubcmd->ori_cbs[j]->size);
mdw_trace_end();
}
}
}
}
static void mdw_cmd_put_cmdbufs(struct mdw_fpriv *mpriv, struct mdw_cmd *c)
{
struct mdw_subcmd_kinfo *ksubcmd = NULL;
unsigned int i = 0, j = 0;
if (!c->cmdbufs)
return;
mdw_trace_begin("apumdw:cbs_put|c:0x%llx num_subcmds:%u num_cmdbufs:%u",
c->kid, c->num_subcmds, c->num_cmdbufs);
for (i = 0; i < c->num_subcmds; i++) {
ksubcmd = &c->ksubcmds[i];
for (j = 0; j < ksubcmd->info->num_cmdbufs; j++) {
if (!ksubcmd->ori_cbs[j]) {
mdw_drv_warn("no ori mems(%d-%d)\n", i, j);
continue;
}
/* put mem */
mdw_mem_put(mpriv, ksubcmd->ori_cbs[j]);
ksubcmd->ori_cbs[j] = NULL;
}
}
mdw_mem_pool_free(c->cmdbufs);
c->cmdbufs = NULL;
mdw_trace_end();
}
static int mdw_cmd_get_cmdbufs(struct mdw_fpriv *mpriv, struct mdw_cmd *c)
{
unsigned int i = 0, j = 0, ofs = 0;
int ret = -EINVAL;
struct mdw_subcmd_kinfo *ksubcmd = NULL;
struct mdw_mem *m = NULL;
struct apusys_cmdbuf *acbs = NULL;
mdw_trace_begin("apumdw:cbs_get|c:0x%llx num_subcmds:%u num_cmdbufs:%u",
c->kid, c->num_subcmds, c->num_cmdbufs);
if (!c->size_cmdbufs || c->cmdbufs)
goto out;
c->cmdbufs = mdw_mem_pool_alloc(&mpriv->cmd_buf_pool, c->size_cmdbufs,
MDW_DEFAULT_ALIGN);
if (!c->cmdbufs) {
mdw_drv_err("s(0x%llx)c(0x%llx) alloc buffer for duplicate fail\n",
(uint64_t) mpriv, c->kid);
ret = -ENOMEM;
goto out;
}
/* alloc mem for duplicated cmdbuf */
for (i = 0; i < c->num_subcmds; i++) {
mdw_cmd_debug("sc(0x%llx-%u) #cmdbufs(%u)\n",
c->kid, i, c->ksubcmds[i].info->num_cmdbufs);
acbs = kcalloc(c->ksubcmds[i].info->num_cmdbufs, sizeof(*acbs), GFP_KERNEL);
if (!acbs)
goto free_cmdbufs;
ksubcmd = &c->ksubcmds[i];
for (j = 0; j < ksubcmd->info->num_cmdbufs; j++) {
/* calc align */
if (ksubcmd->cmdbufs[j].align)
ofs = MDW_ALIGN(ofs, ksubcmd->cmdbufs[j].align);
else
ofs = MDW_ALIGN(ofs, MDW_DEFAULT_ALIGN);
mdw_cmd_debug("sc(0x%llx-%u) cb#%u offset(%u)\n",
c->kid, i, j, ofs);
/* get mem from handle */
m = mdw_mem_get(mpriv, ksubcmd->cmdbufs[j].handle);
if (!m) {
mdw_drv_err("sc(0x%llx-%u) cb#%u(%llu) get fail\n",
c->kid, i, j,
ksubcmd->cmdbufs[j].handle);
ret = -EINVAL;
goto free_cmdbufs;
}
/* check mem boundary */
if (m->vaddr == NULL ||
ksubcmd->cmdbufs[j].size != m->size) {
mdw_drv_err("sc(0x%llx-%u) cb#%u invalid range(%p/%u/%u)\n",
c->kid, i, j, m->vaddr,
ksubcmd->cmdbufs[j].size,
m->size);
ret = -EINVAL;
goto free_cmdbufs;
}
/* cmdbuf copy in */
if (ksubcmd->cmdbufs[j].direction != MDW_CB_OUT) {
mdw_trace_begin("apumdw:cbs_copy_in|cb:%u-%u size:%u type:%u",
i, j,
ksubcmd->cmdbufs[j].size,
ksubcmd->info->type);
memcpy(c->cmdbufs->vaddr + ofs,
m->vaddr,
ksubcmd->cmdbufs[j].size);
mdw_trace_end();
}
/* record buffer info */
ksubcmd->ori_cbs[j] = m;
ksubcmd->kvaddrs[j] =
(uint64_t)(c->cmdbufs->vaddr + ofs);
ksubcmd->daddrs[j] =
(uint64_t)(c->cmdbufs->device_va + ofs);
ofs += ksubcmd->cmdbufs[j].size;
mdw_cmd_debug("sc(0x%llx-%u) cb#%u (0x%llx/0x%llx/%u)\n",
c->kid, i, j,
ksubcmd->kvaddrs[j],
ksubcmd->daddrs[j],
ksubcmd->cmdbufs[j].size);
acbs[j].kva = (void *)ksubcmd->kvaddrs[j];
acbs[j].size = ksubcmd->cmdbufs[j].size;
}
mdw_trace_begin("apumdw:dev validation|c:0x%llx type:%u",
c->kid, ksubcmd->info->type);
ret = mdw_dev_validation(mpriv, ksubcmd->info->type,
c, acbs, ksubcmd->info->num_cmdbufs);
mdw_trace_end();
kfree(acbs);
acbs = NULL;
if (ret) {
mdw_drv_err("sc(0x%llx-%u) dev(%u) validate cb(%u) fail(%d)\n",
c->kid, i, ksubcmd->info->type, ksubcmd->info->num_cmdbufs, ret);
goto free_cmdbufs;
}
}
/* flush cmdbufs */
if (mdw_mem_flush(mpriv, c->cmdbufs))
mdw_drv_warn("s(0x%llx) c(0x%llx) flush cmdbufs(%u) fail\n",
(uint64_t)mpriv, c->kid, c->cmdbufs->size);
ret = 0;
goto out;
free_cmdbufs:
mdw_cmd_put_cmdbufs(mpriv, c);
kfree(acbs);
out:
mdw_cmd_debug("ret(%d)\n", ret);
mdw_trace_end();
return ret;
}
static unsigned int mdw_cmd_create_infos(struct mdw_fpriv *mpriv,
struct mdw_cmd *c)
{
unsigned int i = 0, j = 0, total_size = 0, tmp_size = 0;
struct mdw_subcmd_exec_info *sc_einfo = NULL;
int ret = -ENOMEM;
c->einfos = c->exec_infos->vaddr;
if (!c->einfos) {
mdw_drv_err("invalid exec info addr\n");
return -EINVAL;
} else {
/* clear run infos for return */
memset(c->exec_infos->vaddr, 0, c->exec_infos->size);
}
sc_einfo = &c->einfos->sc;
for (i = 0; i < c->num_subcmds; i++) {
c->ksubcmds[i].info = &c->subcmds[i];
mdw_cmd_debug("subcmd(%u)(%u/%u/%u/%u/%u/%u/%u/%u/0x%llx)\n",
i, c->subcmds[i].type,
c->subcmds[i].suggest_time, c->subcmds[i].vlm_usage,
c->subcmds[i].vlm_ctx_id, c->subcmds[i].vlm_force,
c->subcmds[i].boost, c->subcmds[i].pack_id,
c->subcmds[i].num_cmdbufs, c->subcmds[i].cmdbufs);
/* kva for oroginal buffer */
c->ksubcmds[i].ori_cbs = kcalloc(c->subcmds[i].num_cmdbufs,
sizeof(c->ksubcmds[i].ori_cbs), GFP_KERNEL);
if (!c->ksubcmds[i].ori_cbs)
goto free_cmdbufs;
/* record kva for duplicate */
c->ksubcmds[i].kvaddrs = kcalloc(c->subcmds[i].num_cmdbufs,
sizeof(*c->ksubcmds[i].kvaddrs), GFP_KERNEL);
if (!c->ksubcmds[i].kvaddrs)
goto free_cmdbufs;
/* record dva for cmdbufs */
c->ksubcmds[i].daddrs = kcalloc(c->subcmds[i].num_cmdbufs,
sizeof(*c->ksubcmds[i].daddrs), GFP_KERNEL);
if (!c->ksubcmds[i].daddrs)
goto free_cmdbufs;
/* allocate for subcmd cmdbuf */
c->ksubcmds[i].cmdbufs = kcalloc(c->subcmds[i].num_cmdbufs,
sizeof(*c->ksubcmds[i].cmdbufs), GFP_KERNEL);
if (!c->ksubcmds[i].cmdbufs)
goto free_cmdbufs;
/* copy cmdbuf info */
if (copy_from_user(c->ksubcmds[i].cmdbufs,
(void __user *)c->subcmds[i].cmdbufs,
c->subcmds[i].num_cmdbufs *
sizeof(*c->ksubcmds[i].cmdbufs))) {
goto free_cmdbufs;
}
c->ksubcmds[i].sc_einfo = &sc_einfo[i];
/* accumulate cmdbuf size with alignment */
for (j = 0; j < c->subcmds[i].num_cmdbufs; j++) {
c->num_cmdbufs++;
/* alignment */
if (c->ksubcmds[i].cmdbufs[j].align) {
tmp_size = MDW_ALIGN(total_size,
c->ksubcmds[i].cmdbufs[j].align);
} else {
tmp_size = MDW_ALIGN(total_size, MDW_DEFAULT_ALIGN);
}
if (tmp_size < total_size) {
mdw_drv_err("cmdbuf(%u,%u) size align overflow(%u/%u/%u)\n",
i, j, total_size,
c->ksubcmds[i].cmdbufs[j].align, tmp_size);
goto free_cmdbufs;
}
total_size = tmp_size;
/* accumulator */
tmp_size = total_size + c->ksubcmds[i].cmdbufs[j].size;
if (tmp_size < total_size) {
mdw_drv_err("cmdbuf(%u,%u) size overflow(%u/%u/%u)\n",
i, j, total_size,
c->ksubcmds[i].cmdbufs[j].size, tmp_size);
goto free_cmdbufs;
}
total_size = tmp_size;
}
}
c->size_cmdbufs = total_size;
mdw_cmd_debug("sc(0x%llx) cb_num(%u) total size(%u)\n",
c->kid, c->num_cmdbufs, c->size_cmdbufs);
ret = mdw_cmd_get_cmdbufs(mpriv, c);
if (ret)
goto free_cmdbufs;
goto out;
free_cmdbufs:
for (i = 0; i < c->num_subcmds; i++) {
/* free dvaddrs */
kfree(c->ksubcmds[i].daddrs);
c->ksubcmds[i].daddrs = NULL;
/* free kvaddrs */
kfree(c->ksubcmds[i].kvaddrs);
c->ksubcmds[i].kvaddrs = NULL;
/* free ori kvas */
kfree(c->ksubcmds[i].ori_cbs);
c->ksubcmds[i].ori_cbs = NULL;
/* free cmdbufs */
kfree(c->ksubcmds[i].cmdbufs);
c->ksubcmds[i].cmdbufs = NULL;
}
out:
return ret;
}
static void mdw_cmd_delete_infos(struct mdw_fpriv *mpriv, struct mdw_cmd *c)
{
unsigned int i = 0;
mdw_cmd_put_cmdbufs(mpriv, c);
for (i = 0; i < c->num_subcmds; i++) {
/* free dvaddrs */
kfree(c->ksubcmds[i].daddrs);
c->ksubcmds[i].daddrs = NULL;
/* free kvaddrs */
kfree(c->ksubcmds[i].kvaddrs);
c->ksubcmds[i].kvaddrs = NULL;
/* free ori kvas */
kfree(c->ksubcmds[i].ori_cbs);
c->ksubcmds[i].ori_cbs = NULL;
/* free cmdbufs */
kfree(c->ksubcmds[i].cmdbufs);
c->ksubcmds[i].cmdbufs = NULL;
}
}
void mdw_cmd_mpriv_release(struct mdw_fpriv *mpriv)
{
struct mdw_cmd *c = NULL;
uint32_t id = 0;
if (!atomic_read(&mpriv->active) && !atomic_read(&mpriv->active_cmds)) {
mdw_flw_debug("s(0x%llx) release cmd\n", (uint64_t)mpriv);
idr_for_each_entry(&mpriv->cmds, c, id) {
idr_remove(&mpriv->cmds, id);
mdw_cmd_delete(c);
}
mdw_flw_debug("s(0x%llx) release mem\n", (uint64_t)mpriv);
mutex_lock(&mpriv->mdev->mctl_mtx);
mdw_mem_mpriv_release(mpriv);
mutex_unlock(&mpriv->mdev->mctl_mtx);
}
}
//--------------------------------------------
static uint64_t mdw_fence_ctx_alloc(struct mdw_device *mdev)
{
uint64_t idx = 0, ctx = 0;
mutex_lock(&mdev->f_mtx);
idx = find_first_zero_bit(mdev->fence_ctx_mask, mdev->num_fence_ctx);
if (idx >= mdev->num_fence_ctx) {
ctx = dma_fence_context_alloc(1);
mdw_drv_warn("no free fence ctx(%llu), alloc ctx(%llu)\n", idx, ctx);
} else {
set_bit(idx, mdev->fence_ctx_mask);
ctx = mdev->base_fence_ctx + idx;
}
mutex_unlock(&mdev->f_mtx);
mdw_cmd_debug("alloc fence ctx(%llu) idx(%d) base(%llu)\n",
ctx, idx, mdev->base_fence_ctx);
return ctx;
}
static void mdw_fence_ctx_free(struct mdw_device *mdev, uint64_t ctx)
{
int idx = 0;
idx = ctx - mdev->base_fence_ctx;
if (idx < 0 || idx >= mdev->num_fence_ctx) {
mdw_cmd_debug("out of range ctx(%llu/%llu)\n", ctx, mdev->base_fence_ctx);
return;
}
mutex_lock(&mdev->f_mtx);
if (!test_bit(idx, mdev->fence_ctx_mask))
mdw_drv_warn("ctx state conflict(%d)\n", idx);
else
clear_bit(idx, mdev->fence_ctx_mask);
mutex_unlock(&mdev->f_mtx);
}
static const char *mdw_fence_get_driver_name(struct dma_fence *fence)
{
return "apu_mdw";
}
static const char *mdw_fence_get_timeline_name(struct dma_fence *fence)
{
struct mdw_fence *f =
container_of(fence, struct mdw_fence, base_fence);
return f->name;
}
static bool mdw_fence_enable_signaling(struct dma_fence *fence)
{
return true;
}
static void mdw_fence_release(struct dma_fence *fence)
{
struct mdw_fence *mf =
container_of(fence, struct mdw_fence, base_fence);
mdw_flw_debug("fence release, fence(%s/%llu-%llu)\n",
mf->name, mf->base_fence.context, mf->base_fence.seqno);
mdw_fence_ctx_free(mf->mdev, mf->base_fence.context);
kfree(mf);
}
static const struct dma_fence_ops mdw_fence_ops = {
.get_driver_name = mdw_fence_get_driver_name,
.get_timeline_name = mdw_fence_get_timeline_name,
.enable_signaling = mdw_fence_enable_signaling,
.wait = dma_fence_default_wait,
.release = mdw_fence_release,
};
//--------------------------------------------
static int mdw_fence_init(struct mdw_cmd *c, int fd)
{
int ret = 0;
struct mdw_device *mdev = c->mpriv->mdev;
c->fence = kzalloc(sizeof(*c->fence), GFP_KERNEL);
if (!c->fence)
return -ENOMEM;
if (snprintf(c->fence->name, sizeof(c->fence->name), "%d:%s", fd, c->comm) <= 0)
mdw_drv_warn("set fance name fail\n");
c->fence->mdev = c->mpriv->mdev;
spin_lock_init(&c->fence->lock);
dma_fence_init(&c->fence->base_fence, &mdw_fence_ops,
&c->fence->lock, mdw_fence_ctx_alloc(mdev),
atomic_add_return(1, &c->mpriv->exec_seqno));
mdw_flw_debug("fence init, c(0x%llx) fence(%s/%llu-%llu)\n",
(uint64_t)c, c->fence->name, c->fence->base_fence.context,
c->fence->base_fence.seqno);
return ret;
}
static void mdw_cmd_unvoke_map(struct mdw_cmd *c)
{
struct mdw_cmd_map_invoke *cm_invoke = NULL, *tmp = NULL;
list_for_each_entry_safe(cm_invoke, tmp, &c->map_invokes, c_node) {
list_del(&cm_invoke->c_node);
mdw_cmd_debug("s(0x%llx)c(0x%llx) unvoke m(0x%llx/%u)\n",
(uint64_t)c->mpriv, (uint64_t)c,
cm_invoke->map->m->device_va,
cm_invoke->map->m->dva_size);
cm_invoke->map->put(cm_invoke->map);
kfree(cm_invoke);
}
}
int mdw_cmd_invoke_map(struct mdw_cmd *c, struct mdw_mem_map *map)
{
struct mdw_cmd_map_invoke *cm_invoke = NULL;
if (map == NULL)
return -EINVAL;
/* query */
list_for_each_entry(cm_invoke, &c->map_invokes, c_node) {
/* already invoked */
if (cm_invoke->map == map)
return 0;
}
cm_invoke = kzalloc(sizeof(*cm_invoke), GFP_KERNEL);
if (cm_invoke == NULL)
return -ENOMEM;
map->get(map);
cm_invoke->map = map;
list_add_tail(&cm_invoke->c_node, &c->map_invokes);
mdw_cmd_debug("s(0x%llx)c(0x%llx) invoke m(0x%llx/%u)\n",
(uint64_t)c->mpriv, (uint64_t)c, map->m->device_va, map->m->dva_size);
return 0;
}
static void mdw_cmd_release(struct kref *ref)
{
struct mdw_cmd *c =
container_of(ref, struct mdw_cmd, ref);
struct mdw_fpriv *mpriv = c->mpriv;
mdw_cmd_show(c, mdw_drv_debug);
mdw_trace_begin("apumdw:cmd_release|c:0x%llx", c->kid);
if (c->del_internal)
c->del_internal(c);
mutex_lock(&mpriv->mdev->mctl_mtx);
mdw_cmd_unvoke_map(c);
mutex_unlock(&mpriv->mdev->mctl_mtx);
mdw_cmd_delete_infos(c->mpriv, c);
mdw_mem_put(c->mpriv, c->exec_infos);
kfree(c->adj_matrix);
kfree(c->ksubcmds);
kfree(c->subcmds);
kfree(c);
mpriv->put(mpriv);
mdw_trace_end();
}
void mdw_cmd_put(struct mdw_cmd *c)
{
kref_put(&c->ref, mdw_cmd_release);
}
static void mdw_cmd_get(struct mdw_cmd *c)
{
kref_get(&c->ref);
}
void mdw_cmd_delete(struct mdw_cmd *c)
{
mdw_cmd_show(c, mdw_drv_debug);
mdw_cmd_put(c);
}
static void mdw_cmd_delete_async(struct mdw_cmd *c)
{
struct mdw_device *mdev = c->mpriv->mdev;
/* add cmd list to delete */
mutex_lock(&mdev->c_mtx);
list_add_tail(&c->d_node, &mdev->d_cmds);
mutex_unlock(&mdev->c_mtx);
schedule_work(&mdev->c_wk);
}
static int mdw_cmd_sanity_check(struct mdw_cmd *c)
{
if (c->priority >= MDW_PRIORITY_MAX ||
c->num_subcmds > MDW_SUBCMD_MAX ||
c->num_links > c->num_subcmds) {
mdw_drv_err("s(0x%llx)cmd invalid(0x%llx/0x%llx)(%u/%u/%u)\n",
(uint64_t)c->mpriv, c->uid, c->kid,
c->priority, c->num_subcmds, c->num_links);
return -EINVAL;
}
if (c->exec_infos->size != sizeof(struct mdw_cmd_exec_info) +
c->num_subcmds * sizeof(struct mdw_subcmd_exec_info)) {
mdw_drv_err("s(0x%llx)cmd invalid(0x%llx/0x%llx) einfo(%u/%u)\n",
(uint64_t)c->mpriv, c->uid, c->kid,
c->exec_infos->size,
sizeof(struct mdw_cmd_exec_info) +
c->num_subcmds * sizeof(struct mdw_subcmd_exec_info));
return -EINVAL;
}
return 0;
}
static int mdw_cmd_adj_check(struct mdw_cmd *c)
{
uint32_t i = 0, j = 0;
for (i = 0; i < c->num_subcmds; i++) {
for (j = 0; j < c->num_subcmds; j++) {
if (i == j) {
c->adj_matrix[i * c->num_subcmds + j] = 0;
continue;
}
if (i < j)
continue;
if (!c->adj_matrix[i * c->num_subcmds + j] ||
!c->adj_matrix[i + j * c->num_subcmds])
continue;
mdw_drv_err("s(0x%llx)c(0x%llx/0x%llx) adj matrix(%u/%u) fail\n",
(uint64_t)c->mpriv, c->uid, c->kid, i, j);
return -EINVAL;
}
}
return 0;
}
static int mdw_cmd_link_check(struct mdw_cmd *c)
{
uint32_t i = 0;
for (i = 0; i < c->num_links; i++) {
if (c->links[i].producer_idx > c->num_subcmds ||
c->links[i].consumer_idx > c->num_subcmds ||
!c->links[i].x || !c->links[i].y ||
!c->links[i].va) {
mdw_drv_err("link(%u) invalid(%u/%u)(%u/%u)(0x%llx)\n",
c->links[i].producer_idx,
c->links[i].consumer_idx,
c->links[i].x,
c->links[i].y,
c->links[i].va);
return -EINVAL;
}
}
return 0;
}
static int mdw_cmd_sc_sanity_check(struct mdw_cmd *c)
{
unsigned int i = 0;
/* subcmd info */
for (i = 0; i < c->num_subcmds; i++) {
if (c->subcmds[i].type >= MDW_DEV_MAX ||
c->subcmds[i].vlm_ctx_id >= MDW_SUBCMD_MAX ||
c->subcmds[i].boost > MDW_BOOST_MAX ||
c->subcmds[i].pack_id >= MDW_SUBCMD_MAX) {
mdw_drv_err("subcmd(%u) invalid (%u/%u/%u/%u)\n",
i, c->subcmds[i].type,
c->subcmds[i].boost,
c->subcmds[i].pack_id);
return -EINVAL;
}
}
return 0;
}
static int mdw_cmd_run(struct mdw_fpriv *mpriv, struct mdw_cmd *c)
{
struct mdw_device *mdev = mpriv->mdev;
struct dma_fence *f = &c->fence->base_fence;
int ret = 0;
mdw_cmd_show(c, mdw_cmd_debug);
c->start_ts = sched_clock();
ret = mdev->dev_funcs->run_cmd(mpriv, c);
if (ret) {
mdw_drv_err("s(0x%llx) run cmd(0x%llx) fail(%d)\n",
(uint64_t) c->mpriv, c->kid, ret);
dma_fence_set_error(f, ret);
if (dma_fence_signal(f)) {
mdw_drv_warn("c(0x%llx) signal fence fail\n", (uint64_t)c);
if (f->ops->get_timeline_name && f->ops->get_driver_name) {
mdw_drv_warn(" fence name(%s-%s)\n",
f->ops->get_driver_name(f), f->ops->get_timeline_name(f));
}
}
dma_fence_put(f);
} else {
mdw_flw_debug("s(0x%llx) cmd(0x%llx) run\n",
(uint64_t)c->mpriv, c->kid);
}
return ret;
}
static void mdw_cmd_check_rets(struct mdw_cmd *c, int ret)
{
uint32_t idx = 0, is_dma = 0, is_aps = 0;
DECLARE_BITMAP(tmp, 64);
memcpy(&tmp, &c->einfos->c.sc_rets, sizeof(c->einfos->c.sc_rets));
/* extract fail subcmd */
do {
idx = find_next_bit((unsigned long *)&tmp, c->num_subcmds, idx);
if (idx >= c->num_subcmds)
break;
mdw_drv_warn("sc(0x%llx-#%u) type(%u) softlimit(%u) boost(%u) fail\n",
c->kid, idx, c->subcmds[idx].type,
c->softlimit, c->subcmds[idx].boost);
switch (c->subcmds[idx].type) {
case APUSYS_DEVICE_EDMA:
is_dma++;
break;
case APUSYS_DEVICE_APS:
is_aps++;
break;
default:
break;
}
idx++;
} while (idx < c->num_subcmds);
/* trigger exception if dma */
if (is_dma) {
dma_exception("dma exec fail:%s:ret(%d/0x%llx)pid(%d/%d)c(0x%llx)\n",
c->comm, ret, c->einfos->c.sc_rets,
c->pid, c->tgid, c->kid);
}
if (is_aps) {
aps_exception("aps exec fail:%s:ret(%d/0x%llx)pid(%d/%d)c(0x%llx)\n",
c->comm, ret, c->einfos->c.sc_rets,
c->pid, c->tgid, c->kid);
}
}
static int mdw_cmd_complete(struct mdw_cmd *c, int ret)
{
struct dma_fence *f = &c->fence->base_fence;
struct mdw_fpriv *mpriv = c->mpriv;
mdw_trace_begin("apumdw:cmd_complete|cmd:0x%llx/0x%llx", c->uid, c->kid);
mutex_lock(&c->mtx);
c->end_ts = sched_clock();
c->einfos->c.total_us = (c->end_ts - c->start_ts) / 1000;
mdw_flw_debug("s(0x%llx) c(%s/0x%llx/0x%llx/0x%llx) ret(%d) sc_rets(0x%llx) complete, pid(%d/%d)(%d)\n",
(uint64_t)mpriv, c->comm, c->uid, c->kid, c->rvid,
ret, c->einfos->c.sc_rets,
c->pid, c->tgid, task_pid_nr(current));
/* check subcmds return value */
if (c->einfos->c.sc_rets) {
if (!ret)
ret = -EIO;
mdw_cmd_check_rets(c, ret);
}
c->einfos->c.ret = ret;
if (ret) {
mdw_drv_err("s(0x%llx) c(%s/0x%llx/0x%llx/0x%llx) ret(%d/0x%llx) time(%llu) pid(%d/%d)\n",
(uint64_t)mpriv, c->comm, c->uid, c->kid, c->rvid,
ret, c->einfos->c.sc_rets,
c->einfos->c.total_us, c->pid, c->tgid);
dma_fence_set_error(f, ret);
if (mdw_debug_on(MDW_DBG_EXP))
mdw_exception("exec fail:%s:ret(%d/0x%llx)pid(%d/%d)\n",
c->comm, ret, c->einfos->c.sc_rets, c->pid, c->tgid);
} else {
mdw_flw_debug("s(0x%llx) c(%s/0x%llx/0x%llx/0x%llx) ret(%d/0x%llx) time(%llu) pid(%d/%d)\n",
(uint64_t)mpriv, c->comm, c->uid, c->kid, c->rvid,
ret, c->einfos->c.sc_rets,
c->einfos->c.total_us, c->pid, c->tgid);
}
mdw_cmd_cmdbuf_out(mpriv, c);
/* signal done */
c->fence = NULL;
atomic_dec(&c->is_running);
if (dma_fence_signal(f)) {
mdw_drv_warn("c(0x%llx) signal fence fail\n", (uint64_t)c);
if (f->ops->get_timeline_name && f->ops->get_driver_name) {
mdw_drv_warn(" fence name(%s-%s)\n",
f->ops->get_driver_name(f), f->ops->get_timeline_name(f));
}
}
dma_fence_put(f);
atomic_dec(&mpriv->active_cmds);
mutex_unlock(&c->mtx);
/* check mpriv to clean cmd */
mutex_lock(&mpriv->mtx);
mdw_cmd_mpriv_release(mpriv);
mutex_unlock(&mpriv->mtx);
/* put cmd execution ref */
mdw_cmd_put(c);
mdw_trace_end();
return 0;
}
static void mdw_cmd_trigger_func(struct work_struct *wk)
{
struct mdw_cmd *c =
container_of(wk, struct mdw_cmd, t_wk);
int ret = 0;
if (c->wait_fence) {
dma_fence_wait(c->wait_fence, false);
dma_fence_put(c->wait_fence);
}
mdw_flw_debug("s(0x%llx) c(0x%llx) wait fence done, start run\n",
(uint64_t)c->mpriv, c->kid);
mutex_lock(&c->mtx);
ret = mdw_cmd_run(c->mpriv, c);
mutex_unlock(&c->mtx);
/* put cmd execution ref */
if (ret) {
atomic_dec(&c->is_running);
mdw_cmd_put(c);
}
}
static struct mdw_cmd *mdw_cmd_create(struct mdw_fpriv *mpriv,
union mdw_cmd_args *args)
{
struct mdw_cmd_in *in = (struct mdw_cmd_in *)args;
struct mdw_cmd *c = NULL;
mdw_trace_begin("apumdw:cmd_create|s:0x%llx", (uint64_t)mpriv);
/* check num subcmds maximum */
if (in->exec.num_subcmds > MDW_SUBCMD_MAX) {
mdw_drv_err("too much subcmds(%u)\n", in->exec.num_subcmds);
goto out;
}
/* alloc mdw cmd */
c = kzalloc(sizeof(*c), GFP_KERNEL);
if (!c)
goto out;
mutex_init(&c->mtx);
INIT_LIST_HEAD(&c->map_invokes);
c->mpriv = mpriv;
atomic_set(&c->is_running, 0);
/* setup cmd info */
c->pid = task_pid_nr(current);
c->tgid = task_tgid_nr(current);
c->kid = (uint64_t)c;
c->uid = in->exec.uid;
get_task_comm(c->comm, current);
c->priority = in->exec.priority;
c->hardlimit = in->exec.hardlimit;
c->softlimit = in->exec.softlimit;
c->power_save = in->exec.power_save;
c->power_plcy = in->exec.power_plcy;
c->power_dtime = in->exec.power_dtime;
c->fastmem_ms = in->exec.fastmem_ms;
c->app_type = in->exec.app_type;
c->num_subcmds = in->exec.num_subcmds;
c->num_links = in->exec.num_links;
c->exec_infos = mdw_mem_get(mpriv, in->exec.exec_infos);
if (!c->exec_infos) {
mdw_drv_err("get exec info fail\n");
goto free_cmd;
}
/* check input params */
if (mdw_cmd_sanity_check(c)) {
mdw_drv_err("cmd sanity check fail\n");
goto put_execinfos;
}
/* subcmds/ksubcmds */
c->subcmds = kzalloc(c->num_subcmds * sizeof(*c->subcmds), GFP_KERNEL);
if (!c->subcmds)
goto put_execinfos;
if (copy_from_user(c->subcmds, (void __user *)in->exec.subcmd_infos,
c->num_subcmds * sizeof(*c->subcmds))) {
mdw_drv_err("copy subcmds fail\n");
goto free_subcmds;
}
if (mdw_cmd_sc_sanity_check(c)) {
mdw_drv_err("sc sanity check fail\n");
goto free_subcmds;
}
c->ksubcmds = kzalloc(c->num_subcmds * sizeof(*c->ksubcmds),
GFP_KERNEL);
if (!c->ksubcmds)
goto free_subcmds;
/* adj matrix */
c->adj_matrix = kzalloc(c->num_subcmds *
c->num_subcmds * sizeof(uint8_t), GFP_KERNEL);
if (!c->adj_matrix)
goto free_ksubcmds;
if (copy_from_user(c->adj_matrix, (void __user *)in->exec.adj_matrix,
(c->num_subcmds * c->num_subcmds * sizeof(uint8_t)))) {
mdw_drv_err("copy adj matrix fail\n");
goto free_adj;
}
if (g_mdw_klog & MDW_DBG_CMD) {
print_hex_dump(KERN_INFO, "[apusys] adj matrix: ",
DUMP_PREFIX_OFFSET, 16, 1, c->adj_matrix,
c->num_subcmds * c->num_subcmds, 0);
}
if (mdw_cmd_adj_check(c))
goto free_adj;
/* link */
if (c->num_links) {
c->links = kcalloc(c->num_links, sizeof(*c->links), GFP_KERNEL);
if (!c->links)
goto free_adj;
if (copy_from_user(c->links, (void __user *)in->exec.links,
c->num_links * sizeof(*c->links))) {
mdw_drv_err("copy links fail\n");
goto free_link;
}
}
if (mdw_cmd_link_check(c))
goto free_link;
/* create infos */
if (mdw_cmd_create_infos(mpriv, c)) {
mdw_drv_err("create cmd info fail\n");
goto free_link;
}
c->mpriv->get(c->mpriv);
c->complete = mdw_cmd_complete;
INIT_WORK(&c->t_wk, &mdw_cmd_trigger_func);
kref_init(&c->ref);
mdw_cmd_show(c, mdw_drv_debug);
goto out;
free_link:
kfree(c->links);
free_adj:
kfree(c->adj_matrix);
free_ksubcmds:
kfree(c->ksubcmds);
free_subcmds:
kfree(c->subcmds);
put_execinfos:
mdw_mem_put(mpriv, c->exec_infos);
free_cmd:
kfree(c);
c = NULL;
out:
mdw_trace_end();
return c;
}
static int mdw_cmd_ioctl_del(struct mdw_fpriv *mpriv, union mdw_cmd_args *args)
{
struct mdw_cmd_in *in = (struct mdw_cmd_in *)args;
struct mdw_cmd *c = NULL;
int ret = 0;
mdw_trace_begin("apumdw:user_delete");
mutex_lock(&mpriv->mtx);
c = (struct mdw_cmd *)idr_find(&mpriv->cmds, in->id);
if (!c) {
ret = -EINVAL;
mdw_drv_warn("can't find id(%d)\n", in->id);
} else {
if (c != idr_remove(&mpriv->cmds, c->id))
mdw_drv_warn("remove cmd idr conflict(0x%llx/%d)\n", c->kid, c->id);
mdw_cmd_delete(c);
}
mutex_unlock(&mpriv->mtx);
mdw_trace_end();
return ret;
}
static int mdw_cmd_ioctl_run(struct mdw_fpriv *mpriv, union mdw_cmd_args *args)
{
struct mdw_cmd_in *in = (struct mdw_cmd_in *)args;
struct mdw_cmd *c = NULL, *priv_c = NULL;
struct sync_file *sync_file = NULL;
int ret = 0, fd = 0, wait_fd = 0, is_running = 0;
mdw_trace_begin("apumdw:user_run");
/* get wait fd */
wait_fd = in->exec.fence;
mutex_lock(&mpriv->mtx);
/* get stale cmd */
c = (struct mdw_cmd *)idr_find(&mpriv->cmds, in->id);
if (!c) {
/* no stale cmd, create cmd */
mdw_cmd_debug("s(0x%llx) create new\n", (uint64_t)mpriv);
} else if (in->op == MDW_CMD_IOCTL_RUN_STALE) {
is_running = atomic_read(&c->is_running);
if (is_running) {
mdw_drv_err("s(0x%llx) c(0x%llx) is running(%d), can't execute again\n",
(uint64_t)mpriv, (uint64_t)c, is_running);
ret = -ETXTBSY;
goto out;
}
/* run stale cmd */
mdw_cmd_debug("s(0x%llx) run stale(0x%llx)\n",
(uint64_t)mpriv, (uint64_t)c);
goto exec;
} else {
/* release stale cmd and create new */
mdw_cmd_debug("s(0x%llx) delete stale(0x%llx) and create new\n",
(uint64_t)mpriv, (uint64_t)c);
priv_c = c;
c = NULL;
if (priv_c != idr_remove(&mpriv->cmds, priv_c->id)) {
mdw_drv_warn("remove cmd idr conflict(0x%llx/%d)\n",
priv_c->kid, priv_c->id);
}
}
/* create cmd */
c = mdw_cmd_create(mpriv, args);
if (!c) {
mdw_drv_err("create cmd fail\n");
ret = -EINVAL;
goto out;
}
memset(args, 0, sizeof(*args));
/* alloc idr */
c->id = idr_alloc(&mpriv->cmds, c, MDW_CMD_IDR_MIN, MDW_CMD_IDR_MAX, GFP_KERNEL);
if (c->id < MDW_CMD_IDR_MIN) {
mdw_drv_err("alloc idr fail(%d)\n", c->id);
goto delete_cmd;
}
exec:
mutex_lock(&c->mtx);
mdw_cmd_trace(c, MDW_CMD_ENQUE);
/* get sync_file fd */
fd = get_unused_fd_flags(O_CLOEXEC);
if (fd < 0) {
mdw_drv_err("get unused fd fail\n");
ret = -EINVAL;
goto delete_idr;
}
if (mdw_fence_init(c, fd)) {
mdw_drv_err("cmd init fence fail\n");
goto put_fd;
}
sync_file = sync_file_create(&c->fence->base_fence);
if (!sync_file) {
mdw_drv_err("create sync file fail\n");
dma_fence_put(&c->fence->base_fence);
ret = -ENOMEM;
goto put_fd;
}
/* get cmd execution ref */
atomic_inc(&c->is_running);
mdw_cmd_get(c);
/* check wait fence from other module */
mdw_flw_debug("s(0x%llx)c(0x%llx) wait fence(%d)...\n",
(uint64_t)c->mpriv, c->kid, wait_fd);
c->wait_fence = sync_file_get_fence(wait_fd);
if (!c->wait_fence) {
mdw_flw_debug("s(0x%llx)c(0x%llx) no wait fence, trigger directly\n",
(uint64_t)c->mpriv, c->kid);
ret = mdw_cmd_run(mpriv, c);
} else {
/* wait fence from wq */
schedule_work(&c->t_wk);
}
if (ret) {
/* put cmd execution ref */
atomic_dec(&c->is_running);
mdw_cmd_put(c);
goto put_file;
}
/* assign fd */
fd_install(fd, sync_file->file);
/* get ref for cmd exec */
atomic_inc(&mpriv->active_cmds);
/* return fd */
args->out.exec.fence = fd;
args->out.exec.id = c->id;
mdw_flw_debug("async fd(%d) id(%d)\n", fd, c->id);
mutex_unlock(&c->mtx);
goto out;
put_file:
fput(sync_file->file);
put_fd:
put_unused_fd(fd);
delete_idr:
if (c != idr_remove(&mpriv->cmds, c->id))
mdw_drv_warn("remove cmd idr conflict(0x%llx/%d)\n", c->kid, c->id);
mutex_unlock(&c->mtx);
delete_cmd:
mdw_cmd_delete(c);
out:
mutex_unlock(&mpriv->mtx);
if (priv_c)
mdw_cmd_delete_async(priv_c);
mdw_trace_end();
return ret;
}
int mdw_cmd_ioctl(struct mdw_fpriv *mpriv, void *data)
{
union mdw_cmd_args *args = (union mdw_cmd_args *)data;
int ret = 0;
mdw_flw_debug("s(0x%llx) op::%d\n", (uint64_t)mpriv, args->in.op);
switch (args->in.op) {
case MDW_CMD_IOCTL_RUN:
case MDW_CMD_IOCTL_RUN_STALE:
ret = mdw_cmd_ioctl_run(mpriv, args);
break;
case MDW_CMD_IOCTL_DEL:
ret = mdw_cmd_ioctl_del(mpriv, args);
break;
default:
ret = -EINVAL;
break;
}
mdw_flw_debug("done\n");
return ret;
}