summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/video/tegra/host/dev.c119
-rw-r--r--drivers/video/tegra/host/nvhost_channel.h1
-rw-r--r--drivers/video/tegra/host/nvhost_hardware.h6
-rw-r--r--drivers/video/tegra/host/nvhost_syncpt.c62
-rw-r--r--drivers/video/tegra/host/nvhost_syncpt.h6
-rw-r--r--drivers/video/tegra/nvmap/nvmap.c50
6 files changed, 214 insertions, 30 deletions
diff --git a/drivers/video/tegra/host/dev.c b/drivers/video/tegra/host/dev.c
index 8b577cc9045f..fdbf5cc8efc1 100644
--- a/drivers/video/tegra/host/dev.c
+++ b/drivers/video/tegra/host/dev.c
@@ -46,10 +46,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;
struct nvhost_op_pair *gathers;
int num_gathers;
@@ -57,6 +54,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];
+ u32 num_waitchks;
};
struct nvhost_ctrl_userctx {
@@ -137,10 +136,25 @@ static void add_gather(struct nvhost_channel_userctx *ctx, int idx,
ctx->gathers[idx].op1 = nvhost_opcode_gather(0, words);
}
+static int set_submit(struct nvhost_channel_userctx *ctx)
+{
+ int err = 0;
+
+ /* submit should have at least 1 cmdbuf */
+ if (!ctx->hdr.num_cmdbufs)
+ return -EFAULT;
+
+ /* leave room for ctx switch */
+ ctx->num_gathers = 2;
+ ctx->pinarray_size = 0;
+ return err;
+}
+
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,
@@ -152,22 +166,19 @@ 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;
+ BUG_ON(priv->hdr.submit_version != NVHOST_SUBMIT_VERSION_V0);
+ err = set_submit(priv);
+ if (err)
break;
- }
- /* leave room for ctx switch */
- priv->num_gathers = 2;
- priv->pinarray_size = 0;
- } else if (priv->cmdbufs_pending) {
+ } else if (priv->hdr.num_cmdbufs) {
struct nvhost_cmdbuf cmdbuf;
consumed = sizeof(cmdbuf);
if (remaining < consumed)
@@ -178,12 +189,12 @@ static ssize_t nvhost_channelwrite(struct file *filp, const char __user *buf,
}
add_gather(priv, priv->num_gathers++,
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)) {
@@ -191,7 +202,19 @@ static ssize_t nvhost_channelwrite(struct file *filp, const char __user *buf,
break;
}
priv->pinarray_size += numrelocs;
- priv->relocs_pending -= numrelocs;
+ priv->hdr.num_relocs -= numrelocs;
+ } else if (priv->hdr.num_waitchks) {
+ struct nvhost_waitchk *waitp;
+ consumed = sizeof(struct nvhost_waitchk);
+ if (remaining < consumed)
+ break;
+ waitp = &priv->waitchks[priv->num_waitchks];
+ if (copy_from_user(waitp, buf, consumed)) {
+ err = -EFAULT;
+ break;
+ }
+ priv->num_waitchks++;
+ priv->hdr.num_waitchks--;
} else {
err = -EFAULT;
break;
@@ -219,9 +242,9 @@ static int nvhost_ioctl_channel_flush(struct nvhost_channel_userctx *ctx,
u32 syncval;
int num_unpin;
int err;
- int nulled_incrs = null_kickoff ? ctx->syncpt_incrs : 0;
+ int nulled_incrs = null_kickoff ? ctx->hdr.syncpt_incrs : 0;
- 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(&ctx->ch->dev->pdev->dev, "channel submit out of sync\n");
return -EFAULT;
@@ -256,6 +279,22 @@ static int nvhost_ioctl_channel_flush(struct nvhost_channel_userctx *ctx,
return err;
}
+ /* remove stale waits */
+ if (ctx->num_waitchks) {
+ err = nvhost_syncpt_wait_check(ctx->nvmap,
+ &ctx->ch->dev->syncpt, ctx->hdr.waitchk_mask,
+ ctx->waitchks, ctx->num_waitchks);
+ if (err) {
+ dev_warn(&ctx->ch->dev->pdev->dev,
+ "nvhost_syncpt_wait_check failed: %d\n", err);
+ mutex_unlock(&ctx->ch->submitlock);
+ nvmap_unpin_handles(ctx->nvmap, ctx->unpinarray, num_unpin);
+ nvhost_module_idle(&ctx->ch->mod);
+ return err;
+ }
+ ctx->num_waitchks = 0;
+ }
+
/* context switch */
if (ctx->ch->cur_ctx != ctx->hwctx) {
struct nvhost_hwctx *hw = ctx->hwctx;
@@ -264,7 +303,7 @@ static int nvhost_ioctl_channel_flush(struct nvhost_channel_userctx *ctx,
ctx->gathers[gather_idx].op1 =
nvhost_opcode_gather(0, hw->restore_size);
ctx->gathers[gather_idx].op2 = hw->restore_phys;
- ctx->syncpt_incrs += hw->restore_incrs;
+ ctx->hdr.syncpt_incrs += hw->restore_incrs;
}
hw = ctx->ch->cur_ctx;
if (hw) {
@@ -272,7 +311,7 @@ static int nvhost_ioctl_channel_flush(struct nvhost_channel_userctx *ctx,
ctx->gathers[gather_idx].op1 =
nvhost_opcode_gather(0, hw->save_size);
ctx->gathers[gather_idx].op2 = hw->save_phys;
- ctx->syncpt_incrs += hw->save_incrs;
+ ctx->hdr.syncpt_incrs += hw->save_incrs;
num_intrs = 1;
ctxsw.syncpt_val = hw->save_incrs - 1;
ctxsw.intr_data = hw;
@@ -291,24 +330,24 @@ static int nvhost_ioctl_channel_flush(struct nvhost_channel_userctx *ctx,
}
/* get absolute sync value */
- if (BIT(ctx->syncpt_id) & NVSYNCPTS_CLIENT_MANAGED)
+ if (BIT(ctx->hdr.syncpt_id) & NVSYNCPTS_CLIENT_MANAGED)
syncval = nvhost_syncpt_set_max(&ctx->ch->dev->syncpt,
- ctx->syncpt_id, ctx->syncpt_incrs);
+ ctx->hdr.syncpt_id, ctx->hdr.syncpt_incrs);
else
syncval = nvhost_syncpt_incr_max(&ctx->ch->dev->syncpt,
- ctx->syncpt_id, ctx->syncpt_incrs);
+ ctx->hdr.syncpt_id, ctx->hdr.syncpt_incrs);
/* patch absolute syncpt value into interrupt triggers */
- ctxsw.syncpt_val += syncval - ctx->syncpt_incrs;
+ ctxsw.syncpt_val += syncval - ctx->hdr.syncpt_incrs;
nvhost_channel_submit(ctx->ch, ctx->nvmap, &ctx->gathers[gather_idx],
(null_kickoff ? 2 : ctx->num_gathers) - gather_idx, &ctxsw, num_intrs,
ctx->unpinarray, num_unpin,
- ctx->syncpt_id, syncval,
+ ctx->hdr.syncpt_id, syncval,
nulled_incrs);
/* schedule a submit complete interrupt */
- nvhost_intr_add_action(&ctx->ch->dev->intr, ctx->syncpt_id, syncval,
+ nvhost_intr_add_action(&ctx->ch->dev->intr, ctx->hdr.syncpt_id, syncval,
NVHOST_INTR_ACTION_SUBMIT_COMPLETE, ctx->ch, NULL);
mutex_unlock(&ctx->ch->submitlock);
@@ -342,7 +381,29 @@ 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 = (struct nvhost_submit_hdr_ext *)buf;
+
+ 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 = -EFAULT;
+ break;
+ }
+ 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);
+ 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;
diff --git a/drivers/video/tegra/host/nvhost_channel.h b/drivers/video/tegra/host/nvhost_channel.h
index 59ba06543a48..37ac769d4a61 100644
--- a/drivers/video/tegra/host/nvhost_channel.h
+++ b/drivers/video/tegra/host/nvhost_channel.h
@@ -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
diff --git a/drivers/video/tegra/host/nvhost_hardware.h b/drivers/video/tegra/host/nvhost_hardware.h
index a7663489727e..d1411b515ea5 100644
--- a/drivers/video/tegra/host/nvhost_hardware.h
+++ b/drivers/video/tegra/host/nvhost_hardware.h
@@ -141,6 +141,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)
{
diff --git a/drivers/video/tegra/host/nvhost_syncpt.c b/drivers/video/tegra/host/nvhost_syncpt.c
index dd2ab0d379e0..1881716ed428 100644
--- a/drivers/video/tegra/host/nvhost_syncpt.c
+++ b/drivers/video/tegra/host/nvhost_syncpt.c
@@ -226,7 +226,7 @@ done:
}
static const char *s_syncpt_names[32] = {
- "", "", "", "", "", "", "", "", "", "", "", "",
+ "gfx_host", "", "", "", "", "", "", "", "", "", "", "",
"vi_isp_0", "vi_isp_1", "vi_isp_2", "vi_isp_3", "vi_isp_4", "vi_isp_5",
"2d_0", "2d_1",
"", "",
@@ -254,3 +254,63 @@ 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 x >= y (mod 1 << 32) */
+static bool nvhost_syncpt_wrapping_comparison(u32 x, u32 y)
+{
+ return nvhost_syncpt_is_between(y, x, (1UL<<31UL)+y);
+}
+
+/* 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 *waitp, u32 waitchks)
+{
+ 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(!waitp);
+
+ /* compare syncpt vs wait threshold */
+ while (waitchks) {
+ u32 syncpt, override;
+
+ BUG_ON(waitp->syncpt_id > NV_HOST1X_SYNCPT_NB_PTS);
+
+ syncpt = atomic_read(&sp->min_val[waitp->syncpt_id]);
+ if (nvhost_syncpt_wrapping_comparison(syncpt, waitp->thresh)) {
+
+ /* wait has completed already, so can be removed */
+ dev_dbg(&syncpt_to_dev(sp)->pdev->dev,
+ "drop WAIT id %d (%s) thresh 0x%x, syncpt 0x%x\n",
+ waitp->syncpt_id, nvhost_syncpt_name(waitp->syncpt_id),
+ waitp->thresh, syncpt);
+
+ /* move wait to a kernel reserved syncpt (that's always 0) */
+ override = nvhost_class_host_wait_syncpt(NVSYNCPT_GRAPHICS_HOST, 0);
+
+ /* patch the wait */
+ err = nvmap_patch_wait(nvmap,
+ (struct nvmap_handle *)waitp->mem,
+ waitp->offset, override);
+ if (err)
+ break;
+ }
+ waitchks--;
+ waitp++;
+ }
+ return err;
+}
diff --git a/drivers/video/tegra/host/nvhost_syncpt.h b/drivers/video/tegra/host/nvhost_syncpt.h
index f161f2051406..b4ce3c6ee6d4 100644
--- a/drivers/video/tegra/host/nvhost_syncpt.h
+++ b/drivers/video/tegra/host/nvhost_syncpt.h
@@ -25,10 +25,13 @@
#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_VI_ISP_0 (12)
#define NVSYNCPT_VI_ISP_1 (13)
#define NVSYNCPT_VI_ISP_2 (14)
@@ -142,6 +145,9 @@ static inline int nvhost_syncpt_wait(struct nvhost_syncpt *sp, u32 id, u32 thres
return nvhost_syncpt_wait_timeout(sp, id, thresh, MAX_SCHEDULE_TIMEOUT);
}
+int nvhost_syncpt_wait_check(struct nvmap_client *nvmap,
+ struct nvhost_syncpt *sp, u32 mask,
+ struct nvhost_waitchk *waitp, u32 num_waits);
const char *nvhost_syncpt_name(u32 id);
diff --git a/drivers/video/tegra/nvmap/nvmap.c b/drivers/video/tegra/nvmap/nvmap.c
index 00b9a5adb49f..e8d795006082 100644
--- a/drivers/video/tegra/nvmap/nvmap.c
+++ b/drivers/video/tegra/nvmap/nvmap.c
@@ -730,3 +730,53 @@ 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_wait(struct nvmap_client *client,
+ struct nvmap_handle *patch,
+ u32 patch_offset, u32 patch_value)
+{
+ unsigned long phys;
+ unsigned int pfn, last_pfn = 0;
+ void *addr;
+ pte_t **pte;
+
+ 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);
+
+ /* write PTE, so addr points to cmdbuf PFN */
+ if (pfn != last_pfn) {
+ pgprot_t prot = nvmap_pgprot(patch, pgprot_kernel);
+ unsigned long kaddr = (unsigned long)addr;
+ set_pte_at(&init_mm, kaddr, *pte, pfn_pte(pfn, prot));
+ flush_tlb_kernel_page(kaddr);
+ last_pfn = pfn;
+ }
+
+ /* write patch_value to addr + page offset */
+ __raw_writel(patch_value, addr + (phys & ~PAGE_MASK));
+
+ nvmap_free_pte(client->dev, pte);
+ wmb();
+ return 0;
+}