blob: ff602efd6d6684f7a0d37113431bb8a6ff35ccf7 [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_vma.c
* Implementation of the VM 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"
/* BEGIN drivers/staging/omapdrm/omap_gem_helpers.c */
/**
* drm_gem_put_pages - helper to free backing pages for a GEM object
* @obj: obj in question
* @pages: pages to free
*/
static void _drm_gem_put_pages(struct drm_gem_object *obj, struct page **pages,
bool dirty, bool accessed)
{
int i, npages;
struct pl111_gem_bo *bo;
npages = obj->size >> PAGE_SHIFT;
bo = PL111_BO_FROM_GEM(obj);
for (i = 0; i < npages; i++) {
if (dirty)
set_page_dirty(pages[i]);
if (accessed)
mark_page_accessed(pages[i]);
/* Undo the reference we took when populating the table */
page_cache_release(pages[i]);
}
drm_free_large(pages);
}
void put_pages(struct drm_gem_object *obj, struct page **pages)
{
int i, npages;
struct pl111_gem_bo *bo;
npages = obj->size >> PAGE_SHIFT;
bo = PL111_BO_FROM_GEM(obj);
_drm_gem_put_pages(obj, pages, true, true);
if (bo->backing_data.shm.dma_addrs) {
for (i = 0; i < npages; i++) {
/* Filter pages unmapped because of CPU accesses */
if (!bo->backing_data.shm.dma_addrs[i])
continue;
if (!dma_mapping_error(obj->dev->dev,
bo->backing_data.shm.dma_addrs[i])) {
dma_unmap_page(obj->dev->dev,
bo->backing_data.shm.dma_addrs[i],
PAGE_SIZE,
DMA_BIDIRECTIONAL);
}
}
kfree(bo->backing_data.shm.dma_addrs);
bo->backing_data.shm.dma_addrs = NULL;
}
}
/**
* drm_gem_get_pages - helper to allocate backing pages for a GEM object
* @obj: obj in question
* @gfpmask: gfp mask of requested pages
*/
static struct page **_drm_gem_get_pages(struct drm_gem_object *obj,
gfp_t gfpmask)
{
struct inode *inode;
struct address_space *mapping;
struct page *p, **pages;
int i, npages;
/* This is the shared memory object that backs the GEM resource */
inode = obj->filp->f_path.dentry->d_inode;
mapping = inode->i_mapping;
npages = obj->size >> PAGE_SHIFT;
pages = drm_malloc_ab(npages, sizeof(struct page *));
if (pages == NULL)
return ERR_PTR(-ENOMEM);
gfpmask |= mapping_gfp_mask(mapping);
for (i = 0; i < npages; i++) {
p = shmem_read_mapping_page_gfp(mapping, i, gfpmask);
if (IS_ERR(p))
goto fail;
pages[i] = p;
/*
* There is a hypothetical issue w/ drivers that require
* buffer memory in the low 4GB.. if the pages are un-
* pinned, and swapped out, they can end up swapped back
* in above 4GB. If pages are already in memory, then
* shmem_read_mapping_page_gfp will ignore the gfpmask,
* even if the already in-memory page disobeys the mask.
*
* It is only a theoretical issue today, because none of
* the devices with this limitation can be populated with
* enough memory to trigger the issue. But this BUG_ON()
* is here as a reminder in case the problem with
* shmem_read_mapping_page_gfp() isn't solved by the time
* it does become a real issue.
*
* See this thread: http://lkml.org/lkml/2011/7/11/238
*/
BUG_ON((gfpmask & __GFP_DMA32) &&
(page_to_pfn(p) >= 0x00100000UL));
}
return pages;
fail:
while (i--)
page_cache_release(pages[i]);
drm_free_large(pages);
return ERR_PTR(PTR_ERR(p));
}
struct page **get_pages(struct drm_gem_object *obj)
{
struct pl111_gem_bo *bo;
bo = PL111_BO_FROM_GEM(obj);
if (bo->backing_data.shm.pages == NULL) {
struct page **p;
int npages = obj->size >> PAGE_SHIFT;
int i;
p = _drm_gem_get_pages(obj, GFP_KERNEL);
if (IS_ERR(p))
return ERR_PTR(-ENOMEM);
bo->backing_data.shm.pages = p;
if (bo->backing_data.shm.dma_addrs == NULL) {
bo->backing_data.shm.dma_addrs =
kzalloc(npages * sizeof(dma_addr_t),
GFP_KERNEL);
if (bo->backing_data.shm.dma_addrs == NULL)
goto error_out;
}
if (!(bo->type & PL111_BOT_CACHED)) {
for (i = 0; i < npages; ++i) {
bo->backing_data.shm.dma_addrs[i] =
dma_map_page(obj->dev->dev, p[i], 0, PAGE_SIZE,
DMA_BIDIRECTIONAL);
if (dma_mapping_error(obj->dev->dev,
bo->backing_data.shm.dma_addrs[i]))
goto error_out;
}
}
}
return bo->backing_data.shm.pages;
error_out:
put_pages(obj, bo->backing_data.shm.pages);
bo->backing_data.shm.pages = NULL;
return ERR_PTR(-ENOMEM);
}
/* END drivers/staging/omapdrm/omap_gem_helpers.c */
int pl111_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
struct page **pages;
unsigned long pfn;
struct drm_gem_object *obj = vma->vm_private_data;
struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj);
struct drm_device *dev = obj->dev;
int ret;
mutex_lock(&dev->struct_mutex);
/*
* Our mmap calls setup a valid vma->vm_pgoff
* so we can use vmf->pgoff
*/
if (bo->type & PL111_BOT_DMA) {
pfn = (bo->backing_data.dma.fb_dev_addr >> PAGE_SHIFT) +
vmf->pgoff;
} else { /* PL111_BOT_SHM */
pages = get_pages(obj);
if (IS_ERR(pages)) {
dev_err(obj->dev->dev,
"could not get pages: %ld\n", PTR_ERR(pages));
ret = PTR_ERR(pages);
goto error;
}
pfn = page_to_pfn(pages[vmf->pgoff]);
pl111_gem_sync_to_cpu(bo, vmf->pgoff);
}
DRM_DEBUG("bo=%p physaddr=0x%.8x for offset 0x%x\n",
bo, PFN_PHYS(pfn), PFN_PHYS(vmf->pgoff));
ret = vm_insert_mixed(vma, (unsigned long)vmf->virtual_address, pfn);
error:
mutex_unlock(&dev->struct_mutex);
switch (ret) {
case 0:
case -ERESTARTSYS:
case -EINTR:
case -EBUSY:
return VM_FAULT_NOPAGE;
case -ENOMEM:
return VM_FAULT_OOM;
default:
return VM_FAULT_SIGBUS;
}
}
/*
* The core drm_vm_ functions in kernel 3.4 are not ready
* to handle dma_buf cases where vma->vm_file->private_data
* cannot be accessed to get the device.
*
* We use these functions from 3.5 instead where the device
* pointer is passed explicitly.
*
* However they aren't exported from the kernel until 3.10
*/
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0))
void pl111_drm_vm_open_locked(struct drm_device *dev,
struct vm_area_struct *vma)
{
struct drm_vma_entry *vma_entry;
DRM_DEBUG("0x%08lx,0x%08lx\n",
vma->vm_start, vma->vm_end - vma->vm_start);
atomic_inc(&dev->vma_count);
vma_entry = kmalloc(sizeof(*vma_entry), GFP_KERNEL);
if (vma_entry) {
vma_entry->vma = vma;
vma_entry->pid = current->pid;
list_add(&vma_entry->head, &dev->vmalist);
}
}
void pl111_drm_vm_close_locked(struct drm_device *dev,
struct vm_area_struct *vma)
{
struct drm_vma_entry *pt, *temp;
DRM_DEBUG("0x%08lx,0x%08lx\n",
vma->vm_start, vma->vm_end - vma->vm_start);
atomic_dec(&dev->vma_count);
list_for_each_entry_safe(pt, temp, &dev->vmalist, head) {
if (pt->vma == vma) {
list_del(&pt->head);
kfree(pt);
break;
}
}
}
void pl111_gem_vm_open(struct vm_area_struct *vma)
{
struct drm_gem_object *obj = vma->vm_private_data;
drm_gem_object_reference(obj);
mutex_lock(&obj->dev->struct_mutex);
pl111_drm_vm_open_locked(obj->dev, vma);
mutex_unlock(&obj->dev->struct_mutex);
}
void pl111_gem_vm_close(struct vm_area_struct *vma)
{
struct drm_gem_object *obj = vma->vm_private_data;
struct drm_device *dev = obj->dev;
mutex_lock(&dev->struct_mutex);
pl111_drm_vm_close_locked(obj->dev, vma);
drm_gem_object_unreference(obj);
mutex_unlock(&dev->struct_mutex);
}
#endif