diff options
Diffstat (limited to 'drivers/staging/omapdrm/omap_gem.c')
-rw-r--r-- | drivers/staging/omapdrm/omap_gem.c | 774 |
1 files changed, 774 insertions, 0 deletions
diff --git a/drivers/staging/omapdrm/omap_gem.c b/drivers/staging/omapdrm/omap_gem.c new file mode 100644 index 0000000..bc1709c --- /dev/null +++ b/drivers/staging/omapdrm/omap_gem.c @@ -0,0 +1,774 @@ +/* + * drivers/staging/omapdrm/omap_gem.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include <linux/spinlock.h> +#include <linux/shmem_fs.h> + +#include "omap_drv.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); + +/* + * GEM buffer object implementation. + */ + +#define to_omap_bo(x) container_of(x, struct omap_gem_object, base) + +/* note: we use upper 8 bits of flags for driver-internal flags: */ +#define OMAP_BO_DMA 0x01000000 /* actually is physically contiguous */ +#define OMAP_BO_EXT_SYNC 0x02000000 /* externally allocated sync object */ +#define OMAP_BO_EXT_MEM 0x04000000 /* externally allocated memory */ + + +struct omap_gem_object { + struct drm_gem_object base; + + uint32_t flags; + + /** + * If buffer is allocated physically contiguous, the OMAP_BO_DMA flag + * is set and the paddr is valid. + * + * 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 + * OMAP_BO_DMA flag to determine if the buffer has a DMA capable + * physical address. + */ + dma_addr_t paddr; + + /** + * Array of backing pages, if allocated. Note that pages are never + * allocated for buffers originally allocated from contiguous memory + */ + struct page **pages; + + /** + * Virtual address, if mapped. + */ + void *vaddr; + + /** + * sync-object allocated on demand (if needed) + * + * Per-buffer sync-object for tracking pending and completed hw/dma + * read and write operations. The layout in memory is dictated by + * the SGX firmware, which uses this information to stall the command + * stream if a surface is not ready yet. + * + * Note that when buffer is used by SGX, the sync-object needs to be + * allocated from a special heap of sync-objects. This way many sync + * objects can be packed in a page, and not waste GPU virtual address + * space. Because of this we have to have a omap_gem_set_sync_object() + * API to allow replacement of the syncobj after it has (potentially) + * already been allocated. A bit ugly but I haven't thought of a + * better alternative. + */ + struct { + uint32_t write_pending; + uint32_t write_complete; + uint32_t read_pending; + uint32_t read_complete; + } *sync; +}; + +/* 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 + * contiguous... but we don't do this all the time to reduce pressure + * on TILER/DMM space when we know at allocation time that the buffer + * will need to be scanned out. + */ +static inline bool is_shmem(struct drm_gem_object *obj) +{ + return obj->filp != NULL; +} + +static int get_pages(struct drm_gem_object *obj, struct page ***pages); + +static DEFINE_SPINLOCK(sync_lock); + +/** ensure backing pages are allocated */ +static int omap_gem_attach_pages(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + struct page **pages; + + WARN_ON(omap_obj->pages); + + /* TODO: __GFP_DMA32 .. but somehow GFP_HIGHMEM is coming from the + * mapping_gfp_mask(mapping) which conflicts w/ GFP_DMA32.. probably + * we actually want CMA memory for it all anyways.. + */ + pages = _drm_gem_get_pages(obj, GFP_KERNEL); + if (IS_ERR(pages)) { + dev_err(obj->dev->dev, "could not get pages: %ld\n", PTR_ERR(pages)); + return PTR_ERR(pages); + } + + omap_obj->pages = pages; + return 0; +} + +/** release backing pages */ +static void omap_gem_detach_pages(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + _drm_gem_put_pages(obj, omap_obj->pages, true, false); + omap_obj->pages = NULL; +} + +/** get mmap offset */ +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); + if (ret) { + dev_err(obj->dev->dev, "could not allocate mmap offset"); + return 0; + } + } + + return (uint64_t)obj->map_list.hash.key << PAGE_SHIFT; +} + +/** + * omap_gem_fault - pagefault handler for GEM objects + * @vma: the VMA of the GEM object + * @vmf: fault detail + * + * Invoked when a fault occurs on an mmap of a GEM managed area. GEM + * does most of the work for us including the actual map/unmap calls + * but we need to do the actual page work. + * + * The VMA was set up by GEM. In doing so it also ensured that the + * vma->vm_private_data points to the GEM object that is backing this + * mapping. + */ +int omap_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct drm_gem_object *obj = vma->vm_private_data; + 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 + * something from beneath our feet + */ + mutex_lock(&dev->struct_mutex); + + /* if a shmem backed object, make sure we have pages attached now */ + ret = get_pages(obj, &pages); + if (ret) { + goto fail; + } + + /* where should we do corresponding put_pages().. we are mapping + * the original page, rather than thru a GART, so we can't rely + * on eviction to trigger this. But munmap() or all mappings should + * 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->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); + switch (ret) { + case 0: + case -ERESTARTSYS: + case -EINTR: + return VM_FAULT_NOPAGE; + case -ENOMEM: + return VM_FAULT_OOM; + default: + return VM_FAULT_SIGBUS; + } +} + +/** We override mainly to fix up some of the vm mapping flags.. */ +int omap_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct omap_gem_object *omap_obj; + int ret; + + ret = drm_gem_mmap(filp, vma); + if (ret) { + DBG("mmap failed: %d", ret); + return ret; + } + + /* after drm_gem_mmap(), it is safe to access the obj */ + omap_obj = to_omap_bo(vma->vm_private_data); + + vma->vm_flags &= ~VM_PFNMAP; + vma->vm_flags |= VM_MIXEDMAP; + + if (omap_obj->flags & OMAP_BO_WC) { + vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); + } else if (omap_obj->flags & OMAP_BO_UNCACHED) { + vma->vm_page_prot = pgprot_noncached(vm_get_page_prot(vma->vm_flags)); + } else { + vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); + } + + return ret; +} + +/** + * omap_gem_dumb_create - create a dumb buffer + * @drm_file: our client file + * @dev: our device + * @args: the requested arguments copied from userspace + * + * Allocate a buffer suitable for use for a frame buffer of the + * form described by user space. Give userspace a handle by which + * to reference it. + */ +int omap_gem_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + union omap_gem_size gsize; + + /* in case someone tries to feed us a completely bogus stride: */ + args->pitch = align_pitch(args->pitch, args->width, args->bpp); + args->size = PAGE_ALIGN(args->pitch * args->height); + + gsize = (union omap_gem_size){ + .bytes = args->size, + }; + + return omap_gem_new_handle(dev, file, gsize, + OMAP_BO_SCANOUT | OMAP_BO_WC, &args->handle); +} + +/** + * omap_gem_dumb_destroy - destroy a dumb buffer + * @file: client file + * @dev: our DRM device + * @handle: the object handle + * + * Destroy a handle that was created via omap_gem_dumb_create. + */ +int omap_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, + uint32_t handle) +{ + /* No special work needed, drop the reference and see what falls out */ + return drm_gem_handle_delete(file, handle); +} + +/** + * omap_gem_dumb_map - buffer mapping for dumb interface + * @file: our drm client file + * @dev: drm device + * @handle: GEM handle to the object (from dumb_create) + * + * Do the necessary setup to allow the mapping of the frame buffer + * into user memory. We don't have to do much here at the moment. + */ +int omap_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev, + uint32_t handle, uint64_t *offset) +{ + 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) { + ret = -ENOENT; + goto fail; + } + + *offset = omap_gem_mmap_offset(obj); + + drm_gem_object_unreference_unlocked(obj); + +fail: + mutex_unlock(&dev->struct_mutex); + return ret; +} + +/* Get physical address for DMA.. if 'remap' is true, and the buffer is not + * already contiguous, remap it to pin in physically contiguous memory.. (ie. + * map in TILER) + */ +int omap_gem_get_paddr(struct drm_gem_object *obj, + dma_addr_t *paddr, bool remap) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + if (is_shmem(obj)) { + /* TODO: remap to TILER */ + return -ENOMEM; + } + + *paddr = omap_obj->paddr; + + return ret; +} + +/* Release physical address, when DMA is no longer being performed.. this + * could potentially unpin and unmap buffers from TILER + */ +int omap_gem_put_paddr(struct drm_gem_object *obj) +{ + /* do something here when remap to TILER is used.. */ + return 0; +} + +/* acquire pages when needed (for example, for DMA where physically + * contiguous buffer is not required + */ +static int get_pages(struct drm_gem_object *obj, struct page ***pages) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + if (is_shmem(obj) && !omap_obj->pages) { + ret = omap_gem_attach_pages(obj); + if (ret) { + dev_err(obj->dev->dev, "could not attach pages\n"); + return ret; + } + } + + /* TODO: even phys-contig.. we should have a list of pages? */ + *pages = omap_obj->pages; + + return 0; +} + +int omap_gem_get_pages(struct drm_gem_object *obj, struct page ***pages) +{ + int ret; + mutex_lock(&obj->dev->struct_mutex); + ret = get_pages(obj, pages); + mutex_unlock(&obj->dev->struct_mutex); + return ret; +} + +/* release pages when DMA no longer being performed */ +int omap_gem_put_pages(struct drm_gem_object *obj) +{ + /* do something here if we dynamically attach/detach pages.. at + * least they would no longer need to be pinned if everyone has + * released the pages.. + */ + 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 + */ +void *omap_gem_vaddr(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + return omap_obj->vaddr; +} + +/* Buffer Synchronization: + */ + +struct omap_gem_sync_waiter { + struct list_head list; + struct omap_gem_object *omap_obj; + enum omap_gem_op op; + uint32_t read_target, write_target; + /* notify called w/ sync_lock held */ + void (*notify)(void *arg); + void *arg; +}; + +/* list of omap_gem_sync_waiter.. the notify fxn gets called back when + * the read and/or write target count is achieved which can call a user + * callback (ex. to kick 3d and/or 2d), wakeup blocked task (prep for + * cpu access), etc. + */ +static LIST_HEAD(waiters); + +static inline bool is_waiting(struct omap_gem_sync_waiter *waiter) +{ + struct omap_gem_object *omap_obj = waiter->omap_obj; + if ((waiter->op & OMAP_GEM_READ) && + (omap_obj->sync->read_complete < waiter->read_target)) + return true; + if ((waiter->op & OMAP_GEM_WRITE) && + (omap_obj->sync->write_complete < waiter->write_target)) + return true; + return false; +} + +/* macro for sync debug.. */ +#define SYNCDBG 0 +#define SYNC(fmt, ...) do { if (SYNCDBG) \ + printk(KERN_ERR "%s:%d: "fmt"\n", \ + __func__, __LINE__, ##__VA_ARGS__); \ + } while (0) + + +static void sync_op_update(void) +{ + struct omap_gem_sync_waiter *waiter, *n; + list_for_each_entry_safe(waiter, n, &waiters, list) { + if (!is_waiting(waiter)) { + list_del(&waiter->list); + SYNC("notify: %p", waiter); + waiter->notify(waiter->arg); + kfree(waiter); + } + } +} + +static inline int sync_op(struct drm_gem_object *obj, + enum omap_gem_op op, bool start) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + spin_lock(&sync_lock); + + if (!omap_obj->sync) { + omap_obj->sync = kzalloc(sizeof(*omap_obj->sync), GFP_ATOMIC); + if (!omap_obj->sync) { + ret = -ENOMEM; + goto unlock; + } + } + + if (start) { + if (op & OMAP_GEM_READ) + omap_obj->sync->read_pending++; + if (op & OMAP_GEM_WRITE) + omap_obj->sync->write_pending++; + } else { + if (op & OMAP_GEM_READ) + omap_obj->sync->read_complete++; + if (op & OMAP_GEM_WRITE) + omap_obj->sync->write_complete++; + sync_op_update(); + } + +unlock: + spin_unlock(&sync_lock); + + return ret; +} + +/* it is a bit lame to handle updates in this sort of polling way, but + * in case of PVR, the GPU can directly update read/write complete + * values, and not really tell us which ones it updated.. this also + * means that sync_lock is not quite sufficient. So we'll need to + * do something a bit better when it comes time to add support for + * separate 2d hw.. + */ +void omap_gem_op_update(void) +{ + spin_lock(&sync_lock); + sync_op_update(); + spin_unlock(&sync_lock); +} + +/* mark the start of read and/or write operation */ +int omap_gem_op_start(struct drm_gem_object *obj, enum omap_gem_op op) +{ + return sync_op(obj, op, true); +} + +int omap_gem_op_finish(struct drm_gem_object *obj, enum omap_gem_op op) +{ + return sync_op(obj, op, false); +} + +static DECLARE_WAIT_QUEUE_HEAD(sync_event); + +static void sync_notify(void *arg) +{ + struct task_struct **waiter_task = arg; + *waiter_task = NULL; + wake_up_all(&sync_event); +} + +int omap_gem_op_sync(struct drm_gem_object *obj, enum omap_gem_op op) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + if (omap_obj->sync) { + struct task_struct *waiter_task = current; + struct omap_gem_sync_waiter *waiter = + kzalloc(sizeof(*waiter), GFP_KERNEL); + + if (!waiter) { + return -ENOMEM; + } + + waiter->omap_obj = omap_obj; + waiter->op = op; + waiter->read_target = omap_obj->sync->read_pending; + waiter->write_target = omap_obj->sync->write_pending; + waiter->notify = sync_notify; + waiter->arg = &waiter_task; + + spin_lock(&sync_lock); + if (is_waiting(waiter)) { + SYNC("waited: %p", waiter); + list_add_tail(&waiter->list, &waiters); + spin_unlock(&sync_lock); + ret = wait_event_interruptible(sync_event, + (waiter_task == NULL)); + spin_lock(&sync_lock); + if (waiter_task) { + SYNC("interrupted: %p", waiter); + /* we were interrupted */ + list_del(&waiter->list); + waiter_task = NULL; + } else { + /* freed in sync_op_update() */ + waiter = NULL; + } + } + spin_unlock(&sync_lock); + + if (waiter) { + kfree(waiter); + } + } + return ret; +} + +/* call fxn(arg), either synchronously or asynchronously if the op + * is currently blocked.. fxn() can be called from any context + * + * (TODO for now fxn is called back from whichever context calls + * omap_gem_op_update().. but this could be better defined later + * if needed) + * + * TODO more code in common w/ _sync().. + */ +int omap_gem_op_async(struct drm_gem_object *obj, enum omap_gem_op op, + void (*fxn)(void *arg), void *arg) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + if (omap_obj->sync) { + struct omap_gem_sync_waiter *waiter = + kzalloc(sizeof(*waiter), GFP_ATOMIC); + + if (!waiter) { + return -ENOMEM; + } + + waiter->omap_obj = omap_obj; + waiter->op = op; + waiter->read_target = omap_obj->sync->read_pending; + waiter->write_target = omap_obj->sync->write_pending; + waiter->notify = fxn; + waiter->arg = arg; + + spin_lock(&sync_lock); + if (is_waiting(waiter)) { + SYNC("waited: %p", waiter); + list_add_tail(&waiter->list, &waiters); + spin_unlock(&sync_lock); + return 0; + } + + spin_unlock(&sync_lock); + } + + /* no waiting.. */ + fxn(arg); + + return 0; +} + +/* special API so PVR can update the buffer to use a sync-object allocated + * from it's sync-obj heap. Only used for a newly allocated (from PVR's + * perspective) sync-object, so we overwrite the new syncobj w/ values + * from the already allocated syncobj (if there is one) + */ +int omap_gem_set_sync_object(struct drm_gem_object *obj, void *syncobj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + spin_lock(&sync_lock); + + if ((omap_obj->flags & OMAP_BO_EXT_SYNC) && !syncobj) { + /* clearing a previously set syncobj */ + syncobj = kzalloc(sizeof(*omap_obj->sync), GFP_ATOMIC); + if (!syncobj) { + ret = -ENOMEM; + goto unlock; + } + memcpy(syncobj, omap_obj->sync, sizeof(*omap_obj->sync)); + omap_obj->flags &= ~OMAP_BO_EXT_SYNC; + omap_obj->sync = syncobj; + } else if (syncobj && !(omap_obj->flags & OMAP_BO_EXT_SYNC)) { + /* replacing an existing syncobj */ + if (omap_obj->sync) { + memcpy(syncobj, omap_obj->sync, sizeof(*omap_obj->sync)); + kfree(omap_obj->sync); + } + omap_obj->flags |= OMAP_BO_EXT_SYNC; + omap_obj->sync = syncobj; + } + +unlock: + spin_unlock(&sync_lock); + return ret; +} + +int omap_gem_init_object(struct drm_gem_object *obj) +{ + return -EINVAL; /* unused */ +} + +/* don't call directly.. called from GEM core when it is time to actually + * free the object.. + */ +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); + + if (obj->map_list.map) { + drm_gem_free_mmap_offset(obj); + } + + /* don't free externally allocated backing memory */ + if (!(omap_obj->flags & OMAP_BO_EXT_MEM)) { + if (omap_obj->pages) { + omap_gem_detach_pages(obj); + } + if (!is_shmem(obj)) { + dma_free_writecombine(dev->dev, obj->size, + omap_obj->vaddr, omap_obj->paddr); + } + } + + /* don't free externally allocated syncobj */ + if (!(omap_obj->flags & OMAP_BO_EXT_SYNC)) { + kfree(omap_obj->sync); + } + + drm_gem_object_release(obj); + + kfree(obj); +} + +/* convenience method to construct a GEM buffer object, and userspace handle */ +int omap_gem_new_handle(struct drm_device *dev, struct drm_file *file, + union omap_gem_size gsize, uint32_t flags, uint32_t *handle) +{ + struct drm_gem_object *obj; + int ret; + + obj = omap_gem_new(dev, gsize, flags); + if (!obj) + return -ENOMEM; + + ret = drm_gem_handle_create(file, obj, handle); + if (ret) { + drm_gem_object_release(obj); + kfree(obj); /* TODO isn't there a dtor to call? just copying i915 */ + return ret; + } + + /* drop reference from allocate - handle holds it now */ + drm_gem_object_unreference_unlocked(obj); + + return 0; +} + +/* GEM buffer object constructor */ +struct drm_gem_object *omap_gem_new(struct drm_device *dev, + union omap_gem_size gsize, uint32_t flags) +{ + struct omap_gem_object *omap_obj; + struct drm_gem_object *obj = NULL; + size_t size; + int ret; + + if (flags & OMAP_BO_TILED) { + /* TODO: not implemented yet */ + goto fail; + } + + size = PAGE_ALIGN(gsize.bytes); + + omap_obj = kzalloc(sizeof(*omap_obj), GFP_KERNEL); + if (!omap_obj) { + dev_err(dev->dev, "could not allocate GEM object\n"); + goto fail; + } + + obj = &omap_obj->base; + + if (flags & OMAP_BO_SCANOUT) { + /* attempt to allocate contiguous memory */ + omap_obj->vaddr = dma_alloc_writecombine(dev->dev, size, + &omap_obj->paddr, GFP_KERNEL); + if (omap_obj->vaddr) { + flags |= OMAP_BO_DMA; + } + } + + omap_obj->flags = flags; + + if (flags & (OMAP_BO_DMA|OMAP_BO_EXT_MEM)) { + ret = drm_gem_private_object_init(dev, obj, size); + } else { + ret = drm_gem_object_init(dev, obj, size); + } + + if (ret) { + goto fail; + } + + return obj; + +fail: + if (obj) { + omap_gem_free_object(obj); + } + return NULL; +} |