OpenCloudOS-Kernel/drivers/gpu/drm/etnaviv/etnaviv_gem.c

923 lines
22 KiB
C
Raw Normal View History

/*
* Copyright (C) 2015 Etnaviv Project
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/spinlock.h>
#include <linux/shmem_fs.h>
#include "etnaviv_drv.h"
#include "etnaviv_gem.h"
#include "etnaviv_gpu.h"
#include "etnaviv_mmu.h"
static void etnaviv_gem_scatter_map(struct etnaviv_gem_object *etnaviv_obj)
{
struct drm_device *dev = etnaviv_obj->base.dev;
struct sg_table *sgt = etnaviv_obj->sgt;
/*
* For non-cached buffers, ensure the new pages are clean
* because display controller, GPU, etc. are not coherent.
*/
if (etnaviv_obj->flags & ETNA_BO_CACHE_MASK)
dma_map_sg(dev->dev, sgt->sgl, sgt->nents, DMA_BIDIRECTIONAL);
}
static void etnaviv_gem_scatterlist_unmap(struct etnaviv_gem_object *etnaviv_obj)
{
struct drm_device *dev = etnaviv_obj->base.dev;
struct sg_table *sgt = etnaviv_obj->sgt;
/*
* For non-cached buffers, ensure the new pages are clean
* because display controller, GPU, etc. are not coherent:
*
* WARNING: The DMA API does not support concurrent CPU
* and device access to the memory area. With BIDIRECTIONAL,
* we will clean the cache lines which overlap the region,
* and invalidate all cache lines (partially) contained in
* the region.
*
* If you have dirty data in the overlapping cache lines,
* that will corrupt the GPU-written data. If you have
* written into the remainder of the region, this can
* discard those writes.
*/
if (etnaviv_obj->flags & ETNA_BO_CACHE_MASK)
dma_unmap_sg(dev->dev, sgt->sgl, sgt->nents, DMA_BIDIRECTIONAL);
}
/* called with etnaviv_obj->lock held */
static int etnaviv_gem_shmem_get_pages(struct etnaviv_gem_object *etnaviv_obj)
{
struct drm_device *dev = etnaviv_obj->base.dev;
struct page **p = drm_gem_get_pages(&etnaviv_obj->base);
if (IS_ERR(p)) {
dev_err(dev->dev, "could not get pages: %ld\n", PTR_ERR(p));
return PTR_ERR(p);
}
etnaviv_obj->pages = p;
return 0;
}
static void put_pages(struct etnaviv_gem_object *etnaviv_obj)
{
if (etnaviv_obj->sgt) {
etnaviv_gem_scatterlist_unmap(etnaviv_obj);
sg_free_table(etnaviv_obj->sgt);
kfree(etnaviv_obj->sgt);
etnaviv_obj->sgt = NULL;
}
if (etnaviv_obj->pages) {
drm_gem_put_pages(&etnaviv_obj->base, etnaviv_obj->pages,
true, false);
etnaviv_obj->pages = NULL;
}
}
struct page **etnaviv_gem_get_pages(struct etnaviv_gem_object *etnaviv_obj)
{
int ret;
lockdep_assert_held(&etnaviv_obj->lock);
if (!etnaviv_obj->pages) {
ret = etnaviv_obj->ops->get_pages(etnaviv_obj);
if (ret < 0)
return ERR_PTR(ret);
}
if (!etnaviv_obj->sgt) {
struct drm_device *dev = etnaviv_obj->base.dev;
int npages = etnaviv_obj->base.size >> PAGE_SHIFT;
struct sg_table *sgt;
sgt = drm_prime_pages_to_sg(etnaviv_obj->pages, npages);
if (IS_ERR(sgt)) {
dev_err(dev->dev, "failed to allocate sgt: %ld\n",
PTR_ERR(sgt));
return ERR_CAST(sgt);
}
etnaviv_obj->sgt = sgt;
etnaviv_gem_scatter_map(etnaviv_obj);
}
return etnaviv_obj->pages;
}
void etnaviv_gem_put_pages(struct etnaviv_gem_object *etnaviv_obj)
{
lockdep_assert_held(&etnaviv_obj->lock);
/* when we start tracking the pin count, then do something here */
}
static int etnaviv_gem_mmap_obj(struct etnaviv_gem_object *etnaviv_obj,
struct vm_area_struct *vma)
{
pgprot_t vm_page_prot;
vma->vm_flags &= ~VM_PFNMAP;
vma->vm_flags |= VM_MIXEDMAP;
vm_page_prot = vm_get_page_prot(vma->vm_flags);
if (etnaviv_obj->flags & ETNA_BO_WC) {
vma->vm_page_prot = pgprot_writecombine(vm_page_prot);
} else if (etnaviv_obj->flags & ETNA_BO_UNCACHED) {
vma->vm_page_prot = pgprot_noncached(vm_page_prot);
} else {
/*
* Shunt off cached objs to shmem file so they have their own
* address_space (so unmap_mapping_range does what we want,
* in particular in the case of mmap'd dmabufs)
*/
fput(vma->vm_file);
get_file(etnaviv_obj->base.filp);
vma->vm_pgoff = 0;
vma->vm_file = etnaviv_obj->base.filp;
vma->vm_page_prot = vm_page_prot;
}
return 0;
}
int etnaviv_gem_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct etnaviv_gem_object *obj;
int ret;
ret = drm_gem_mmap(filp, vma);
if (ret) {
DBG("mmap failed: %d", ret);
return ret;
}
obj = to_etnaviv_bo(vma->vm_private_data);
return obj->ops->mmap(obj, vma);
}
int etnaviv_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
struct drm_gem_object *obj = vma->vm_private_data;
struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj);
struct page **pages, *page;
pgoff_t pgoff;
int ret;
/*
* Make sure we don't parallel update on a fault, nor move or remove
* something from beneath our feet. Note that vm_insert_page() is
* specifically coded to take care of this, so we don't have to.
*/
ret = mutex_lock_interruptible(&etnaviv_obj->lock);
if (ret)
goto out;
/* make sure we have pages attached now */
pages = etnaviv_gem_get_pages(etnaviv_obj);
mutex_unlock(&etnaviv_obj->lock);
if (IS_ERR(pages)) {
ret = PTR_ERR(pages);
goto out;
}
/* We don't use vmf->pgoff since that has the fake offset: */
pgoff = (vmf->address - vma->vm_start) >> PAGE_SHIFT;
page = pages[pgoff];
VERB("Inserting %p pfn %lx, pa %lx", (void *)vmf->address,
page_to_pfn(page), page_to_pfn(page) << PAGE_SHIFT);
ret = vm_insert_page(vma, vmf->address, page);
out:
switch (ret) {
case -EAGAIN:
case 0:
case -ERESTARTSYS:
case -EINTR:
case -EBUSY:
/*
* EBUSY is ok: this just means that another thread
* already did the job.
*/
return VM_FAULT_NOPAGE;
case -ENOMEM:
return VM_FAULT_OOM;
default:
return VM_FAULT_SIGBUS;
}
}
int etnaviv_gem_mmap_offset(struct drm_gem_object *obj, u64 *offset)
{
int ret;
/* Make it mmapable */
ret = drm_gem_create_mmap_offset(obj);
if (ret)
dev_err(obj->dev->dev, "could not allocate mmap offset\n");
else
*offset = drm_vma_node_offset_addr(&obj->vma_node);
return ret;
}
static struct etnaviv_vram_mapping *
etnaviv_gem_get_vram_mapping(struct etnaviv_gem_object *obj,
struct etnaviv_iommu *mmu)
{
struct etnaviv_vram_mapping *mapping;
list_for_each_entry(mapping, &obj->vram_list, obj_node) {
if (mapping->mmu == mmu)
return mapping;
}
return NULL;
}
void etnaviv_gem_mapping_reference(struct etnaviv_vram_mapping *mapping)
{
struct etnaviv_gem_object *etnaviv_obj = mapping->object;
drm_gem_object_reference(&etnaviv_obj->base);
mutex_lock(&etnaviv_obj->lock);
WARN_ON(mapping->use == 0);
mapping->use += 1;
mutex_unlock(&etnaviv_obj->lock);
}
void etnaviv_gem_mapping_unreference(struct etnaviv_vram_mapping *mapping)
{
struct etnaviv_gem_object *etnaviv_obj = mapping->object;
mutex_lock(&etnaviv_obj->lock);
WARN_ON(mapping->use == 0);
mapping->use -= 1;
mutex_unlock(&etnaviv_obj->lock);
drm_gem_object_unreference_unlocked(&etnaviv_obj->base);
}
struct etnaviv_vram_mapping *etnaviv_gem_mapping_get(
struct drm_gem_object *obj, struct etnaviv_gpu *gpu)
{
struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj);
struct etnaviv_vram_mapping *mapping;
struct page **pages;
int ret = 0;
mutex_lock(&etnaviv_obj->lock);
mapping = etnaviv_gem_get_vram_mapping(etnaviv_obj, gpu->mmu);
if (mapping) {
/*
* Holding the object lock prevents the use count changing
* beneath us. If the use count is zero, the MMU might be
* reaping this object, so take the lock and re-check that
* the MMU owns this mapping to close this race.
*/
if (mapping->use == 0) {
mutex_lock(&gpu->mmu->lock);
if (mapping->mmu == gpu->mmu)
mapping->use += 1;
else
mapping = NULL;
mutex_unlock(&gpu->mmu->lock);
if (mapping)
goto out;
} else {
mapping->use += 1;
goto out;
}
}
pages = etnaviv_gem_get_pages(etnaviv_obj);
if (IS_ERR(pages)) {
ret = PTR_ERR(pages);
goto out;
}
/*
* See if we have a reaped vram mapping we can re-use before
* allocating a fresh mapping.
*/
mapping = etnaviv_gem_get_vram_mapping(etnaviv_obj, NULL);
if (!mapping) {
mapping = kzalloc(sizeof(*mapping), GFP_KERNEL);
if (!mapping) {
ret = -ENOMEM;
goto out;
}
INIT_LIST_HEAD(&mapping->scan_node);
mapping->object = etnaviv_obj;
} else {
list_del(&mapping->obj_node);
}
mapping->mmu = gpu->mmu;
mapping->use = 1;
ret = etnaviv_iommu_map_gem(gpu->mmu, etnaviv_obj, gpu->memory_base,
mapping);
if (ret < 0)
kfree(mapping);
else
list_add_tail(&mapping->obj_node, &etnaviv_obj->vram_list);
out:
mutex_unlock(&etnaviv_obj->lock);
if (ret)
return ERR_PTR(ret);
/* Take a reference on the object */
drm_gem_object_reference(obj);
return mapping;
}
void *etnaviv_gem_vmap(struct drm_gem_object *obj)
{
struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj);
if (etnaviv_obj->vaddr)
return etnaviv_obj->vaddr;
mutex_lock(&etnaviv_obj->lock);
/*
* Need to check again, as we might have raced with another thread
* while waiting for the mutex.
*/
if (!etnaviv_obj->vaddr)
etnaviv_obj->vaddr = etnaviv_obj->ops->vmap(etnaviv_obj);
mutex_unlock(&etnaviv_obj->lock);
return etnaviv_obj->vaddr;
}
static void *etnaviv_gem_vmap_impl(struct etnaviv_gem_object *obj)
{
struct page **pages;
lockdep_assert_held(&obj->lock);
pages = etnaviv_gem_get_pages(obj);
if (IS_ERR(pages))
return NULL;
return vmap(pages, obj->base.size >> PAGE_SHIFT,
VM_MAP, pgprot_writecombine(PAGE_KERNEL));
}
static inline enum dma_data_direction etnaviv_op_to_dma_dir(u32 op)
{
if (op & ETNA_PREP_READ)
return DMA_FROM_DEVICE;
else if (op & ETNA_PREP_WRITE)
return DMA_TO_DEVICE;
else
return DMA_BIDIRECTIONAL;
}
int etnaviv_gem_cpu_prep(struct drm_gem_object *obj, u32 op,
struct timespec *timeout)
{
struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj);
struct drm_device *dev = obj->dev;
bool write = !!(op & ETNA_PREP_WRITE);
unsigned long remain =
op & ETNA_PREP_NOSYNC ? 0 : etnaviv_timeout_to_jiffies(timeout);
long lret;
lret = reservation_object_wait_timeout_rcu(etnaviv_obj->resv,
write, true, remain);
if (lret < 0)
return lret;
else if (lret == 0)
return remain == 0 ? -EBUSY : -ETIMEDOUT;
if (etnaviv_obj->flags & ETNA_BO_CACHED) {
if (!etnaviv_obj->sgt) {
void *ret;
mutex_lock(&etnaviv_obj->lock);
ret = etnaviv_gem_get_pages(etnaviv_obj);
mutex_unlock(&etnaviv_obj->lock);
if (IS_ERR(ret))
return PTR_ERR(ret);
}
dma_sync_sg_for_cpu(dev->dev, etnaviv_obj->sgt->sgl,
etnaviv_obj->sgt->nents,
etnaviv_op_to_dma_dir(op));
etnaviv_obj->last_cpu_prep_op = op;
}
return 0;
}
int etnaviv_gem_cpu_fini(struct drm_gem_object *obj)
{
struct drm_device *dev = obj->dev;
struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj);
if (etnaviv_obj->flags & ETNA_BO_CACHED) {
/* fini without a prep is almost certainly a userspace error */
WARN_ON(etnaviv_obj->last_cpu_prep_op == 0);
dma_sync_sg_for_device(dev->dev, etnaviv_obj->sgt->sgl,
etnaviv_obj->sgt->nents,
etnaviv_op_to_dma_dir(etnaviv_obj->last_cpu_prep_op));
etnaviv_obj->last_cpu_prep_op = 0;
}
return 0;
}
int etnaviv_gem_wait_bo(struct etnaviv_gpu *gpu, struct drm_gem_object *obj,
struct timespec *timeout)
{
struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj);
return etnaviv_gpu_wait_obj_inactive(gpu, etnaviv_obj, timeout);
}
#ifdef CONFIG_DEBUG_FS
dma-buf: Rename struct fence to dma_fence I plan to usurp the short name of struct fence for a core kernel struct, and so I need to rename the specialised fence/timeline for DMA operations to make room. A consensus was reached in https://lists.freedesktop.org/archives/dri-devel/2016-July/113083.html that making clear this fence applies to DMA operations was a good thing. Since then the patch has grown a bit as usage increases, so hopefully it remains a good thing! (v2...: rebase, rerun spatch) v3: Compile on msm, spotted a manual fixup that I broke. v4: Try again for msm, sorry Daniel coccinelle script: @@ @@ - struct fence + struct dma_fence @@ @@ - struct fence_ops + struct dma_fence_ops @@ @@ - struct fence_cb + struct dma_fence_cb @@ @@ - struct fence_array + struct dma_fence_array @@ @@ - enum fence_flag_bits + enum dma_fence_flag_bits @@ @@ ( - fence_init + dma_fence_init | - fence_release + dma_fence_release | - fence_free + dma_fence_free | - fence_get + dma_fence_get | - fence_get_rcu + dma_fence_get_rcu | - fence_put + dma_fence_put | - fence_signal + dma_fence_signal | - fence_signal_locked + dma_fence_signal_locked | - fence_default_wait + dma_fence_default_wait | - fence_add_callback + dma_fence_add_callback | - fence_remove_callback + dma_fence_remove_callback | - fence_enable_sw_signaling + dma_fence_enable_sw_signaling | - fence_is_signaled_locked + dma_fence_is_signaled_locked | - fence_is_signaled + dma_fence_is_signaled | - fence_is_later + dma_fence_is_later | - fence_later + dma_fence_later | - fence_wait_timeout + dma_fence_wait_timeout | - fence_wait_any_timeout + dma_fence_wait_any_timeout | - fence_wait + dma_fence_wait | - fence_context_alloc + dma_fence_context_alloc | - fence_array_create + dma_fence_array_create | - to_fence_array + to_dma_fence_array | - fence_is_array + dma_fence_is_array | - trace_fence_emit + trace_dma_fence_emit | - FENCE_TRACE + DMA_FENCE_TRACE | - FENCE_WARN + DMA_FENCE_WARN | - FENCE_ERR + DMA_FENCE_ERR ) ( ... ) Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk> Reviewed-by: Gustavo Padovan <gustavo.padovan@collabora.co.uk> Acked-by: Sumit Semwal <sumit.semwal@linaro.org> Acked-by: Christian König <christian.koenig@amd.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Link: http://patchwork.freedesktop.org/patch/msgid/20161025120045.28839-1-chris@chris-wilson.co.uk
2016-10-25 20:00:45 +08:00
static void etnaviv_gem_describe_fence(struct dma_fence *fence,
const char *type, struct seq_file *m)
{
dma-buf: Rename struct fence to dma_fence I plan to usurp the short name of struct fence for a core kernel struct, and so I need to rename the specialised fence/timeline for DMA operations to make room. A consensus was reached in https://lists.freedesktop.org/archives/dri-devel/2016-July/113083.html that making clear this fence applies to DMA operations was a good thing. Since then the patch has grown a bit as usage increases, so hopefully it remains a good thing! (v2...: rebase, rerun spatch) v3: Compile on msm, spotted a manual fixup that I broke. v4: Try again for msm, sorry Daniel coccinelle script: @@ @@ - struct fence + struct dma_fence @@ @@ - struct fence_ops + struct dma_fence_ops @@ @@ - struct fence_cb + struct dma_fence_cb @@ @@ - struct fence_array + struct dma_fence_array @@ @@ - enum fence_flag_bits + enum dma_fence_flag_bits @@ @@ ( - fence_init + dma_fence_init | - fence_release + dma_fence_release | - fence_free + dma_fence_free | - fence_get + dma_fence_get | - fence_get_rcu + dma_fence_get_rcu | - fence_put + dma_fence_put | - fence_signal + dma_fence_signal | - fence_signal_locked + dma_fence_signal_locked | - fence_default_wait + dma_fence_default_wait | - fence_add_callback + dma_fence_add_callback | - fence_remove_callback + dma_fence_remove_callback | - fence_enable_sw_signaling + dma_fence_enable_sw_signaling | - fence_is_signaled_locked + dma_fence_is_signaled_locked | - fence_is_signaled + dma_fence_is_signaled | - fence_is_later + dma_fence_is_later | - fence_later + dma_fence_later | - fence_wait_timeout + dma_fence_wait_timeout | - fence_wait_any_timeout + dma_fence_wait_any_timeout | - fence_wait + dma_fence_wait | - fence_context_alloc + dma_fence_context_alloc | - fence_array_create + dma_fence_array_create | - to_fence_array + to_dma_fence_array | - fence_is_array + dma_fence_is_array | - trace_fence_emit + trace_dma_fence_emit | - FENCE_TRACE + DMA_FENCE_TRACE | - FENCE_WARN + DMA_FENCE_WARN | - FENCE_ERR + DMA_FENCE_ERR ) ( ... ) Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk> Reviewed-by: Gustavo Padovan <gustavo.padovan@collabora.co.uk> Acked-by: Sumit Semwal <sumit.semwal@linaro.org> Acked-by: Christian König <christian.koenig@amd.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Link: http://patchwork.freedesktop.org/patch/msgid/20161025120045.28839-1-chris@chris-wilson.co.uk
2016-10-25 20:00:45 +08:00
if (!test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags))
seq_printf(m, "\t%9s: %s %s seq %u\n",
type,
fence->ops->get_driver_name(fence),
fence->ops->get_timeline_name(fence),
fence->seqno);
}
static void etnaviv_gem_describe(struct drm_gem_object *obj, struct seq_file *m)
{
struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj);
struct reservation_object *robj = etnaviv_obj->resv;
struct reservation_object_list *fobj;
dma-buf: Rename struct fence to dma_fence I plan to usurp the short name of struct fence for a core kernel struct, and so I need to rename the specialised fence/timeline for DMA operations to make room. A consensus was reached in https://lists.freedesktop.org/archives/dri-devel/2016-July/113083.html that making clear this fence applies to DMA operations was a good thing. Since then the patch has grown a bit as usage increases, so hopefully it remains a good thing! (v2...: rebase, rerun spatch) v3: Compile on msm, spotted a manual fixup that I broke. v4: Try again for msm, sorry Daniel coccinelle script: @@ @@ - struct fence + struct dma_fence @@ @@ - struct fence_ops + struct dma_fence_ops @@ @@ - struct fence_cb + struct dma_fence_cb @@ @@ - struct fence_array + struct dma_fence_array @@ @@ - enum fence_flag_bits + enum dma_fence_flag_bits @@ @@ ( - fence_init + dma_fence_init | - fence_release + dma_fence_release | - fence_free + dma_fence_free | - fence_get + dma_fence_get | - fence_get_rcu + dma_fence_get_rcu | - fence_put + dma_fence_put | - fence_signal + dma_fence_signal | - fence_signal_locked + dma_fence_signal_locked | - fence_default_wait + dma_fence_default_wait | - fence_add_callback + dma_fence_add_callback | - fence_remove_callback + dma_fence_remove_callback | - fence_enable_sw_signaling + dma_fence_enable_sw_signaling | - fence_is_signaled_locked + dma_fence_is_signaled_locked | - fence_is_signaled + dma_fence_is_signaled | - fence_is_later + dma_fence_is_later | - fence_later + dma_fence_later | - fence_wait_timeout + dma_fence_wait_timeout | - fence_wait_any_timeout + dma_fence_wait_any_timeout | - fence_wait + dma_fence_wait | - fence_context_alloc + dma_fence_context_alloc | - fence_array_create + dma_fence_array_create | - to_fence_array + to_dma_fence_array | - fence_is_array + dma_fence_is_array | - trace_fence_emit + trace_dma_fence_emit | - FENCE_TRACE + DMA_FENCE_TRACE | - FENCE_WARN + DMA_FENCE_WARN | - FENCE_ERR + DMA_FENCE_ERR ) ( ... ) Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk> Reviewed-by: Gustavo Padovan <gustavo.padovan@collabora.co.uk> Acked-by: Sumit Semwal <sumit.semwal@linaro.org> Acked-by: Christian König <christian.koenig@amd.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Link: http://patchwork.freedesktop.org/patch/msgid/20161025120045.28839-1-chris@chris-wilson.co.uk
2016-10-25 20:00:45 +08:00
struct dma_fence *fence;
unsigned long off = drm_vma_node_start(&obj->vma_node);
seq_printf(m, "%08x: %c %2d (%2d) %08lx %p %zd\n",
etnaviv_obj->flags, is_active(etnaviv_obj) ? 'A' : 'I',
obj->name, obj->refcount.refcount.counter,
off, etnaviv_obj->vaddr, obj->size);
rcu_read_lock();
fobj = rcu_dereference(robj->fence);
if (fobj) {
unsigned int i, shared_count = fobj->shared_count;
for (i = 0; i < shared_count; i++) {
fence = rcu_dereference(fobj->shared[i]);
etnaviv_gem_describe_fence(fence, "Shared", m);
}
}
fence = rcu_dereference(robj->fence_excl);
if (fence)
etnaviv_gem_describe_fence(fence, "Exclusive", m);
rcu_read_unlock();
}
void etnaviv_gem_describe_objects(struct etnaviv_drm_private *priv,
struct seq_file *m)
{
struct etnaviv_gem_object *etnaviv_obj;
int count = 0;
size_t size = 0;
mutex_lock(&priv->gem_lock);
list_for_each_entry(etnaviv_obj, &priv->gem_list, gem_node) {
struct drm_gem_object *obj = &etnaviv_obj->base;
seq_puts(m, " ");
etnaviv_gem_describe(obj, m);
count++;
size += obj->size;
}
mutex_unlock(&priv->gem_lock);
seq_printf(m, "Total %d objects, %zu bytes\n", count, size);
}
#endif
static void etnaviv_gem_shmem_release(struct etnaviv_gem_object *etnaviv_obj)
{
vunmap(etnaviv_obj->vaddr);
put_pages(etnaviv_obj);
}
static const struct etnaviv_gem_ops etnaviv_gem_shmem_ops = {
.get_pages = etnaviv_gem_shmem_get_pages,
.release = etnaviv_gem_shmem_release,
.vmap = etnaviv_gem_vmap_impl,
.mmap = etnaviv_gem_mmap_obj,
};
void etnaviv_gem_free_object(struct drm_gem_object *obj)
{
struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj);
struct etnaviv_vram_mapping *mapping, *tmp;
/* object should not be active */
WARN_ON(is_active(etnaviv_obj));
list_del(&etnaviv_obj->gem_node);
list_for_each_entry_safe(mapping, tmp, &etnaviv_obj->vram_list,
obj_node) {
struct etnaviv_iommu *mmu = mapping->mmu;
WARN_ON(mapping->use);
if (mmu)
etnaviv_iommu_unmap_gem(mmu, mapping);
list_del(&mapping->obj_node);
kfree(mapping);
}
drm_gem_free_mmap_offset(obj);
etnaviv_obj->ops->release(etnaviv_obj);
if (etnaviv_obj->resv == &etnaviv_obj->_resv)
reservation_object_fini(&etnaviv_obj->_resv);
drm_gem_object_release(obj);
kfree(etnaviv_obj);
}
int etnaviv_gem_obj_add(struct drm_device *dev, struct drm_gem_object *obj)
{
struct etnaviv_drm_private *priv = dev->dev_private;
struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj);
mutex_lock(&priv->gem_lock);
list_add_tail(&etnaviv_obj->gem_node, &priv->gem_list);
mutex_unlock(&priv->gem_lock);
return 0;
}
static int etnaviv_gem_new_impl(struct drm_device *dev, u32 size, u32 flags,
struct reservation_object *robj, const struct etnaviv_gem_ops *ops,
struct drm_gem_object **obj)
{
struct etnaviv_gem_object *etnaviv_obj;
unsigned sz = sizeof(*etnaviv_obj);
bool valid = true;
/* validate flags */
switch (flags & ETNA_BO_CACHE_MASK) {
case ETNA_BO_UNCACHED:
case ETNA_BO_CACHED:
case ETNA_BO_WC:
break;
default:
valid = false;
}
if (!valid) {
dev_err(dev->dev, "invalid cache flag: %x\n",
(flags & ETNA_BO_CACHE_MASK));
return -EINVAL;
}
etnaviv_obj = kzalloc(sz, GFP_KERNEL);
if (!etnaviv_obj)
return -ENOMEM;
etnaviv_obj->flags = flags;
etnaviv_obj->ops = ops;
if (robj) {
etnaviv_obj->resv = robj;
} else {
etnaviv_obj->resv = &etnaviv_obj->_resv;
reservation_object_init(&etnaviv_obj->_resv);
}
mutex_init(&etnaviv_obj->lock);
INIT_LIST_HEAD(&etnaviv_obj->vram_list);
*obj = &etnaviv_obj->base;
return 0;
}
static struct drm_gem_object *__etnaviv_gem_new(struct drm_device *dev,
u32 size, u32 flags)
{
struct drm_gem_object *obj = NULL;
int ret;
size = PAGE_ALIGN(size);
ret = etnaviv_gem_new_impl(dev, size, flags, NULL,
&etnaviv_gem_shmem_ops, &obj);
if (ret)
goto fail;
ret = drm_gem_object_init(dev, obj, size);
if (ret == 0) {
struct address_space *mapping;
/*
* Our buffers are kept pinned, so allocating them
* from the MOVABLE zone is a really bad idea, and
* conflicts with CMA. See coments above new_inode()
* why this is required _and_ expected if you're
* going to pin these pages.
*/
mapping = obj->filp->f_mapping;
mapping_set_gfp_mask(mapping, GFP_HIGHUSER);
}
if (ret)
goto fail;
return obj;
fail:
drm_gem_object_unreference_unlocked(obj);
return ERR_PTR(ret);
}
/* convenience method to construct a GEM buffer object, and userspace handle */
int etnaviv_gem_new_handle(struct drm_device *dev, struct drm_file *file,
u32 size, u32 flags, u32 *handle)
{
struct drm_gem_object *obj;
int ret;
obj = __etnaviv_gem_new(dev, size, flags);
if (IS_ERR(obj))
return PTR_ERR(obj);
ret = etnaviv_gem_obj_add(dev, obj);
if (ret < 0) {
drm_gem_object_unreference_unlocked(obj);
return ret;
}
ret = drm_gem_handle_create(file, obj, handle);
/* drop reference from allocate - handle holds it now */
drm_gem_object_unreference_unlocked(obj);
return ret;
}
struct drm_gem_object *etnaviv_gem_new(struct drm_device *dev,
u32 size, u32 flags)
{
struct drm_gem_object *obj;
int ret;
obj = __etnaviv_gem_new(dev, size, flags);
if (IS_ERR(obj))
return obj;
ret = etnaviv_gem_obj_add(dev, obj);
if (ret < 0) {
drm_gem_object_unreference_unlocked(obj);
return ERR_PTR(ret);
}
return obj;
}
int etnaviv_gem_new_private(struct drm_device *dev, size_t size, u32 flags,
struct reservation_object *robj, const struct etnaviv_gem_ops *ops,
struct etnaviv_gem_object **res)
{
struct drm_gem_object *obj;
int ret;
ret = etnaviv_gem_new_impl(dev, size, flags, robj, ops, &obj);
if (ret)
return ret;
drm_gem_private_object_init(dev, obj, size);
*res = to_etnaviv_bo(obj);
return 0;
}
struct get_pages_work {
struct work_struct work;
struct mm_struct *mm;
struct task_struct *task;
struct etnaviv_gem_object *etnaviv_obj;
};
static struct page **etnaviv_gem_userptr_do_get_pages(
struct etnaviv_gem_object *etnaviv_obj, struct mm_struct *mm, struct task_struct *task)
{
int ret = 0, pinned, npages = etnaviv_obj->base.size >> PAGE_SHIFT;
struct page **pvec;
uintptr_t ptr;
unsigned int flags = 0;
pvec = drm_malloc_ab(npages, sizeof(struct page *));
if (!pvec)
return ERR_PTR(-ENOMEM);
if (!etnaviv_obj->userptr.ro)
flags |= FOLL_WRITE;
pinned = 0;
ptr = etnaviv_obj->userptr.ptr;
down_read(&mm->mmap_sem);
while (pinned < npages) {
mm/gup: Introduce get_user_pages_remote() For protection keys, we need to understand whether protections should be enforced in software or not. In general, we enforce protections when working on our own task, but not when on others. We call these "current" and "remote" operations. This patch introduces a new get_user_pages() variant: get_user_pages_remote() Which is a replacement for when get_user_pages() is called on non-current tsk/mm. We also introduce a new gup flag: FOLL_REMOTE which can be used for the "__" gup variants to get this new behavior. The uprobes is_trap_at_addr() location holds mmap_sem and calls get_user_pages(current->mm) on an instruction address. This makes it a pretty unique gup caller. Being an instruction access and also really originating from the kernel (vs. the app), I opted to consider this a 'remote' access where protection keys will not be enforced. Without protection keys, this patch should not change any behavior. Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com> Reviewed-by: Thomas Gleixner <tglx@linutronix.de> Cc: Andrea Arcangeli <aarcange@redhat.com> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Andy Lutomirski <luto@amacapital.net> Cc: Borislav Petkov <bp@alien8.de> Cc: Brian Gerst <brgerst@gmail.com> Cc: Dave Hansen <dave@sr71.net> Cc: Denys Vlasenko <dvlasenk@redhat.com> Cc: H. Peter Anvin <hpa@zytor.com> Cc: Kirill A. Shutemov <kirill.shutemov@linux.intel.com> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Naoya Horiguchi <n-horiguchi@ah.jp.nec.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Rik van Riel <riel@redhat.com> Cc: Srikar Dronamraju <srikar@linux.vnet.ibm.com> Cc: Vlastimil Babka <vbabka@suse.cz> Cc: jack@suse.cz Cc: linux-mm@kvack.org Link: http://lkml.kernel.org/r/20160212210154.3F0E51EA@viggo.jf.intel.com Signed-off-by: Ingo Molnar <mingo@kernel.org>
2016-02-13 05:01:54 +08:00
ret = get_user_pages_remote(task, mm, ptr, npages - pinned,
mm: add locked parameter to get_user_pages_remote() Patch series "mm: unexport __get_user_pages_unlocked()". This patch series continues the cleanup of get_user_pages*() functions taking advantage of the fact we can now pass gup_flags as we please. It firstly adds an additional 'locked' parameter to get_user_pages_remote() to allow for its callers to utilise VM_FAULT_RETRY functionality. This is necessary as the invocation of __get_user_pages_unlocked() in process_vm_rw_single_vec() makes use of this and no other existing higher level function would allow it to do so. Secondly existing callers of __get_user_pages_unlocked() are replaced with the appropriate higher-level replacement - get_user_pages_unlocked() if the current task and memory descriptor are referenced, or get_user_pages_remote() if other task/memory descriptors are referenced (having acquiring mmap_sem.) This patch (of 2): Add a int *locked parameter to get_user_pages_remote() to allow VM_FAULT_RETRY faulting behaviour similar to get_user_pages_[un]locked(). Taking into account the previous adjustments to get_user_pages*() functions allowing for the passing of gup_flags, we are now in a position where __get_user_pages_unlocked() need only be exported for his ability to allow VM_FAULT_RETRY behaviour, this adjustment allows us to subsequently unexport __get_user_pages_unlocked() as well as allowing for future flexibility in the use of get_user_pages_remote(). [sfr@canb.auug.org.au: merge fix for get_user_pages_remote API change] Link: http://lkml.kernel.org/r/20161122210511.024ec341@canb.auug.org.au Link: http://lkml.kernel.org/r/20161027095141.2569-2-lstoakes@gmail.com Signed-off-by: Lorenzo Stoakes <lstoakes@gmail.com> Acked-by: Michal Hocko <mhocko@suse.com> Cc: Jan Kara <jack@suse.cz> Cc: Hugh Dickins <hughd@google.com> Cc: Dave Hansen <dave.hansen@linux.intel.com> Cc: Rik van Riel <riel@redhat.com> Cc: Mel Gorman <mgorman@techsingularity.net> Cc: Paolo Bonzini <pbonzini@redhat.com> Cc: Radim Krcmar <rkrcmar@redhat.com> Signed-off-by: Stephen Rothwell <sfr@canb.auug.org.au> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-12-15 07:06:52 +08:00
flags, pvec + pinned, NULL, NULL);
if (ret < 0)
break;
ptr += ret * PAGE_SIZE;
pinned += ret;
}
up_read(&mm->mmap_sem);
if (ret < 0) {
release_pages(pvec, pinned, 0);
drm_free_large(pvec);
return ERR_PTR(ret);
}
return pvec;
}
static void __etnaviv_gem_userptr_get_pages(struct work_struct *_work)
{
struct get_pages_work *work = container_of(_work, typeof(*work), work);
struct etnaviv_gem_object *etnaviv_obj = work->etnaviv_obj;
struct page **pvec;
pvec = etnaviv_gem_userptr_do_get_pages(etnaviv_obj, work->mm, work->task);
mutex_lock(&etnaviv_obj->lock);
if (IS_ERR(pvec)) {
etnaviv_obj->userptr.work = ERR_CAST(pvec);
} else {
etnaviv_obj->userptr.work = NULL;
etnaviv_obj->pages = pvec;
}
mutex_unlock(&etnaviv_obj->lock);
drm_gem_object_unreference_unlocked(&etnaviv_obj->base);
mmput(work->mm);
put_task_struct(work->task);
kfree(work);
}
static int etnaviv_gem_userptr_get_pages(struct etnaviv_gem_object *etnaviv_obj)
{
struct page **pvec = NULL;
struct get_pages_work *work;
struct mm_struct *mm;
int ret, pinned, npages = etnaviv_obj->base.size >> PAGE_SHIFT;
if (etnaviv_obj->userptr.work) {
if (IS_ERR(etnaviv_obj->userptr.work)) {
ret = PTR_ERR(etnaviv_obj->userptr.work);
etnaviv_obj->userptr.work = NULL;
} else {
ret = -EAGAIN;
}
return ret;
}
mm = get_task_mm(etnaviv_obj->userptr.task);
pinned = 0;
if (mm == current->mm) {
pvec = drm_malloc_ab(npages, sizeof(struct page *));
if (!pvec) {
mmput(mm);
return -ENOMEM;
}
pinned = __get_user_pages_fast(etnaviv_obj->userptr.ptr, npages,
!etnaviv_obj->userptr.ro, pvec);
if (pinned < 0) {
drm_free_large(pvec);
mmput(mm);
return pinned;
}
if (pinned == npages) {
etnaviv_obj->pages = pvec;
mmput(mm);
return 0;
}
}
release_pages(pvec, pinned, 0);
drm_free_large(pvec);
work = kmalloc(sizeof(*work), GFP_KERNEL);
if (!work) {
mmput(mm);
return -ENOMEM;
}
get_task_struct(current);
drm_gem_object_reference(&etnaviv_obj->base);
work->mm = mm;
work->task = current;
work->etnaviv_obj = etnaviv_obj;
etnaviv_obj->userptr.work = &work->work;
INIT_WORK(&work->work, __etnaviv_gem_userptr_get_pages);
etnaviv_queue_work(etnaviv_obj->base.dev, &work->work);
return -EAGAIN;
}
static void etnaviv_gem_userptr_release(struct etnaviv_gem_object *etnaviv_obj)
{
if (etnaviv_obj->sgt) {
etnaviv_gem_scatterlist_unmap(etnaviv_obj);
sg_free_table(etnaviv_obj->sgt);
kfree(etnaviv_obj->sgt);
}
if (etnaviv_obj->pages) {
int npages = etnaviv_obj->base.size >> PAGE_SHIFT;
release_pages(etnaviv_obj->pages, npages, 0);
drm_free_large(etnaviv_obj->pages);
}
put_task_struct(etnaviv_obj->userptr.task);
}
static int etnaviv_gem_userptr_mmap_obj(struct etnaviv_gem_object *etnaviv_obj,
struct vm_area_struct *vma)
{
return -EINVAL;
}
static const struct etnaviv_gem_ops etnaviv_gem_userptr_ops = {
.get_pages = etnaviv_gem_userptr_get_pages,
.release = etnaviv_gem_userptr_release,
.vmap = etnaviv_gem_vmap_impl,
.mmap = etnaviv_gem_userptr_mmap_obj,
};
int etnaviv_gem_new_userptr(struct drm_device *dev, struct drm_file *file,
uintptr_t ptr, u32 size, u32 flags, u32 *handle)
{
struct etnaviv_gem_object *etnaviv_obj;
int ret;
ret = etnaviv_gem_new_private(dev, size, ETNA_BO_CACHED, NULL,
&etnaviv_gem_userptr_ops, &etnaviv_obj);
if (ret)
return ret;
etnaviv_obj->userptr.ptr = ptr;
etnaviv_obj->userptr.task = current;
etnaviv_obj->userptr.ro = !(flags & ETNA_USERPTR_WRITE);
get_task_struct(current);
ret = etnaviv_gem_obj_add(dev, &etnaviv_obj->base);
if (ret)
goto unreference;
ret = drm_gem_handle_create(file, &etnaviv_obj->base, handle);
unreference:
/* drop reference from allocate - handle holds it now */
drm_gem_object_unreference_unlocked(&etnaviv_obj->base);
return ret;
}