tegrakernel/kernel/nvidia/drivers/video/tegra/nvmap/nv2/nvmap_handle.c

1016 lines
22 KiB
C

/*
* drivers/video/tegra/nvmap/nvmap_handle.c
*
* Handle allocation and freeing routines for nvmap
*
* Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/rbtree.h>
#include <linux/dma-buf.h>
#include <linux/moduleparam.h>
#include <linux/nvmap.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/version.h>
#include <linux/uaccess.h>
#include <soc/tegra/chip-id.h>
#include <asm/pgtable.h>
#include <trace/events/nvmap.h>
#include "nvmap_stats.h"
#include "nvmap_misc.h"
#include "nvmap_cache.h"
#include "nvmap_heap_alloc.h"
#include "nvmap_carveout.h"
#include "nvmap_carveout.h"
#include "nvmap_dev.h"
#include "nvmap_dmabuf.h"
#include "nvmap_vma.h"
#include "nvmap_tag.h"
#include "nvmap_handle.h"
#include "nvmap_handle_priv.h"
// TODO: Add page coloring
static void handle_add_to_dev(struct nvmap_handle *h, struct nvmap_device *dev);
static int handle_remove_from_dev(struct nvmap_handle *h,
struct nvmap_device *dev);
// TODO: Remove these global variables
extern size_t cache_maint_inner_threshold;
extern int nvmap_cache_maint_by_set_ways;
extern struct static_key nvmap_disable_vaddr_for_cache_maint;
static struct nvmap_handle_info *handle_create_info(struct nvmap_handle *handle)
{
struct nvmap_handle_info *info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info) {
return ERR_PTR(-ENOMEM);
}
info->handle = handle;
INIT_LIST_HEAD(&info->maps);
mutex_init(&info->maps_lock);
return info;
}
struct dma_buf *nvmap_handle_to_dmabuf(struct nvmap_handle *handle)
{
return handle->dmabuf;
}
void nvmap_handle_install_fd(struct nvmap_handle *handle, int fd)
{
nvmap_dmabuf_install_fd(handle->dmabuf, fd);
// TODO: why is this get_dma_buf here?
get_dma_buf(handle->dmabuf);
}
void nvmap_handle_kmap_inc(struct nvmap_handle *h)
{
atomic_inc(&h->kmap_count);
}
void nvmap_handle_kmap_dec(struct nvmap_handle *h)
{
atomic_dec(&h->kmap_count);
}
void nvmap_handle_umap_inc(struct nvmap_handle *h)
{
atomic_inc(&h->umap_count);
}
void nvmap_handle_umap_dec(struct nvmap_handle *h)
{
atomic_dec(&h->umap_count);
}
size_t nvmap_handle_size(struct nvmap_handle *h)
{
return h->size;
}
int nvmap_handle_is_allocated(struct nvmap_handle *h)
{
return h->alloc;
}
size_t nvmap_handle_ivm_id(struct nvmap_handle *h)
{
return h->ivm_id;
}
u32 nvmap_handle_heap_type(struct nvmap_handle *h)
{
return h->heap_type;
}
u32 nvmap_handle_userflag(struct nvmap_handle *h)
{
return h->userflags;
}
u32 nvmap_handle_flags(struct nvmap_handle *h)
{
return h->flags;
}
bool nvmap_handle_is_heap(struct nvmap_handle *h)
{
return h->heap_pgalloc;
}
bool nvmap_handle_track_dirty(struct nvmap_handle *h)
{
if (!h->heap_pgalloc)
return false;
return h->userflags & (NVMAP_HANDLE_CACHE_SYNC |
NVMAP_HANDLE_CACHE_SYNC_AT_RESERVE);
}
struct nvmap_handle *nvmap_handle_from_fd(int fd)
{
struct nvmap_handle *handle = ERR_PTR(-EINVAL);
struct dma_buf *dmabuf;
dmabuf = nvmap_dmabuf_from_fd(fd);
if (IS_ERR(dmabuf))
return ERR_CAST(dmabuf);
handle = nvmap_dmabuf_to_handle(dmabuf);
if (IS_ERR(handle))
return ERR_CAST(handle);
return handle;
}
struct list_head *nvmap_handle_lru(struct nvmap_handle *h)
{
return &h->lru;
}
atomic_t *nvmap_handle_pin(struct nvmap_handle *h)
{
return &h->pin;
}
/*
* NOTE: this does not ensure the continued existence of the underlying
* dma_buf. If you want ensure the existence of the dma_buf you must get an
* nvmap_handle_ref as that is what tracks the dma_buf refs.
*/
struct nvmap_handle *nvmap_handle_get(struct nvmap_handle *h)
{
if (WARN_ON(!virt_addr_valid(h))) {
pr_err("%s: invalid handle\n", current->group_leader->comm);
return NULL;
}
if (unlikely(atomic_inc_return(&h->ref) <= 1)) {
pr_err("%s: %s attempt to get a freed handle\n",
__func__, current->group_leader->comm);
atomic_dec(&h->ref);
return NULL;
}
return h;
}
void nvmap_handle_put(struct nvmap_handle *h)
{
int cnt;
if (WARN_ON(!virt_addr_valid(h)))
return;
cnt = atomic_dec_return(&h->ref);
if (WARN_ON(cnt < 0)) {
pr_err("%s: %s put to negative references\n",
__func__, current->comm);
} else if (cnt == 0) {
nvmap_handle_destroy(h);
}
}
struct nvmap_handle *nvmap_handle_create(size_t size)
{
void *err = ERR_PTR(-ENOMEM);
struct nvmap_handle *h;
struct nvmap_handle_info *info;
if (!size)
return ERR_PTR(-EINVAL);
h = kzalloc(sizeof(*h), GFP_KERNEL);
if (!h)
return ERR_PTR(-ENOMEM);
/* This reference of 1 is the reference the dmabuf has on the handle
* It's removed when the dma_buf is released through
* nvmap_dmabuf_release
*/
atomic_set(&h->ref, 1);
atomic_set(&h->pin, 0);
h->orig_size = size;
h->size = PAGE_ALIGN(size);
h->flags = NVMAP_HANDLE_WRITE_COMBINE;
h->peer = NVMAP_IVM_INVALID_PEER;
mutex_init(&h->lock);
INIT_LIST_HEAD(&h->vmas);
INIT_LIST_HEAD(&h->lru);
INIT_LIST_HEAD(&h->dmabuf_priv);
info = handle_create_info(h);
if (IS_ERR(info)) {
err = info;
goto make_info_fail;
}
h->dmabuf = nvmap_dmabuf_create(info, h->size);
if (IS_ERR_OR_NULL(h->dmabuf)) {
err = ERR_PTR(-ENOMEM);
goto make_dmabuf_fail;
}
handle_add_to_dev(h, nvmap_dev);
return h;
make_dmabuf_fail:
kfree(info);
make_info_fail:
kfree(h);
return err;
}
static void handle_pgalloc_free(struct nvmap_pgalloc *pgalloc, size_t size,
int from_va)
{
int i;
unsigned int nr_page;
unsigned int page_index = 0;
nr_page = DIV_ROUND_UP(size, PAGE_SIZE);
BUG_ON(size & ~PAGE_MASK);
BUG_ON(!pgalloc->pages);
for (i = 0; i < nr_page; i++)
pgalloc->pages[i] = nvmap_to_page(pgalloc->pages[i]);
#ifdef CONFIG_NVMAP_PAGE_POOLS
if (!from_va)
page_index = nvmap_page_pool_fill_lots(&nvmap_dev->pool,
pgalloc->pages, nr_page);
#endif
for (i = page_index; i < nr_page; i++) {
if (from_va)
put_page(pgalloc->pages[i]);
else
__free_page(pgalloc->pages[i]);
}
nvmap_altfree(pgalloc->pages, nr_page * sizeof(struct page *));
}
static void handle_dealloc(struct nvmap_handle *h)
{
if (!h->alloc)
return;
nvmap_stats_inc(NS_RELEASE, h->size);
nvmap_stats_dec(NS_TOTAL, h->size);
if (!h->heap_pgalloc) {
if (h->vaddr) {
struct vm_struct *vm;
void *addr = h->vaddr;
addr -= (h->carveout->base & ~PAGE_MASK);
vm = find_vm_area(addr);
BUG_ON(!vm);
free_vm_area(vm);
}
nvmap_heap_free(h->carveout);
nvmap_handle_kmap_dec(h);
h->vaddr = NULL;
return;
} else if (nvmap_heap_type_is_dma(h->heap_type)){
nvmap_heap_dealloc_dma_pages(h->size, h->heap_type,
h->pgalloc.pages);
} else {
if (h->vaddr) {
nvmap_handle_kmap_dec(h);
vm_unmap_ram(h->vaddr, h->size >> PAGE_SHIFT);
h->vaddr = NULL;
}
handle_pgalloc_free(&h->pgalloc, h->size, h->from_va);
}
}
/* adds a newly-created handle to the device master tree */
static void handle_add_to_dev(struct nvmap_handle *h, struct nvmap_device *dev)
{
struct rb_node **p;
struct rb_node *parent = NULL;
spin_lock(&dev->handle_lock);
p = &dev->handles.rb_node;
while (*p) {
struct nvmap_handle *b;
parent = *p;
b = rb_entry(parent, struct nvmap_handle, node);
if (h > b)
p = &parent->rb_right;
else
p = &parent->rb_left;
}
rb_link_node(&h->node, parent, p);
rb_insert_color(&h->node, &dev->handles);
nvmap_lru_add(&h->lru);
spin_unlock(&dev->handle_lock);
}
/* remove a handle from the device's tree of all handles; called
* when freeing handles. */
static int handle_remove_from_dev(struct nvmap_handle *h,
struct nvmap_device *dev)
{
spin_lock(&dev->handle_lock);
/* re-test inside the spinlock if the handle really has no clients;
* only remove the handle if it is unreferenced */
if (atomic_add_return(0, &h->ref) > 0) {
spin_unlock(&dev->handle_lock);
return -EBUSY;
}
smp_rmb();
BUG_ON(atomic_read(&h->ref) < 0);
BUG_ON(atomic_read(&h->pin) != 0);
nvmap_lru_del(&h->lru);
rb_erase(&h->node, &dev->handles);
spin_unlock(&dev->handle_lock);
return 0;
}
void nvmap_handle_add_owner(struct nvmap_handle *handle,
struct nvmap_client *client)
{
if (!handle->owner)
handle->owner = client;
}
void nvmap_handle_destroy(struct nvmap_handle *h)
{
nvmap_dmabufs_free(&h->dmabuf_priv);
if (handle_remove_from_dev(h, nvmap_dev) != 0)
return;
handle_dealloc(h);
NVMAP_TAG_TRACE(trace_nvmap_destroy_handle,
NULL, get_current()->pid, 0, NVMAP_TP_ARGS_H(h));
kfree(h);
}
static void heap_alloc_and_set_handle(
struct nvmap_handle *h, unsigned int orig_heap)
{
unsigned int heap_type;
struct page **pages;
BUG_ON(orig_heap & (orig_heap - 1));
heap_type = nvmap_heap_type_conversion(orig_heap);
if (nvmap_heap_type_is_carveout(heap_type)) {
int err;
err = nvmap_handle_alloc_carveout(h, heap_type, NULL);
if (!err) {
h->heap_type = heap_type;
h->heap_pgalloc = false;
goto success;
}
pages = nvmap_heap_alloc_dma_pages(h->size, heap_type);
if (IS_ERR_OR_NULL(pages))
return;
h->pgalloc.pages = pages;
h->pgalloc.contig = 0;
atomic_set(&h->pgalloc.ndirty, 0);
h->heap_type = NVMAP_HEAP_CARVEOUT_VPR;
h->heap_pgalloc = true;
} else if (nvmap_heap_type_is_iovmm(heap_type)) {
pages = nvmap_heap_alloc_iovmm_pages(h->size,
h->userflags & NVMAP_HANDLE_PHYS_CONTIG);
if (IS_ERR_OR_NULL(pages))
return;
h->pgalloc.pages = pages;
h->pgalloc.contig =
h->userflags & NVMAP_HANDLE_PHYS_CONTIG;
atomic_set(&h->pgalloc.ndirty, 0);
h->heap_type = NVMAP_HEAP_IOVMM;
h->heap_pgalloc = true;
}
success:
/* barrier to ensure all handle alloc data
* is visible before alloc is seen by other
* processors.
*/
mb();
h->alloc = true;
return;
}
/* TODO: Change this to return an alloc_handle or something
* stop this and the next function from editing handle,
* and push the functions back into heap_alloc
*/
static void heap_alloc_handle_from_heaps(
struct nvmap_handle *handle,
const unsigned int *alloc_policy,
unsigned int heap_mask)
{
while (!handle->alloc && *alloc_policy) {
unsigned int heap_type;
heap_type = *alloc_policy++;
heap_type &= heap_mask;
if (!heap_type)
continue;
heap_mask &= ~heap_type;
while (heap_type && !handle->alloc) {
unsigned int heap;
/* iterate possible heaps MSB-to-LSB, since higher-
* priority carveouts will have higher usage masks */
heap = 1 << __fls(heap_type);
heap_alloc_and_set_handle(handle, heap);
heap_type &= ~heap;
}
}
}
int nvmap_handle_alloc(
struct nvmap_handle *h, unsigned int heap_mask,
size_t align,
u8 kind,
unsigned int flags,
int peer)
{
const unsigned int *alloc_policy;
int nr_page;
int err = -ENOMEM;
if (!h)
return -EINVAL;
if (h->alloc) {
return -EEXIST;
}
h->userflags = flags;
h->peer = NVMAP_IVM_INVALID_PEER;
nr_page = ((h->size + PAGE_SIZE - 1) >> PAGE_SHIFT);
/* Force mapping to uncached for VPR memory. */
if (heap_mask & (NVMAP_HEAP_CARVEOUT_VPR | ~nvmap_dev->cpu_access_mask))
h->flags = NVMAP_HANDLE_UNCACHEABLE;
else
h->flags = (flags & NVMAP_HANDLE_CACHE_FLAG);
h->align = max_t(size_t, align, L1_CACHE_BYTES);
alloc_policy = nvmap_heap_mask_to_policy(heap_mask, nr_page);
if (!alloc_policy) {
err = -EINVAL;
goto out;
}
heap_alloc_handle_from_heaps(h, alloc_policy, heap_mask);
out:
return err;
}
struct nvmap_handle *nvmap_handle_from_ivmid(u64 ivm_id)
{
struct nvmap_handle *handle = NULL;
struct rb_node *n;
spin_lock(&nvmap_dev->handle_lock);
n = nvmap_dev->handles.rb_node;
for (n = rb_first(&nvmap_dev->handles); n; n = rb_next(n)) {
handle = rb_entry(n, struct nvmap_handle, node);
if (handle->ivm_id == ivm_id) {
BUG_ON(!virt_addr_valid(handle));
spin_unlock(&nvmap_dev->handle_lock);
return handle;
}
}
spin_unlock(&nvmap_dev->handle_lock);
return NULL;
}
int nvmap_handle_alloc_from_va(struct nvmap_handle *h,
ulong addr,
unsigned int flags)
{
h = nvmap_handle_get(h);
if (!h)
return -EINVAL;
if (h->alloc) {
nvmap_handle_put(h);
return -EEXIST;
}
h->userflags = flags;
h->flags = (flags & NVMAP_HANDLE_CACHE_FLAG);
h->align = PAGE_SIZE;
h->pgalloc.pages = nvmap_heap_alloc_from_va(h->size, addr);
if (!h->pgalloc.pages) {
nvmap_handle_put(h);
return -ENOMEM;
}
atomic_set(&h->pgalloc.ndirty, 0);
h->heap_type = NVMAP_HEAP_IOVMM;
h->heap_pgalloc = true;
h->from_va = true;
mb();
h->alloc = true;
nvmap_handle_put(h);
return 0;
}
int nvmap_handle_alloc_carveout(struct nvmap_handle *handle,
unsigned long type,
phys_addr_t *start)
{
struct nvmap_carveout_node *co_heap;
struct nvmap_device *dev = nvmap_dev;
int i;
for (i = 0; i < dev->nr_carveouts; i++) {
struct nvmap_heap_block *block;
co_heap = nvmap_dev_to_carveout(dev, i);
if (!(nvmap_carveout_heap_bit(co_heap) & type))
continue;
if (type & NVMAP_HEAP_CARVEOUT_IVM)
handle->size = ALIGN(handle->size, NVMAP_IVM_ALIGNMENT);
block = nvmap_carveout_alloc(co_heap, start,
handle->size,
handle->align,
handle->flags,
handle->peer);
if (block) {
handle->carveout = block;
handle->ivm_id = nvmap_carveout_ivm(co_heap, block,
handle->size);
return 0;
}
}
return -1;
}
int nvmap_handle_alloc_from_ivmid(struct nvmap_handle *handle, u64 ivm_id)
{
phys_addr_t offs = nvmap_ivmid_to_offset(ivm_id);
int peer = nvmap_ivmid_to_peer(ivm_id);
int err;
handle->peer = peer;
err = nvmap_handle_alloc_carveout(handle, NVMAP_HEAP_CARVEOUT_IVM,
&offs);
if (err) {
return -1;
}
handle->heap_type = NVMAP_HEAP_CARVEOUT_IVM;
handle->heap_pgalloc = false;
handle->ivm_id = ivm_id;
mb();
handle->alloc = true;
return 0;
}
void nvmap_handle_zap(struct nvmap_handle *handle, u64 offset, u64 size)
{
if (!handle->heap_pgalloc)
return;
/* if no dirty page is present, no need to zap */
if (nvmap_handle_track_dirty(handle)
&& !atomic_read(&handle->pgalloc.ndirty))
return;
if (!size) {
offset = 0;
size = handle->size;
}
size = PAGE_ALIGN((offset & ~PAGE_MASK) + size);
mutex_lock(&handle->lock);
nvmap_vma_zap(&handle->vmas, offset, size);
mutex_unlock(&handle->lock);
}
static int handle_cache_maint_heap_page_inner(struct nvmap_handle *handle,
unsigned int op,
unsigned long start, unsigned long end)
{
if (static_key_false(&nvmap_disable_vaddr_for_cache_maint))
return 0;
if (!handle->vaddr) {
/* TODO: We need better naming than mapping and then unmapping */
if (nvmap_handle_mmap(handle))
nvmap_handle_munmap(handle, handle->vaddr);
else
return 1;
}
/* Fast inner cache maintenance using single mapping */
nvmap_cache_maint_inner(op, handle->vaddr + start, end - start);
return 0;
}
void nvmap_handle_cache_maint_heap_page(struct nvmap_handle *handle,
unsigned long op,
unsigned long start, unsigned long end,
int outer)
{
int err;
if (handle->userflags & NVMAP_HANDLE_CACHE_SYNC) {
/*
* zap user VA->PA mappings so that any access to the pages
* will result in a fault and can be marked dirty
*/
nvmap_handle_mkclean(handle, start, end-start);
nvmap_handle_zap(handle, start, end - start);
}
err = handle_cache_maint_heap_page_inner(handle, op, start, end);
if (err && outer) {
nvmap_cache_maint_heap_page_outer(handle->pgalloc.pages, op,
start, end);
}
}
/*
* This is the equivalent of __nvmap_do_cache_maint
* The clean_only_dirty flag has been removed because it is always passed as
* false
*/
int nvmap_handle_cache_maint(struct nvmap_handle *handle, unsigned long start,
unsigned long end, unsigned int op)
{
int ret = 0;
if ((start >= handle->size) || (end > handle->size)) {
return -EFAULT;
}
if (!(handle->heap_type & nvmap_dev->cpu_access_mask)) {
return -EPERM;
}
if (!handle || !handle->alloc)
return -EFAULT;
nvmap_handle_kmap_inc(handle);
if (op == NVMAP_CACHE_OP_INV)
op = NVMAP_CACHE_OP_WB_INV;
if (!end)
end = handle->size;
wmb();
if (handle->flags == NVMAP_HANDLE_UNCACHEABLE ||
handle->flags == NVMAP_HANDLE_WRITE_COMBINE || start == end)
goto out;
if (start > handle->size || end > handle->size) {
pr_warn("cache maintenance outside handle\n");
ret = -EINVAL;
goto out;
}
if (nvmap_cache_can_fast_maint(start, end, op)) {
if (handle->userflags & NVMAP_HANDLE_CACHE_SYNC) {
nvmap_handle_mkclean(handle, 0, handle->size);
nvmap_handle_zap(handle, 0, handle->size);
}
nvmap_cache_fast_maint(op);
} else if (handle->heap_pgalloc) {
nvmap_handle_cache_maint_heap_page(handle, op, start, end,
(handle->flags != NVMAP_HANDLE_INNER_CACHEABLE));
} else {
ret = nvmap_cache_maint_phys_range(op,
start + handle->carveout->base,
end + handle->carveout->base);
}
out:
/* TODO: Add more stats counting here */
nvmap_stats_inc(NS_CFLUSH_RQ, end - start);
nvmap_handle_kmap_dec(handle);
return ret;
}
static void cache_maint_large(struct nvmap_handle **handles, u64 total,
u64 thresh, int op, int nr)
{
int i;
for (i = 0; i < nr; i++) {
if (handles[i]->userflags & NVMAP_HANDLE_CACHE_SYNC) {
nvmap_handle_mkclean(handles[i], 0, handles[i]->size);
nvmap_handle_zap(handles[i], 0, handles[i]->size);
}
}
if (op == NVMAP_CACHE_OP_WB)
nvmap_cache_inner_clean_all();
else
nvmap_cache_inner_flush_all();
nvmap_stats_inc(NS_CFLUSH_RQ, total);
nvmap_stats_inc(NS_CFLUSH_DONE, thresh);
trace_nvmap_cache_flush(total,
nvmap_stats_read(NS_ALLOC),
nvmap_stats_read(NS_CFLUSH_RQ),
nvmap_stats_read(NS_CFLUSH_DONE));
}
static int handles_get_total_cache_size(struct nvmap_handle **handles,
u64 *sizes, int op, int nr)
{
int i;
int total = 0;
for (i = 0; i < nr; i++) {
bool inner, outer;
nvmap_handle_get_cacheability(handles[i], &inner, &outer);
if (!inner && !outer)
continue;
if ((op == NVMAP_CACHE_OP_WB)
&& nvmap_handle_track_dirty(handles[i])) {
/* TODO: shouldn't this be shifted by page size? */
total += atomic_read(&handles[i]->pgalloc.ndirty);
} else {
total += sizes[i];
}
}
return total;
}
/*
* Perform cache op on the list of memory regions within passed handles.
* A memory region within handle[i] is identified by offsets[i], sizes[i]
*
* This will optimze the op if it can.
* In the case that all the handles together are larger than the inner cache
* maint threshold it is possible to just do an entire inner cache flush.
*
* NOTE: this omits outer cache operations which is fine for ARM64
*/
int nvmap_handles_cache_maint(struct nvmap_handle **handles,
u64 *offsets, u64 *sizes, int op, int nr)
{
int i;
int err;
u64 total = 0;
u64 thresh = ~0;
/*
* As io-coherency is enabled by default from T194 onwards,
* Don't do cache maint from CPU side. The HW, SCF will do.
*/
if (tegra_get_chip_id() == TEGRA194)
return 0;
WARN(!IS_ENABLED(CONFIG_ARM64),
"cache list operation may not function properly");
if (nvmap_cache_maint_by_set_ways)
thresh = cache_maint_inner_threshold;
total = handles_get_total_cache_size(handles, sizes, op, nr);
if (!total)
return 0;
/* Full flush in the case the passed list is bigger than our
* threshold. */
if (total >= thresh) {
cache_maint_large(handles, total, thresh, op, nr);
} else {
for (i = 0; i < nr; i++) {
err = nvmap_handle_cache_maint(handles[i],
offsets[i],
offsets[i] + sizes[i],
op);
if (err) {
pr_err("cache maint per handle failed [%d]\n",
err);
return err;
}
}
}
return 0;
}
static int handle_read(struct nvmap_handle *h, unsigned long h_offs,
unsigned long sys_addr, void *addr,
unsigned long elem_size)
{
if (!(h->userflags & NVMAP_HANDLE_CACHE_SYNC_AT_RESERVE)) {
nvmap_handle_cache_maint(h, h_offs, h_offs + elem_size,
NVMAP_CACHE_OP_INV);
}
return copy_to_user((void *)sys_addr, addr, elem_size);
}
static int handle_write(struct nvmap_handle *h, unsigned long h_offs,
unsigned long sys_addr, void *addr,
unsigned long elem_size)
{
int ret = 0;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
if (h->heap_type == NVMAP_HEAP_CARVEOUT_VPR) {
uaccess_enable();
memcpy_toio(addr, (void *)sys_addr, elem_size);
uaccess_disable();
ret = 0;
} else {
ret = copy_from_user(addr, (void *)sys_addr, elem_size);
}
#else
ret = copy_from_user(addr, (void *)sys_addr, elem_size);
#endif
if (ret)
return ret;
if (!(h->userflags & NVMAP_HANDLE_CACHE_SYNC_AT_RESERVE))
nvmap_handle_cache_maint(h, h_offs, h_offs + elem_size,
NVMAP_CACHE_OP_WB_INV);
return ret;
}
ssize_t nvmap_handle_rw(struct nvmap_handle *h,
unsigned long h_offs, unsigned long h_stride,
unsigned long sys_addr, unsigned long sys_stride,
unsigned long elem_size, unsigned long count,
int is_read)
{
ssize_t copied = 0;
void *addr;
int ret = 0;
if (!(h->heap_type & nvmap_dev->cpu_access_mask))
return -EPERM;
if (!elem_size || !count)
return -EINVAL;
if (!h->alloc)
return -EFAULT;
/*
* TODO: Add an english description of this
* I think it is:
* If all of the strides are equal, and the offset is byte aligned,
* then do all of the copying in one go
*/
if (elem_size == h_stride && elem_size == sys_stride && (h_offs % 8 == 0)) {
elem_size *= count;
h_stride = elem_size;
sys_stride = elem_size;
count = 1;
}
if (elem_size > sys_stride || elem_size > h_stride)
return -EINVAL;
if (elem_size > h->size ||
h_offs >= h->size ||
h_stride * (count - 1) + elem_size > (h->size - h_offs) ||
sys_stride * count > (h->size - h_offs))
return -EINVAL;
if (!h->vaddr) {
if (nvmap_handle_mmap(h) == NULL)
return -ENOMEM;
nvmap_handle_munmap(h, h->vaddr);
}
addr = h->vaddr + h_offs;
while (count--) {
if (h_offs + elem_size > h->size) {
pr_warn("read/write outside of handle\n");
ret = -EFAULT;
break;
}
if (is_read) {
ret = handle_read(h, h_offs,
sys_addr, addr, elem_size);
} else {
ret = handle_write(h, h_offs,
sys_addr, addr, elem_size);
}
if (ret)
break;
copied += elem_size;
sys_addr += sys_stride;
h_offs += h_stride;
addr += h_stride;
}
if (ret)
return ret;
return copied;
}
struct nvmap_handle *nvmap_handle_from_node(struct rb_node *n)
{
return rb_entry(n, struct nvmap_handle, node);
}
struct nvmap_handle *nvmap_handle_from_lru(struct list_head *n)
{
return list_entry(n, struct nvmap_handle, lru);
}