video: tegra: host: Do not access cmdbuf_ext if NULL
[linux-3.10.git] / drivers / video / tegra / host / bus_client.c
index 3259f15..030e28c 100644 (file)
@@ -1,9 +1,7 @@
 /*
- * drivers/video/tegra/host/bus_client.c
- *
  * Tegra Graphics Host Client Module
  *
- * Copyright (c) 2010-2013, NVIDIA Corporation.
+ * Copyright (c) 2010-2014, 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,
@@ -29,6 +27,8 @@
 #include <linux/hrtimer.h>
 #include <linux/export.h>
 #include <linux/firmware.h>
+#include <linux/dma-mapping.h>
+#include <linux/tegra-soc.h>
 
 #include <trace/events/nvhost.h>
 
 #include <linux/nvhost_ioctl.h>
 
 #include <mach/gpufuse.h>
-#include <mach/hardware.h>
 
 #include "debug.h"
 #include "bus_client.h"
 #include "dev.h"
+#include "class_ids.h"
+#include "nvhost_as.h"
 #include "nvhost_memmgr.h"
 #include "chip_support.h"
 #include "nvhost_acm.h"
 #include "nvhost_job.h"
 #include "nvhost_hwctx.h"
 #include "user_hwctx.h"
+#include "nvhost_sync.h"
 
 static int validate_reg(struct platform_device *ndev, u32 offset, int count)
 {
-       struct resource *r = platform_get_resource(ndev, IORESOURCE_MEM, 0);
        int err = 0;
+       struct resource *r;
+       struct nvhost_device_data *pdata = platform_get_drvdata(ndev);
+
+       r = platform_get_resource(pdata->master ? pdata->master : 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))
@@ -66,19 +76,49 @@ static int validate_reg(struct platform_device *ndev, u32 offset, int count)
        return err;
 }
 
+static __iomem void *get_aperture(struct platform_device *pdev)
+{
+       struct nvhost_device_data *pdata = platform_get_drvdata(pdev);
+
+       if (pdata->master)
+               pdata = platform_get_drvdata(pdata->master);
+
+       return pdata->aperture[0];
+}
+
+void host1x_writel(struct platform_device *pdev, u32 r, u32 v)
+{
+       void __iomem *addr = get_aperture(pdev) + 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) + r;
+       u32 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);
+
 int nvhost_read_module_regs(struct platform_device *ndev,
                        u32 offset, int count, u32 *values)
 {
-       struct nvhost_device_data *pdata = platform_get_drvdata(ndev);
-       void __iomem *p = pdata->aperture[0] + offset;
+       void __iomem *p = get_aperture(ndev);
        int err;
 
+       if (!p)
+               return -ENODEV;
+
        /* verify offset */
        err = validate_reg(ndev, offset, count);
        if (err)
                return err;
 
        nvhost_module_busy(ndev);
+       p += offset;
        while (count--) {
                *(values++) = readl(p);
                p += 4;
@@ -92,11 +132,11 @@ int nvhost_read_module_regs(struct platform_device *ndev,
 int nvhost_write_module_regs(struct platform_device *ndev,
                        u32 offset, int count, const u32 *values)
 {
-       void __iomem *p;
        int err;
-       struct nvhost_device_data *pdata = platform_get_drvdata(ndev);
+       void __iomem *p = get_aperture(ndev);
 
-       p = pdata->aperture[0] + offset;
+       if (!p)
+               return -ENODEV;
 
        /* verify offset */
        err = validate_reg(ndev, offset, count);
@@ -104,6 +144,7 @@ int nvhost_write_module_regs(struct platform_device *ndev,
                return err;
 
        nvhost_module_busy(ndev);
+       p += offset;
        while (count--) {
                writel(*(values++), p);
                p += 4;
@@ -117,8 +158,6 @@ int nvhost_write_module_regs(struct platform_device *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 mem_mgr *memmgr;
        u32 timeout;
@@ -136,14 +175,24 @@ static int nvhost_channelrelease(struct inode *inode, struct file *filp)
        filp->private_data = NULL;
 
        nvhost_module_remove_client(priv->ch->dev, priv);
-       nvhost_putchannel(priv->ch, priv->hwctx);
 
-       if (priv->hwctx)
+       if (priv->hwctx) {
+               struct nvhost_channel *ch = priv->ch;
+               struct nvhost_hwctx *ctx = priv->hwctx;
+
+               mutex_lock(&ch->submitlock);
+               if (ch->cur_ctx == ctx)
+                       ch->cur_ctx = NULL;
+               mutex_unlock(&ch->submitlock);
+
                priv->hwctx->h->put(priv->hwctx);
+       }
 
        if (priv->job)
                nvhost_job_put(priv->job);
 
+       nvhost_putchannel(priv->ch);
+
        nvhost_memmgr_put_mgr(priv->memmgr);
        kfree(priv);
        return 0;
@@ -153,264 +202,165 @@ static int nvhost_channelopen(struct inode *inode, struct file *filp)
 {
        struct nvhost_channel_userctx *priv;
        struct nvhost_channel *ch;
+       struct nvhost_device_data *pdata;
 
        ch = container_of(inode->i_cdev, struct nvhost_channel, cdev);
-       ch = nvhost_getchannel(ch);
+       ch = nvhost_getchannel(ch, false);
        if (!ch)
                return -ENOMEM;
        trace_nvhost_channel_open(dev_name(&ch->dev->dev));
 
        priv = kzalloc(sizeof(*priv), GFP_KERNEL);
        if (!priv) {
-               nvhost_putchannel(ch, NULL);
+               nvhost_putchannel(ch);
                return -ENOMEM;
        }
        filp->private_data = priv;
        priv->ch = ch;
-       if(nvhost_module_add_client(ch->dev, priv))
+       if (nvhost_module_add_client(ch->dev, priv))
                goto fail;
 
        if (ch->ctxhandler && ch->ctxhandler->alloc) {
+               nvhost_module_busy(ch->dev);
                priv->hwctx = ch->ctxhandler->alloc(ch->ctxhandler, ch);
+               nvhost_module_idle(ch->dev);
                if (!priv->hwctx)
                        goto fail;
        }
        priv->priority = NVHOST_PRIORITY_MEDIUM;
        priv->clientid = atomic_add_return(1,
                        &nvhost_get_host(ch->dev)->clientid);
-       priv->timeout = CONFIG_TEGRA_GRHOST_DEFAULT_TIMEOUT;
+       pdata = dev_get_drvdata(ch->dev->dev.parent);
+       priv->timeout = pdata->nvhost_timeout_default;
        priv->timeout_debug_dump = true;
-       if (tegra_platform_is_linsim())
+       if (!tegra_platform_is_silicon())
                priv->timeout = 0;
-
        return 0;
 fail:
        nvhost_channelrelease(inode, filp);
        return -ENOMEM;
 }
 
-static int set_submit(struct nvhost_channel_userctx *ctx)
+static int nvhost_ioctl_channel_alloc_obj_ctx(
+       struct nvhost_channel_userctx *ctx,
+       struct nvhost_alloc_obj_ctx_args *args)
 {
-       struct platform_device *ndev = ctx->ch->dev;
-       struct nvhost_master *host = nvhost_get_host(ndev);
-
-       /* submit should have at least 1 cmdbuf */
-       if (!ctx->hdr.num_cmdbufs ||
-                       !nvhost_syncpt_is_valid(&host->syncpt,
-                               ctx->hdr.syncpt_id))
-               return -EIO;
-
-       if (!ctx->memmgr) {
-               dev_err(&ndev->dev, "no nvmap context set\n");
-               return -EFAULT;
-       }
+       int ret;
 
-       if (ctx->job) {
-               dev_warn(&ndev->dev, "performing channel submit when a job already exists\n");
-               nvhost_job_put(ctx->job);
-       }
-       ctx->job = nvhost_job_alloc(ctx->ch,
-                       ctx->hwctx,
-                       ctx->hdr.num_cmdbufs,
-                       ctx->hdr.num_relocs,
-                       ctx->hdr.num_waitchks,
-                       1,
-                       ctx->memmgr);
-       if (!ctx->job)
-               return -ENOMEM;
-       ctx->job->timeout = ctx->timeout;
-       ctx->job->sp->id = ctx->hdr.syncpt_id;
-       ctx->job->sp->incrs = ctx->hdr.syncpt_incrs;
-       ctx->job->hwctx_syncpt_idx = 0;
-       ctx->job->num_syncpts = 1;
-       ctx->job->priority = ctx->priority;
-       ctx->job->clientid = ctx->clientid;
-       ctx->job->timeout_debug_dump = ctx->timeout_debug_dump;
-
-       if (ctx->hdr.submit_version >= NVHOST_SUBMIT_VERSION_V2)
-               ctx->num_relocshifts = ctx->hdr.num_relocs;
-
-       return 0;
+       BUG_ON(!channel_op(ctx->ch).alloc_obj);
+       nvhost_module_busy(ctx->ch->dev);
+       ret = channel_op(ctx->ch).alloc_obj(ctx->hwctx, args);
+       nvhost_module_idle(ctx->ch->dev);
+       return ret;
 }
 
-static void reset_submit(struct nvhost_channel_userctx *ctx)
+static int nvhost_ioctl_channel_free_obj_ctx(
+       struct nvhost_channel_userctx *ctx,
+       struct nvhost_free_obj_ctx_args *args)
 {
-       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;
-       }
+       int ret;
+
+       BUG_ON(!channel_op(ctx->ch).free_obj);
+       nvhost_module_busy(ctx->ch->dev);
+       ret = channel_op(ctx->ch).free_obj(ctx->hwctx, args);
+       nvhost_module_idle(ctx->ch->dev);
+       return ret;
 }
 
-static ssize_t nvhost_channelwrite(struct file *filp, const char __user *buf,
-                               size_t count, loff_t *offp)
+static int nvhost_ioctl_channel_alloc_gpfifo(
+       struct nvhost_channel_userctx *ctx,
+       struct nvhost_alloc_gpfifo_args *args)
 {
-       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);
-                       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;
-       }
+       int ret;
 
-       if (err < 0) {
-               dev_err(&priv->ch->dev->dev, "channel write error\n");
-               reset_submit(priv);
-               return err;
-       }
+       BUG_ON(!channel_op(ctx->ch).alloc_gpfifo);
+       nvhost_module_busy(ctx->ch->dev);
+       ret = channel_op(ctx->ch).alloc_gpfifo(ctx->hwctx, args);
+       nvhost_module_idle(ctx->ch->dev);
+       return ret;
+}
 
-       return count - remaining;
+static int nvhost_ioctl_channel_set_error_notifier(
+       struct nvhost_channel_userctx *ctx,
+       struct nvhost_set_error_notifier *args)
+{
+       int ret;
+       BUG_ON(!channel_op(ctx->ch).set_error_notifier);
+       ret = channel_op(ctx->ch).set_error_notifier(ctx->hwctx, args);
+       return ret;
 }
 
-static int nvhost_ioctl_channel_flush(
+static int nvhost_ioctl_channel_submit_gpfifo(
        struct nvhost_channel_userctx *ctx,
-       struct nvhost_get_param_args *args,
-       int null_kickoff)
+       struct nvhost_submit_gpfifo_args *args)
 {
-       struct platform_device *ndev = to_platform_device(&ctx->ch->dev->dev);
-       int err;
+       void *gpfifo;
+       u32 size;
+       int ret = 0;
 
-       trace_nvhost_ioctl_channel_flush(ctx->ch->dev->name);
+       if (!ctx->hwctx || ctx->hwctx->has_timedout)
+               return -ETIMEDOUT;
 
-       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;
-       }
+       size = args->num_entries * sizeof(struct nvhost_gpfifo);
 
-       err = nvhost_job_pin(ctx->job, &nvhost_get_host(ndev)->syncpt);
-       if (err) {
-               dev_warn(&ndev->dev, "nvhost_job_pin failed: %d\n", err);
-               goto fail;
+       gpfifo = kzalloc(size, GFP_KERNEL);
+       if (!gpfifo)
+               return -ENOMEM;
+
+       if (copy_from_user(gpfifo,
+                          (void __user *)(uintptr_t)args->gpfifo, size)) {
+               ret = -EINVAL;
+               goto clean_up;
        }
 
-       if (nvhost_debug_null_kickoff_pid == current->tgid)
-               null_kickoff = 1;
-       ctx->job->null_kickoff = null_kickoff;
+       BUG_ON(!channel_op(ctx->ch).submit_gpfifo);
 
-       if ((nvhost_debug_force_timeout_pid == current->tgid) &&
-           (nvhost_debug_force_timeout_channel == ctx->ch->chid)) {
-               ctx->timeout = nvhost_debug_force_timeout_val;
-       }
+       nvhost_module_busy(ctx->ch->dev);
+       ret = channel_op(ctx->ch).submit_gpfifo(ctx->hwctx, gpfifo,
+                       args->num_entries, &args->fence, args->flags);
+       nvhost_module_idle(ctx->ch->dev);
+clean_up:
+       kfree(gpfifo);
+       return ret;
+}
 
-       /* context switch if needed, and submit user's gathers to the channel */
-       err = nvhost_channel_submit(ctx->job);
-       args->value = ctx->job->sp->fence;
+static int nvhost_ioctl_channel_wait(
+       struct nvhost_channel_userctx *ctx,
+       struct nvhost_wait_args *args)
+{
+       int ret;
 
-fail:
-       if (err)
-               nvhost_job_unpin(ctx->job);
+       BUG_ON(!channel_op(ctx->ch).wait);
+       nvhost_module_busy(ctx->ch->dev);
+       ret = channel_op(ctx->ch).wait(ctx->hwctx, args);
+       nvhost_module_idle(ctx->ch->dev);
+       return ret;
+}
 
-       nvhost_job_put(ctx->job);
-       ctx->job = NULL;
+static int nvhost_ioctl_channel_set_priority(
+       struct nvhost_channel_userctx *ctx,
+       struct nvhost_set_priority_args *args)
+{
+       int ret = 0;
+       if (channel_op(ctx->ch).set_priority) {
+               nvhost_module_busy(ctx->ch->dev);
+               ret = channel_op(ctx->ch).set_priority(ctx->hwctx, args);
+               nvhost_module_idle(ctx->ch->dev);
+       }
+       return ret;
+}
 
-       return err;
+static int nvhost_ioctl_channel_zcull_bind(
+       struct nvhost_channel_userctx *ctx,
+       struct nvhost_zcull_bind_args *args)
+{
+       int ret;
+
+       BUG_ON(!channel_zcull_op(ctx->ch).bind);
+       nvhost_module_busy(ctx->ch->dev);
+       ret = channel_zcull_op(ctx->ch).bind(ctx->hwctx, args);
+       nvhost_module_idle(ctx->ch->dev);
+       return ret;
 }
 
 static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx,
@@ -423,6 +373,8 @@ static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx,
        int num_syncpt_incrs = args->num_syncpt_incrs;
        struct nvhost_cmdbuf __user *cmdbufs =
                (struct nvhost_cmdbuf *)(uintptr_t)args->cmdbufs;
+       struct nvhost_cmdbuf __user *cmdbuf_exts =
+               (struct nvhost_cmdbuf *)(uintptr_t)args->cmdbuf_exts;
        struct nvhost_reloc __user *relocs =
                (struct nvhost_reloc *)(uintptr_t)args->relocs;
        struct nvhost_reloc_shift __user *reloc_shifts =
@@ -433,9 +385,10 @@ static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx,
                (struct nvhost_syncpt_incr *)(uintptr_t)args->syncpt_incrs;
        u32 __user *waitbases = (u32 *)(uintptr_t)args->waitbases;
        u32 __user *fences = (u32 *)(uintptr_t)args->fences;
+       u32 __user *class_ids = (u32 *)(uintptr_t)args->class_ids;
 
        struct nvhost_master *host = nvhost_get_host(ctx->ch->dev);
-       u32 *local_waitbases = NULL;
+       u32 *local_waitbases = NULL, *local_class_ids = NULL;
        int err, i, hwctx_syncpt_idx = -1;
 
        if (num_syncpt_incrs > host->info.nb_pts)
@@ -457,17 +410,46 @@ static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx,
        job->priority = ctx->priority;
        job->clientid = ctx->clientid;
 
-       while (num_cmdbufs) {
+       /* mass copy class_ids */
+       if (args->class_ids) {
+               local_class_ids = kzalloc(sizeof(u32) * num_cmdbufs,
+                       GFP_KERNEL);
+               if (!local_class_ids) {
+                       err = -ENOMEM;
+                       goto fail;
+               }
+               err = copy_from_user(local_class_ids, class_ids,
+                       sizeof(u32) * num_cmdbufs);
+               if (err) {
+                       err = -EINVAL;
+                       goto fail;
+               }
+       }
+
+       for (i = 0; i < num_cmdbufs; ++i) {
                struct nvhost_cmdbuf cmdbuf;
-               err = copy_from_user(&cmdbuf, cmdbufs, sizeof(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)
                        goto fail;
-               nvhost_job_add_gather(job,
-                               cmdbuf.mem, cmdbuf.words, cmdbuf.offset);
-               num_cmdbufs--;
-               cmdbufs++;
+
+               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;
+
+               nvhost_job_add_gather(job, cmdbuf.mem, cmdbuf.words,
+                                     cmdbuf.offset, class_id,
+                                     cmdbuf_ext.pre_fence);
        }
 
+       kfree(local_class_ids);
+       local_class_ids = NULL;
+
        err = copy_from_user(job->relocarray,
                        relocs, sizeof(*relocs) * num_relocs);
        if (err)
@@ -487,6 +469,11 @@ static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx,
        if (args->waitbases) {
                local_waitbases = kzalloc(sizeof(u32) * num_syncpt_incrs,
                        GFP_KERNEL);
+               if (!local_waitbases) {
+                       err = -ENOMEM;
+                       goto fail;
+               }
+
                err = copy_from_user(local_waitbases, waitbases,
                        sizeof(u32) * num_syncpt_incrs);
                if (err) {
@@ -495,8 +482,8 @@ static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx,
                }
        }
 
-       /* set valid id for hwctx_syncpt_idx if no hwctx is present */
-       if (!ctx->hwctx)
+       /* set valid id for hwctx_syncpt_idx if hwctx does not provide one */
+       if (!ctx->hwctx || ctx->hwctx->h->syncpt == NVSYNCPT_INVALID)
                hwctx_syncpt_idx = 0;
 
        /*
@@ -556,7 +543,9 @@ static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx,
                job->sp[job->hwctx_syncpt_idx].id,
                job->sp[job->hwctx_syncpt_idx].incrs);
 
+       nvhost_module_busy(ctx->ch->dev);
        err = nvhost_job_pin(job, &nvhost_get_host(ctx->ch->dev)->syncpt);
+       nvhost_module_idle(ctx->ch->dev);
        if (err)
                goto fail;
 
@@ -580,7 +569,26 @@ static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx,
                        fences++;
                }
 
-       args->fence = job->sp[job->hwctx_syncpt_idx].fence;
+       /* 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[num_syncpt_incrs];
+
+               for (i = 0; i < num_syncpt_incrs; i++) {
+                       pts[i].id = job->sp[i].id;
+                       pts[i].thresh = job->sp[i].fence;
+               }
+
+               err = nvhost_sync_create_fence(
+                               &nvhost_get_host(ctx->ch->dev)->syncpt,
+                               pts, num_syncpt_incrs, "fence", &args->fence);
+               if (err)
+                       goto fail;
+       } else if (num_syncpt_incrs == 1)
+               args->fence = job->sp[job->hwctx_syncpt_idx].fence;
+       else
+               args->fence = 0;
 
        nvhost_job_put(job);
 
@@ -590,6 +598,7 @@ fail_submit:
        nvhost_job_unpin(job);
 fail:
        nvhost_job_put(job);
+       kfree(local_class_ids);
        kfree(local_waitbases);
        return err;
 }
@@ -684,7 +693,10 @@ static int nvhost_ioctl_channel_set_ctxswitch(
                        pdata->syncpts[0], pdata->waitbases[0],
                        save_incr.syncpt_incrs, restore_incr.syncpt_incrs);
 
-       nhwctx->memmgr = ctx->hwctx->memmgr;
+       nhwctx->memmgr = nvhost_memmgr_get_mgr(ctx->memmgr);
+       if (!nhwctx->memmgr)
+               goto fail_set_restore;
+
        err = user_hwctx_set_restore(hwctx, cmdbuf_restore.mem,
                        cmdbuf_restore.offset, cmdbuf_restore.words);
        if (err)
@@ -713,6 +725,18 @@ fail:
        return err;
 }
 
+#if defined(CONFIG_GK20A_CYCLE_STATS)
+static int nvhost_ioctl_channel_cycle_stats(
+       struct nvhost_channel_userctx *ctx,
+       struct nvhost_cycle_stats_args *args)
+{
+       int ret;
+       BUG_ON(!channel_op(ctx->ch).cycle_stats);
+       ret = channel_op(ctx->ch).cycle_stats(ctx->hwctx, args);
+       return ret;
+}
+#endif
+
 static int nvhost_ioctl_channel_read_3d_reg(struct nvhost_channel_userctx *ctx,
        struct nvhost_read_3d_reg_args *args)
 {
@@ -822,7 +846,7 @@ static u32 create_mask(u32 *words, int num)
 {
        int i;
        u32 word = 0;
-       for (i = 0; i < num && words[i] && words[i] < BITS_PER_LONG; i++)
+       for (i = 0; i < num && words[i] && words[i] < 32; i++)
                word |= BIT(words[i]);
 
        return word;
@@ -832,6 +856,7 @@ static long nvhost_channelctl(struct file *filp,
        unsigned int cmd, unsigned long arg)
 {
        struct nvhost_channel_userctx *priv = filp->private_data;
+       struct device *dev = &priv->ch->dev->dev;
        u8 buf[NVHOST_IOCTL_CHANNEL_MAX_ARG_SIZE];
        int err = 0;
 
@@ -847,45 +872,6 @@ static long nvhost_channelctl(struct file *filp,
        }
 
        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:
        {
                struct nvhost_device_data *pdata = \
@@ -957,7 +943,6 @@ static long nvhost_channelctl(struct file *filp,
                        err = PTR_ERR(new_client);
                        break;
                }
-
                if (priv->memmgr)
                        nvhost_memmgr_put_mgr(priv->memmgr);
 
@@ -968,6 +953,33 @@ static long nvhost_channelctl(struct file *filp,
 
                break;
        }
+       case NVHOST_IOCTL_CHANNEL_ALLOC_OBJ_CTX:
+               err = nvhost_ioctl_channel_alloc_obj_ctx(priv, (void *)buf);
+               break;
+       case NVHOST_IOCTL_CHANNEL_FREE_OBJ_CTX:
+               err = nvhost_ioctl_channel_free_obj_ctx(priv, (void *)buf);
+               break;
+       case NVHOST_IOCTL_CHANNEL_ALLOC_GPFIFO:
+               err = nvhost_ioctl_channel_alloc_gpfifo(priv, (void *)buf);
+               break;
+       case NVHOST_IOCTL_CHANNEL_SUBMIT_GPFIFO:
+               err = nvhost_ioctl_channel_submit_gpfifo(priv, (void *)buf);
+               break;
+       case NVHOST_IOCTL_CHANNEL_WAIT:
+               err = nvhost_ioctl_channel_wait(priv, (void *)buf);
+               break;
+       case NVHOST_IOCTL_CHANNEL_ZCULL_BIND:
+               err = nvhost_ioctl_channel_zcull_bind(priv, (void *)buf);
+               break;
+       case NVHOST_IOCTL_CHANNEL_SET_ERROR_NOTIFIER:
+               err = nvhost_ioctl_channel_set_error_notifier(priv,
+                       (void *)buf);
+               break;
+#if defined(CONFIG_GK20A_CYCLE_STATS)
+       case NVHOST_IOCTL_CHANNEL_CYCLE_STATS:
+               err = nvhost_ioctl_channel_cycle_stats(priv, (void *)buf);
+               break;
+#endif
        case NVHOST_IOCTL_CHANNEL_READ_3D_REG:
                err = nvhost_ioctl_channel_read_3d_reg(priv, (void *)buf);
                break;
@@ -989,40 +1001,97 @@ static long nvhost_channelctl(struct file *filp,
                break;
        }
        case NVHOST_IOCTL_CHANNEL_SET_TIMEOUT:
-               priv->timeout =
+       {
+               u32 timeout =
                        (u32)((struct nvhost_set_timeout_args *)buf)->timeout;
+
+               priv->timeout = timeout;
                dev_dbg(&priv->ch->dev->dev,
                        "%s: setting buffer timeout (%d ms) for userctx 0x%p\n",
                        __func__, priv->timeout, priv);
+               if (priv->hwctx)
+                       priv->hwctx->timeout_ms_max = timeout;
                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:
+               nvhost_ioctl_channel_set_priority(priv, (void *)buf);
                priv->priority =
                        (u32)((struct nvhost_set_priority_args *)buf)->priority;
                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 nvhost32_submit_args *args32 = (void *)buf;
+               struct nvhost_submit_args args;
+
+               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.waitbases = args32->waitbases;
+               args.class_ids = args32->class_ids;
+               args.fences = args32->fences;
+
+               err = nvhost_ioctl_channel_submit(priv, &args);
+               args32->fence = args.fence;
+               break;
+       }
        case NVHOST_IOCTL_CHANNEL_SUBMIT:
                err = nvhost_ioctl_channel_submit(priv, (void *)buf);
                break;
        case NVHOST_IOCTL_CHANNEL_SET_TIMEOUT_EX:
-               priv->timeout = (u32)
-                       ((struct nvhost_set_timeout_ex_args *)buf)->timeout;
-               priv->timeout_debug_dump = !((u32)
+       {
+               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->ch->dev->dev,
                        "%s: setting buffer timeout (%d ms) for userctx 0x%p\n",
                        __func__, priv->timeout, priv);
+               if (priv->hwctx) {
+                       priv->hwctx->timeout_ms_max = timeout;
+                       priv->hwctx->timeout_debug_dump = timeout_debug_dump;
+               }
                break;
+       }
        case NVHOST_IOCTL_CHANNEL_SET_CTXSWITCH:
                err = nvhost_ioctl_channel_set_ctxswitch(priv, (void *)buf);
                break;
        default:
+               nvhost_err(dev, "unrecognized ioctl cmd: 0x%x", cmd);
                err = -ENOTTY;
                break;
        }
@@ -1037,47 +1106,233 @@ static const struct file_operations nvhost_channelops = {
        .owner = THIS_MODULE,
        .release = nvhost_channelrelease,
        .open = nvhost_channelopen,
-       .write = nvhost_channelwrite,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl = nvhost_channelctl,
+#endif
        .unlocked_ioctl = nvhost_channelctl
 };
 
+struct nvhost_hwctx *nvhost_channel_get_file_hwctx(int fd)
+{
+       struct nvhost_channel_userctx *userctx;
+       struct file *f = fget(fd);
+       if (!f)
+               return 0;
+
+       if (f->f_op != &nvhost_channelops) {
+               fput(f);
+               return 0;
+       }
+
+       userctx = (struct nvhost_channel_userctx *)f->private_data;
+       fput(f);
+       return userctx->hwctx;
+}
+
+
+static const struct file_operations nvhost_asops = {
+       .owner = THIS_MODULE,
+       .release = nvhost_as_dev_release,
+       .open = nvhost_as_dev_open,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl = nvhost_as_dev_ctl,
+#endif
+       .unlocked_ioctl = nvhost_as_dev_ctl,
+};
+
+static struct {
+       int class_id;
+       const char *dev_name;
+} class_id_dev_name_map[] = {
+       /*      { NV_HOST1X_CLASS_ID, ""}, */
+       { NV_VIDEO_ENCODE_MPEG_CLASS_ID, "mpe" },
+       { NV_VIDEO_ENCODE_MSENC_CLASS_ID, "msenc" },
+       { NV_GRAPHICS_3D_CLASS_ID, "gr3d" },
+       { NV_GRAPHICS_GPU_CLASS_ID, "gpu"},
+       { NV_GRAPHICS_VIC_CLASS_ID, "vic"},
+       { NV_TSEC_CLASS_ID, "tsec" },
+};
+
+static struct {
+       int module_id;
+       const char *dev_name;
+} module_id_dev_name_map[] = {
+       { NVHOST_MODULE_VI, "vi"},
+       { NVHOST_MODULE_ISP, "isp"},
+       { NVHOST_MODULE_MPE, "mpe"},
+       { NVHOST_MODULE_MSENC, "msenc"},
+       { NVHOST_MODULE_TSEC, "tsec"},
+       { NVHOST_MODULE_GPU, "gpu"},
+       { NVHOST_MODULE_VIC, "vic"},
+};
+
+static const char *get_device_name_for_dev(struct platform_device *dev)
+{
+       int i;
+       /* first choice is to use the class id if specified */
+       for (i = 0; i < ARRAY_SIZE(class_id_dev_name_map); i++) {
+               struct nvhost_device_data *pdata = nvhost_get_devdata(dev);
+               if (pdata->class == class_id_dev_name_map[i].class_id)
+                       return class_id_dev_name_map[i].dev_name;
+       }
+
+       /* second choice is module name if specified */
+       for (i = 0; i < ARRAY_SIZE(module_id_dev_name_map); i++) {
+               struct nvhost_device_data *pdata = nvhost_get_devdata(dev);
+               if (pdata->moduleid == module_id_dev_name_map[i].module_id)
+                       return module_id_dev_name_map[i].dev_name;
+       }
+
+       /* last choice is to just use the given dev name */
+       return dev->name;
+}
+
+static struct device *nvhost_client_device_create(
+       struct platform_device *pdev, struct cdev *cdev,
+       const char *cdev_name, int devno,
+       const struct file_operations *ops)
+{
+       struct nvhost_master *host = nvhost_get_host(pdev);
+       struct nvhost_device_data *pdata = nvhost_get_devdata(pdev);
+       const char *use_dev_name;
+       struct device *dev;
+       int err;
+
+       nvhost_dbg_fn("");
+
+       BUG_ON(!host);
+
+       cdev_init(cdev, ops);
+       cdev->owner = THIS_MODULE;
+
+       err = cdev_add(cdev, devno, 1);
+       if (err < 0) {
+               dev_err(&pdev->dev,
+                       "failed to add chan %i cdev\n", pdata->index);
+               return NULL;
+       }
+       use_dev_name = get_device_name_for_dev(pdev);
+
+       dev = device_create(host->nvhost_class,
+                       NULL, 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)) {
+               err = PTR_ERR(dev);
+               dev_err(&pdev->dev,
+                       "failed to create %s %s device for %s\n",
+                       use_dev_name, cdev_name, pdev->name);
+               return NULL;
+       }
+
+       return dev;
+}
+
 int nvhost_client_user_init(struct platform_device *dev)
 {
        int err, devno;
        struct nvhost_device_data *pdata = platform_get_drvdata(dev);
-
        struct nvhost_channel *ch = pdata->channel;
-       err = alloc_chrdev_region(&devno, 0, 1, IFACE_NAME);
+
+       BUG_ON(!ch);
+       /* reserve 5 minor #s for <dev> and as-<dev>, ctrl-<dev>,
+        * dbg-<dev> and prof-<dev> */
+
+       err = alloc_chrdev_region(&devno, 0, 5, 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", pdata->index);
+       ch->node = nvhost_client_device_create(dev, &ch->cdev,
+                               "", devno, &nvhost_channelops);
+       if (ch->node == NULL)
                goto fail;
+       if (pdata->as_ops) {
+               ++devno;
+               ch->as_node = nvhost_client_device_create(dev, &ch->as_cdev,
+                                       "as-", devno, &nvhost_asops);
+               if (ch->as_node == NULL)
+                       goto fail;
        }
-       ch->node = device_create(nvhost_get_host(dev)->nvhost_class,
-                       NULL, devno, NULL,
-                       IFACE_NAME "-%s", dev_name(&dev->dev));
-       if (IS_ERR(ch->node)) {
-               err = PTR_ERR(ch->node);
-               dev_err(&dev->dev,
-                       "failed to create %s channel device\n",
-                       dev_name(&dev->dev));
-               goto fail;
+
+       /* 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 (pdata->ctrl_node == NULL)
+                       goto fail;
+       }
+
+       /* module debugger interface (per channel and global) */
+       if (pdata->dbg_ops) {
+               ++devno;
+               pdata->dbg_node = nvhost_client_device_create(dev,
+                                       &pdata->dbg_cdev, "dbg-",
+                                       devno, pdata->dbg_ops);
+               if (pdata->dbg_node == NULL)
+                       goto fail;
+       }
+
+       /* module profiler interface (per channel and global) */
+       if (pdata->prof_ops) {
+               ++devno;
+               pdata->prof_node = nvhost_client_device_create(dev,
+                                       &pdata->prof_cdev, "prof-",
+                                       devno, pdata->prof_ops);
+               if (pdata->prof_node == NULL)
+                       goto fail;
        }
 
+
+
        return 0;
 fail:
        return err;
 }
 
+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);
+       struct nvhost_channel *ch = pdata->channel;
+
+       BUG_ON(!ch);
+
+       if (ch->node) {
+               device_destroy(nvhost_master->nvhost_class, ch->cdev.dev);
+               cdev_del(&ch->cdev);
+       }
+
+       if (ch->as_node) {
+               device_destroy(nvhost_master->nvhost_class, ch->as_cdev.dev);
+               cdev_del(&ch->as_cdev);
+       }
+
+       if (pdata->ctrl_node) {
+               device_destroy(nvhost_master->nvhost_class,
+                              pdata->ctrl_cdev.dev);
+               cdev_del(&pdata->ctrl_cdev);
+       }
+
+       if (pdata->dbg_node) {
+               device_destroy(nvhost_master->nvhost_class,
+                              pdata->dbg_cdev.dev);
+               cdev_del(&pdata->dbg_cdev);
+       }
+
+       if (pdata->prof_node) {
+               device_destroy(nvhost_master->nvhost_class,
+                              pdata->prof_cdev.dev);
+               cdev_del(&pdata->prof_cdev);
+       }
+}
+
 int nvhost_client_device_init(struct platform_device *dev)
 {
        int err;
@@ -1097,7 +1352,7 @@ int nvhost_client_device_init(struct platform_device *dev)
 
        err = nvhost_channel_init(ch, nvhost_master, pdata->index);
        if (err)
-               goto fail;
+               goto fail1;
 
        err = nvhost_client_user_init(dev);
        if (err)
@@ -1118,29 +1373,59 @@ int nvhost_client_device_init(struct platform_device *dev)
        nvhost_syncpt_reset_client(dev);
        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->slave && !pdata->slave_initialized) {
+               struct nvhost_device_data *slave_pdata =
+                                       pdata->slave->dev.platform_data;
+               slave_pdata->master = dev;
+               pdata->slave->dev.parent = dev->dev.parent;
+               platform_device_register(pdata->slave);
+               pdata->slave_initialized = 1;
+       }
+
        return 0;
 
 fail:
        /* Add clean-up */
+       dev_err(&dev->dev, "failed to init client device\n");
+       nvhost_client_user_deinit(dev);
+fail1:
+       nvhost_device_debug_deinit(dev);
        nvhost_free_channel(ch);
        return err;
 }
+EXPORT_SYMBOL(nvhost_client_device_init);
 
-int nvhost_client_device_suspend(struct platform_device *dev)
+int nvhost_client_device_release(struct platform_device *dev)
 {
-       int ret = 0;
+       struct nvhost_channel *ch;
        struct nvhost_device_data *pdata = platform_get_drvdata(dev);
 
-       ret = nvhost_channel_suspend(pdata->channel);
-       if (ret)
-               return ret;
+       ch = pdata->channel;
 
-       dev_info(&dev->dev, "suspend status: %d\n", ret);
+       /* Release nvhost module resources */
+       nvhost_module_deinit(dev);
 
-       return ret;
+       /* 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);
+
+       /* Free nvhost channel */
+       nvhost_free_channel(ch);
+
+       return 0;
 }
+EXPORT_SYMBOL(nvhost_client_device_release);
 
 int nvhost_client_device_get_resources(struct platform_device *dev)
 {
@@ -1170,6 +1455,7 @@ fail:
 
        return -ENXIO;
 }
+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.
@@ -1183,6 +1469,13 @@ nvhost_client_request_firmware(struct platform_device *dev, const char *fw_name)
        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) {
+               BUG();
+               return NULL;
+       }
+
        if (!fw_name)
                return NULL;