1973 lines
47 KiB
C
1973 lines
47 KiB
C
|
/*
|
||
|
* 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 <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/string.h>
|
||
|
#include <linux/spinlock.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/cdev.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/file.h>
|
||
|
#include <linux/clk.h>
|
||
|
#include <linux/hrtimer.h>
|
||
|
#include <linux/export.h>
|
||
|
#include <linux/firmware.h>
|
||
|
#include <linux/dma-mapping.h>
|
||
|
#include <soc/tegra/chip-id.h>
|
||
|
#include <linux/anon_inodes.h>
|
||
|
#include <linux/crc32.h>
|
||
|
|
||
|
#include <trace/events/nvhost.h>
|
||
|
#include <uapi/linux/nvhost_events.h>
|
||
|
#include <uapi/linux/nvdev_fence.h>
|
||
|
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/string.h>
|
||
|
|
||
|
#include <linux/nvhost.h>
|
||
|
#include <uapi/linux/nvhost_ioctl.h>
|
||
|
#include <linux/nospec.h>
|
||
|
|
||
|
#ifdef CONFIG_EVENTLIB
|
||
|
#include <linux/keventlib.h>
|
||
|
#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 <dev>, and ctrl-<dev> */
|
||
|
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);
|