| /* |
| * |
| * (C) COPYRIGHT 2012-2015 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_gem.c |
| * Implementation of the GEM 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 <asm/cacheflush.h> |
| #include <asm/outercache.h> |
| #include "pl111_drm.h" |
| |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)) |
| #include <linux/dma-attrs.h> |
| #endif |
| |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)) |
| #include <drm/drm_vma_manager.h> |
| #endif |
| |
| void pl111_gem_free_object(struct drm_gem_object *obj) |
| { |
| struct pl111_gem_bo *bo; |
| struct drm_device *dev = obj->dev; |
| DRM_DEBUG_KMS("DRM %s on drm_gem_object=%p\n", __func__, obj); |
| |
| bo = PL111_BO_FROM_GEM(obj); |
| |
| if (obj->import_attach) |
| drm_prime_gem_destroy(obj, bo->sgt); |
| |
| #if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 11, 0)) |
| if (obj->map_list.map != NULL) |
| drm_gem_free_mmap_offset(obj); |
| #else |
| drm_gem_free_mmap_offset(obj); |
| #endif |
| /* |
| * Only free the backing memory if the object has not been imported. |
| * If it has been imported, the exporter is in charge to free that |
| * once dmabuf's refcount becomes 0. |
| */ |
| if (obj->import_attach) |
| goto imported_out; |
| |
| if (bo->type & PL111_BOT_DMA) { |
| dma_free_writecombine(dev->dev, obj->size, |
| bo->backing_data.dma.fb_cpu_addr, |
| bo->backing_data.dma.fb_dev_addr); |
| } else if (bo->backing_data.shm.pages != NULL) { |
| put_pages(obj, bo->backing_data.shm.pages); |
| } |
| |
| imported_out: |
| drm_gem_object_release(obj); |
| |
| kfree(bo); |
| |
| DRM_DEBUG_KMS("Destroyed dumb_bo handle 0x%p\n", bo); |
| } |
| |
| static int pl111_gem_object_create(struct drm_device *dev, u64 size, |
| u32 flags, struct drm_file *file_priv, |
| u32 *handle) |
| { |
| int ret = 0; |
| struct pl111_gem_bo *bo = NULL; |
| |
| bo = kzalloc(sizeof(*bo), GFP_KERNEL); |
| if (bo == NULL) { |
| ret = -ENOMEM; |
| goto finish; |
| } |
| |
| bo->type = flags; |
| |
| #ifndef ARCH_HAS_SG_CHAIN |
| /* |
| * If the ARCH can't chain we can't have non-contiguous allocs larger |
| * than a single sg can hold. |
| * In this case we fall back to using contiguous memory |
| */ |
| if (!(bo->type & PL111_BOT_DMA)) { |
| long unsigned int n_pages = |
| PAGE_ALIGN(size) >> PAGE_SHIFT; |
| if (n_pages > SG_MAX_SINGLE_ALLOC) { |
| bo->type |= PL111_BOT_DMA; |
| /* |
| * Non-contiguous allocation request changed to |
| * contigous |
| */ |
| DRM_INFO("non-contig alloc to contig %lu > %lu pages.", |
| n_pages, SG_MAX_SINGLE_ALLOC); |
| } |
| } |
| #endif |
| if (bo->type & PL111_BOT_DMA) { |
| /* scanout compatible - use physically contiguous buffer */ |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)) |
| DEFINE_DMA_ATTRS(attrs); |
| |
| dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs); |
| bo->backing_data.dma.fb_cpu_addr = |
| dma_alloc_attrs(dev->dev, size, |
| &bo->backing_data.dma.fb_dev_addr, |
| GFP_KERNEL, |
| &attrs); |
| if (bo->backing_data.dma.fb_cpu_addr == NULL) { |
| DRM_ERROR("dma_alloc_attrs failed\n"); |
| ret = -ENOMEM; |
| goto free_bo; |
| } |
| #else |
| bo->backing_data.dma.fb_cpu_addr = |
| dma_alloc_writecombine(dev->dev, size, |
| &bo->backing_data.dma.fb_dev_addr, |
| GFP_KERNEL); |
| if (bo->backing_data.dma.fb_cpu_addr == NULL) { |
| DRM_ERROR("dma_alloc_writecombine failed\n"); |
| ret = -ENOMEM; |
| goto free_bo; |
| } |
| #endif |
| |
| #if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 11, 0)) |
| ret = drm_gem_private_object_init(dev, &bo->gem_object, |
| size); |
| if (ret != 0) { |
| DRM_ERROR("DRM could not initialise GEM object\n"); |
| goto free_dma; |
| } |
| #else |
| drm_gem_private_object_init(dev, &bo->gem_object, size); |
| #endif |
| |
| } else { /* PL111_BOT_SHM */ |
| /* not scanout compatible - use SHM backed object */ |
| ret = drm_gem_object_init(dev, &bo->gem_object, size); |
| if (ret != 0) { |
| DRM_ERROR("DRM could not init SHM backed GEM obj\n"); |
| ret = -ENOMEM; |
| goto free_bo; |
| } |
| DRM_DEBUG_KMS("Num bytes: %d\n", bo->gem_object.size); |
| } |
| |
| DRM_DEBUG("s=%llu, flags=0x%x, %s 0x%.8lx, type=%d\n", |
| size, flags, |
| (bo->type & PL111_BOT_DMA) ? "physaddr" : "shared page array", |
| (bo->type & PL111_BOT_DMA) ? |
| (unsigned long)bo->backing_data.dma.fb_dev_addr: |
| (unsigned long)bo->backing_data.shm.pages, |
| bo->type); |
| |
| ret = drm_gem_handle_create(file_priv, &bo->gem_object, handle); |
| if (ret != 0) { |
| DRM_ERROR("DRM failed to create GEM handle\n"); |
| goto obj_release; |
| } |
| |
| /* drop reference from allocate - handle holds it now */ |
| drm_gem_object_unreference_unlocked(&bo->gem_object); |
| |
| return 0; |
| |
| obj_release: |
| drm_gem_object_release(&bo->gem_object); |
| #if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 11, 0)) |
| free_dma: |
| #endif |
| |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)) |
| if (bo->type & PL111_BOT_DMA) { |
| DEFINE_DMA_ATTRS(attrs); |
| |
| dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs); |
| dma_free_attrs(dev->dev, size, |
| bo->backing_data.dma.fb_cpu_addr, |
| bo->backing_data.dma.fb_dev_addr, |
| &attrs); |
| } |
| #else |
| if (bo->type & PL111_BOT_DMA) |
| dma_free_writecombine(dev->dev, size, |
| bo->backing_data.dma.fb_cpu_addr, |
| bo->backing_data.dma.fb_dev_addr); |
| #endif |
| free_bo: |
| kfree(bo); |
| finish: |
| return ret; |
| } |
| |
| int pl111_drm_gem_create_ioctl(struct drm_device *dev, void *data, |
| struct drm_file *file_priv) |
| { |
| struct drm_pl111_gem_create *args = data; |
| uint32_t bytes_pp; |
| |
| /* Round bpp up, to allow for case where bpp<8 */ |
| bytes_pp = args->bpp >> 3; |
| if (args->bpp & ((1 << 3) - 1)) |
| bytes_pp++; |
| |
| if (args->flags & ~PL111_BOT_MASK) { |
| DRM_ERROR("wrong flags: 0x%x\n", args->flags); |
| return -EINVAL; |
| } |
| |
| args->pitch = ALIGN(args->width * bytes_pp, 64); |
| args->size = PAGE_ALIGN(args->pitch * args->height); |
| |
| DRM_DEBUG_KMS("gem_create w=%d h=%d p=%d bpp=%d b=%d s=%llu f=0x%x\n", |
| args->width, args->height, args->pitch, args->bpp, |
| bytes_pp, args->size, args->flags); |
| |
| return pl111_gem_object_create(dev, args->size, args->flags, file_priv, |
| &args->handle); |
| } |
| |
| int pl111_dumb_create(struct drm_file *file_priv, |
| struct drm_device *dev, struct drm_mode_create_dumb *args) |
| { |
| uint32_t bytes_pp; |
| |
| /* Round bpp up, to allow for case where bpp<8 */ |
| bytes_pp = args->bpp >> 3; |
| if (args->bpp & ((1 << 3) - 1)) |
| bytes_pp++; |
| |
| if (args->flags) { |
| DRM_ERROR("flags must be zero: 0x%x\n", args->flags); |
| return -EINVAL; |
| } |
| |
| args->pitch = ALIGN(args->width * bytes_pp, 64); |
| args->size = PAGE_ALIGN(args->pitch * args->height); |
| |
| DRM_DEBUG_KMS("dumb_create w=%d h=%d p=%d bpp=%d b=%d s=%llu f=0x%x\n", |
| args->width, args->height, args->pitch, args->bpp, |
| bytes_pp, args->size, args->flags); |
| |
| return pl111_gem_object_create(dev, args->size, |
| PL111_BOT_DMA | PL111_BOT_UNCACHED, |
| file_priv, &args->handle); |
| } |
| |
| int pl111_dumb_destroy(struct drm_file *file_priv, struct drm_device *dev, |
| uint32_t handle) |
| { |
| DRM_DEBUG_KMS("DRM %s on file_priv=%p handle=0x%.8x\n", __func__, |
| file_priv, handle); |
| return drm_gem_handle_delete(file_priv, handle); |
| } |
| |
| int pl111_dumb_map_offset(struct drm_file *file_priv, |
| struct drm_device *dev, uint32_t handle, |
| uint64_t *offset) |
| { |
| struct drm_gem_object *obj; |
| int ret = 0; |
| DRM_DEBUG_KMS("DRM %s on file_priv=%p handle=0x%.8x\n", __func__, |
| file_priv, handle); |
| |
| /* GEM does all our handle to object mapping */ |
| obj = drm_gem_object_lookup(dev, file_priv, handle); |
| if (obj == NULL) { |
| ret = -ENOENT; |
| goto fail; |
| } |
| |
| #if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 11, 0)) |
| if (obj->map_list.map == NULL) { |
| ret = drm_gem_create_mmap_offset(obj); |
| if (ret != 0) { |
| drm_gem_object_unreference_unlocked(obj); |
| goto fail; |
| } |
| } |
| #else |
| ret = drm_gem_create_mmap_offset(obj); |
| if (ret != 0) { |
| drm_gem_object_unreference_unlocked(obj); |
| goto fail; |
| } |
| #endif |
| |
| #if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 11, 0)) |
| *offset = (uint64_t) obj->map_list.hash.key << PAGE_SHIFT; |
| #else |
| *offset = drm_vma_node_offset_addr(&obj->vma_node); |
| DRM_DEBUG_KMS("offset = 0x%lx\n", (unsigned long)*offset); |
| #endif |
| |
| drm_gem_object_unreference_unlocked(obj); |
| fail: |
| return ret; |
| } |
| |
| /* sync the buffer for DMA access */ |
| void pl111_gem_sync_to_dma(struct pl111_gem_bo *bo) |
| { |
| struct drm_device *dev = bo->gem_object.dev; |
| |
| if (!(bo->type & PL111_BOT_DMA) && (bo->type & PL111_BOT_CACHED)) { |
| int i, npages = bo->gem_object.size >> PAGE_SHIFT; |
| struct page **pages = bo->backing_data.shm.pages; |
| bool dirty = false; |
| |
| for (i = 0; i < npages; i++) { |
| if (!bo->backing_data.shm.dma_addrs[i]) { |
| DRM_DEBUG("%s: dma map page=%d bo=%p\n", __func__, i, bo); |
| bo->backing_data.shm.dma_addrs[i] = |
| dma_map_page(dev->dev, pages[i], 0, |
| PAGE_SIZE, DMA_BIDIRECTIONAL); |
| dirty = true; |
| } |
| } |
| |
| if (dirty) { |
| DRM_DEBUG("%s: zap ptes (and flush cache) bo=%p\n", __func__, bo); |
| /* |
| * TODO MIDEGL-1813 |
| * |
| * Use flush_cache_page() and outer_flush_range() to |
| * flush only the user space mappings of the dirty pages |
| */ |
| flush_cache_all(); |
| outer_flush_all(); |
| unmap_mapping_range(bo->gem_object.filp->f_mapping, 0, |
| bo->gem_object.size, 1); |
| } |
| } |
| } |
| |
| void pl111_gem_sync_to_cpu(struct pl111_gem_bo *bo, int pgoff) |
| { |
| struct drm_device *dev = bo->gem_object.dev; |
| |
| /* |
| * TODO MIDEGL-1808 |
| * |
| * The following check was meant to detect if the CPU is trying to access |
| * a buffer that is currently mapped for DMA accesses, which is illegal |
| * as described by the DMA-API. |
| * |
| * However, some of our tests are trying to do that, which triggers the message |
| * below and avoids dma-unmapping the pages not to annoy the DMA device but that |
| * leads to the test failing because of caches not being properly flushed. |
| */ |
| |
| /* |
| if (bo->sgt) { |
| DRM_ERROR("%s: the CPU is trying to access a dma-mapped buffer\n", __func__); |
| return; |
| } |
| */ |
| |
| if (!(bo->type & PL111_BOT_DMA) && (bo->type & PL111_BOT_CACHED) && |
| bo->backing_data.shm.dma_addrs[pgoff]) { |
| DRM_DEBUG("%s: unmap bo=%p (s=%d), paddr=%08x\n", |
| __func__, bo, bo->gem_object.size, |
| bo->backing_data.shm.dma_addrs[pgoff]); |
| dma_unmap_page(dev->dev, bo->backing_data.shm.dma_addrs[pgoff], |
| PAGE_SIZE, DMA_BIDIRECTIONAL); |
| bo->backing_data.shm.dma_addrs[pgoff] = 0; |
| } |
| } |
| |
| /* Based on omapdrm driver */ |
| int pl111_bo_mmap(struct drm_gem_object *obj, struct pl111_gem_bo *bo, |
| struct vm_area_struct *vma, size_t size) |
| { |
| DRM_DEBUG("DRM %s on drm_gem_object=%p, pl111_gem_bo=%p type=%08x\n", |
| __func__, obj, bo, bo->type); |
| |
| vma->vm_flags &= ~VM_PFNMAP; |
| vma->vm_flags |= VM_MIXEDMAP; |
| |
| if (bo->type & PL111_BOT_WC) { |
| vma->vm_page_prot = |
| pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); |
| } else if (bo->type & PL111_BOT_CACHED) { |
| /* |
| * Objects that do not have a filp (DMA backed) can't be |
| * mapped as cached now. Write-combine should be enough. |
| */ |
| if (WARN_ON(!obj->filp)) |
| return -EINVAL; |
| |
| /* |
| * As explained in Documentation/dma-buf-sharing.txt |
| * we need this trick so that we can manually zap ptes |
| * in order to fake coherency. |
| */ |
| fput(vma->vm_file); |
| get_file(obj->filp); |
| vma->vm_file = obj->filp; |
| |
| vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); |
| } else { /* PL111_BOT_UNCACHED */ |
| vma->vm_page_prot = |
| pgprot_noncached(vm_get_page_prot(vma->vm_flags)); |
| } |
| return 0; |
| } |
| |
| int pl111_gem_mmap(struct file *file_priv, struct vm_area_struct *vma) |
| { |
| int ret; |
| struct drm_file *priv = file_priv->private_data; |
| struct drm_device *dev = priv->minor->dev; |
| #if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 13, 0)) |
| struct drm_gem_mm *mm = dev->mm_private; |
| struct drm_hash_item *hash; |
| struct drm_local_map *map = NULL; |
| #else |
| struct drm_vma_offset_node *node; |
| #endif |
| struct drm_gem_object *obj; |
| struct pl111_gem_bo *bo; |
| |
| DRM_DEBUG_KMS("DRM %s\n", __func__); |
| |
| #if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 13, 0)) |
| drm_ht_find_item(&mm->offset_hash, vma->vm_pgoff, &hash); |
| map = drm_hash_entry(hash, struct drm_map_list, hash)->map; |
| obj = map->handle; |
| #else |
| node = drm_vma_offset_exact_lookup(dev->vma_offset_manager, |
| vma->vm_pgoff, |
| vma_pages(vma)); |
| obj = container_of(node, struct drm_gem_object, vma_node); |
| #endif |
| bo = PL111_BO_FROM_GEM(obj); |
| |
| DRM_DEBUG_KMS("DRM %s on pl111_gem_bo %p bo->type 0x%08x\n", __func__, bo, bo->type); |
| |
| /* for an imported buffer we let the exporter handle the mmap */ |
| if (obj->import_attach) |
| return dma_buf_mmap(obj->import_attach->dmabuf, vma, 0); |
| |
| ret = drm_gem_mmap(file_priv, vma); |
| if (ret < 0) { |
| DRM_ERROR("failed to mmap\n"); |
| return ret; |
| } |
| |
| /* Our page fault handler uses the page offset calculated from the vma, |
| * so we need to remove the gem cookie offset specified in the call. |
| */ |
| vma->vm_pgoff = 0; |
| |
| return pl111_bo_mmap(obj, bo, vma, vma->vm_end - vma->vm_start); |
| } |