/*
* drivers/video/tegra/host/bus_client.c
*
* Tegra Graphics Host Client Module
*
* Copyright (c) 2010-2012, NVIDIA Corporation.
*
* 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 "debug.h"
#include "bus_client.h"
#include "dev.h"
#include "nvhost_acm.h"
#include "nvhost_channel.h"
#include "nvhost_job.h"
#include "nvhost_hwctx.h"
void nvhost_read_module_regs(struct nvhost_device *ndev,
u32 offset, int count, u32 *values)
{
void __iomem *p = ndev->aperture + offset;
nvhost_module_busy(ndev);
while (count--) {
*(values++) = readl(p);
p += 4;
}
rmb();
nvhost_module_idle(ndev);
}
void nvhost_write_module_regs(struct nvhost_device *ndev,
u32 offset, int count, const u32 *values)
{
void __iomem *p = ndev->aperture + offset;
nvhost_module_busy(ndev);
while (count--) {
writel(*(values++), p);
p += 4;
}
wmb();
nvhost_module_idle(ndev);
}
struct nvhost_channel_userctx {
struct nvhost_channel *ch;
struct nvhost_hwctx *hwctx;
struct nvhost_submit_hdr_ext hdr;
int num_relocshifts;
struct nvhost_job *job;
struct nvmap_client *nvmap;
u32 timeout;
u32 priority;
int clientid;
};
static int nvhost_channelrelease(struct inode *inode, struct file *filp)
{
struct nvhost_channel_userctx *priv = filp->private_data;
trace_nvhost_channel_release(priv->ch->dev->name);
filp->private_data = NULL;
nvhost_module_remove_client(priv->ch->dev, priv);
nvhost_putchannel(priv->ch, priv->hwctx);
if (priv->hwctx)
priv->ch->ctxhandler->put(priv->hwctx);
if (priv->job)
nvhost_job_put(priv->job);
nvmap_client_put(priv->nvmap);
kfree(priv);
return 0;
}
static int nvhost_channelopen(struct inode *inode, struct file *filp)
{
struct nvhost_channel_userctx *priv;
struct nvhost_channel *ch;
ch = container_of(inode->i_cdev, struct nvhost_channel, cdev);
ch = nvhost_getchannel(ch);
if (!ch)
return -ENOMEM;
trace_nvhost_channel_open(ch->dev->name);
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
nvhost_putchannel(ch, NULL);
return -ENOMEM;
}
filp->private_data = priv;
priv->ch = ch;
nvhost_module_add_client(ch->dev, priv);
if (ch->ctxhandler && ch->ctxhandler->alloc) {
priv->hwctx = ch->ctxhandler->alloc(ch->ctxhandler, ch);
if (!priv->hwctx)
goto fail;
}
priv->priority = NVHOST_PRIORITY_MEDIUM;
priv->clientid = atomic_add_return(1,
&nvhost_get_host(ch->dev)->clientid);
priv->timeout = MAX_STUCK_CHECK_COUNT * SYNCPT_CHECK_PERIOD;
return 0;
fail:
nvhost_channelrelease(inode, filp);
return -ENOMEM;
}
static int set_submit(struct nvhost_channel_userctx *ctx)
{
struct device *device = &ctx->ch->dev->dev;
/* submit should have at least 1 cmdbuf */
if (!ctx->hdr.num_cmdbufs)
return -EIO;
if (!ctx->nvmap) {
dev_err(device, "no nvmap context set\n");
return -EFAULT;
}
ctx->job = nvhost_job_alloc(ctx->ch,
ctx->hwctx,
&ctx->hdr,
ctx->nvmap,
ctx->priority,
ctx->clientid);
if (!ctx->job)
return -ENOMEM;
ctx->job->timeout = ctx->timeout;
if (ctx->hdr.submit_version >= NVHOST_SUBMIT_VERSION_V2)
ctx->num_relocshifts = ctx->hdr.num_relocs;
return 0;
}
static void reset_submit(struct nvhost_channel_userctx *ctx)
{
ctx->hdr.num_cmdbufs = 0;
ctx->hdr.num_relocs = 0;
ctx->num_relocshifts = 0;
ctx->hdr.num_waitchks = 0;
if (ctx->job) {
nvhost_job_put(ctx->job);
ctx->job = NULL;
}
}
static ssize_t nvhost_channelwrite(struct file *filp, const char __user *buf,
size_t count, loff_t *offp)
{
struct nvhost_channel_userctx *priv = filp->private_data;
size_t remaining = count;
int err = 0;
struct nvhost_job *job = priv->job;
struct nvhost_submit_hdr_ext *hdr = &priv->hdr;
const char *chname = priv->ch->dev->name;
if (!job)
return -EIO;
while (remaining) {
size_t consumed;
if (!hdr->num_relocs &&
!priv->num_relocshifts &&
!hdr->num_cmdbufs &&
!hdr->num_waitchks) {
consumed = sizeof(struct nvhost_submit_hdr);
if (remaining < consumed)
break;
if (copy_from_user(hdr, buf, consumed)) {
err = -EFAULT;
break;
}
hdr->submit_version = NVHOST_SUBMIT_VERSION_V0;
err = set_submit(priv);
if (err)
break;
trace_nvhost_channel_write_submit(chname,
count, hdr->num_cmdbufs, hdr->num_relocs,
hdr->syncpt_id, hdr->syncpt_incrs);
} else if (hdr->num_cmdbufs) {
struct nvhost_cmdbuf cmdbuf;
consumed = sizeof(cmdbuf);
if (remaining < consumed)
break;
if (copy_from_user(&cmdbuf, buf, consumed)) {
err = -EFAULT;
break;
}
trace_nvhost_channel_write_cmdbuf(chname,
cmdbuf.mem, cmdbuf.words, cmdbuf.offset);
nvhost_job_add_gather(job,
cmdbuf.mem, cmdbuf.words, cmdbuf.offset);
hdr->num_cmdbufs--;
} else if (hdr->num_relocs) {
int numrelocs = remaining / sizeof(struct nvhost_reloc);
if (!numrelocs)
break;
numrelocs = min_t(int, numrelocs, priv->hdr.num_relocs);
consumed = numrelocs * sizeof(struct nvhost_reloc);
if (copy_from_user(&job->relocarray[job->num_relocs],
buf, consumed)) {
err = -EFAULT;
break;
}
while (numrelocs) {
struct nvhost_reloc *reloc =
&job->relocarray[job->num_relocs];
trace_nvhost_channel_write_reloc(chname,
reloc->cmdbuf_mem,
reloc->cmdbuf_offset,
reloc->target,
reloc->target_offset);
job->num_relocs++;
hdr->num_relocs--;
numrelocs--;
}
} else if (hdr->num_waitchks) {
int numwaitchks =
(remaining / sizeof(struct nvhost_waitchk));
if (!numwaitchks)
break;
numwaitchks = min_t(int,
numwaitchks, hdr->num_waitchks);
consumed = numwaitchks * sizeof(struct nvhost_waitchk);
if (copy_from_user(&job->waitchk[job->num_waitchk],
buf, consumed)) {
err = -EFAULT;
break;
}
trace_nvhost_channel_write_waitchks(
chname, numwaitchks,
hdr->waitchk_mask);
job->num_waitchk += numwaitchks;
hdr->num_waitchks -= numwaitchks;
} else if (priv->num_relocshifts) {
int next_shift =
job->num_relocs - priv->num_relocshifts;
int num =
(remaining / sizeof(struct nvhost_reloc_shift));
if (!num)
break;
num = min_t(int, num, priv->num_relocshifts);
consumed = num * sizeof(struct nvhost_reloc_shift);
if (copy_from_user(&job->relocshiftarray[next_shift],
buf, consumed)) {
err = -EFAULT;
break;
}
priv->num_relocshifts -= num;
} else {
err = -EFAULT;
break;
}
remaining -= consumed;
buf += consumed;
}
if (err < 0) {
dev_err(&priv->ch->dev->dev, "channel write error\n");
reset_submit(priv);
return err;
}
return count - remaining;
}
static int nvhost_ioctl_channel_flush(
struct nvhost_channel_userctx *ctx,
struct nvhost_get_param_args *args,
int null_kickoff)
{
struct nvhost_device *ndev = to_nvhost_device(&ctx->ch->dev->dev);
int err;
trace_nvhost_ioctl_channel_flush(ctx->ch->dev->name);
if (!ctx->job ||
ctx->hdr.num_relocs ||
ctx->hdr.num_cmdbufs ||
ctx->hdr.num_waitchks) {
reset_submit(ctx);
dev_err(&ndev->dev, "channel submit out of sync\n");
return -EFAULT;
}
err = nvhost_job_pin(ctx->job, &nvhost_get_host(ndev)->syncpt);
if (err) {
dev_warn(&ndev->dev, "nvhost_job_pin failed: %d\n", err);
return err;
}
if (nvhost_debug_null_kickoff_pid == current->tgid)
null_kickoff = 1;
ctx->job->null_kickoff = null_kickoff;
if ((nvhost_debug_force_timeout_pid == current->tgid) &&
(nvhost_debug_force_timeout_channel == ctx->ch->chid)) {
ctx->timeout = nvhost_debug_force_timeout_val;
}
/* context switch if needed, and submit user's gathers to the channel */
err = nvhost_channel_submit(ctx->job);
args->value = ctx->job->syncpt_end;
if (err)
nvhost_job_unpin(ctx->job);
nvhost_job_put(ctx->job);
ctx->job = NULL;
return err;
}
static int nvhost_ioctl_channel_read_3d_reg(struct nvhost_channel_userctx *ctx,
struct nvhost_read_3d_reg_args *args)
{
BUG_ON(!channel_op().read3dreg);
return channel_op().read3dreg(ctx->ch, ctx->hwctx,
args->offset, &args->value);
}
static long nvhost_channelctl(struct file *filp,
unsigned int cmd, unsigned long arg)
{
struct nvhost_channel_userctx *priv = filp->private_data;
u8 buf[NVHOST_IOCTL_CHANNEL_MAX_ARG_SIZE];
int err = 0;
if ((_IOC_TYPE(cmd) != NVHOST_IOCTL_MAGIC) ||
(_IOC_NR(cmd) == 0) ||
(_IOC_NR(cmd) > NVHOST_IOCTL_CHANNEL_LAST))
return -EFAULT;
BUG_ON(_IOC_SIZE(cmd) > NVHOST_IOCTL_CHANNEL_MAX_ARG_SIZE);
if (_IOC_DIR(cmd) & _IOC_WRITE) {
if (copy_from_user(buf, (void __user *)arg, _IOC_SIZE(cmd)))
return -EFAULT;
}
switch (cmd) {
case NVHOST_IOCTL_CHANNEL_FLUSH:
err = nvhost_ioctl_channel_flush(priv, (void *)buf, 0);
break;
case NVHOST_IOCTL_CHANNEL_NULL_KICKOFF:
err = nvhost_ioctl_channel_flush(priv, (void *)buf, 1);
break;
case NVHOST_IOCTL_CHANNEL_SUBMIT_EXT:
{
struct nvhost_submit_hdr_ext *hdr;
if (priv->hdr.num_relocs ||
priv->num_relocshifts ||
priv->hdr.num_cmdbufs ||
priv->hdr.num_waitchks) {
reset_submit(priv);
dev_err(&priv->ch->dev->dev,
"channel submit out of sync\n");
err = -EIO;
break;
}
hdr = (struct nvhost_submit_hdr_ext *)buf;
if (hdr->submit_version > NVHOST_SUBMIT_VERSION_MAX_SUPPORTED) {
dev_err(&priv->ch->dev->dev,
"submit version %d > max supported %d\n",
hdr->submit_version,
NVHOST_SUBMIT_VERSION_MAX_SUPPORTED);
err = -EINVAL;
break;
}
memcpy(&priv->hdr, hdr, sizeof(struct nvhost_submit_hdr_ext));
err = set_submit(priv);
trace_nvhost_ioctl_channel_submit(priv->ch->dev->name,
priv->hdr.submit_version,
priv->hdr.num_cmdbufs, priv->hdr.num_relocs,
priv->hdr.num_waitchks,
priv->hdr.syncpt_id, priv->hdr.syncpt_incrs);
break;
}
case NVHOST_IOCTL_CHANNEL_GET_SYNCPOINTS:
/* host syncpt ID is used by the RM (and never be given out) */
BUG_ON(priv->ch->dev->syncpts & (1 << NVSYNCPT_GRAPHICS_HOST));
((struct nvhost_get_param_args *)buf)->value =
priv->ch->dev->syncpts;
break;
case NVHOST_IOCTL_CHANNEL_GET_WAITBASES:
((struct nvhost_get_param_args *)buf)->value =
priv->ch->dev->waitbases;
break;
case NVHOST_IOCTL_CHANNEL_GET_MODMUTEXES:
((struct nvhost_get_param_args *)buf)->value =
priv->ch->dev->modulemutexes;
break;
case NVHOST_IOCTL_CHANNEL_SET_NVMAP_FD:
{
int fd = (int)((struct nvhost_set_nvmap_fd_args *)buf)->fd;
struct nvmap_client *new_client = nvmap_client_get_file(fd);
if (IS_ERR(new_client)) {
err = PTR_ERR(new_client);
break;
}
if (priv->nvmap)
nvmap_client_put(priv->nvmap);
priv->nvmap = new_client;
break;
}
case NVHOST_IOCTL_CHANNEL_READ_3D_REG:
err = nvhost_ioctl_channel_read_3d_reg(priv, (void *)buf);
break;
case NVHOST_IOCTL_CHANNEL_GET_CLK_RATE:
{
unsigned long rate;
struct nvhost_clk_rate_args *arg =
(struct nvhost_clk_rate_args *)buf;
err = nvhost_module_get_rate(priv->ch->dev, &rate, 0);
if (err == 0)
arg->rate = rate;
break;
}
case NVHOST_IOCTL_CHANNEL_SET_CLK_RATE:
{
struct nvhost_clk_rate_args *arg =
(struct nvhost_clk_rate_args *)buf;
unsigned long rate = (unsigned long)arg->rate;
err = nvhost_module_set_rate(priv->ch->dev, priv, rate, 0);
break;
}
case NVHOST_IOCTL_CHANNEL_SET_TIMEOUT:
priv->timeout =
(u32)((struct nvhost_set_timeout_args *)buf)->timeout;
dev_dbg(&priv->ch->dev->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 =
priv->hwctx->has_timedout;
break;
case NVHOST_IOCTL_CHANNEL_SET_PRIORITY:
priv->priority =
(u32)((struct nvhost_set_priority_args *)buf)->priority;
break;
default:
err = -ENOTTY;
break;
}
if ((err == 0) && (_IOC_DIR(cmd) & _IOC_READ))
err = copy_to_user((void __user *)arg, buf, _IOC_SIZE(cmd));
return err;
}
static const struct file_operations nvhost_channelops = {
.owner = THIS_MODULE,
.release = nvhost_channelrelease,
.open = nvhost_channelopen,
.write = nvhost_channelwrite,
.unlocked_ioctl = nvhost_channelctl
};
int nvhost_client_user_init(struct nvhost_device *dev)
{
int err, devno;
struct nvhost_channel *ch = dev->channel;
err = alloc_chrdev_region(&devno, 0, 1, IFACE_NAME);
if (err < 0) {
dev_err(&dev->dev, "failed to allocate devno\n");
goto fail;
}
cdev_init(&ch->cdev, &nvhost_channelops);
ch->cdev.owner = THIS_MODULE;
err = cdev_add(&ch->cdev, devno, 1);
if (err < 0) {
dev_err(&dev->dev,
"failed to add chan %i cdev\n", dev->index);
goto fail;
}
ch->node = device_create(nvhost_get_host(dev)->nvhost_class,
NULL, devno, NULL,
IFACE_NAME "-%s", dev->name);
if (IS_ERR(ch->node)) {
err = PTR_ERR(ch->node);
dev_err(&dev->dev,
"failed to create %s channel device\n", dev->name);
goto fail;
}
return 0;
fail:
return err;
}
int nvhost_client_device_init(struct nvhost_device *dev)
{
int err;
struct nvhost_master *nvhost_master = nvhost_get_host(dev);
struct nvhost_channel *ch;
ch = nvhost_alloc_channel(dev->index);
if (ch == NULL)
return -ENODEV;
/* store the pointer to this device for channel */
ch->dev = dev;
err = nvhost_channel_init(ch, nvhost_master, dev->index);
if (err)
goto fail;
err = nvhost_client_user_init(dev);
if (err)
goto fail;
err = nvhost_module_init(dev);
if (err)
goto fail;
dev_info(&dev->dev, "initialized\n");
return 0;
fail:
/* Add clean-up */
nvhost_free_channel(ch);
return err;
}
int nvhost_client_device_suspend(struct nvhost_device *dev)
{
int ret = 0;
ret = nvhost_channel_suspend(dev->channel);
if (ret)
return ret;
dev_info(&dev->dev, "suspend status: %d\n", ret);
return ret;
}
int nvhost_client_device_get_resources(struct nvhost_device *dev)
{
struct resource *r = NULL;
void __iomem *regs = NULL;
struct resource *reg_mem = NULL;
r = nvhost_get_resource(dev, IORESOURCE_MEM, 0);
if (!r)
goto fail;
reg_mem = request_mem_region(r->start, resource_size(r), dev->name);
if (!reg_mem)
goto fail;
regs = ioremap(r->start, resource_size(r));
if (!regs)
goto fail;
dev->reg_mem = reg_mem;
dev->aperture = regs;
return 0;
fail:
if (reg_mem)
release_mem_region(r->start, resource_size(r));
if (regs)
iounmap(regs);
dev_err(&dev->dev, "failed to get register memory\n");
return -ENXIO;
}