/* * * (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 #include #include #include #include #include #include #include #include #include #include "pl111_drm.h" #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)) #include #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)) #include #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); }