/* * Tegra Graphics Host Client Module * * Copyright (c) 2010-2021, 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_EVENTLIB #include #include "nvhost_events_json.h" #endif #include "debug.h" #include "bus_client.h" #include "dev.h" #include "class_ids.h" #include "chip_support.h" #include "nvhost_acm.h" #include "nvhost_syncpt.h" #include "nvhost_channel.h" #include "nvhost_job.h" #include "nvhost_sync.h" #include "vhost/vhost.h" static int validate_reg(struct platform_device *ndev, u32 offset, int count) { int err = 0; struct resource *r; /* check if offset is u32 aligned */ if (offset & 3) { nvhost_err(&ndev->dev, "misaligned register offset 0x%x", offset); return -EINVAL; } r = platform_get_resource(ndev, IORESOURCE_MEM, 0); if (!r) { dev_err(&ndev->dev, "failed to get memory resource\n"); return -ENODEV; } if (offset + 4 * count > resource_size(r) || (offset + 4 * count < offset)) { nvhost_err(&ndev->dev, "invalid register range offset 0x%x count %u", offset, count); err = -EPERM; } /* prevent speculative access to mod's aperture + offset */ speculation_barrier(); return err; } int validate_max_size(struct platform_device *ndev, u32 size) { struct resource *r; /* check if size is non-zero and u32 aligned */ if (!size || size & 3) { nvhost_err(&ndev->dev, "invalid dev size 0x%x", size); return -EINVAL; } r = platform_get_resource(ndev, IORESOURCE_MEM, 0); if (!r) { dev_err(&ndev->dev, "failed to get memory resource\n"); return -ENODEV; } if (size > resource_size(r)) { nvhost_err(&ndev->dev, "invalid dev size 0x%x", size); return -EPERM; } return 0; } void __iomem *get_aperture(struct platform_device *pdev, int index) { struct nvhost_device_data *pdata = platform_get_drvdata(pdev); return pdata->aperture[index]; } void host1x_writel(struct platform_device *pdev, u32 r, u32 v) { void __iomem *addr = get_aperture(pdev, 0) + r; nvhost_dbg(dbg_reg, " d=%s r=0x%x v=0x%x", pdev->name, r, v); writel(v, addr); } EXPORT_SYMBOL_GPL(host1x_writel); u32 host1x_readl(struct platform_device *pdev, u32 r) { void __iomem *addr = get_aperture(pdev, 0) + r; u32 v; nvhost_dbg(dbg_reg, " d=%s r=0x%x", pdev->name, r); v = readl(addr); nvhost_dbg(dbg_reg, " d=%s r=0x%x v=0x%x", pdev->name, r, v); return v; } EXPORT_SYMBOL_GPL(host1x_readl); void host1x_channel_writel(struct nvhost_channel *ch, u32 r, u32 v) { void __iomem *addr = ch->aperture + r; nvhost_dbg(dbg_reg, " chid=%d r=0x%x v=0x%x", ch->chid, r, v); writel(v, addr); } EXPORT_SYMBOL_GPL(host1x_channel_writel); u32 host1x_channel_readl(struct nvhost_channel *ch, u32 r) { void __iomem *addr = ch->aperture + r; u32 v; nvhost_dbg(dbg_reg, " chid=%d r=0x%x", ch->chid, r); v = readl(addr); nvhost_dbg(dbg_reg, " chid=%d r=0x%x v=0x%x", ch->chid, r, v); return v; } EXPORT_SYMBOL_GPL(host1x_channel_readl); void host1x_sync_writel(struct nvhost_master *dev, u32 r, u32 v) { void __iomem *addr = dev->sync_aperture + r; nvhost_dbg(dbg_reg, " d=%s r=0x%x v=0x%x", dev->dev->name, r, v); writel(v, addr); } EXPORT_SYMBOL_GPL(host1x_sync_writel); u32 host1x_sync_readl(struct nvhost_master *dev, u32 r) { void __iomem *addr = dev->sync_aperture + r; u32 v; nvhost_dbg(dbg_reg, " d=%s r=0x%x", dev->dev->name, r); v = readl(addr); nvhost_dbg(dbg_reg, " d=%s r=0x%x v=0x%x", dev->dev->name, r, v); return v; } EXPORT_SYMBOL_GPL(host1x_sync_readl); int nvhost_read_module_regs(struct platform_device *ndev, u32 offset, int count, u32 *values) { int err; /* verify offset */ err = validate_reg(ndev, offset, count); if (err) return err; err = nvhost_module_busy(ndev); if (err) return err; while (count--) { *(values++) = host1x_readl(ndev, offset); offset += 4; } rmb(); nvhost_module_idle(ndev); return 0; } int nvhost_write_module_regs(struct platform_device *ndev, u32 offset, int count, const u32 *values) { int err; /* verify offset */ err = validate_reg(ndev, offset, count); if (err) return err; err = nvhost_module_busy(ndev); if (err) return err; while (count--) { host1x_writel(ndev, offset, *(values++)); offset += 4; } wmb(); nvhost_module_idle(ndev); return 0; } struct nvhost_channel_userctx { struct nvhost_channel *ch; u32 timeout; int clientid; bool timeout_debug_dump; struct platform_device *pdev; u32 syncpts[NVHOST_MODULE_MAX_SYNCPTS]; u32 client_managed_syncpt; /* error notificatiers used channel submit timeout */ struct dma_buf *error_notifier_ref; u64 error_notifier_offset; /* lock to protect this structure from concurrent ioctl usage */ struct mutex ioctl_lock; /* used for attaching to ctx list in device pdata */ struct list_head node; }; static int nvhost_channelrelease(struct inode *inode, struct file *filp) { struct nvhost_channel_userctx *priv = filp->private_data; struct nvhost_device_data *pdata = platform_get_drvdata(priv->pdev); struct nvhost_master *host = nvhost_get_host(pdata->pdev); int i = 0; trace_nvhost_channel_release(dev_name(&priv->pdev->dev)); mutex_lock(&pdata->userctx_list_lock); list_del(&priv->node); mutex_unlock(&pdata->userctx_list_lock); /* remove this client from acm */ nvhost_module_remove_client(priv->pdev, priv); /* drop error notifier reference */ if (priv->error_notifier_ref) dma_buf_put(priv->error_notifier_ref); /* Abort the channel */ if (pdata->support_abort_on_close) nvhost_channel_abort(pdata, (void *)priv); /* Clear the identifier */ if ((pdata->resource_policy == RESOURCE_PER_CHANNEL_INSTANCE) || (pdata->resource_policy == RESOURCE_PER_DEVICE && pdata->exclusive)) nvhost_channel_remove_identifier(pdata, (void *)priv); /* If the device is in exclusive mode, drop the reference */ if (pdata->exclusive) pdata->num_mapped_chs--; /* drop instance syncpoints reference */ for (i = 0; i < NVHOST_MODULE_MAX_SYNCPTS; ++i) { if (priv->syncpts[i]) { nvhost_syncpt_put_ref(&host->syncpt, priv->syncpts[i]); priv->syncpts[i] = 0; } } if (priv->client_managed_syncpt) { nvhost_syncpt_put_ref(&host->syncpt, priv->client_managed_syncpt); priv->client_managed_syncpt = 0; } if (pdata->keepalive) nvhost_module_idle(priv->pdev); kfree(priv); return 0; } static int __nvhost_channelopen(struct inode *inode, struct platform_device *pdev, struct file *filp) { struct nvhost_channel_userctx *priv; struct nvhost_device_data *pdata, *host1x_pdata; struct nvhost_master *host; int ret; /* grab pdev and pdata based on inputs */ if (pdev) { pdata = platform_get_drvdata(pdev); } else if (inode) { pdata = container_of(inode->i_cdev, struct nvhost_device_data, cdev); pdev = pdata->pdev; } else { nvhost_err(NULL, "could not open the channel"); return -EINVAL; } /* ..and host1x specific data */ host1x_pdata = dev_get_drvdata(pdev->dev.parent); host = nvhost_get_host(pdev); trace_nvhost_channel_open(dev_name(&pdev->dev)); /* If the device is in exclusive mode, make channel reservation here */ if (pdata->exclusive) { if (pdata->num_mapped_chs == pdata->num_channels) { nvhost_err(&pdev->dev, "no more available channels for an exclusive device"); goto fail_mark_used; } pdata->num_mapped_chs++; } priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) { nvhost_err(&pdev->dev, "failed to allocate priv structure"); goto fail_allocate_priv; } filp->private_data = priv; /* Register this client to acm */ if (nvhost_module_add_client(pdev, priv)) goto fail_add_client; /* Check that the device can be powered */ ret = nvhost_module_busy(pdev); if (ret) goto fail_power_on; /* Turn off the device if we do not need to keep it powered */ if (!pdata->keepalive) nvhost_module_idle(pdev); if (nvhost_dev_is_virtual(pdev) && !host->info.vmserver_owns_engines) { /* If virtual, allocate a client id on the server side. This is * needed for channel recovery, to distinguish which clients * own which gathers. */ int virt_moduleid = vhost_virt_moduleid(pdata->moduleid); struct nvhost_virt_ctx *virt_ctx = nvhost_get_virt_data(pdev); if (virt_moduleid < 0) { ret = -EINVAL; goto fail_virt_clientid; } priv->clientid = vhost_channel_alloc_clientid(virt_ctx->handle, virt_moduleid); if (priv->clientid == 0) { dev_err(&pdev->dev, "vhost_channel_alloc_clientid failed\n"); ret = -ENOMEM; goto fail_virt_clientid; } } else { /* Get client id */ priv->clientid = atomic_add_return(1, &host->clientid); if (!priv->clientid) priv->clientid = atomic_add_return(1, &host->clientid); } /* Initialize private structure */ priv->timeout = host1x_pdata->nvhost_timeout_default; priv->timeout_debug_dump = true; mutex_init(&priv->ioctl_lock); priv->pdev = pdev; if (!tegra_platform_is_silicon()) priv->timeout = 0; INIT_LIST_HEAD(&priv->node); mutex_lock(&pdata->userctx_list_lock); list_add_tail(&priv->node, &pdata->userctx_list); mutex_unlock(&pdata->userctx_list_lock); return 0; fail_virt_clientid: if (pdata->keepalive) nvhost_module_idle(pdev); fail_power_on: nvhost_module_remove_client(pdev, priv); fail_add_client: kfree(priv); fail_allocate_priv: if (pdata->exclusive) pdata->num_mapped_chs--; fail_mark_used: return -ENOMEM; } static int nvhost_channelopen(struct inode *inode, struct file *filp) { return __nvhost_channelopen(inode, NULL, filp); } static int nvhost_init_error_notifier(struct nvhost_channel_userctx *ctx, struct nvhost_set_error_notifier *args) { struct dma_buf *dmabuf; void *va; u64 end = args->offset + sizeof(struct nvhost_notification); /* are we releasing old reference? */ if (!args->mem) { if (ctx->error_notifier_ref) dma_buf_put(ctx->error_notifier_ref); ctx->error_notifier_ref = NULL; return 0; } /* take reference for the userctx */ dmabuf = dma_buf_get(args->mem); if (IS_ERR(dmabuf)) { pr_err("%s: Invalid handle: %d\n", __func__, args->mem); return -EINVAL; } if (end > dmabuf->size || end < sizeof(struct nvhost_notification)) { dma_buf_put(dmabuf); pr_err("%s: invalid offset\n", __func__); return -EINVAL; } /* map handle and clear error notifier struct */ va = dma_buf_vmap(dmabuf); if (!va) { dma_buf_put(dmabuf); pr_err("%s: Cannot map notifier handle\n", __func__); return -ENOMEM; } memset(va + args->offset, 0, sizeof(struct nvhost_notification)); dma_buf_vunmap(dmabuf, va); /* release old reference */ if (ctx->error_notifier_ref) dma_buf_put(ctx->error_notifier_ref); /* finally, store error notifier data */ ctx->error_notifier_ref = dmabuf; ctx->error_notifier_offset = args->offset; return 0; } static inline u32 get_job_fence(struct nvhost_job *job, u32 id) { struct nvhost_channel *ch = job->ch; struct nvhost_device_data *pdata = platform_get_drvdata(ch->dev); u32 fence = job->sp[id].fence; /* take into account work done increment */ if (pdata->push_work_done && id == 0) return fence - 1; /* otherwise the fence is valid "as is" */ return fence; } static int submit_add_gathers(struct nvhost_submit_args *args, struct nvhost_job *job, struct nvhost_device_data *pdata) { struct nvhost_cmdbuf __user *cmdbufs = (struct nvhost_cmdbuf __user *)(uintptr_t)args->cmdbufs; struct nvhost_cmdbuf_ext __user *cmdbuf_exts = (struct nvhost_cmdbuf_ext __user *)(uintptr_t)args->cmdbuf_exts; u32 __user *class_ids = (u32 __user *)(uintptr_t)args->class_ids; u32 *local_class_ids = NULL; int err; u32 i; /* mass copy class_ids */ if (class_ids) { local_class_ids = kcalloc(args->num_cmdbufs, sizeof(u32), GFP_KERNEL); if (!local_class_ids) return -ENOMEM; err = copy_from_user(local_class_ids, class_ids, sizeof(u32) * args->num_cmdbufs); if (err) { nvhost_err(&pdata->pdev->dev, "failed to copy user inputs: class_ids=%px num_cmdbufs=%u", class_ids, args->num_cmdbufs); err = -EINVAL; goto free_local_class_ids; } } for (i = 0; i < args->num_cmdbufs; ++i) { struct nvhost_cmdbuf cmdbuf; struct nvhost_cmdbuf_ext cmdbuf_ext; u32 class_id = class_ids ? local_class_ids[i] : 0; err = copy_from_user(&cmdbuf, cmdbufs + i, sizeof(cmdbuf)); if (err) { nvhost_err(&pdata->pdev->dev, "failed to copy user inputs: cmdbufs+%d=%px", i, cmdbufs + i); err = -EINVAL; goto free_local_class_ids; } cmdbuf_ext.pre_fence = -1; if (cmdbuf_exts) err = copy_from_user(&cmdbuf_ext, cmdbuf_exts + i, sizeof(cmdbuf_ext)); if (err) cmdbuf_ext.pre_fence = -1; /* verify that the given class id is valid for this engine */ if (class_id && class_id != pdata->class && class_id != NV_HOST1X_CLASS_ID) { nvhost_err(&pdata->pdev->dev, "invalid class id 0x%x", class_id); err = -EINVAL; goto free_local_class_ids; } nvhost_job_add_gather(job, cmdbuf.mem, cmdbuf.words, cmdbuf.offset, class_id, cmdbuf_ext.pre_fence); } kfree(local_class_ids); return 0; free_local_class_ids: kfree(local_class_ids); return err; } static int submit_copy_relocs(struct nvhost_submit_args *args, struct nvhost_job *job) { struct nvhost_reloc __user *relocs = (struct nvhost_reloc __user *)(uintptr_t)args->relocs; struct nvhost_reloc_shift __user *reloc_shifts = (struct nvhost_reloc_shift __user *) (uintptr_t)args->reloc_shifts; struct nvhost_reloc_type __user *reloc_types = (struct nvhost_reloc_type __user *) (uintptr_t)args->reloc_types; struct device *d = &job->ch->dev->dev; int err; job->num_relocs = args->num_relocs; err = copy_from_user(job->relocarray, relocs, sizeof(*relocs) * args->num_relocs); if (err) { nvhost_err(d, "failed to copy user input: relocs=%px num_relocs=%u", relocs, args->num_relocs); return -EINVAL; } err = copy_from_user(job->relocshiftarray, reloc_shifts, sizeof(*reloc_shifts) * args->num_relocs); if (err) { nvhost_err(d, "failed to copy user input: reloc_shifts=%px num_relocs=%u", reloc_shifts, args->num_relocs); return -EINVAL; } if (reloc_types) { err = copy_from_user(job->reloctypearray, reloc_types, sizeof(*reloc_types) * args->num_relocs); if (err) { nvhost_err(d, "failed to copy user input: reloc_types=%px num_relocs=%u", reloc_types, args->num_relocs); return -EINVAL; } } return 0; } static int submit_get_syncpoints(struct nvhost_submit_args *args, struct nvhost_job *job, struct nvhost_channel_userctx *ctx) { struct nvhost_device_data *pdata = platform_get_drvdata(ctx->pdev); struct nvhost_syncpt_incr __user *syncpt_incrs = (struct nvhost_syncpt_incr __user *) (uintptr_t)args->syncpt_incrs; int err; u32 i; if (args->num_syncpt_incrs > NVHOST_SUBMIT_MAX_NUM_SYNCPT_INCRS) { nvhost_err(&pdata->pdev->dev, "num_syncpt_incrs=%u is larger than max=%u", args->num_syncpt_incrs, NVHOST_SUBMIT_MAX_NUM_SYNCPT_INCRS); return -EINVAL; } /* * Go through each syncpoint from userspace. Here we: * - Copy syncpoint information * - Validate each syncpoint * - Determine the index of hwctx syncpoint in the table */ for (i = 0; i < args->num_syncpt_incrs; ++i) { struct nvhost_syncpt_incr sp; bool found = false; int j; /* Copy */ err = copy_from_user(&sp, syncpt_incrs + i, sizeof(sp)); if (err) { nvhost_err(&pdata->pdev->dev, "failed to copy user input: syncpt_incrs+%d=%px", i, syncpt_incrs + i); return -EINVAL; } /* Validate the trivial case */ if (sp.syncpt_id == 0) { nvhost_err(&pdata->pdev->dev, "syncpt_id 0 forbidden"); return -EINVAL; } /* ..and then ensure that the syncpoints have been reserved * for this client */ for (j = 0; j < NVHOST_MODULE_MAX_SYNCPTS; j++) { if (ctx->syncpts[j] == sp.syncpt_id) { found = true; break; } } if (!found) { nvhost_err(&pdata->pdev->dev, "tried to use unreserved syncpoint %u", sp.syncpt_id); return -EINVAL; } /* Store and get a reference */ job->sp[i].id = sp.syncpt_id; job->sp[i].incrs = sp.syncpt_incrs; } return 0; } static int submit_deliver_fences(struct nvhost_submit_args *args, struct nvhost_job *job, struct nvhost_channel_userctx *ctx) { u32 __user *fences = (u32 __user *)(uintptr_t)args->fences; int err; u32 i; /* Deliver multiple fences back to the userspace */ if (fences) for (i = 0; i < args->num_syncpt_incrs; ++i) { u32 fence = get_job_fence(job, i); err = copy_to_user(fences + i, &fence, sizeof(u32)); if (err) break; } /* Deliver the fence using the old mechanism _only_ if a single * syncpoint is used. */ if (args->flags & BIT(NVHOST_SUBMIT_FLAG_SYNC_FENCE_FD)) { struct nvhost_ctrl_sync_fence_info *pts; pts = kcalloc(args->num_syncpt_incrs, sizeof(struct nvhost_ctrl_sync_fence_info), GFP_KERNEL); if (!pts) { nvhost_err(&job->ch->dev->dev, "failed to allocate pts"); return -ENOMEM; } for (i = 0; i < args->num_syncpt_incrs; i++) { pts[i].id = job->sp[i].id; pts[i].thresh = get_job_fence(job, i); } err = nvhost_sync_create_fence_fd(ctx->pdev, pts, args->num_syncpt_incrs, "fence", &args->fence); kfree(pts); if (err) return err; } else if (args->num_syncpt_incrs == 1) { args->fence = get_job_fence(job, 0); } else { args->fence = 0; } return 0; } static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx, struct nvhost_submit_args *args) { struct nvhost_job *job; struct nvhost_waitchk __user *waitchks = (struct nvhost_waitchk __user *)(uintptr_t)args->waitchks; struct nvhost_device_data *pdata = platform_get_drvdata(ctx->pdev); int err; if ((args->num_syncpt_incrs < 1) || (args->num_syncpt_incrs > nvhost_syncpt_nb_pts(&nvhost_get_host(ctx->pdev)->syncpt))) { nvhost_err(&pdata->pdev->dev, "invalid num_syncpt_incrs=%u", args->num_syncpt_incrs); return -EINVAL; } job = nvhost_job_alloc(ctx->ch, args->num_cmdbufs, args->num_relocs, args->num_waitchks, args->num_syncpt_incrs); if (!job) return -ENOMEM; job->num_syncpts = args->num_syncpt_incrs; job->clientid = ctx->clientid; job->client_managed_syncpt = ctx->client_managed_syncpt; /* copy error notifier settings for this job */ if (ctx->error_notifier_ref) { get_dma_buf(ctx->error_notifier_ref); job->error_notifier_ref = ctx->error_notifier_ref; job->error_notifier_offset = ctx->error_notifier_offset; } err = submit_add_gathers(args, job, pdata); if (err) goto put_job; err = submit_copy_relocs(args, job); if (err) goto put_job; job->num_waitchk = args->num_waitchks; err = copy_from_user(job->waitchk, waitchks, sizeof(*waitchks) * args->num_waitchks); if (err) { nvhost_err(&pdata->pdev->dev, "failed to copy user input: waitchks=%px num_waitchks=%u", waitchks, args->num_waitchks); err = -EINVAL; goto put_job; } err = submit_get_syncpoints(args, job, ctx); if (err) goto put_job; trace_nvhost_channel_submit(ctx->pdev->name, job->num_gathers, job->num_relocs, job->num_waitchk, job->sp[0].id, job->sp[0].incrs); err = nvhost_module_busy(ctx->pdev); if (err) goto put_job; err = nvhost_job_pin(job, &nvhost_get_host(ctx->pdev)->syncpt); nvhost_module_idle(ctx->pdev); if (err) goto put_job; if (args->timeout) job->timeout = min(ctx->timeout, args->timeout); else job->timeout = ctx->timeout; job->timeout_debug_dump = ctx->timeout_debug_dump; err = nvhost_channel_submit(job); if (err) goto unpin_job; nvhost_eventlib_log_submit(ctx->pdev, job->sp[0].id, pdata->push_work_done ? (job->sp[0].fence - 1) : job->sp[0].fence, arch_counter_get_cntvct()); err = submit_deliver_fences(args, job, ctx); if (err) goto put_job; nvhost_job_put(job); return 0; unpin_job: nvhost_job_unpin(job); put_job: nvhost_job_put(job); nvhost_err(&pdata->pdev->dev, "failed with err %d", err); return err; } static int moduleid_to_index(struct platform_device *dev, u32 moduleid) { int i; struct nvhost_device_data *pdata = platform_get_drvdata(dev); for (i = 0; i < NVHOST_MODULE_MAX_CLOCKS; i++) { if (pdata->clocks[i].moduleid == moduleid) return i; } /* Old user space is sending a random number in args. Return clock * zero in these cases. */ return 0; } static int nvhost_ioctl_channel_set_rate(struct nvhost_channel_userctx *ctx, struct nvhost_clk_rate_args *arg) { u32 moduleid = (arg->moduleid >> NVHOST_MODULE_ID_BIT_POS) & ((1 << NVHOST_MODULE_ID_BIT_WIDTH) - 1); u32 attr = (arg->moduleid >> NVHOST_CLOCK_ATTR_BIT_POS) & ((1 << NVHOST_CLOCK_ATTR_BIT_WIDTH) - 1); int index = moduleid ? moduleid_to_index(ctx->pdev, moduleid) : 0; int err; err = nvhost_module_set_rate(ctx->pdev, ctx, arg->rate, index, attr); if (!tegra_platform_is_silicon() && err) { nvhost_dbg(dbg_clk, "ignoring error: module=%u, attr=%u, index=%d, err=%d", moduleid, attr, index, err); err = 0; } return err; } static int nvhost_ioctl_channel_get_rate(struct nvhost_channel_userctx *ctx, u32 moduleid, u32 *rate) { int index = moduleid ? moduleid_to_index(ctx->pdev, moduleid) : 0; int err; err = nvhost_module_get_rate(ctx->pdev, (unsigned long *)rate, index); if (!tegra_platform_is_silicon() && err) { nvhost_dbg(dbg_clk, "ignoring error: module=%u, rate=%u, error=%d", moduleid, *rate, err); err = 0; /* fake the return value */ *rate = 32 * 1024; } return err; } static int nvhost_ioctl_channel_module_regrdwr( struct nvhost_channel_userctx *ctx, struct nvhost_ctrl_module_regrdwr_args *args) { u32 num_offsets = args->num_offsets; u32 __user *offsets = (u32 __user *)(uintptr_t)args->offsets; u32 __user *values = (u32 __user *)(uintptr_t)args->values; u32 vals[64]; struct platform_device *ndev; trace_nvhost_ioctl_channel_module_regrdwr(args->id, args->num_offsets, args->write); /* Check that there is something to read and that block size is * u32 aligned */ if (num_offsets == 0 || args->block_size & 3) { nvhost_err(&ctx->pdev->dev, "invalid regrdwr parameters: num_offsets=%u block_size=0x%x", num_offsets, args->block_size); return -EINVAL; } ndev = ctx->pdev; if (nvhost_dev_is_virtual(ndev)) return vhost_rdwr_module_regs(ndev, num_offsets, args->block_size, offsets, values, args->write); while (num_offsets--) { int err; u32 offs; int remaining = args->block_size >> 2; if (get_user(offs, offsets)) { nvhost_err(&ndev->dev, "failed to copy user's input: offsets=%px", offsets); return -EFAULT; } offsets++; while (remaining) { int batch = min(remaining, 64); if (args->write) { if (copy_from_user(vals, values, batch * sizeof(u32))) { nvhost_err(&ndev->dev, "failed to copy user's input: values=%px batch=%u", values, batch); return -EFAULT; } err = nvhost_write_module_regs(ndev, offs, batch, vals); if (err) return err; } else { err = nvhost_read_module_regs(ndev, offs, batch, vals); if (err) return err; if (copy_to_user(values, vals, batch * sizeof(u32))) { nvhost_err(&ndev->dev, "failed to copy vals to user"); return -EFAULT; } } remaining -= batch; offs += batch * sizeof(u32); values += batch; } } return 0; } static u32 create_mask(u32 *words, int num) { int i; u32 word = 0; for (i = 0; i < num; i++) { if (!words[i] || words[i] > 31) continue; word |= BIT(words[i]); } return word; } static u32 nvhost_ioctl_channel_get_syncpt_mask( struct nvhost_channel_userctx *priv) { u32 mask; mask = create_mask(priv->syncpts, NVHOST_MODULE_MAX_SYNCPTS); return mask; } static u32 nvhost_ioctl_channel_get_syncpt_instance( struct nvhost_channel_userctx *ctx, struct nvhost_device_data *pdata, u32 index) { u32 id; /* if we already have required syncpt then return it ... */ if (ctx->syncpts[index]) { id = ctx->syncpts[index]; return id; } /* ... otherwise get a new syncpt dynamically */ id = nvhost_get_syncpt_host_managed(pdata->pdev, index, NULL); if (!id) return 0; /* ... and store it for further references */ ctx->syncpts[index] = id; return id; } static int nvhost_ioctl_channel_get_client_syncpt( struct nvhost_channel_userctx *ctx, struct nvhost_get_client_managed_syncpt_arg *args) { struct nvhost_device_data *pdata = platform_get_drvdata(ctx->pdev); const char __user *args_name = (const char __user *)(uintptr_t)args->name; char name[32]; char set_name[32]; /* prepare syncpoint name (in case it is needed) */ if (args_name) { if (strncpy_from_user(name, args_name, sizeof(name)) < 0) { nvhost_err(&ctx->pdev->dev, "failed to copy from user: args_name=%px", args_name); return -EFAULT; } name[sizeof(name) - 1] = '\0'; } else { name[0] = '\0'; } snprintf(set_name, sizeof(set_name), "%s_%s", dev_name(&ctx->pdev->dev), name); if (!ctx->client_managed_syncpt) ctx->client_managed_syncpt = nvhost_get_syncpt_client_managed(pdata->pdev, set_name); args->value = ctx->client_managed_syncpt; if (!args->value) return -EAGAIN; return 0; } static int nvhost_ioctl_channel_set_syncpoint_name( struct nvhost_channel_userctx *ctx, struct nvhost_set_syncpt_name_args *buf) { struct nvhost_device_data *pdata = platform_get_drvdata(ctx->pdev); struct nvhost_master *host = nvhost_get_host(pdata->pdev); const char __user *args_name = (const char __user *)(uintptr_t)buf->name; char *syncpt_name; char name[32]; int j; if (!nvhost_syncpt_is_valid_hw_pt_nospec(&host->syncpt, &buf->syncpt_id)) return -EINVAL; if (args_name) { if (strncpy_from_user(name, args_name, sizeof(name)) < 0) return -EFAULT; name[sizeof(name) - 1] = '\0'; } else { name[0] = '\0'; } for (j = 0; j < NVHOST_MODULE_MAX_SYNCPTS; j++) { if (ctx->syncpts[j] == buf->syncpt_id) { syncpt_name = kasprintf(GFP_KERNEL, "%s", name); if (syncpt_name) { return nvhost_channel_set_syncpoint_name( &host->syncpt, buf->syncpt_id, (const char *)syncpt_name); } else { nvhost_err(&pdata->pdev->dev, "failed to allocate syncpt_name"); return -ENOMEM; } } } nvhost_err(&pdata->pdev->dev, "invalid syncpoint id %u", buf->syncpt_id); return -EINVAL; } static long nvhost_channelctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct nvhost_channel_userctx *priv = filp->private_data; struct device *dev; u8 buf[NVHOST_IOCTL_CHANNEL_MAX_ARG_SIZE] __aligned(sizeof(u64)); int err = 0; if ((_IOC_TYPE(cmd) != NVHOST_IOCTL_MAGIC) || (_IOC_NR(cmd) == 0) || (_IOC_NR(cmd) > NVHOST_IOCTL_CHANNEL_LAST) || (_IOC_SIZE(cmd) > NVHOST_IOCTL_CHANNEL_MAX_ARG_SIZE)) { nvhost_err(NULL, "invalid cmd 0x%x", cmd); return -ENOIOCTLCMD; } if (_IOC_DIR(cmd) & _IOC_WRITE) { if (copy_from_user(buf, (void __user *)arg, _IOC_SIZE(cmd))) { nvhost_err_ratelimited(NULL, "failed to copy from user: arg=%px", (void __user *)arg); return -EFAULT; } } /* serialize calls from this fd */ mutex_lock(&priv->ioctl_lock); if (!priv->pdev) { pr_warn("Channel already unmapped\n"); mutex_unlock(&priv->ioctl_lock); return -EFAULT; } dev = &priv->pdev->dev; switch (cmd) { case NVHOST_IOCTL_CHANNEL_OPEN: { int fd; struct file *file; char *name; err = get_unused_fd_flags(O_RDWR); if (err < 0) { nvhost_err(dev, "failed to get unused fd"); break; } fd = err; name = kasprintf(GFP_KERNEL, "nvhost-%s-fd%d", dev_name(dev), fd); if (!name) { nvhost_err(dev, "failed to allocate name"); err = -ENOMEM; put_unused_fd(fd); break; } file = anon_inode_getfile(name, filp->f_op, NULL, O_RDWR); kfree(name); if (IS_ERR(file)) { nvhost_err(dev, "failed to get file"); err = PTR_ERR(file); put_unused_fd(fd); break; } err = __nvhost_channelopen(NULL, priv->pdev, file); if (err) { put_unused_fd(fd); fput(file); break; } ((struct nvhost_channel_open_args *)buf)->channel_fd = fd; fd_install(fd, file); break; } case NVHOST_IOCTL_CHANNEL_GET_SYNCPOINTS: { ((struct nvhost_get_param_args *)buf)->value = nvhost_ioctl_channel_get_syncpt_mask(priv); break; } case NVHOST_IOCTL_CHANNEL_GET_SYNCPOINT: { struct nvhost_device_data *pdata = platform_get_drvdata(priv->pdev); struct nvhost_get_param_arg *arg = (struct nvhost_get_param_arg *)buf; if (arg->param >= NVHOST_MODULE_MAX_SYNCPTS) { nvhost_err(dev, "invalid syncpoint id %u", arg->param); err = -EINVAL; break; } /* prevent speculative access to ctx->syncpts[index] */ arg->param = array_index_nospec(arg->param, NVHOST_MODULE_MAX_SYNCPTS); arg->value = nvhost_ioctl_channel_get_syncpt_instance( priv, pdata, arg->param); if (!arg->value) { err = -EAGAIN; break; } break; } case NVHOST_IOCTL_CHANNEL_GET_CLIENT_MANAGED_SYNCPOINT: { err = nvhost_ioctl_channel_get_client_syncpt(priv, (struct nvhost_get_client_managed_syncpt_arg *)buf); break; } case NVHOST_IOCTL_CHANNEL_FREE_CLIENT_MANAGED_SYNCPOINT: break; case NVHOST_IOCTL_CHANNEL_GET_WAITBASES: { ((struct nvhost_get_param_args *)buf)->value = 0; break; } case NVHOST_IOCTL_CHANNEL_GET_WAITBASE: { nvhost_err(dev, "GET_WAITBASE (%d) not supported", cmd); err = -EINVAL; break; } case NVHOST_IOCTL_CHANNEL_GET_MODMUTEXES: { struct nvhost_device_data *pdata = \ platform_get_drvdata(priv->pdev); ((struct nvhost_get_param_args *)buf)->value = create_mask(pdata->modulemutexes, NVHOST_MODULE_MAX_MODMUTEXES); break; } case NVHOST_IOCTL_CHANNEL_GET_MODMUTEX: { struct nvhost_device_data *pdata = \ platform_get_drvdata(priv->pdev); struct nvhost_get_param_arg *arg = (struct nvhost_get_param_arg *)buf; if (arg->param >= NVHOST_MODULE_MAX_MODMUTEXES) { nvhost_err(dev, "invalid modmutex 0x%x", arg->param); err = -EINVAL; break; } arg->param = array_index_nospec(arg->param, NVHOST_MODULE_MAX_MODMUTEXES); if (!pdata->modulemutexes[arg->param]) { nvhost_err(dev, "invalid modmutex 0x%x", arg->param); err = -EINVAL; break; } arg->value = pdata->modulemutexes[arg->param]; break; } case NVHOST_IOCTL_CHANNEL_SET_NVMAP_FD: break; case NVHOST_IOCTL_CHANNEL_GET_CLK_RATE: { struct nvhost_clk_rate_args *arg = (struct nvhost_clk_rate_args *)buf; /* if virtualized, just return 0 */ if (nvhost_dev_is_virtual(priv->pdev)) { arg->rate = 0; break; } err = nvhost_ioctl_channel_get_rate(priv, arg->moduleid, &arg->rate); break; } case NVHOST_IOCTL_CHANNEL_SET_CLK_RATE: { struct nvhost_clk_rate_args *arg = (struct nvhost_clk_rate_args *)buf; /* if virtualized, client requests to change clock rate * are ignored */ if (nvhost_dev_is_virtual(priv->pdev)) break; err = nvhost_ioctl_channel_set_rate(priv, arg); break; } case NVHOST_IOCTL_CHANNEL_SET_TIMEOUT: { u32 timeout = (u32)((struct nvhost_set_timeout_args *)buf)->timeout; priv->timeout = timeout; dev_dbg(&priv->pdev->dev, "%s: setting buffer timeout (%d ms) for userctx 0x%p\n", __func__, priv->timeout, priv); break; } case NVHOST_IOCTL_CHANNEL_GET_TIMEDOUT: ((struct nvhost_get_param_args *)buf)->value = false; break; case NVHOST_IOCTL_CHANNEL_SET_PRIORITY: nvhost_err(dev, "SET_PRIORITY not supported"); err = -EINVAL; break; case NVHOST32_IOCTL_CHANNEL_MODULE_REGRDWR: { struct nvhost32_ctrl_module_regrdwr_args *args32 = (struct nvhost32_ctrl_module_regrdwr_args *)buf; struct nvhost_ctrl_module_regrdwr_args args; args.id = args32->id; args.num_offsets = args32->num_offsets; args.block_size = args32->block_size; args.offsets = args32->offsets; args.values = args32->values; args.write = args32->write; err = nvhost_ioctl_channel_module_regrdwr(priv, &args); break; } case NVHOST_IOCTL_CHANNEL_MODULE_REGRDWR: err = nvhost_ioctl_channel_module_regrdwr(priv, (void *)buf); break; case NVHOST32_IOCTL_CHANNEL_SUBMIT: { struct nvhost_device_data *pdata = platform_get_drvdata(priv->pdev); struct nvhost32_submit_args *args32 = (void *)buf; struct nvhost_submit_args args; void *identifier; if (pdata->resource_policy == RESOURCE_PER_DEVICE && !pdata->exclusive) identifier = (void *)pdata; else identifier = (void *)priv; memset(&args, 0, sizeof(args)); args.submit_version = args32->submit_version; args.num_syncpt_incrs = args32->num_syncpt_incrs; args.num_cmdbufs = args32->num_cmdbufs; args.num_relocs = args32->num_relocs; args.num_waitchks = args32->num_waitchks; args.timeout = args32->timeout; args.syncpt_incrs = args32->syncpt_incrs; args.fence = args32->fence; args.cmdbufs = args32->cmdbufs; args.relocs = args32->relocs; args.reloc_shifts = args32->reloc_shifts; args.waitchks = args32->waitchks; args.class_ids = args32->class_ids; args.fences = args32->fences; /* first, get a channel */ err = nvhost_channel_map(pdata, &priv->ch, identifier); if (err) break; /* submit work */ err = nvhost_ioctl_channel_submit(priv, &args); /* ..and drop the local reference */ nvhost_putchannel(priv->ch, 1); args32->fence = args.fence; break; } case NVHOST_IOCTL_CHANNEL_SUBMIT: { struct nvhost_device_data *pdata = platform_get_drvdata(priv->pdev); void *identifier; if (pdata->resource_policy == RESOURCE_PER_DEVICE && !pdata->exclusive) identifier = (void *)pdata; else identifier = (void *)priv; /* first, get a channel */ err = nvhost_channel_map(pdata, &priv->ch, identifier); if (err) break; /* submit work */ err = nvhost_ioctl_channel_submit(priv, (void *)buf); /* ..and drop the local reference */ nvhost_putchannel(priv->ch, 1); break; } case NVHOST_IOCTL_CHANNEL_SET_ERROR_NOTIFIER: err = nvhost_init_error_notifier(priv, (struct nvhost_set_error_notifier *)buf); break; case NVHOST_IOCTL_CHANNEL_SET_TIMEOUT_EX: { u32 timeout = (u32)((struct nvhost_set_timeout_args *)buf)->timeout; bool timeout_debug_dump = !((u32) ((struct nvhost_set_timeout_ex_args *)buf)->flags & (1 << NVHOST_TIMEOUT_FLAG_DISABLE_DUMP)); priv->timeout = timeout; priv->timeout_debug_dump = timeout_debug_dump; dev_dbg(&priv->pdev->dev, "%s: setting buffer timeout (%d ms) for userctx 0x%p\n", __func__, priv->timeout, priv); break; } case NVHOST_IOCTL_CHANNEL_SET_SYNCPOINT_NAME: { err = nvhost_ioctl_channel_set_syncpoint_name(priv, (struct nvhost_set_syncpt_name_args *)buf); break; } default: nvhost_dbg_info("unrecognized ioctl cmd: 0x%x", cmd); err = -ENOTTY; break; } mutex_unlock(&priv->ioctl_lock); if ((err == 0) && (_IOC_DIR(cmd) & _IOC_READ)) { err = copy_to_user((void __user *)arg, buf, _IOC_SIZE(cmd)); if (err) { nvhost_err(dev, "failed to copy output to user: arg=%px", (void __user *)arg); err = -EFAULT; } } return err; } static const struct file_operations nvhost_channelops = { .owner = THIS_MODULE, .release = nvhost_channelrelease, .open = nvhost_channelopen, #ifdef CONFIG_COMPAT .compat_ioctl = nvhost_channelctl, #endif .unlocked_ioctl = nvhost_channelctl }; const char *get_device_name_for_dev(struct platform_device *dev) { struct nvhost_device_data *pdata = nvhost_get_devdata(dev); if (pdata->devfs_name) return pdata->devfs_name; return dev->name; } static struct device *nvhost_client_device_create( struct platform_device *pdev, struct cdev *cdev, const char *cdev_name, dev_t devno, const struct file_operations *ops) { struct nvhost_master *host = nvhost_get_host(pdev); const char *use_dev_name; struct device *dev; int err; nvhost_dbg_fn(""); if (!host) { dev_err(&pdev->dev, "No nvhost_master!\n"); return ERR_PTR(-EINVAL); } cdev_init(cdev, ops); cdev->owner = THIS_MODULE; err = cdev_add(cdev, devno, 1); if (err < 0) { dev_err(&pdev->dev, "failed to add cdev\n"); return ERR_PTR(err); } use_dev_name = get_device_name_for_dev(pdev); dev = device_create(host->nvhost_class, &pdev->dev, devno, NULL, (pdev->id <= 0) ? IFACE_NAME "-%s%s" : IFACE_NAME "-%s%s.%d", cdev_name, use_dev_name, pdev->id); if (IS_ERR(dev)) { dev_err(&pdev->dev, "failed to create %s %s device for %s\n", use_dev_name, cdev_name, pdev->name); cdev_del(cdev); } return dev; } #define NVHOST_NUM_CDEV 4 int nvhost_client_user_init(struct platform_device *dev) { int err; dev_t devno; struct nvhost_device_data *pdata = platform_get_drvdata(dev); struct nvhost_master *nvhost_master = nvhost_get_host(dev); if (pdata->kernel_only) return 0; if (!nvhost_master) { dev_err(&dev->dev, "failed to get nvhost_master!\n"); return -ENODEV; } if (!nvhost_master->major) { dev_err(&dev->dev, "Major chrdev number not allocated!\n"); return -EINVAL; } mutex_lock(&nvhost_master->chrdev_mutex); devno = MKDEV(nvhost_master->major, nvhost_master->next_minor); /* reserve 3 minor #s for , and ctrl- */ err = register_chrdev_region(devno, NVHOST_NUM_CDEV, IFACE_NAME); if (err < 0) { dev_err(&dev->dev, "failed to allocate devno %d %d\n", nvhost_master->major, nvhost_master->next_minor); mutex_unlock(&nvhost_master->chrdev_mutex); return err; } nvhost_master->next_minor += NVHOST_NUM_CDEV; mutex_unlock(&nvhost_master->chrdev_mutex); pdata->cdev_region = devno; pdata->node = nvhost_client_device_create(dev, &pdata->cdev, "", devno, &nvhost_channelops); if (IS_ERR(pdata->node)) return PTR_ERR(pdata->node); /* module control (npn-channel based, global) interface */ if (pdata->ctrl_ops) { ++devno; pdata->ctrl_node = nvhost_client_device_create(dev, &pdata->ctrl_cdev, "ctrl-", devno, pdata->ctrl_ops); if (IS_ERR(pdata->ctrl_node)) return PTR_ERR(pdata->ctrl_node); } return 0; } static void nvhost_client_user_deinit(struct platform_device *dev) { struct nvhost_master *nvhost_master = nvhost_get_host(dev); struct nvhost_device_data *pdata = platform_get_drvdata(dev); if (pdata->kernel_only) return; if (!IS_ERR_OR_NULL(pdata->node)) { device_destroy(nvhost_master->nvhost_class, pdata->cdev.dev); cdev_del(&pdata->cdev); } if (!IS_ERR_OR_NULL(pdata->as_node)) { device_destroy(nvhost_master->nvhost_class, pdata->as_cdev.dev); cdev_del(&pdata->as_cdev); } if (!IS_ERR_OR_NULL(pdata->ctrl_node)) { device_destroy(nvhost_master->nvhost_class, pdata->ctrl_cdev.dev); cdev_del(&pdata->ctrl_cdev); } unregister_chrdev_region(pdata->cdev_region, NVHOST_NUM_CDEV); } int nvhost_client_device_init(struct platform_device *dev) { int err; struct nvhost_master *nvhost_master = nvhost_get_host(dev); struct nvhost_device_data *pdata = platform_get_drvdata(dev); mutex_init(&pdata->userctx_list_lock); INIT_LIST_HEAD(&pdata->userctx_list); nvhost_client_devfs_name_init(dev); /* Create debugfs directory for the device */ nvhost_device_debug_init(dev); err = nvhost_client_user_init(dev); if (err) goto fail; err = nvhost_device_list_add(dev); if (err) goto fail; if (pdata->scaling_init) pdata->scaling_init(dev); #ifdef CONFIG_EVENTLIB pdata->eventlib_id = keventlib_register(4 * PAGE_SIZE, dev_name(&dev->dev), nvhost_events_json, nvhost_events_json_len); if (pdata->eventlib_id < 0) { nvhost_warn(&dev->dev, "failed to register eventlib (err=%d)", pdata->eventlib_id); pdata->eventlib_id = 0; } #endif /* reset syncpoint values for this unit */ err = nvhost_module_busy(nvhost_master->dev); if (err) goto fail_busy; nvhost_module_idle(nvhost_master->dev); /* Initialize dma parameters */ dev->dev.dma_parms = &pdata->dma_parms; dma_set_max_seg_size(&dev->dev, UINT_MAX); dev_info(&dev->dev, "initialized\n"); if (pdata->resource_policy == RESOURCE_PER_CHANNEL_INSTANCE) { nvhost_master->info.channel_policy = MAP_CHANNEL_ON_SUBMIT; nvhost_update_characteristics(dev); } if (pdata->hw_init) return pdata->hw_init(dev); return 0; fail_busy: /* Remove from nvhost device list */ nvhost_device_list_remove(dev); fail: /* Add clean-up */ dev_err(&dev->dev, "failed to init client device\n"); nvhost_client_user_deinit(dev); nvhost_device_debug_deinit(dev); return err; } EXPORT_SYMBOL(nvhost_client_device_init); int nvhost_client_device_release(struct platform_device *dev) { #ifdef CONFIG_EVENTLIB struct nvhost_device_data *pdata = platform_get_drvdata(dev); if (pdata->eventlib_id) keventlib_unregister(pdata->eventlib_id); #endif /* Release nvhost module resources */ nvhost_module_deinit(dev); /* Remove from nvhost device list */ nvhost_device_list_remove(dev); /* Release chardev and device node for user space */ nvhost_client_user_deinit(dev); /* Remove debugFS */ nvhost_device_debug_deinit(dev); return 0; } EXPORT_SYMBOL(nvhost_client_device_release); int nvhost_device_get_resources(struct platform_device *dev) { int i; void __iomem *regs = NULL; struct nvhost_device_data *pdata = platform_get_drvdata(dev); int ret; for (i = 0; i < dev->num_resources; i++) { struct resource *r = NULL; r = platform_get_resource(dev, IORESOURCE_MEM, i); /* We've run out of mem resources */ if (!r) break; regs = devm_ioremap_resource(&dev->dev, r); if (IS_ERR(regs)) { ret = PTR_ERR(regs); goto fail; } pdata->aperture[i] = regs; } return 0; fail: dev_err(&dev->dev, "failed to get register memory\n"); return ret; } int nvhost_client_device_get_resources(struct platform_device *dev) { return nvhost_device_get_resources(dev); } EXPORT_SYMBOL(nvhost_client_device_get_resources); /* This is a simple wrapper around request_firmware that takes * 'fw_name' and if available applies a SOC relative path prefix to it. * The caller is responsible for calling release_firmware later. */ const struct firmware * nvhost_client_request_firmware(struct platform_device *dev, const char *fw_name) { struct nvhost_chip_support *op = nvhost_get_chip_ops(); const struct firmware *fw; char *fw_path = NULL; int path_len, err; /* This field is NULL when calling from SYS_EXIT. Add a check here to prevent crash in request_firmware */ if (!current->fs) { WARN_ON(1); return NULL; } if (!fw_name) return NULL; if (op->soc_name) { path_len = strlen(fw_name) + strlen(op->soc_name); path_len += 2; /* for the path separator and zero terminator*/ fw_path = kzalloc(sizeof(*fw_path) * path_len, GFP_KERNEL); if (!fw_path) return NULL; snprintf(fw_path, sizeof(*fw_path) * path_len, "%s/%s", op->soc_name, fw_name); fw_name = fw_path; } err = request_firmware(&fw, fw_name, &dev->dev); kfree(fw_path); if (err) { dev_err(&dev->dev, "failed to get firmware\n"); return NULL; } /* note: caller must release_firmware */ return fw; } EXPORT_SYMBOL(nvhost_client_request_firmware); struct nvhost_channel *nvhost_find_chan_by_clientid( struct platform_device *pdev, int clientid) { struct nvhost_device_data *pdata = platform_get_drvdata(pdev); struct nvhost_channel_userctx *ctx; struct nvhost_channel *ch = NULL; mutex_lock(&pdata->userctx_list_lock); list_for_each_entry(ctx, &pdata->userctx_list, node) { if (ctx->clientid == clientid) { ch = ctx->ch; break; } } mutex_unlock(&pdata->userctx_list_lock); return ch; } #ifdef CONFIG_EVENTLIB void nvhost_eventlib_log_task(struct platform_device *pdev, u32 syncpt_id, u32 syncpt_thresh, u64 timestamp_start, u64 timestamp_end) { struct nvhost_device_data *pdata = platform_get_drvdata(pdev); struct nvhost_task_begin task_begin; struct nvhost_task_end task_end; if (!pdata->eventlib_id) return; /* * Write task start event */ task_begin.syncpt_id = syncpt_id; task_begin.syncpt_thresh = syncpt_thresh; task_begin.class_id = pdata->class; keventlib_write(pdata->eventlib_id, &task_begin, sizeof(task_begin), NVHOST_TASK_BEGIN, timestamp_start); /* * Write task end event */ task_end.syncpt_id = syncpt_id; task_end.syncpt_thresh = syncpt_thresh; task_end.class_id = pdata->class; keventlib_write(pdata->eventlib_id, &task_end, sizeof(task_end), NVHOST_TASK_END, timestamp_end); } void nvhost_eventlib_log_submit(struct platform_device *pdev, u32 syncpt_id, u32 syncpt_thresh, u64 timestamp) { struct nvhost_device_data *pdata = platform_get_drvdata(pdev); struct nvhost_task_submit task_submit; if (!pdata->eventlib_id) return; /* * Write task start event */ task_submit.syncpt_id = syncpt_id; task_submit.syncpt_thresh = syncpt_thresh; task_submit.class_id = pdata->class; task_submit.pid = current->tgid; task_submit.tid = current->pid; keventlib_write(pdata->eventlib_id, &task_submit, sizeof(task_submit), NVHOST_TASK_SUBMIT, timestamp); } void nvhost_eventlib_log_fences(struct platform_device *pdev, u32 task_syncpt_id, u32 task_syncpt_thresh, struct nvdev_fence *fences, u8 num_fences, enum nvdev_fence_kind kind, u64 timestamp) { struct nvhost_device_data *pdata = platform_get_drvdata(pdev); u8 i; if (!pdata->eventlib_id) return; for (i = 0; i < num_fences; i++) { struct nvhost_task_fence task_fence; memset(&task_fence, 0, sizeof(task_fence)); /* Basic fence fields common for all types */ task_fence.class_id = pdata->class; task_fence.kind = (u32) kind; task_fence.fence_type = fences[i].type; task_fence.task_syncpt_id = task_syncpt_id; task_fence.task_syncpt_thresh = task_syncpt_thresh; switch (fences[i].type) { case NVDEV_FENCE_TYPE_SYNCPT: task_fence.syncpt_id = fences[i].syncpoint_index; task_fence.syncpt_thresh = fences[i].syncpoint_value; break; case NVDEV_FENCE_TYPE_SYNC_FD: task_fence.sync_fd = fences[i].sync_fd; break; case NVDEV_FENCE_TYPE_SEMAPHORE: case NVDEV_FENCE_TYPE_SEMAPHORE_TS: task_fence.semaphore_handle = fences[i].semaphore_handle; task_fence.semaphore_offset = fences[i].semaphore_offset; task_fence.semaphore_value = fences[i].semaphore_value; break; default: nvhost_warn(&pdev->dev, "unknown fence type %d", fences[i].type); break; } keventlib_write(pdata->eventlib_id, &task_fence, sizeof(task_fence), NVHOST_TASK_FENCE, timestamp); } } #else void nvhost_eventlib_log_task(struct platform_device *pdev, u32 syncpt_id, u32 syncpt_thres, u64 timestamp_start, u64 timestamp_end) { } void nvhost_eventlib_log_submit(struct platform_device *pdev, u32 syncpt_id, u32 syncpt_thresh, u64 timestamp) { } void nvhost_eventlib_log_fences(struct platform_device *pdev, u32 task_syncpt_id, u32 task_syncpt_thresh, struct nvdev_fence *fences, u8 num_fences, enum nvdev_fence_kind kind, u64 timestamp) { } #endif EXPORT_SYMBOL(nvhost_eventlib_log_submit); EXPORT_SYMBOL(nvhost_eventlib_log_task); EXPORT_SYMBOL(nvhost_eventlib_log_fences);