diff mbox

[2/2] drm/omap: add GEM support for tiled/dmm buffers

Message ID 1323134362-3146-3-git-send-email-rob.clark@linaro.org
State Accepted
Commit f7f9f4536a09f9f73c800663df2e600c9405e1ef
Headers show

Commit Message

Rob Clark Dec. 6, 2011, 1:19 a.m. UTC
From: Rob Clark <rob@ti.com>

TILER/DMM provides two features for omapdrm GEM objects:
1) providing a physically contiguous view to discontiguous memory
   for hw initiators that cannot otherwise support discontiguous
   buffers (DSS scanout, IVAHD video decode/encode, etc)
2) providing untiling for 2d tiled buffers, which are used in some
   cases to provide rotation and reduce memory bandwidth for hw
   initiators that tend to access data in 2d block patterns.

For 2d tiled buffers, there are some additional complications when
it comes to userspace mmap'ings.  For non-tiled buffers, the original
(potentially physically discontiguous) pages are used to back the
mmap.  For tiled buffers, we need to mmap via the tiler/dmm region to
provide an unswizzled view of the buffer.  But (a) the buffer is not
necessarily pinned in TILER all the time (it can be unmapped when
there is no DMA access to the buffer), and (b) when they are they
are pinned, they not necessarily page aligned from the perspective of
the CPU.  And non-page aligned userspace buffer mapping is evil.

To solve this, we reserve one or more small regions in each of the 2d
containers when the driver is loaded to use as a "user-GART" where we
can create a second page-aligned mapping of parts of the buffer being
accessed from userspace.  Page faulting is used to evict and remap

Comments

Alan Cox Dec. 6, 2011, 11:30 a.m. UTC | #1
On Mon,  5 Dec 2011 19:19:22 -0600
Rob Clark <rob.clark@linaro.org> wrote:

> +	usergart = kzalloc(3 * sizeof(*usergart), GFP_KERNEL);
> +
> +	/* reserve 4k aligned/wide regions for userspace mappings: */
> +	for (i = 0; i < ARRAY_SIZE(fmts); i++) {
> +		uint16_t h = 1, w = PAGE_SIZE >> i;
> +		tiler_align(fmts[i], &w, &h);
> +		/* note: since each region is 1 4kb page wide, and minimum
> +		 * number of rows, the height ends up being the same as the
> +		 * # of pages in the region
> +		 */
> +		usergart[i].height = h;
> +		usergart[i].height_shift = ilog2(h);

Seems to be missing anallocation failure check
Rob Clark Dec. 7, 2011, 4:25 a.m. UTC | #2
On Tue, Dec 6, 2011 at 5:30 AM, Alan Cox <alan@lxorguk.ukuu.org.uk> wrote:
> On Mon,  5 Dec 2011 19:19:22 -0600
> Rob Clark <rob.clark@linaro.org> wrote:
>
>> +     usergart = kzalloc(3 * sizeof(*usergart), GFP_KERNEL);
>> +
>> +     /* reserve 4k aligned/wide regions for userspace mappings: */
>> +     for (i = 0; i < ARRAY_SIZE(fmts); i++) {
>> +             uint16_t h = 1, w = PAGE_SIZE >> i;
>> +             tiler_align(fmts[i], &w, &h);
>> +             /* note: since each region is 1 4kb page wide, and minimum
>> +              * number of rows, the height ends up being the same as the
>> +              * # of pages in the region
>> +              */
>> +             usergart[i].height = h;
>> +             usergart[i].height_shift = ilog2(h);
>
> Seems to be missing anallocation failure check
>

oh, woops.. I've fixed that.. will give a couple more days to see if
there are any other comments and then send v2..

BR,
-R
diff mbox

Patch

different regions of whichever buffers are being accessed from user-
space.

Signed-off-by: Rob Clark <rob@ti.com>
---
 drivers/staging/omapdrm/TODO               |    5 +
 drivers/staging/omapdrm/omap_drv.c         |    6 +-
 drivers/staging/omapdrm/omap_drv.h         |    3 +
 drivers/staging/omapdrm/omap_fb.c          |    2 +-
 drivers/staging/omapdrm/omap_gem.c         |  432 +++++++++++++++++++++++++---
 drivers/staging/omapdrm/omap_gem_helpers.c |   55 ++++
 6 files changed, 466 insertions(+), 37 deletions(-)

diff --git a/drivers/staging/omapdrm/TODO b/drivers/staging/omapdrm/TODO
index 18677e7..55b1837 100644
--- a/drivers/staging/omapdrm/TODO
+++ b/drivers/staging/omapdrm/TODO
@@ -22,6 +22,11 @@  TODO
 . Review DSS vs KMS mismatches.  The omap_dss_device is sort of part encoder,
   part connector.  Which results in a bit of duct tape to fwd calls from
   encoder to connector.  Possibly this could be done a bit better.
+. Solve PM sequencing on resume.  DMM/TILER must be reloaded before any
+  access is made from any component in the system.  Which means on suspend
+  CRTC's should be disabled, and on resume the LUT should be reprogrammed
+  before CRTC's are re-enabled, to prevent DSS from trying to DMA from a
+  buffer mapped in DMM/TILER before LUT is reloaded.
 . Add debugfs information for DMM/TILER
 
 Userspace:
diff --git a/drivers/staging/omapdrm/omap_drv.c b/drivers/staging/omapdrm/omap_drv.c
index 71de7cf..7ecf578 100644
--- a/drivers/staging/omapdrm/omap_drv.c
+++ b/drivers/staging/omapdrm/omap_drv.c
@@ -509,7 +509,7 @@  static int ioctl_gem_info(struct drm_device *dev, void *data,
 		return -ENOENT;
 	}
 
-	args->size = obj->size;  /* for now */
+	args->size = omap_gem_mmap_size(obj);
 	args->offset = omap_gem_mmap_offset(obj);
 
 	drm_gem_object_unreference_unlocked(obj);
@@ -557,6 +557,8 @@  static int dev_load(struct drm_device *dev, unsigned long flags)
 
 	dev->dev_private = priv;
 
+	omap_gem_init(dev);
+
 	ret = omap_modeset_init(dev);
 	if (ret) {
 		dev_err(dev->dev, "omap_modeset_init failed: ret=%d\n", ret);
@@ -589,8 +591,8 @@  static int dev_unload(struct drm_device *dev)
 	drm_kms_helper_poll_fini(dev);
 
 	omap_fbdev_free(dev);
-
 	omap_modeset_free(dev);
+	omap_gem_deinit(dev);
 
 	kfree(dev->dev_private);
 	dev->dev_private = NULL;
diff --git a/drivers/staging/omapdrm/omap_drv.h b/drivers/staging/omapdrm/omap_drv.h
index c8f2752..9d0783d 100644
--- a/drivers/staging/omapdrm/omap_drv.h
+++ b/drivers/staging/omapdrm/omap_drv.h
@@ -84,6 +84,8 @@  struct drm_connector *omap_framebuffer_get_next_connector(
 void omap_framebuffer_flush(struct drm_framebuffer *fb,
 		int x, int y, int w, int h);
 
+void omap_gem_init(struct drm_device *dev);
+void omap_gem_deinit(struct drm_device *dev);
 
 struct drm_gem_object *omap_gem_new(struct drm_device *dev,
 		union omap_gem_size gsize, uint32_t flags);
@@ -109,6 +111,7 @@  int omap_gem_get_paddr(struct drm_gem_object *obj,
 		dma_addr_t *paddr, bool remap);
 int omap_gem_put_paddr(struct drm_gem_object *obj);
 uint64_t omap_gem_mmap_offset(struct drm_gem_object *obj);
+size_t omap_gem_mmap_size(struct drm_gem_object *obj);
 
 static inline int align_pitch(int pitch, int width, int bpp)
 {
diff --git a/drivers/staging/omapdrm/omap_fb.c b/drivers/staging/omapdrm/omap_fb.c
index 82ed612..491be53 100644
--- a/drivers/staging/omapdrm/omap_fb.c
+++ b/drivers/staging/omapdrm/omap_fb.c
@@ -102,7 +102,7 @@  int omap_framebuffer_get_buffer(struct drm_framebuffer *fb, int x, int y,
 		 * dma_alloc_coherent()).  But this should be ok because it
 		 * is only used by legacy fbdev
 		 */
-		BUG_ON(!bo_vaddr);
+		BUG_ON(IS_ERR_OR_NULL(bo_vaddr));
 		*vaddr = bo_vaddr + offset;
 	}
 
diff --git a/drivers/staging/omapdrm/omap_gem.c b/drivers/staging/omapdrm/omap_gem.c
index bc1709c..1054da3 100644
--- a/drivers/staging/omapdrm/omap_gem.c
+++ b/drivers/staging/omapdrm/omap_gem.c
@@ -22,11 +22,13 @@ 
 #include <linux/shmem_fs.h>
 
 #include "omap_drv.h"
+#include "omap_dmm_tiler.h"
 
 /* remove these once drm core helpers are merged */
 struct page ** _drm_gem_get_pages(struct drm_gem_object *obj, gfp_t gfpmask);
 void _drm_gem_put_pages(struct drm_gem_object *obj, struct page **pages,
 		bool dirty, bool accessed);
+int _drm_gem_create_mmap_offset_size(struct drm_gem_object *obj, size_t size);
 
 /*
  * GEM buffer object implementation.
@@ -45,9 +47,16 @@  struct omap_gem_object {
 
 	uint32_t flags;
 
+	/** width/height for tiled formats (rounded up to slot boundaries) */
+	uint16_t width, height;
+
 	/**
 	 * If buffer is allocated physically contiguous, the OMAP_BO_DMA flag
-	 * is set and the paddr is valid.
+	 * is set and the paddr is valid.  Also if the buffer is remapped in
+	 * TILER and paddr_cnt > 0, then paddr is valid.  But if you are using
+	 * the physical address and OMAP_BO_DMA is not set, then you should
+	 * be going thru omap_gem_{get,put}_paddr() to ensure the mapping is
+	 * not removed from under your feet.
 	 *
 	 * Note that OMAP_BO_SCANOUT is a hint from userspace that DMA capable
 	 * buffer is requested, but doesn't mean that it is.  Use the
@@ -57,6 +66,16 @@  struct omap_gem_object {
 	dma_addr_t paddr;
 
 	/**
+	 * # of users of paddr
+	 */
+	uint32_t paddr_cnt;
+
+	/**
+	 * tiler block used when buffer is remapped in DMM/TILER.
+	 */
+	struct tiler_block *block;
+
+	/**
 	 * Array of backing pages, if allocated.  Note that pages are never
 	 * allocated for buffers originally allocated from contiguous memory
 	 */
@@ -91,6 +110,67 @@  struct omap_gem_object {
 	} *sync;
 };
 
+/* To deal with userspace mmap'ings of 2d tiled buffers, which (a) are
+ * not necessarily pinned in TILER all the time, and (b) when they are
+ * they are not necessarily page aligned, we reserve one or more small
+ * regions in each of the 2d containers to use as a user-GART where we
+ * can create a second page-aligned mapping of parts of the buffer
+ * being accessed from userspace.
+ *
+ * Note that we could optimize slightly when we know that multiple
+ * tiler containers are backed by the same PAT.. but I'll leave that
+ * for later..
+ */
+#define NUM_USERGART_ENTRIES 2
+struct usergart_entry {
+	struct tiler_block *block;	/* the reserved tiler block */
+	dma_addr_t paddr;
+	struct drm_gem_object *obj;	/* the current pinned obj */
+	pgoff_t obj_pgoff;		/* page offset of obj currently
+					   mapped in */
+};
+static struct {
+	struct usergart_entry entry[NUM_USERGART_ENTRIES];
+	int height;				/* height in rows */
+	int height_shift;		/* ilog2(height in rows) */
+	int slot_shift;			/* ilog2(width per slot) */
+	int stride_pfn;			/* stride in pages */
+	int last;				/* index of last used entry */
+} *usergart;
+
+static void evict_entry(struct drm_gem_object *obj,
+		enum tiler_fmt fmt, struct usergart_entry *entry)
+{
+	if (obj->dev->dev_mapping) {
+		size_t size = PAGE_SIZE * usergart[fmt].height;
+		loff_t off = omap_gem_mmap_offset(obj) +
+				(entry->obj_pgoff << PAGE_SHIFT);
+		unmap_mapping_range(obj->dev->dev_mapping, off, size, 1);
+	}
+
+	entry->obj = NULL;
+}
+
+/* Evict a buffer from usergart, if it is mapped there */
+static void evict(struct drm_gem_object *obj)
+{
+	struct omap_gem_object *omap_obj = to_omap_bo(obj);
+
+	if (omap_obj->flags & OMAP_BO_TILED) {
+		enum tiler_fmt fmt = gem2fmt(omap_obj->flags);
+		int i;
+
+		if (!usergart)
+			return;
+
+		for (i = 0; i < NUM_USERGART_ENTRIES; i++) {
+			struct usergart_entry *entry = &usergart[fmt].entry[i];
+			if (entry->obj == obj)
+				evict_entry(obj, fmt, entry);
+		}
+	}
+}
+
 /* GEM objects can either be allocated from contiguous memory (in which
  * case obj->filp==NULL), or w/ shmem backing (obj->filp!=NULL).  But non
  * contiguous buffers can be remapped in TILER/DMM if they need to be
@@ -142,7 +222,9 @@  uint64_t omap_gem_mmap_offset(struct drm_gem_object *obj)
 {
 	if (!obj->map_list.map) {
 		/* Make it mmapable */
-		int ret = drm_gem_create_mmap_offset(obj);
+		size_t size = omap_gem_mmap_size(obj);
+		int ret = _drm_gem_create_mmap_offset_size(obj, size);
+
 		if (ret) {
 			dev_err(obj->dev->dev, "could not allocate mmap offset");
 			return 0;
@@ -152,6 +234,134 @@  uint64_t omap_gem_mmap_offset(struct drm_gem_object *obj)
 	return (uint64_t)obj->map_list.hash.key << PAGE_SHIFT;
 }
 
+/** get mmap size */
+size_t omap_gem_mmap_size(struct drm_gem_object *obj)
+{
+	struct omap_gem_object *omap_obj = to_omap_bo(obj);
+	size_t size = obj->size;
+
+	if (omap_obj->flags & OMAP_BO_TILED) {
+		/* for tiled buffers, the virtual size has stride rounded up
+		 * to 4kb.. (to hide the fact that row n+1 might start 16kb or
+		 * 32kb later!).  But we don't back the entire buffer with
+		 * pages, only the valid picture part.. so need to adjust for
+		 * this in the size used to mmap and generate mmap offset
+		 */
+		size = tiler_vsize(gem2fmt(omap_obj->flags),
+				omap_obj->width, omap_obj->height);
+	}
+
+	return size;
+}
+
+
+/* Normal handling for the case of faulting in non-tiled buffers */
+static int fault_1d(struct drm_gem_object *obj,
+		struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+	struct omap_gem_object *omap_obj = to_omap_bo(obj);
+	unsigned long pfn;
+	pgoff_t pgoff;
+
+	/* We don't use vmf->pgoff since that has the fake offset: */
+	pgoff = ((unsigned long)vmf->virtual_address -
+			vma->vm_start) >> PAGE_SHIFT;
+
+	if (omap_obj->pages) {
+		pfn = page_to_pfn(omap_obj->pages[pgoff]);
+	} else {
+		BUG_ON(!(omap_obj->flags & OMAP_BO_DMA));
+		pfn = (omap_obj->paddr >> PAGE_SHIFT) + pgoff;
+	}
+
+	VERB("Inserting %p pfn %lx, pa %lx", vmf->virtual_address,
+			pfn, pfn << PAGE_SHIFT);
+
+	return vm_insert_mixed(vma, (unsigned long)vmf->virtual_address, pfn);
+}
+
+/* Special handling for the case of faulting in 2d tiled buffers */
+static int fault_2d(struct drm_gem_object *obj,
+		struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+	struct omap_gem_object *omap_obj = to_omap_bo(obj);
+	struct usergart_entry *entry;
+	enum tiler_fmt fmt = gem2fmt(omap_obj->flags);
+	struct page *pages[64];  /* XXX is this too much to have on stack? */
+	unsigned long pfn;
+	pgoff_t pgoff, base_pgoff;
+	void __user *vaddr;
+	int i, ret, slots;
+
+	if (!usergart)
+		return -EFAULT;
+
+	/* TODO: this fxn might need a bit tweaking to deal w/ tiled buffers
+	 * that are wider than 4kb
+	 */
+
+	/* We don't use vmf->pgoff since that has the fake offset: */
+	pgoff = ((unsigned long)vmf->virtual_address -
+			vma->vm_start) >> PAGE_SHIFT;
+
+	/* actual address we start mapping at is rounded down to previous slot
+	 * boundary in the y direction:
+	 */
+	base_pgoff = round_down(pgoff, usergart[fmt].height);
+	vaddr = vmf->virtual_address - ((pgoff - base_pgoff) << PAGE_SHIFT);
+	entry = &usergart[fmt].entry[usergart[fmt].last];
+
+	slots = omap_obj->width >> usergart[fmt].slot_shift;
+
+	/* evict previous buffer using this usergart entry, if any: */
+	if (entry->obj)
+		evict_entry(entry->obj, fmt, entry);
+
+	entry->obj = obj;
+	entry->obj_pgoff = base_pgoff;
+
+	/* now convert base_pgoff to phys offset from virt offset:
+	 */
+	base_pgoff = (base_pgoff >> usergart[fmt].height_shift) * slots;
+
+	/* map in pages.  Note the height of the slot is also equal to the
+	 * number of pages that need to be mapped in to fill 4kb wide CPU page.
+	 * If the height is 64, then 64 pages fill a 4kb wide by 64 row region.
+	 * Beyond the valid pixel part of the buffer, we set pages[i] to NULL to
+	 * get a dummy page mapped in.. if someone reads/writes it they will get
+	 * random/undefined content, but at least it won't be corrupting
+	 * whatever other random page used to be mapped in, or other undefined
+	 * behavior.
+	 */
+	memcpy(pages, &omap_obj->pages[base_pgoff],
+			sizeof(struct page *) * slots);
+	memset(pages + slots, 0,
+			sizeof(struct page *) * (usergart[fmt].height - slots));
+
+	ret = tiler_pin(entry->block, pages, true);
+	if (ret) {
+		dev_err(obj->dev->dev, "failed to pin: %d\n", ret);
+		return ret;
+	}
+
+	i = usergart[fmt].height;
+	pfn = entry->paddr >> PAGE_SHIFT;
+
+	VERB("Inserting %p pfn %lx, pa %lx", vmf->virtual_address,
+			pfn, pfn << PAGE_SHIFT);
+
+	while (i--) {
+		vm_insert_mixed(vma, (unsigned long)vaddr, pfn);
+		pfn += usergart[fmt].stride_pfn;
+		vaddr += PAGE_SIZE;
+	}
+
+	/* simple round-robin: */
+	usergart[fmt].last = (usergart[fmt].last + 1) % NUM_USERGART_ENTRIES;
+
+	return 0;
+}
+
 /**
  * omap_gem_fault		-	pagefault handler for GEM objects
  * @vma: the VMA of the GEM object
@@ -171,8 +381,6 @@  int omap_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
 	struct omap_gem_object *omap_obj = to_omap_bo(obj);
 	struct drm_device *dev = obj->dev;
 	struct page **pages;
-	unsigned long pfn;
-	pgoff_t pgoff;
 	int ret;
 
 	/* Make sure we don't parallel update on a fault, nor move or remove
@@ -192,21 +400,11 @@  int omap_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
 	 * probably trigger put_pages()?
 	 */
 
-	/* We don't use vmf->pgoff since that has the fake offset: */
-	pgoff = ((unsigned long)vmf->virtual_address -
-			vma->vm_start) >> PAGE_SHIFT;
+	if (omap_obj->flags & OMAP_BO_TILED)
+		ret = fault_2d(obj, vma, vmf);
+	else
+		ret = fault_1d(obj, vma, vmf);
 
-	if (omap_obj->pages) {
-		pfn = page_to_pfn(omap_obj->pages[pgoff]);
-	} else {
-		BUG_ON(!(omap_obj->flags & OMAP_BO_DMA));
-		pfn = (omap_obj->paddr >> PAGE_SHIFT) + pgoff;
-	}
-
-	VERB("Inserting %p pfn %lx, pa %lx", vmf->virtual_address,
-			pfn, pfn << PAGE_SHIFT);
-
-	ret = vm_insert_mixed(vma, (unsigned long)vmf->virtual_address, pfn);
 
 fail:
 	mutex_unlock(&dev->struct_mutex);
@@ -308,8 +506,6 @@  int omap_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev,
 	struct drm_gem_object *obj;
 	int ret = 0;
 
-	mutex_lock(&dev->struct_mutex);
-
 	/* GEM does all our handle to object mapping */
 	obj = drm_gem_object_lookup(dev, file, handle);
 	if (obj == NULL) {
@@ -322,7 +518,6 @@  int omap_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev,
 	drm_gem_object_unreference_unlocked(obj);
 
 fail:
-	mutex_unlock(&dev->struct_mutex);
 	return ret;
 }
 
@@ -336,12 +531,61 @@  int omap_gem_get_paddr(struct drm_gem_object *obj,
 	struct omap_gem_object *omap_obj = to_omap_bo(obj);
 	int ret = 0;
 
-	if (is_shmem(obj)) {
-		/* TODO: remap to TILER */
-		return -ENOMEM;
+	mutex_lock(&obj->dev->struct_mutex);
+
+	if (remap && is_shmem(obj)) {
+		if (omap_obj->paddr_cnt == 0) {
+			struct page **pages;
+			enum tiler_fmt fmt = gem2fmt(omap_obj->flags);
+			struct tiler_block *block;
+			BUG_ON(omap_obj->block);
+
+			ret = get_pages(obj, &pages);
+			if (ret)
+				goto fail;
+
+
+			if (omap_obj->flags & OMAP_BO_TILED) {
+				block = tiler_reserve_2d(fmt,
+						omap_obj->width,
+						omap_obj->height, 0);
+			} else {
+				block = tiler_reserve_1d(obj->size);
+			}
+
+			if (IS_ERR(block)) {
+				ret = PTR_ERR(block);
+				dev_err(obj->dev->dev,
+					"could not remap: %d (%d)\n", ret, fmt);
+				goto fail;
+			}
+
+			/* TODO: enable async refill.. */
+			ret = tiler_pin(block, pages, true);
+			if (ret) {
+				tiler_release(block);
+				dev_err(obj->dev->dev,
+						"could not pin: %d\n", ret);
+				goto fail;
+			}
+
+			omap_obj->paddr = tiler_ssptr(block);
+			omap_obj->block = block;
+
+			DBG("got paddr: %08x", omap_obj->paddr);
+		}
+
+		omap_obj->paddr_cnt++;
+
+		*paddr = omap_obj->paddr;
+	} else if (omap_obj->flags & OMAP_BO_DMA) {
+		*paddr = omap_obj->paddr;
+	} else {
+		ret = -EINVAL;
 	}
 
-	*paddr = omap_obj->paddr;
+fail:
+	mutex_unlock(&obj->dev->struct_mutex);
 
 	return ret;
 }
@@ -351,8 +595,30 @@  int omap_gem_get_paddr(struct drm_gem_object *obj,
  */
 int omap_gem_put_paddr(struct drm_gem_object *obj)
 {
-	/* do something here when remap to TILER is used.. */
-	return 0;
+	struct omap_gem_object *omap_obj = to_omap_bo(obj);
+	int ret = 0;
+
+	mutex_lock(&obj->dev->struct_mutex);
+	if (omap_obj->paddr_cnt > 0) {
+		omap_obj->paddr_cnt--;
+		if (omap_obj->paddr_cnt == 0) {
+			ret = tiler_unpin(omap_obj->block);
+			if (ret) {
+				dev_err(obj->dev->dev,
+					"could not unpin pages: %d\n", ret);
+				goto fail;
+			}
+			ret = tiler_release(omap_obj->block);
+			if (ret) {
+				dev_err(obj->dev->dev,
+					"could not release unmap: %d\n", ret);
+			}
+			omap_obj->block = NULL;
+		}
+	}
+fail:
+	mutex_unlock(&obj->dev->struct_mutex);
+	return ret;
 }
 
 /* acquire pages when needed (for example, for DMA where physically
@@ -396,13 +662,22 @@  int omap_gem_put_pages(struct drm_gem_object *obj)
 	return 0;
 }
 
-/* Get kernel virtual address for CPU access.. only buffers that are
- * allocated contiguously have a kernel virtual address, so this more
- * or less only exists for omap_fbdev
+/* Get kernel virtual address for CPU access.. this more or less only
+ * exists for omap_fbdev.  This should be called with struct_mutex
+ * held.
  */
 void *omap_gem_vaddr(struct drm_gem_object *obj)
 {
 	struct omap_gem_object *omap_obj = to_omap_bo(obj);
+	WARN_ON(! mutex_is_locked(&obj->dev->struct_mutex));
+	if (!omap_obj->vaddr) {
+		struct page **pages;
+		int ret = get_pages(obj, &pages);
+		if (ret)
+			return ERR_PTR(ret);
+		omap_obj->vaddr = vmap(pages, obj->size >> PAGE_SHIFT,
+				VM_MAP, pgprot_writecombine(PAGE_KERNEL));
+	}
 	return omap_obj->vaddr;
 }
 
@@ -670,6 +945,8 @@  void omap_gem_free_object(struct drm_gem_object *obj)
 	struct drm_device *dev = obj->dev;
 	struct omap_gem_object *omap_obj = to_omap_bo(obj);
 
+	evict(obj);
+
 	if (obj->map_list.map) {
 		drm_gem_free_mmap_offset(obj);
 	}
@@ -682,6 +959,8 @@  void omap_gem_free_object(struct drm_gem_object *obj)
 		if (!is_shmem(obj)) {
 			dma_free_writecombine(dev->dev, obj->size,
 					omap_obj->vaddr, omap_obj->paddr);
+		} else if (omap_obj->vaddr) {
+			vunmap(omap_obj->vaddr);
 		}
 	}
 
@@ -729,11 +1008,32 @@  struct drm_gem_object *omap_gem_new(struct drm_device *dev,
 	int ret;
 
 	if (flags & OMAP_BO_TILED) {
-		/* TODO: not implemented yet */
-		goto fail;
-	}
+		if (!usergart) {
+			dev_err(dev->dev, "Tiled buffers require DMM\n");
+			goto fail;
+		}
+
+		/* tiled buffers are always shmem paged backed.. when they are
+		 * scanned out, they are remapped into DMM/TILER
+		 */
+		flags &= ~OMAP_BO_SCANOUT;
+
+		/* currently don't allow cached buffers.. there is some caching
+		 * stuff that needs to be handled better
+		 */
+		flags &= ~(OMAP_BO_CACHED|OMAP_BO_UNCACHED);
+		flags |= OMAP_BO_WC;
 
-	size = PAGE_ALIGN(gsize.bytes);
+		/* align dimensions to slot boundaries... */
+		tiler_align(gem2fmt(flags),
+				&gsize.tiled.width, &gsize.tiled.height);
+
+		/* ...and calculate size based on aligned dimensions */
+		size = tiler_size(gem2fmt(flags),
+				gsize.tiled.width, gsize.tiled.height);
+	} else {
+		size = PAGE_ALIGN(gsize.bytes);
+	}
 
 	omap_obj = kzalloc(sizeof(*omap_obj), GFP_KERNEL);
 	if (!omap_obj) {
@@ -754,6 +1054,11 @@  struct drm_gem_object *omap_gem_new(struct drm_device *dev,
 
 	omap_obj->flags = flags;
 
+	if (flags & OMAP_BO_TILED) {
+		omap_obj->width = gsize.tiled.width;
+		omap_obj->height = gsize.tiled.height;
+	}
+
 	if (flags & (OMAP_BO_DMA|OMAP_BO_EXT_MEM)) {
 		ret = drm_gem_private_object_init(dev, obj, size);
 	} else {
@@ -772,3 +1077,62 @@  fail:
 	}
 	return NULL;
 }
+
+/* init/cleanup.. if DMM is used, we need to set some stuff up.. */
+void omap_gem_init(struct drm_device *dev)
+{
+	const enum tiler_fmt fmts[] = {
+			TILFMT_8BIT, TILFMT_16BIT, TILFMT_32BIT
+	};
+	int i, j, ret;
+
+	ret = omap_dmm_init(dev);
+	if (ret) {
+		/* DMM only supported on OMAP4 and later, so this isn't fatal */
+		dev_warn(dev->dev, "omap_dmm_init failed, disabling DMM\n");
+		return;
+	}
+
+	usergart = kzalloc(3 * sizeof(*usergart), GFP_KERNEL);
+
+	/* reserve 4k aligned/wide regions for userspace mappings: */
+	for (i = 0; i < ARRAY_SIZE(fmts); i++) {
+		uint16_t h = 1, w = PAGE_SIZE >> i;
+		tiler_align(fmts[i], &w, &h);
+		/* note: since each region is 1 4kb page wide, and minimum
+		 * number of rows, the height ends up being the same as the
+		 * # of pages in the region
+		 */
+		usergart[i].height = h;
+		usergart[i].height_shift = ilog2(h);
+		usergart[i].stride_pfn = tiler_stride(fmts[i]) >> PAGE_SHIFT;
+		usergart[i].slot_shift = ilog2((PAGE_SIZE / h) >> i);
+		for (j = 0; j < NUM_USERGART_ENTRIES; j++) {
+			struct usergart_entry *entry = &usergart[i].entry[j];
+			struct tiler_block *block =
+					tiler_reserve_2d(fmts[i], w, h,
+							PAGE_SIZE);
+			if (IS_ERR(block)) {
+				dev_err(dev->dev,
+						"reserve failed: %d, %d, %ld\n",
+						i, j, PTR_ERR(block));
+				return;
+			}
+			entry->paddr = tiler_ssptr(block);
+			entry->block = block;
+
+			DBG("%d:%d: %dx%d: paddr=%08x stride=%d", i, j, w, h,
+					entry->paddr,
+					usergart[i].stride_pfn << PAGE_SHIFT);
+		}
+	}
+}
+
+void omap_gem_deinit(struct drm_device *dev)
+{
+	/* I believe we can rely on there being no more outstanding GEM
+	 * objects which could depend on usergart/dmm at this point.
+	 */
+	omap_dmm_remove();
+	kfree(usergart);
+}
diff --git a/drivers/staging/omapdrm/omap_gem_helpers.c b/drivers/staging/omapdrm/omap_gem_helpers.c
index 1cfc12d..29275c7 100644
--- a/drivers/staging/omapdrm/omap_gem_helpers.c
+++ b/drivers/staging/omapdrm/omap_gem_helpers.c
@@ -112,3 +112,58 @@  void _drm_gem_put_pages(struct drm_gem_object *obj, struct page **pages,
 
 	drm_free_large(pages);
 }
+
+int
+_drm_gem_create_mmap_offset_size(struct drm_gem_object *obj, size_t size)
+{
+	struct drm_device *dev = obj->dev;
+	struct drm_gem_mm *mm = dev->mm_private;
+	struct drm_map_list *list;
+	struct drm_local_map *map;
+	int ret = 0;
+
+	/* Set the object up for mmap'ing */
+	list = &obj->map_list;
+	list->map = kzalloc(sizeof(struct drm_map_list), GFP_KERNEL);
+	if (!list->map)
+		return -ENOMEM;
+
+	map = list->map;
+	map->type = _DRM_GEM;
+	map->size = size;
+	map->handle = obj;
+
+	/* Get a DRM GEM mmap offset allocated... */
+	list->file_offset_node = drm_mm_search_free(&mm->offset_manager,
+			size / PAGE_SIZE, 0, 0);
+
+	if (!list->file_offset_node) {
+		DRM_ERROR("failed to allocate offset for bo %d\n", obj->name);
+		ret = -ENOSPC;
+		goto out_free_list;
+	}
+
+	list->file_offset_node = drm_mm_get_block(list->file_offset_node,
+			size / PAGE_SIZE, 0);
+	if (!list->file_offset_node) {
+		ret = -ENOMEM;
+		goto out_free_list;
+	}
+
+	list->hash.key = list->file_offset_node->start;
+	ret = drm_ht_insert_item(&mm->offset_hash, &list->hash);
+	if (ret) {
+		DRM_ERROR("failed to add to map hash\n");
+		goto out_free_mm;
+	}
+
+	return 0;
+
+out_free_mm:
+	drm_mm_put_block(list->file_offset_node);
+out_free_list:
+	kfree(list->map);
+	list->map = NULL;
+
+	return ret;
+}