blob: bc1709c7a286f3f49652b947cfbfc83fbd4f7b0a [file] [log] [blame]
/*
* 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;
}