blob: 13fb25605693500c435623161bbb1800578bcfb9 [file] [log] [blame]
/*
*
* (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);
}