diff options
Diffstat (limited to 'drivers/gpu/drm/pl111/pl111_drm_dma_buf.c')
-rwxr-xr-x | drivers/gpu/drm/pl111/pl111_drm_dma_buf.c | 625 |
1 files changed, 625 insertions, 0 deletions
diff --git a/drivers/gpu/drm/pl111/pl111_drm_dma_buf.c b/drivers/gpu/drm/pl111/pl111_drm_dma_buf.c new file mode 100755 index 000000000000..1131f46b27df --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_dma_buf.c @@ -0,0 +1,625 @@ +/* + * + * (C) COPYRIGHT 2012-2014 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + +/** + * pl111_drm_dma_buf.c + * Implementation of the dma_buf functions for PL111 DRM + */ +#include <linux/amba/bus.h> +#include <linux/amba/clcd.h> +#include <linux/version.h> +#include <linux/shmem_fs.h> +#include <linux/dma-buf.h> +#include <linux/module.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "pl111_drm.h" + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 11, 0)) +#define export_dma_buf export_dma_buf +#else +#define export_dma_buf dma_buf +#endif + +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS +static void obtain_kds_if_currently_displayed(struct drm_device *dev, + struct pl111_gem_bo *bo, + struct dma_buf *dma_buf) +{ + unsigned long shared[1] = { 0 }; + struct kds_resource *resource_list[1]; + struct kds_resource_set *kds_res_set; + struct drm_crtc *crtc; + bool cb_has_called = false; + unsigned long flags; + int err; + DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake); + + DRM_DEBUG_KMS("Obtaining initial KDS res for bo:%p dma_buf:%p\n", + bo, dma_buf); + + resource_list[0] = get_dma_buf_kds_resource(dma_buf); + get_dma_buf(dma_buf); + + /* + * Can't use kds_waitall(), because kbase will be let through due to + * locked ignore' + */ + err = kds_async_waitall(&kds_res_set, + &priv.kds_obtain_current_cb, &wake, + &cb_has_called, 1, shared, resource_list); + BUG_ON(err); + wait_event(wake, cb_has_called == true); + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc); + spin_lock_irqsave(&pl111_crtc->current_displaying_lock, flags); + if (pl111_crtc->displaying_fb) { + struct pl111_drm_framebuffer *pl111_fb; + struct drm_framebuffer *fb = pl111_crtc->displaying_fb; + + pl111_fb = PL111_FB_FROM_FRAMEBUFFER(fb); + + if (pl111_fb->bo == bo) { + DRM_DEBUG_KMS("Initial KDS resource for bo %p", bo); + DRM_DEBUG_KMS(" is being displayed, keeping\n"); + /* There shouldn't be a previous buffer to release */ + BUG_ON(pl111_crtc->old_kds_res_set); + + if (kds_res_set == NULL) { + err = kds_async_waitall(&kds_res_set, + &priv.kds_obtain_current_cb, + &wake, &cb_has_called, + 1, shared, resource_list); + BUG_ON(err); + wait_event(wake, cb_has_called == true); + } + + /* Current buffer will need releasing on next flip */ + pl111_crtc->old_kds_res_set = kds_res_set; + + /* + * Clear kds_res_set, so a new kds_res_set is allocated + * for additional CRTCs + */ + kds_res_set = NULL; + } + } + spin_unlock_irqrestore(&pl111_crtc->current_displaying_lock, flags); + } + + /* kds_res_set will be NULL here if any CRTCs are displaying fb */ + if (kds_res_set != NULL) { + DRM_DEBUG_KMS("Initial KDS resource for bo %p", bo); + DRM_DEBUG_KMS(" not being displayed, discarding\n"); + /* They're not being displayed, release them */ + kds_resource_set_release(&kds_res_set); + } + + dma_buf_put(dma_buf); +} +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)) + +static int pl111_dma_buf_mmap(struct dma_buf *buffer, + struct vm_area_struct *vma) +{ + struct drm_gem_object *obj = buffer->priv; + struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj); + struct drm_device *dev = obj->dev; + int ret; + + DRM_DEBUG_KMS("DRM %s on dma_buf=%p\n", __func__, buffer); + + mutex_lock(&dev->struct_mutex); + ret = drm_gem_mmap_obj(obj, obj->size, vma); + mutex_unlock(&dev->struct_mutex); + if (ret) + return ret; + + return pl111_bo_mmap(obj, bo, vma, buffer->size); +} + +#else + +static int pl111_dma_buf_mmap(struct dma_buf *buffer, + struct vm_area_struct *vma) +{ + struct drm_gem_object *obj = buffer->priv; + struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj); + struct drm_device *dev = obj->dev; + + DRM_DEBUG_KMS("DRM %s on dma_buf=%p\n", __func__, buffer); + + mutex_lock(&dev->struct_mutex); + + /* Check for valid size. */ + if (obj->size < vma->vm_end - vma->vm_start) + return -EINVAL; + + BUG_ON(!dev->driver->gem_vm_ops); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)) + vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP; +#else + vma->vm_flags |= VM_RESERVED | VM_IO | VM_PFNMAP | VM_DONTEXPAND; +#endif + + vma->vm_ops = dev->driver->gem_vm_ops; + vma->vm_private_data = obj; + + /* Take a ref for this mapping of the object, so that the fault + * handler can dereference the mmap offset's pointer to the object. + * This reference is cleaned up by the corresponding vm_close + * (which should happen whether the vma was created by this call, or + * by a vm_open due to mremap or partial unmap or whatever). + */ + drm_gem_object_reference(obj); + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)) + pl111_drm_vm_open_locked(dev, vma); +#else + drm_vm_open_locked(dev, vma); +#endif + + mutex_unlock(&dev->struct_mutex); + + return pl111_bo_mmap(obj, bo, vma, buffer->size); +} + +#endif /* KERNEL_VERSION */ + +static void pl111_dma_buf_release(struct dma_buf *buf) +{ + /* + * Need to release the dma_buf's reference on the gem object it was + * exported from, and also clear the gem object's export_dma_buf + * pointer to this dma_buf as it no longer exists + */ + struct drm_gem_object *obj = (struct drm_gem_object *)buf->priv; + struct pl111_gem_bo *bo; +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + struct drm_crtc *crtc; + unsigned long flags; +#endif + bo = PL111_BO_FROM_GEM(obj); + + DRM_DEBUG_KMS("Releasing dma_buf %p, drm_gem_obj=%p\n", buf, obj); + +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + list_for_each_entry(crtc, &bo->gem_object.dev->mode_config.crtc_list, + head) { + struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc); + spin_lock_irqsave(&pl111_crtc->current_displaying_lock, flags); + if (pl111_crtc->displaying_fb) { + struct pl111_drm_framebuffer *pl111_fb; + struct drm_framebuffer *fb = pl111_crtc->displaying_fb; + + pl111_fb = PL111_FB_FROM_FRAMEBUFFER(fb); + if (pl111_fb->bo == bo) { + kds_resource_set_release(&pl111_crtc->old_kds_res_set); + pl111_crtc->old_kds_res_set = NULL; + } + } + spin_unlock_irqrestore(&pl111_crtc->current_displaying_lock, flags); + } +#endif + mutex_lock(&priv.export_dma_buf_lock); + + obj->export_dma_buf = NULL; + drm_gem_object_unreference_unlocked(obj); + + mutex_unlock(&priv.export_dma_buf_lock); +} + +static int pl111_dma_buf_attach(struct dma_buf *buf, struct device *dev, + struct dma_buf_attachment *attach) +{ + DRM_DEBUG_KMS("Attaching dma_buf %p to device %p attach=%p\n", buf, + dev, attach); + + attach->priv = dev; + + return 0; +} + +static void pl111_dma_buf_detach(struct dma_buf *buf, + struct dma_buf_attachment *attach) +{ + DRM_DEBUG_KMS("Detaching dma_buf %p attach=%p\n", attach->dmabuf, + attach); +} + +/* Heavily from exynos_drm_dmabuf.c */ +static struct sg_table *pl111_dma_buf_map_dma_buf(struct dma_buf_attachment + *attach, + enum dma_data_direction + direction) +{ + struct drm_gem_object *obj = attach->dmabuf->priv; + struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj); + struct drm_device *dev = obj->dev; + int size, n_pages, nents; + struct scatterlist *s, *sg; + struct sg_table *sgt; + int ret, i; + + DRM_DEBUG_KMS("Mapping dma_buf %p from attach=%p (bo=%p)\n", attach->dmabuf, + attach, bo); + + /* + * Nothing to do, if we are trying to map a dmabuf that has been imported. + * Just return the existing sgt. + */ + if (obj->import_attach) { + BUG_ON(!bo->sgt); + return bo->sgt; + } + + size = obj->size; + n_pages = PAGE_ALIGN(size) >> PAGE_SHIFT; + + if (bo->type & PL111_BOT_DMA) { + sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); + if (!sgt) { + DRM_ERROR("Failed to allocate sg_table\n"); + return ERR_PTR(-ENOMEM); + } + + ret = sg_alloc_table(sgt, 1, GFP_KERNEL); + if (ret < 0) { + DRM_ERROR("Failed to allocate page table\n"); + return ERR_PTR(-ENOMEM); + } + sg_dma_len(sgt->sgl) = size; + /* We use DMA coherent mappings for PL111_BOT_DMA so we must + * use the virtual address returned at buffer allocation */ + sg_set_buf(sgt->sgl, bo->backing_data.dma.fb_cpu_addr, size); + sg_dma_address(sgt->sgl) = bo->backing_data.dma.fb_dev_addr; + } else { /* PL111_BOT_SHM */ + struct page **pages; + int pg = 0; + + mutex_lock(&dev->struct_mutex); + pages = get_pages(obj); + if (IS_ERR(pages)) { + dev_err(obj->dev->dev, "could not get pages: %ld\n", + PTR_ERR(pages)); + return ERR_CAST(pages); + } + sgt = drm_prime_pages_to_sg(pages, n_pages); + if (sgt == NULL) + return ERR_PTR(-ENOMEM); + + pl111_gem_sync_to_dma(bo); + + /* + * At this point the pages have been dma-mapped by either + * get_pages() for non cached maps or pl111_gem_sync_to_dma() + * for cached. So the physical addresses can be assigned + * to the sg entries. + * drm_prime_pages_to_sg() may have combined contiguous pages + * into chunks so we assign the physical address of the first + * page of a chunk to the chunk and check that the physical + * addresses of the rest of the pages in that chunk are also + * contiguous. + */ + sg = sgt->sgl; + nents = sgt->nents; + + for_each_sg(sg, s, nents, i) { + int j, n_pages_in_chunk = sg_dma_len(s) >> PAGE_SHIFT; + + sg_dma_address(s) = bo->backing_data.shm.dma_addrs[pg]; + + for (j = pg+1; j < pg+n_pages_in_chunk; j++) { + BUG_ON(bo->backing_data.shm.dma_addrs[j] != + bo->backing_data.shm.dma_addrs[j-1]+PAGE_SIZE); + } + + pg += n_pages_in_chunk; + } + + mutex_unlock(&dev->struct_mutex); + } + bo->sgt = sgt; + return sgt; +} + +static void pl111_dma_buf_unmap_dma_buf(struct dma_buf_attachment *attach, + struct sg_table *sgt, + enum dma_data_direction direction) +{ + struct drm_gem_object *obj = attach->dmabuf->priv; + struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj); + + DRM_DEBUG_KMS("Unmapping dma_buf %p from attach=%p (bo=%p)\n", attach->dmabuf, + attach, bo); + + sg_free_table(sgt); + kfree(sgt); + bo->sgt = NULL; +} + +/* + * There isn't any operation here that can sleep or fail so this callback can + * be used for both kmap and kmap_atomic implementations. + */ +static void *pl111_dma_buf_kmap(struct dma_buf *dma_buf, unsigned long pageno) +{ + struct pl111_gem_bo *bo = dma_buf->priv; + void *vaddr = NULL; + + /* Make sure we cannot access outside the memory range */ + if (((pageno + 1) << PAGE_SHIFT) > bo->gem_object.size) + return NULL; + + if (bo->type & PL111_BOT_DMA) { + vaddr = (bo->backing_data.dma.fb_cpu_addr + + (pageno << PAGE_SHIFT)); + } else { + vaddr = page_address(bo->backing_data.shm.pages[pageno]); + } + + return vaddr; +} + +/* + * Find a scatterlist that starts in "start" and has "len" + * or return a NULL dma_handle. + */ +static dma_addr_t pl111_find_matching_sg(struct sg_table *sgt, size_t start, + size_t len) +{ + struct scatterlist *sg; + unsigned int count; + size_t size = 0; + dma_addr_t dma_handle = 0; + + /* Find a scatterlist that starts in "start" and has "len" + * or return error */ + for_each_sg(sgt->sgl, sg, sgt->nents, count) { + if ((size == start) && (len == sg_dma_len(sg))) { + dma_handle = sg_dma_address(sg); + break; + } + size += sg_dma_len(sg); + } + return dma_handle; +} + +static int pl111_dma_buf_begin_cpu(struct dma_buf *dma_buf, + size_t start, size_t len, + enum dma_data_direction dir) +{ + struct pl111_gem_bo *bo = dma_buf->priv; + struct sg_table *sgt = bo->sgt; + dma_addr_t dma_handle; + + if ((start + len) > bo->gem_object.size) + return -EINVAL; + + if (!(bo->type & PL111_BOT_SHM)) { + struct device *dev = bo->gem_object.dev->dev; + + dma_handle = pl111_find_matching_sg(sgt, start, len); + if (!dma_handle) + return -EINVAL; + + dma_sync_single_range_for_cpu(dev, dma_handle, 0, len, dir); + } + /* PL111_BOT_DMA uses coherents mappings, no need to sync */ + return 0; +} + +static void pl111_dma_buf_end_cpu(struct dma_buf *dma_buf, + size_t start, size_t len, + enum dma_data_direction dir) +{ + struct pl111_gem_bo *bo = dma_buf->priv; + struct sg_table *sgt = bo->sgt; + dma_addr_t dma_handle; + + if ((start + len) > bo->gem_object.size) + return; + + if (!(bo->type & PL111_BOT_DMA)) { + struct device *dev = bo->gem_object.dev->dev; + + dma_handle = pl111_find_matching_sg(sgt, start, len); + if (!dma_handle) + return; + + dma_sync_single_range_for_device(dev, dma_handle, 0, len, dir); + } + /* PL111_BOT_DMA uses coherents mappings, no need to sync */ +} + +static struct dma_buf_ops pl111_dma_buf_ops = { + .release = &pl111_dma_buf_release, + .attach = &pl111_dma_buf_attach, + .detach = &pl111_dma_buf_detach, + .map_dma_buf = &pl111_dma_buf_map_dma_buf, + .unmap_dma_buf = &pl111_dma_buf_unmap_dma_buf, + .kmap_atomic = &pl111_dma_buf_kmap, + .kmap = &pl111_dma_buf_kmap, + .begin_cpu_access = &pl111_dma_buf_begin_cpu, + .end_cpu_access = &pl111_dma_buf_end_cpu, + .mmap = &pl111_dma_buf_mmap, +}; + +struct drm_gem_object *pl111_gem_prime_import(struct drm_device *dev, + struct dma_buf *dma_buf) +{ + struct dma_buf_attachment *attachment; + struct drm_gem_object *obj; + struct pl111_gem_bo *bo; + struct scatterlist *sgl; + struct sg_table *sgt; + dma_addr_t cont_phys; + int ret = 0; + int i; + + DRM_DEBUG_KMS("DRM %s on dev=%p dma_buf=%p\n", __func__, dev, dma_buf); + + /* is this one of own objects? */ + if (dma_buf->ops == &pl111_dma_buf_ops) { + obj = dma_buf->priv; + /* is it from our device? */ + if (obj->dev == dev) { + /* + * Importing dmabuf exported from our own gem increases + * refcount on gem itself instead of f_count of dmabuf. + */ + drm_gem_object_reference(obj); + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)) + /* before v3.10.0 we assume the caller has taken a ref on the dma_buf + * we don't want it for self-imported buffers so drop it here */ + dma_buf_put(dma_buf); +#endif + + return obj; + } + } + + attachment = dma_buf_attach(dma_buf, dev->dev); + if (IS_ERR(attachment)) + return ERR_CAST(attachment); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)) + /* from 3.10.0 we assume the caller has not taken a ref so we take one here */ + get_dma_buf(dma_buf); +#endif + + sgt = dma_buf_map_attachment(attachment, DMA_BIDIRECTIONAL); + if (IS_ERR_OR_NULL(sgt)) { + ret = PTR_ERR(sgt); + goto err_buf_detach; + } + + bo = kzalloc(sizeof(*bo), GFP_KERNEL); + if (!bo) { + DRM_ERROR("%s: failed to allocate buffer object.\n", __func__); + ret = -ENOMEM; + goto err_unmap_attach; + } + + /* Find out whether the buffer is contiguous or not */ + sgl = sgt->sgl; + cont_phys = sg_phys(sgl); + bo->type |= PL111_BOT_DMA; + for_each_sg(sgt->sgl, sgl, sgt->nents, i) { + dma_addr_t real_phys = sg_phys(sgl); + if (real_phys != cont_phys) { + bo->type &= ~PL111_BOT_DMA; + break; + } + cont_phys += (PAGE_SIZE - sgl->offset); + } + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 11, 0)) + ret = drm_gem_private_object_init(dev, &bo->gem_object, + dma_buf->size); + if (ret != 0) { + DRM_ERROR("DRM could not import DMA GEM obj\n"); + goto err_free_buffer; + } +#else + drm_gem_private_object_init(dev, &bo->gem_object, dma_buf->size); +#endif + + if (bo->type & PL111_BOT_DMA) { + bo->backing_data.dma.fb_cpu_addr = sg_virt(sgt->sgl); + bo->backing_data.dma.fb_dev_addr = sg_phys(sgt->sgl); + DRM_DEBUG_KMS("DRM %s pl111_gem_bo=%p, contiguous import\n", __func__, bo); + } else { /* PL111_BOT_SHM */ + DRM_DEBUG_KMS("DRM %s pl111_gem_bo=%p, non contiguous import\n", __func__, bo); + } + + bo->gem_object.import_attach = attachment; + bo->sgt = sgt; + + return &bo->gem_object; + +err_free_buffer: + kfree(bo); + bo = NULL; +err_unmap_attach: + dma_buf_unmap_attachment(attachment, sgt, DMA_BIDIRECTIONAL); +err_buf_detach: + dma_buf_detach(dma_buf, attachment); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)) + /* from 3.10.0 we will have taken a ref so drop it here */ + dma_buf_put(dma_buf); +#endif + return ERR_PTR(ret); +} + +struct dma_buf *pl111_gem_prime_export(struct drm_device *dev, + struct drm_gem_object *obj, int flags) +{ + struct dma_buf *new_buf; + struct pl111_gem_bo *bo; + size_t size; + + DRM_DEBUG("DRM %s on dev=%p drm_gem_obj=%p\n", __func__, dev, obj); + size = obj->size; + + new_buf = dma_buf_export(obj /*priv */ , &pl111_dma_buf_ops, size, + flags | O_RDWR); + bo = PL111_BO_FROM_GEM(new_buf->priv); + + /* + * bo->gem_object.export_dma_buf not setup until after gem_prime_export + * finishes + */ + +#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS + /* + * Ensure that we hold the kds resource if it's the currently + * displayed buffer. + */ + obtain_kds_if_currently_displayed(dev, bo, new_buf); +#endif + + DRM_DEBUG("Created dma_buf %p\n", new_buf); + + return new_buf; +} + +int pl111_prime_handle_to_fd(struct drm_device *dev, struct drm_file *file_priv, + uint32_t handle, uint32_t flags, int *prime_fd) +{ + int result; + /* + * This will re-use any existing exports, and calls + * driver->gem_prime_export to do the first export when needed + */ + DRM_DEBUG_KMS("DRM %s on file_priv=%p, handle=0x%.8x\n", __func__, + file_priv, handle); + + mutex_lock(&priv.export_dma_buf_lock); + result = drm_gem_prime_handle_to_fd(dev, file_priv, handle, flags, + prime_fd); + mutex_unlock(&priv.export_dma_buf_lock); + + return result; +} |