nvrm: move stale wait checking into the kernel
Chris Johnson [Sat, 30 Apr 2011 21:24:44 +0000 (14:24 -0700)]
The kernel now receives wait tracking data (similar to gathers and
relocs) and compares the current syncpt with the threshold value.

If it's old, it gets a kernel mapping and rewrites the method data
to use a kernel reserved syncpt that is always 0 (so trivially pops
when seen by the HW).

Bug 519650
Bug 785525
Bug 803452

The waitchk implementation is also backward compatible with older
user space code that didn't supply waitchk data as part of the
submit.

(cherry picked from commit 4069d8e67665624ad3dceb628e572980dd57acd0)
(cherry picked from commit 6e4336408588e348804a62e53386acc9abc06823)
(cherry picked from commit 87a9efe751716ca741caac72b9061fdfdcec540a)
(cherry picked from commit 6d8d2c454e835f7cfe34371e07dac173f3153452)

Original-Change-Id: I1bf4c940fefb6e251aa8c396e92631fa3cbe32df
Reviewed-on: http://git-master/r/23159
Reviewed-on: http://git-master/r/30281
Reviewed-on: http://git-master/r/32669
Reviewed-by: Niket Sirsi <nsirsi@nvidia.com>
Tested-by: Niket Sirsi <nsirsi@nvidia.com>

Rebase-Id: Rbad4942a103de7d64d8a6a11d4a973740c56fa8e

arch/arm/mach-tegra/include/mach/nvhost.h
arch/arm/mach-tegra/include/mach/nvmap.h
arch/arm/mach-tegra/include/trace/events/nvhost.h
drivers/video/tegra/host/dev.c
drivers/video/tegra/host/nvhost_channel.c
drivers/video/tegra/host/nvhost_channel.h
drivers/video/tegra/host/nvhost_hardware.h
drivers/video/tegra/host/nvhost_syncpt.c
drivers/video/tegra/host/nvhost_syncpt.h
drivers/video/tegra/nvmap/nvmap.c

index ae73ce3..fbf0f42 100644 (file)
@@ -76,6 +76,7 @@ int nvhost_bus_register(struct nvhost_master *host);
 #define NVHOST_NO_TIMEOUT (-1)
 #define NVHOST_IOCTL_MAGIC 'H'
 
+/* version 0 header (used with write() submit interface) */
 struct nvhost_submit_hdr {
        __u32 syncpt_id;
        __u32 syncpt_incrs;
@@ -83,6 +84,22 @@ struct nvhost_submit_hdr {
        __u32 num_relocs;
 };
 
+#define NVHOST_SUBMIT_VERSION_V0               0x0
+#define NVHOST_SUBMIT_VERSION_V1               0x1
+#define NVHOST_SUBMIT_VERSION_MAX_SUPPORTED    NVHOST_SUBMIT_VERSION_V1
+
+/* version 1 header (used with ioctl() submit interface) */
+struct nvhost_submit_hdr_ext {
+       __u32 syncpt_id;        /* version 0 fields */
+       __u32 syncpt_incrs;
+       __u32 num_cmdbufs;
+       __u32 num_relocs;
+       __u32 submit_version;   /* version 1 fields */
+       __u32 num_waitchks;
+       __u32 waitchk_mask;
+       __u32 pad[5];           /* future expansion */
+};
+
 struct nvhost_cmdbuf {
        __u32 mem;
        __u32 offset;
@@ -96,6 +113,13 @@ struct nvhost_reloc {
        __u32 target_offset;
 };
 
+struct nvhost_waitchk {
+       __u32 mem;
+       __u32 offset;
+       __u32 syncpt_id;
+       __u32 thresh;
+};
+
 struct nvhost_get_param_args {
        __u32 value;
 };
@@ -116,9 +140,11 @@ struct nvhost_set_nvmap_fd_args {
        _IOW(NVHOST_IOCTL_MAGIC, 5, struct nvhost_set_nvmap_fd_args)
 #define NVHOST_IOCTL_CHANNEL_NULL_KICKOFF      \
        _IOR(NVHOST_IOCTL_MAGIC, 6, struct nvhost_get_param_args)
+#define NVHOST_IOCTL_CHANNEL_SUBMIT_EXT                \
+       _IOW(NVHOST_IOCTL_MAGIC, 7, struct nvhost_submit_hdr_ext)
 #define NVHOST_IOCTL_CHANNEL_LAST              \
-       _IOC_NR(NVHOST_IOCTL_CHANNEL_NULL_KICKOFF)
-#define NVHOST_IOCTL_CHANNEL_MAX_ARG_SIZE sizeof(struct nvhost_get_param_args)
+       _IOC_NR(NVHOST_IOCTL_CHANNEL_SUBMIT_EXT)
+#define NVHOST_IOCTL_CHANNEL_MAX_ARG_SIZE sizeof(struct nvhost_submit_hdr_ext)
 
 struct nvhost_ctrl_syncpt_read_args {
        __u32 id;
index 280dff4..5b2be30 100644 (file)
@@ -97,6 +97,10 @@ int nvmap_pin_array(struct nvmap_client *client, struct nvmap_handle *gather,
 void nvmap_unpin_handles(struct nvmap_client *client,
                         struct nvmap_handle **h, int nr);
 
+int nvmap_patch_word(struct nvmap_client *client,
+                    struct nvmap_handle *patch,
+                    u32 patch_offset, u32 patch_value);
+
 struct nvmap_platform_carveout {
        const char *name;
        unsigned int usage_mask;
index 2d5839f..4c8dd67 100644 (file)
@@ -76,6 +76,34 @@ TRACE_EVENT(nvhost_channel_write_submit,
          (unsigned long)__entry->cmdbufs, (unsigned long)__entry->relocs)
 );
 
+TRACE_EVENT(nvhost_ioctl_channel_submit,
+       TP_PROTO(const char *name, u32 version, u32 cmdbufs, u32 relocs,
+                u32 waitchks),
+
+       TP_ARGS(name, version, cmdbufs, relocs, waitchks),
+
+       TP_STRUCT__entry(
+               __field(const char *, name)
+               __field(u32, version)
+               __field(u32, cmdbufs)
+               __field(u32, relocs)
+               __field(u32, waitchks)
+       ),
+
+       TP_fast_assign(
+               __entry->name = name;
+               __entry->version = version;
+               __entry->cmdbufs = cmdbufs;
+               __entry->relocs = relocs;
+               __entry->waitchks = waitchks;
+       ),
+
+       TP_printk("name=%s, version=%lu, cmdbufs=%lu, relocs=%ld, waitchks=%ld",
+         __entry->name, (unsigned long)__entry->version,
+         (unsigned long)__entry->cmdbufs, (unsigned long)__entry->relocs,
+         (unsigned long)__entry->waitchks)
+);
+
 TRACE_EVENT(nvhost_channel_write_cmdbuf,
        TP_PROTO(const char *name, u32 mem_id, u32 words, u32 offset),
 
@@ -119,6 +147,28 @@ TRACE_EVENT(nvhost_channel_write_relocs,
          __entry->name, (unsigned long)__entry->relocs)
 );
 
+TRACE_EVENT(nvhost_channel_write_waitchks,
+       TP_PROTO(const char *name, u32 waitchks, u32 waitmask),
+
+       TP_ARGS(name, waitchks, waitmask),
+
+       TP_STRUCT__entry(
+               __field(const char *, name)
+               __field(u32, waitchks)
+               __field(u32, waitmask)
+       ),
+
+       TP_fast_assign(
+               __entry->name = name;
+               __entry->waitchks = waitchks;
+               __entry->waitmask = waitmask;
+       ),
+
+       TP_printk("name=%s, waitchks=%lu, waitmask=0x08lx",
+         __entry->name, (unsigned long)__entry->waitchks,
+         (unsigned long)__entry->waitmask)
+);
+
 TRACE_EVENT(nvhost_channel_context_switch,
        TP_PROTO(const char *name, void *old_ctx, void *new_ctx),
 
index 97ea642..85cb329 100644 (file)
@@ -49,10 +49,7 @@ static int nvhost_minor = NVHOST_CHANNEL_BASE;
 struct nvhost_channel_userctx {
        struct nvhost_channel *ch;
        struct nvhost_hwctx *hwctx;
-       u32 syncpt_id;
-       u32 syncpt_incrs;
-       u32 cmdbufs_pending;
-       u32 relocs_pending;
+       struct nvhost_submit_hdr_ext hdr;
        struct nvmap_handle_ref *gather_mem;
        u32 *gathers;
        u32 *cur_gather;
@@ -60,6 +57,8 @@ struct nvhost_channel_userctx {
        struct nvmap_pinarray_elem pinarray[NVHOST_MAX_HANDLES];
        struct nvmap_handle *unpinarray[NVHOST_MAX_HANDLES];
        struct nvmap_client *nvmap;
+       struct nvhost_waitchk waitchks[NVHOST_MAX_WAIT_CHECKS];
+       struct nvhost_waitchk *cur_waitchk;
 };
 
 struct nvhost_ctrl_userctx {
@@ -144,10 +143,40 @@ static void add_gather(struct nvhost_channel_userctx *ctx,
        ctx->cur_gather = cur_gather + 2;
 }
 
+static int set_submit(struct nvhost_channel_userctx *ctx)
+{
+       /* submit should have at least 1 cmdbuf */
+       if (!ctx->hdr.num_cmdbufs)
+               return -EIO;
+
+       /* check submit doesn't exceed static structs */
+       if ((ctx->hdr.num_cmdbufs + ctx->hdr.num_relocs) > NVHOST_MAX_HANDLES) {
+               dev_err(&ctx->ch->dev->pdev->dev,
+                       "channel submit exceeded max handles (%d > %d)\n",
+                       ctx->hdr.num_cmdbufs + ctx->hdr.num_relocs,
+                       NVHOST_MAX_HANDLES);
+               return -EIO;
+       }
+       if (ctx->hdr.num_waitchks > NVHOST_MAX_WAIT_CHECKS) {
+               dev_err(&ctx->ch->dev->pdev->dev,
+                       "channel submit exceeded max waitchks (%d > %d)\n",
+                       ctx->hdr.num_waitchks,
+                       NVHOST_MAX_WAIT_CHECKS);
+               return -EIO;
+       }
+
+       ctx->cur_gather = ctx->gathers;
+       ctx->cur_waitchk = ctx->waitchks;
+       ctx->pinarray_size = 0;
+
+       return 0;
+}
+
 static void reset_submit(struct nvhost_channel_userctx *ctx)
 {
-       ctx->cmdbufs_pending = 0;
-       ctx->relocs_pending = 0;
+       ctx->hdr.num_cmdbufs = 0;
+       ctx->hdr.num_relocs = 0;
+       ctx->hdr.num_waitchks = 0;
 }
 
 static ssize_t nvhost_channelwrite(struct file *filp, const char __user *buf,
@@ -159,23 +188,23 @@ static ssize_t nvhost_channelwrite(struct file *filp, const char __user *buf,
 
        while (remaining) {
                size_t consumed;
-               if (!priv->relocs_pending && !priv->cmdbufs_pending) {
+               if (!priv->hdr.num_relocs &&
+                   !priv->hdr.num_cmdbufs &&
+                   !priv->hdr.num_waitchks) {
                        consumed = sizeof(struct nvhost_submit_hdr);
                        if (remaining < consumed)
                                break;
-                       if (copy_from_user(&priv->syncpt_id, buf, consumed)) {
+                       if (copy_from_user(&priv->hdr, buf, consumed)) {
                                err = -EFAULT;
                                break;
                        }
-                       if (!priv->cmdbufs_pending) {
-                               err = -EFAULT;
+                       priv->hdr.submit_version = NVHOST_SUBMIT_VERSION_V0;
+                       err = set_submit(priv);
+                       if (err)
                                break;
-                       }
                        trace_nvhost_channel_write_submit(priv->ch->desc->name,
-                         count, priv->cmdbufs_pending, priv->relocs_pending);
-                       priv->cur_gather = priv->gathers;
-                       priv->pinarray_size = 0;
-               } else if (priv->cmdbufs_pending) {
+                         count, priv->hdr.num_cmdbufs, priv->hdr.num_relocs);
+               } else if (priv->hdr.num_cmdbufs) {
                        struct nvhost_cmdbuf cmdbuf;
                        consumed = sizeof(cmdbuf);
                        if (remaining < consumed)
@@ -188,12 +217,12 @@ static ssize_t nvhost_channelwrite(struct file *filp, const char __user *buf,
                          cmdbuf.mem, cmdbuf.words, cmdbuf.offset);
                        add_gather(priv,
                                cmdbuf.mem, cmdbuf.words, cmdbuf.offset);
-                       priv->cmdbufs_pending--;
-               } else if (priv->relocs_pending) {
+                       priv->hdr.num_cmdbufs--;
+               } else if (priv->hdr.num_relocs) {
                        int numrelocs = remaining / sizeof(struct nvhost_reloc);
                        if (!numrelocs)
                                break;
-                       numrelocs = min_t(int, numrelocs, priv->relocs_pending);
+                       numrelocs = min_t(int, numrelocs, priv->hdr.num_relocs);
                        consumed = numrelocs * sizeof(struct nvhost_reloc);
                        if (copy_from_user(&priv->pinarray[priv->pinarray_size],
                                                buf, consumed)) {
@@ -203,7 +232,24 @@ static ssize_t nvhost_channelwrite(struct file *filp, const char __user *buf,
                        trace_nvhost_channel_write_relocs(priv->ch->desc->name,
                          numrelocs);
                        priv->pinarray_size += numrelocs;
-                       priv->relocs_pending -= numrelocs;
+                       priv->hdr.num_relocs -= numrelocs;
+               } else if (priv->hdr.num_waitchks) {
+                       int numwaitchks =
+                               (remaining / sizeof(struct nvhost_waitchk));
+                       if (!numwaitchks)
+                               break;
+                       numwaitchks = min_t(int,
+                               numwaitchks, priv->hdr.num_waitchks);
+                       consumed = numwaitchks * sizeof(struct nvhost_waitchk);
+                       if (copy_from_user(priv->cur_waitchk, buf, consumed)) {
+                               err = -EFAULT;
+                               break;
+                       }
+                       trace_nvhost_channel_write_waitchks(
+                         priv->ch->desc->name, numwaitchks,
+                         priv->hdr.waitchk_mask);
+                       priv->cur_waitchk += numwaitchks;
+                       priv->hdr.num_waitchks -= numwaitchks;
                } else {
                        err = -EFAULT;
                        break;
@@ -232,7 +278,9 @@ static int nvhost_ioctl_channel_flush(
 
        trace_nvhost_ioctl_channel_flush(ctx->ch->desc->name);
 
-       if (ctx->relocs_pending || ctx->cmdbufs_pending) {
+       if (ctx->hdr.num_relocs ||
+           ctx->hdr.num_cmdbufs ||
+           ctx->hdr.num_waitchks) {
                reset_submit(ctx);
                dev_err(device, "channel submit out of sync\n");
                return -EFAULT;
@@ -260,8 +308,10 @@ static int nvhost_ioctl_channel_flush(
        /* context switch if needed, and submit user's gathers to the channel */
        err = nvhost_channel_submit(ctx->ch, ctx->hwctx, ctx->nvmap,
                                ctx->gathers, ctx->cur_gather,
+                               ctx->waitchks, ctx->cur_waitchk,
+                               ctx->hdr.waitchk_mask,
                                ctx->unpinarray, num_unpin,
-                               ctx->syncpt_id, ctx->syncpt_incrs,
+                               ctx->hdr.syncpt_id, ctx->hdr.syncpt_incrs,
                                &args->value,
                                null_kickoff);
        if (err)
@@ -296,7 +346,40 @@ static long nvhost_channelctl(struct file *filp,
        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->hdr.num_cmdbufs ||
+                   priv->hdr.num_waitchks) {
+                       reset_submit(priv);
+                       dev_err(&priv->ch->dev->pdev->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->pdev->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->desc->name,
+                       priv->hdr.submit_version,
+                       priv->hdr.num_cmdbufs, priv->hdr.num_relocs,
+                       priv->hdr.num_waitchks);
+               break;
+       }
        case NVHOST_IOCTL_CHANNEL_GET_SYNCPOINTS:
+               /* host syncpt ID is used by the RM (and never be given out) */
+               BUG_ON(priv->ch->desc->syncpts & (1 << NVSYNCPT_GRAPHICS_HOST));
                ((struct nvhost_get_param_args *)buf)->value =
                        priv->ch->desc->syncpts;
                break;
index 8e16f62..896a5ea 100644 (file)
@@ -179,6 +179,9 @@ int nvhost_channel_submit(struct nvhost_channel *channel,
                        struct nvmap_client *user_nvmap,
                        u32 *gather,
                        u32 *gather_end,
+                       struct nvhost_waitchk *waitchk,
+                       struct nvhost_waitchk *waitchk_end,
+                       u32 waitchk_mask,
                        struct nvmap_handle **unpins,
                        int nr_unpins,
                        u32 syncpt_id,
@@ -202,6 +205,20 @@ int nvhost_channel_submit(struct nvhost_channel *channel,
                return err;
        }
 
+       /* remove stale waits */
+       if (waitchk != waitchk_end) {
+               err = nvhost_syncpt_wait_check(user_nvmap,
+                               &channel->dev->syncpt, waitchk_mask,
+                               waitchk, waitchk_end);
+               if (err) {
+                       dev_warn(&channel->dev->pdev->dev,
+                               "nvhost_syncpt_wait_check failed: %d\n", err);
+                       mutex_unlock(&channel->submitlock);
+                       nvhost_module_idle(&channel->mod);
+                       return err;
+               }
+       }
+
        /* context switch */
        if (channel->cur_ctx != hwctx) {
                trace_nvhost_channel_context_switch(channel->desc->name,
index 41c6bdd..31d5f47 100644 (file)
@@ -32,6 +32,7 @@
 
 #define NVHOST_CHANNEL_BASE 0
 #define NVHOST_NUMCHANNELS (NV_HOST1X_CHANNELS - 1)
+#define NVHOST_MAX_WAIT_CHECKS 256
 #define NVHOST_MAX_GATHERS 512
 #define NVHOST_MAX_HANDLES 1280
 
@@ -82,6 +83,9 @@ int nvhost_channel_submit(
        struct nvmap_client *user_nvmap,
        u32 *gather,
        u32 *gather_end,
+       struct nvhost_waitchk *waitchk,
+       struct nvhost_waitchk *waitchk_end,
+       u32 waitchk_mask,
        struct nvmap_handle **unpins,
        int nr_unpins,
        u32 syncpt_id,
index 4e7f968..3cb2901 100644 (file)
@@ -140,6 +140,12 @@ enum {
        NV_CLASS_HOST_INDDATA = 0x2e
 };
 
+static inline u32 nvhost_class_host_wait_syncpt(
+       unsigned indx, unsigned threshold)
+{
+       return (indx << 24) | (threshold & 0xffffff);
+}
+
 static inline u32 nvhost_class_host_wait_syncpt_base(
        unsigned indx, unsigned base_indx, unsigned offset)
 {
index 11e793f..9420308 100644 (file)
@@ -237,7 +237,7 @@ done:
 }
 
 static const char *s_syncpt_names[32] = {
-       "", "", "", "", "", "", "", "", "", "", "",
+       "gfx_host", "", "", "", "", "", "", "", "", "", "",
        "csi_vi_0", "csi_vi_1", "vi_isp_0", "vi_isp_1", "vi_isp_2", "vi_isp_3", "vi_isp_4",
        "2d_0", "2d_1",
        "", "",
@@ -265,3 +265,69 @@ void nvhost_syncpt_debug(struct nvhost_syncpt *sp)
 
        }
 }
+
+/* returns true, if a <= b < c using wrapping comparison */
+static inline bool nvhost_syncpt_is_between(u32 a, u32 b, u32 c)
+{
+       return b-a < c-a;
+}
+
+/* returns true, if syncpt >= threshold (mod 1 << 32) */
+static bool nvhost_syncpt_wrapping_comparison(u32 syncpt, u32 threshold)
+{
+       return nvhost_syncpt_is_between(threshold, syncpt,
+                                       (1UL<<31UL)+threshold);
+}
+
+/* check for old WAITs to be removed (avoiding a wrap) */
+int nvhost_syncpt_wait_check(struct nvmap_client *nvmap,
+                       struct nvhost_syncpt *sp, u32 waitchk_mask,
+                       struct nvhost_waitchk *wait,
+                       struct nvhost_waitchk *waitend)
+{
+       u32 idx;
+       int err = 0;
+
+       /* get current syncpt values */
+       for (idx = 0; idx < NV_HOST1X_SYNCPT_NB_PTS; idx++) {
+               if (BIT(idx) & waitchk_mask)
+                       nvhost_syncpt_update_min(sp, idx);
+       }
+
+       BUG_ON(!wait && !waitend);
+
+       /* compare syncpt vs wait threshold */
+       while (wait != waitend) {
+               u32 syncpt, override;
+
+               BUG_ON(wait->syncpt_id > NV_HOST1X_SYNCPT_NB_PTS);
+
+               syncpt = atomic_read(&sp->min_val[wait->syncpt_id]);
+               if (nvhost_syncpt_wrapping_comparison(syncpt, wait->thresh)) {
+                       /*
+                        * NULL an already satisfied WAIT_SYNCPT host method,
+                        * by patching its args in the command stream. The
+                        * method data is changed to reference a reserved
+                        * (never given out or incr) NVSYNCPT_GRAPHICS_HOST
+                        * syncpt with a matching threshold value of 0, so
+                        * is guaranteed to be popped by the host HW.
+                        */
+                       dev_dbg(&syncpt_to_dev(sp)->pdev->dev,
+                           "drop WAIT id %d (%s) thresh 0x%x, syncpt 0x%x\n",
+                           wait->syncpt_id,
+                           nvhost_syncpt_name(wait->syncpt_id),
+                           wait->thresh, syncpt);
+
+                       /* patch the wait */
+                       override = nvhost_class_host_wait_syncpt(
+                                       NVSYNCPT_GRAPHICS_HOST, 0);
+                       err = nvmap_patch_word(nvmap,
+                                       (struct nvmap_handle *)wait->mem,
+                                       wait->offset, override);
+                       if (err)
+                               break;
+               }
+               wait++;
+       }
+       return err;
+}
index d6e60f1..564cc83 100644 (file)
 
 #include <linux/kernel.h>
 #include <linux/sched.h>
+#include <mach/nvhost.h>
+#include <mach/nvmap.h>
 #include <asm/atomic.h>
 
 #include "nvhost_hardware.h"
 
+#define NVSYNCPT_GRAPHICS_HOST              (0)
 #define NVSYNCPT_CSI_VI_0                   (11)
 #define NVSYNCPT_CSI_VI_1                   (12)
 #define NVSYNCPT_VI_ISP_0                   (13)
@@ -151,6 +154,21 @@ static inline int nvhost_syncpt_wait(struct nvhost_syncpt *sp, u32 id, u32 thres
                                          MAX_SCHEDULE_TIMEOUT, NULL);
 }
 
+/*
+ * Check driver supplied waitchk structs for syncpt thresholds
+ * that have already been satisfied and NULL the comparison (to
+ * avoid a wrap condition in the HW).
+ *
+ * @param: nvmap - needed to access command buffer
+ * @param: sp - global shadowed syncpt struct
+ * @param: mask - bit mask of syncpt IDs referenced in WAITs
+ * @param: wait - start of filled in array of waitchk structs
+ * @param: waitend - end ptr (one beyond last valid waitchk)
+ */
+int nvhost_syncpt_wait_check(struct nvmap_client *nvmap,
+                       struct nvhost_syncpt *sp, u32 mask,
+                       struct nvhost_waitchk *wait,
+                       struct nvhost_waitchk *waitend);
 
 const char *nvhost_syncpt_name(u32 id);
 
index 06ce076..dcd1672 100644 (file)
@@ -776,3 +776,52 @@ void nvmap_free(struct nvmap_client *client, struct nvmap_handle_ref *r)
 {
        nvmap_free_handle_id(client, nvmap_ref_to_id(r));
 }
+
+/*
+ * create a mapping to the user's buffer and write it
+ * (uses similar logic from nvmap_reloc_pin_array to map the cmdbuf)
+ */
+int nvmap_patch_word(struct nvmap_client *client,
+                               struct nvmap_handle *patch,
+                               u32 patch_offset, u32 patch_value)
+{
+       phys_addr_t phys;
+       unsigned long kaddr;
+       unsigned int pfn;
+       void *addr;
+       pte_t **pte;
+       pgprot_t prot;
+
+       if (patch_offset >= patch->size) {
+               nvmap_warn(client, "read/write outside of handle\n");
+               return -EFAULT;
+       }
+
+       pte = nvmap_alloc_pte(client->dev, &addr);
+       if (IS_ERR(pte))
+               return PTR_ERR(pte);
+
+       /* derive physaddr of cmdbuf WAIT to patch */
+       if (patch->heap_pgalloc) {
+               unsigned int page = patch_offset >> PAGE_SHIFT;
+               phys = page_to_phys(patch->pgalloc.pages[page]);
+               phys += (patch_offset & ~PAGE_MASK);
+       } else {
+               phys = patch->carveout->base + patch_offset;
+       }
+
+       pfn = __phys_to_pfn(phys);
+       prot = nvmap_pgprot(patch, pgprot_kernel);
+       kaddr = (unsigned long)addr;
+
+       /* write PTE, so addr points to cmdbuf PFN */
+       set_pte_at(&init_mm, kaddr, *pte, pfn_pte(pfn, prot));
+       flush_tlb_kernel_page(kaddr);
+
+       /* write patch_value to addr + page offset */
+       __raw_writel(patch_value, addr + (phys & ~PAGE_MASK));
+
+       nvmap_free_pte(client->dev, pte);
+       wmb();
+       return 0;
+}