| Index: arch/arm/mach-tegra/nv/nvmap.c
|
| diff --git a/arch/arm/mach-tegra/nv/nvmap.c b/arch/arm/mach-tegra/nv/nvmap.c
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..20ca213ba14a00fc4ef764ae2c8c581d604c0723
|
| --- /dev/null
|
| +++ b/arch/arm/mach-tegra/nv/nvmap.c
|
| @@ -0,0 +1,3350 @@
|
| +/*
|
| + * drivers/char/nvmap.c
|
| + *
|
| + * Memory manager for Tegra GPU memory handles
|
| + *
|
| + * Copyright (c) 2009-2010, NVIDIA Corporation.
|
| + *
|
| + * This program is free software; you can redistribute it and/or modify
|
| + * it under the terms of the GNU General Public License as published by
|
| + * the Free Software Foundation; either version 2 of the License, or
|
| + * (at your option) any later version.
|
| + *
|
| + * 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, write to the Free Software Foundation, Inc.,
|
| + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
| + */
|
| +
|
| +#include <linux/vmalloc.h>
|
| +#include <linux/module.h>
|
| +#include <linux/bitmap.h>
|
| +#include <linux/wait.h>
|
| +#include <linux/miscdevice.h>
|
| +#include <linux/platform_device.h>
|
| +#include <linux/mm.h>
|
| +#include <linux/mman.h>
|
| +#include <linux/uaccess.h>
|
| +#include <linux/backing-dev.h>
|
| +#include <linux/device.h>
|
| +#include <linux/highmem.h>
|
| +#include <linux/smp_lock.h>
|
| +#include <linux/pagemap.h>
|
| +#include <linux/sched.h>
|
| +#include <linux/io.h>
|
| +#include <linux/rbtree.h>
|
| +#include <linux/proc_fs.h>
|
| +#include <linux/ctype.h>
|
| +#include <asm/tlbflush.h>
|
| +#include <mach/iovmm.h>
|
| +#include "linux/nvmem_ioctl.h"
|
| +#include "nvcommon.h"
|
| +#include "nvrm_memmgr.h"
|
| +#include "nvbootargs.h"
|
| +
|
| +#include <linux/dma-mapping.h>
|
| +#include "asm/cacheflush.h"
|
| +
|
| +
|
| +#define NVMAP_BASE (VMALLOC_END + SZ_2M)
|
| +#define NVMAP_SIZE SZ_2M
|
| +
|
| +#define L_PTE_MT_INNER_WB (0x05 << 2) /* 0101 (armv6, armv7) */
|
| +#define pgprot_inner_writeback(prot) \
|
| + __pgprot((pgprot_val(prot) & ~L_PTE_MT_MASK) | L_PTE_MT_INNER_WB)
|
| +
|
| +static void smp_dma_clean_range(const void *start, const void *end)
|
| +{
|
| + dmac_map_area(start, end - start, DMA_TO_DEVICE);
|
| +}
|
| +
|
| +static void smp_dma_inv_range(const void *start, const void *end)
|
| +{
|
| + dmac_unmap_area(start, end - start, DMA_FROM_DEVICE);
|
| +}
|
| +
|
| +static void smp_dma_flush_range(const void *start, const void *end)
|
| +{
|
| + dmac_flush_range(start, end);
|
| +}
|
| +
|
| +int nvmap_add_carveout_heap(unsigned long base, size_t size,
|
| + const char *name, unsigned int bitmask);
|
| +
|
| +
|
| +/*#define IOVMM_FIRST*/ /* enable to force most allocations from iovmm */
|
| +
|
| +static void nvmap_vma_open(struct vm_area_struct *vma);
|
| +
|
| +static void nvmap_vma_close(struct vm_area_struct *vma);
|
| +
|
| +static int nvmap_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf);
|
| +
|
| +static int nvmap_open(struct inode *inode, struct file *filp);
|
| +
|
| +static int nvmap_release(struct inode *inode, struct file *file);
|
| +
|
| +static int nvmap_mmap(struct file *filp, struct vm_area_struct *vma);
|
| +
|
| +static long nvmap_ioctl(struct file *filp,
|
| + unsigned int cmd, unsigned long arg);
|
| +
|
| +static int nvmap_ioctl_getid(struct file *filp, void __user *arg);
|
| +
|
| +static int nvmap_ioctl_get_param(struct file *filp, void __user* arg);
|
| +
|
| +static int nvmap_ioctl_alloc(struct file *filp, void __user *arg);
|
| +
|
| +static int nvmap_ioctl_free(struct file *filp, unsigned long arg);
|
| +
|
| +static int nvmap_ioctl_create(struct file *filp,
|
| + unsigned int cmd, void __user *arg);
|
| +
|
| +static int nvmap_ioctl_pinop(struct file *filp,
|
| + bool is_pin, void __user *arg);
|
| +
|
| +static int nvmap_ioctl_cache_maint(struct file *filp, void __user *arg);
|
| +
|
| +static int nvmap_map_into_caller_ptr(struct file *filp, void __user *arg);
|
| +
|
| +static int nvmap_ioctl_rw_handle(struct file *filp, int is_read,
|
| + void __user* arg);
|
| +
|
| +extern void NvRmPrivMemIncrRef(NvRmMemHandle hmem);
|
| +
|
| +static struct backing_dev_info nvmap_bdi = {
|
| + .ra_pages = 0,
|
| + .capabilities = (BDI_CAP_NO_ACCT_AND_WRITEBACK |
|
| + BDI_CAP_READ_MAP | BDI_CAP_WRITE_MAP),
|
| +};
|
| +
|
| +
|
| +#define NVMAP_PTE_OFFSET(x) (((unsigned long)(x) - NVMAP_BASE) >> PAGE_SHIFT)
|
| +#define NVMAP_PTE_INDEX(x) (((unsigned long)(x) - NVMAP_BASE)>>PGDIR_SHIFT)
|
| +#define NUM_NVMAP_PTES (NVMAP_SIZE >> PGDIR_SHIFT)
|
| +#define NVMAP_END (NVMAP_BASE + NVMAP_SIZE)
|
| +#define NVMAP_PAGES (NVMAP_SIZE >> PAGE_SHIFT)
|
| +
|
| +static pte_t *nvmap_pte[NUM_NVMAP_PTES];
|
| +static unsigned long nvmap_ptebits[NVMAP_PAGES/BITS_PER_LONG];
|
| +
|
| +static DEFINE_SPINLOCK(nvmap_ptelock);
|
| +static DECLARE_WAIT_QUEUE_HEAD(nvmap_ptefull);
|
| +
|
| +/* used to lost the master tree of memory handles */
|
| +static DEFINE_SPINLOCK(nvmap_handle_lock);
|
| +
|
| +/* only one task may be performing pin / unpin operations at once, to
|
| + * prevent deadlocks caused by interleaved IOVMM re-allocations */
|
| +static DEFINE_MUTEX(nvmap_pin_lock);
|
| +
|
| +/* queue of tasks which are blocking on pin, for IOVMM room */
|
| +static DECLARE_WAIT_QUEUE_HEAD(nvmap_pin_wait);
|
| +static struct rb_root nvmap_handles = RB_ROOT;
|
| +
|
| +static struct tegra_iovmm_client *nvmap_vm_client = NULL;
|
| +
|
| +/* first-fit linear allocator carveout heap manager */
|
| +struct nvmap_mem_block {
|
| + unsigned long base;
|
| + size_t size;
|
| + short next; /* next absolute (address-order) block */
|
| + short prev; /* previous absolute (address-order) block */
|
| + short next_free;
|
| + short prev_free;
|
| +};
|
| +
|
| +struct nvmap_carveout {
|
| + unsigned short num_blocks;
|
| + short spare_index;
|
| + short free_index;
|
| + short block_index;
|
| + spinlock_t lock;
|
| + const char *name;
|
| + struct nvmap_mem_block *blocks;
|
| +};
|
| +
|
| +enum {
|
| + CARVEOUT_STAT_TOTAL_SIZE,
|
| + CARVEOUT_STAT_FREE_SIZE,
|
| + CARVEOUT_STAT_NUM_BLOCKS,
|
| + CARVEOUT_STAT_FREE_BLOCKS,
|
| + CARVEOUT_STAT_LARGEST_BLOCK,
|
| + CARVEOUT_STAT_LARGEST_FREE,
|
| + CARVEOUT_STAT_BASE,
|
| +};
|
| +
|
| +static inline pgprot_t _nvmap_flag_to_pgprot(unsigned long flag, pgprot_t base)
|
| +{
|
| + switch (flag) {
|
| + case NVMEM_HANDLE_UNCACHEABLE:
|
| + base = pgprot_noncached(base);
|
| + break;
|
| + case NVMEM_HANDLE_WRITE_COMBINE:
|
| + base = pgprot_writecombine(base);
|
| + break;
|
| + case NVMEM_HANDLE_INNER_CACHEABLE:
|
| + base = pgprot_inner_writeback(base);
|
| + break;
|
| + }
|
| + return base;
|
| +}
|
| +
|
| +static unsigned long _nvmap_carveout_blockstat(struct nvmap_carveout *co,
|
| + int stat)
|
| +{
|
| + unsigned long val = 0;
|
| + short idx;
|
| + spin_lock(&co->lock);
|
| +
|
| + if (stat==CARVEOUT_STAT_BASE) {
|
| + if (co->block_index==-1)
|
| + val = ~0;
|
| + else
|
| + val = co->blocks[co->block_index].base;
|
| + spin_unlock(&co->lock);
|
| + return val;
|
| + }
|
| +
|
| + if (stat==CARVEOUT_STAT_TOTAL_SIZE ||
|
| + stat==CARVEOUT_STAT_NUM_BLOCKS ||
|
| + stat==CARVEOUT_STAT_LARGEST_BLOCK)
|
| + idx = co->block_index;
|
| + else
|
| + idx = co->free_index;
|
| +
|
| + while (idx!=-1) {
|
| + switch (stat) {
|
| + case CARVEOUT_STAT_TOTAL_SIZE:
|
| + val += co->blocks[idx].size;
|
| + idx = co->blocks[idx].next;
|
| + break;
|
| + case CARVEOUT_STAT_NUM_BLOCKS:
|
| + val++;
|
| + idx = co->blocks[idx].next;
|
| + break;
|
| + case CARVEOUT_STAT_LARGEST_BLOCK:
|
| + val = max_t(unsigned long, val, co->blocks[idx].size);
|
| + idx = co->blocks[idx].next;
|
| + break;
|
| + case CARVEOUT_STAT_FREE_SIZE:
|
| + val += co->blocks[idx].size;
|
| + idx = co->blocks[idx].next_free;
|
| + break;
|
| + case CARVEOUT_STAT_FREE_BLOCKS:
|
| + val ++;
|
| + idx = co->blocks[idx].next_free;
|
| + break;
|
| + case CARVEOUT_STAT_LARGEST_FREE:
|
| + val = max_t(unsigned long, val, co->blocks[idx].size);
|
| + idx = co->blocks[idx].next_free;
|
| + break;
|
| + }
|
| + }
|
| +
|
| + spin_unlock(&co->lock);
|
| + return val;
|
| +}
|
| +
|
| +#define co_is_free(_co, _idx) \
|
| + ((_co)->free_index==(_idx) || ((_co)->blocks[(_idx)].prev_free!=-1))
|
| +
|
| +static int _nvmap_init_carveout(struct nvmap_carveout *co,
|
| + const char *name, unsigned long base_address, size_t len)
|
| +{
|
| + const unsigned int min_blocks = 16;
|
| + struct nvmap_mem_block *blocks = NULL;
|
| + int i;
|
| +
|
| + blocks = kzalloc(sizeof(*blocks)*min_blocks, GFP_KERNEL);
|
| +
|
| + if (!blocks) goto fail;
|
| + co->name = kstrdup(name, GFP_KERNEL);
|
| + if (!co->name) goto fail;
|
| +
|
| + for (i=1; i<min_blocks; i++) {
|
| + blocks[i].next = i+1;
|
| + blocks[i].prev = i-1;
|
| + blocks[i].next_free = -1;
|
| + blocks[i].prev_free = -1;
|
| + }
|
| + blocks[i-1].next = -1;
|
| + blocks[1].prev = -1;
|
| +
|
| + blocks[0].next = blocks[0].prev = -1;
|
| + blocks[0].next_free = blocks[0].prev_free = -1;
|
| + blocks[0].base = base_address;
|
| + blocks[0].size = len;
|
| + co->blocks = blocks;
|
| + co->num_blocks = min_blocks;
|
| + spin_lock_init(&co->lock);
|
| + co->block_index = 0;
|
| + co->spare_index = 1;
|
| + co->free_index = 0;
|
| + return 0;
|
| +
|
| +fail:
|
| + if (blocks) kfree(blocks);
|
| + return -ENOMEM;
|
| +}
|
| +
|
| +static int nvmap_grow_blocks(struct nvmap_carveout *co)
|
| +{
|
| + struct nvmap_mem_block *blocks;
|
| + unsigned int i;
|
| +
|
| + if (co->num_blocks >= 1<<(8*sizeof(co->free_index)-1)) return -ENOMEM;
|
| + blocks = kzalloc(sizeof(*blocks)*(co->num_blocks*2), GFP_ATOMIC);
|
| + if (!blocks) {
|
| + printk("NV: %s alloc failed\n", __func__);
|
| + return -ENOMEM;
|
| + }
|
| +
|
| + memcpy(blocks, co->blocks, sizeof(*blocks)*(co->num_blocks));
|
| + kfree(co->blocks);
|
| + co->blocks = blocks;
|
| + for (i=co->num_blocks; i<co->num_blocks*2; i++) {
|
| + blocks[i].next = i+1;
|
| + blocks[i].prev = i-1;
|
| + blocks[i].next_free = -1;
|
| + blocks[i].prev_free = -1;
|
| + }
|
| + blocks[co->num_blocks].prev = -1;
|
| + blocks[i-1].next = -1;
|
| + co->spare_index = co->num_blocks;
|
| + co->num_blocks *= 2;
|
| + return 0;
|
| +}
|
| +
|
| +static int nvmap_get_spare(struct nvmap_carveout *co) {
|
| + int idx;
|
| +
|
| + if (co->spare_index == -1)
|
| + if (nvmap_grow_blocks(co))
|
| + return -1;
|
| +
|
| + BUG_ON(co->spare_index == -1);
|
| + idx = co->spare_index;
|
| + co->spare_index = co->blocks[idx].next;
|
| + co->blocks[idx].next = -1;
|
| + co->blocks[idx].prev = -1;
|
| + co->blocks[idx].next_free = -1;
|
| + co->blocks[idx].prev_free = -1;
|
| + return idx;
|
| +}
|
| +
|
| +#define BLOCK(_co, _idx) ((_idx)==-1 ? NULL : &(_co)->blocks[(_idx)])
|
| +
|
| +static void nvmap_zap_free(struct nvmap_carveout *co, int idx)
|
| +{
|
| + struct nvmap_mem_block *block;
|
| +
|
| + block = BLOCK(co, idx);
|
| + if (block->prev_free != -1)
|
| + BLOCK(co, block->prev_free)->next_free = block->next_free;
|
| + else
|
| + co->free_index = block->next_free;
|
| +
|
| + if (block->next_free != -1)
|
| + BLOCK(co, block->next_free)->prev_free = block->prev_free;
|
| +
|
| + block->prev_free = -1;
|
| + block->next_free = -1;
|
| +}
|
| +
|
| +static void nvmap_split_block(struct nvmap_carveout *co,
|
| + int idx, size_t start, size_t size)
|
| +{
|
| + if (BLOCK(co, idx)->base < start) {
|
| + int spare_idx = nvmap_get_spare(co);
|
| + struct nvmap_mem_block *spare = BLOCK(co, spare_idx);
|
| + struct nvmap_mem_block *block = BLOCK(co, idx);
|
| + if (spare) {
|
| + spare->size = start - block->base;
|
| + spare->base = block->base;
|
| + block->size -= (start - block->base);
|
| + block->base = start;
|
| + spare->next = idx;
|
| + spare->prev = block->prev;
|
| + block->prev = spare_idx;
|
| + if (spare->prev != -1)
|
| + co->blocks[spare->prev].next = spare_idx;
|
| + else
|
| + co->block_index = spare_idx;
|
| + spare->prev_free = -1;
|
| + spare->next_free = co->free_index;
|
| + if (co->free_index != -1)
|
| + co->blocks[co->free_index].prev_free = spare_idx;
|
| + co->free_index = spare_idx;
|
| + } else {
|
| + if (block->prev != -1) {
|
| + spare = BLOCK(co, block->prev);
|
| + spare->size += start - block->base;
|
| + block->base = start;
|
| + }
|
| + }
|
| + }
|
| +
|
| + if (BLOCK(co, idx)->size > size) {
|
| + int spare_idx = nvmap_get_spare(co);
|
| + struct nvmap_mem_block *spare = BLOCK(co, spare_idx);
|
| + struct nvmap_mem_block *block = BLOCK(co, idx);
|
| + if (spare) {
|
| + spare->base = block->base + size;
|
| + spare->size = block->size - size;
|
| + block->size = size;
|
| + spare->prev = idx;
|
| + spare->next = block->next;
|
| + block->next = spare_idx;
|
| + if (spare->next != -1)
|
| + co->blocks[spare->next].prev = spare_idx;
|
| + spare->prev_free = -1;
|
| + spare->next_free = co->free_index;
|
| + if (co->free_index != -1)
|
| + co->blocks[co->free_index].prev_free = spare_idx;
|
| + co->free_index = spare_idx;
|
| + }
|
| + }
|
| +
|
| + nvmap_zap_free(co, idx);
|
| +}
|
| +
|
| +#define next_spare next
|
| +#define prev_spare prev
|
| +
|
| +#define nvmap_insert_block(_list, _co, _idx) \
|
| + do { \
|
| + struct nvmap_mem_block *b = BLOCK((_co), (_idx)); \
|
| + struct nvmap_mem_block *s = BLOCK((_co), (_co)->_list##_index);\
|
| + if (s) s->prev_##_list = (_idx); \
|
| + b->prev_##_list = -1; \
|
| + b->next_##_list = (_co)->_list##_index; \
|
| + (_co)->_list##_index = (_idx); \
|
| + } while (0);
|
| +
|
| +static void nvmap_carveout_free(struct nvmap_carveout *co, int idx)
|
| +{
|
| + struct nvmap_mem_block *b;
|
| +
|
| + spin_lock(&co->lock);
|
| +
|
| + b = BLOCK(co, idx);
|
| +
|
| + if (b->next!=-1 && co_is_free(co, b->next)) {
|
| + int zap = b->next;
|
| + struct nvmap_mem_block *n = BLOCK(co, zap);
|
| + b->size += n->size;
|
| +
|
| + b->next = n->next;
|
| + if (n->next != -1) co->blocks[n->next].prev = idx;
|
| +
|
| + nvmap_zap_free(co, zap);
|
| + nvmap_insert_block(spare, co, zap);
|
| + }
|
| +
|
| + if (b->prev!=-1 && co_is_free(co, b->prev)) {
|
| + int zap = b->prev;
|
| + struct nvmap_mem_block *p = BLOCK(co, zap);
|
| +
|
| + b->base = p->base;
|
| + b->size += p->size;
|
| +
|
| + b->prev = p->prev;
|
| +
|
| + if (p->prev != -1) co->blocks[p->prev].next = idx;
|
| + else co->block_index = idx;
|
| +
|
| + nvmap_zap_free(co, zap);
|
| + nvmap_insert_block(spare, co, zap);
|
| + }
|
| +
|
| + nvmap_insert_block(free, co, idx);
|
| + spin_unlock(&co->lock);
|
| +}
|
| +
|
| +static int nvmap_carveout_alloc(struct nvmap_carveout *co,
|
| + size_t align, size_t size)
|
| +{
|
| + short idx;
|
| +
|
| + spin_lock(&co->lock);
|
| +
|
| + idx = co->free_index;
|
| +
|
| + while (idx != -1) {
|
| + struct nvmap_mem_block *b = BLOCK(co, idx);
|
| + /* try to be a bit more clever about generating block-
|
| + * droppings by comparing the results of a left-justified vs
|
| + * right-justified block split, and choosing the
|
| + * justification style which yields the largest remaining
|
| + * block */
|
| + size_t end = b->base + b->size;
|
| + size_t ljust = (b->base + align - 1) & ~(align-1);
|
| + size_t rjust = (end - size) & ~(align-1);
|
| + size_t l_max, r_max;
|
| +
|
| + if (rjust < b->base) rjust = ljust;
|
| + l_max = max_t(size_t, ljust - b->base, end - (ljust + size));
|
| + r_max = max_t(size_t, rjust - b->base, end - (rjust + size));
|
| +
|
| + if (b->base + b->size >= ljust + size) {
|
| + if (l_max >= r_max)
|
| + nvmap_split_block(co, idx, ljust, size);
|
| + else
|
| + nvmap_split_block(co, idx, rjust, size);
|
| + break;
|
| + }
|
| + idx = b->next_free;
|
| + }
|
| +
|
| + spin_unlock(&co->lock);
|
| + return idx;
|
| +}
|
| +
|
| +#undef next_spare
|
| +#undef prev_spare
|
| +
|
| +#define NVDA_POISON (('n'<<24) | ('v'<<16) | ('d'<<8) | ('a'))
|
| +
|
| +struct nvmap_handle {
|
| + struct rb_node node;
|
| + atomic_t ref;
|
| + atomic_t pin;
|
| + unsigned long flags;
|
| + size_t size;
|
| + size_t orig_size;
|
| + struct task_struct *owner;
|
| + unsigned int poison;
|
| + union {
|
| + struct {
|
| + struct page **pages;
|
| + struct tegra_iovmm_area *area;
|
| + struct list_head mru_list;
|
| + bool contig;
|
| + bool dirty; /* IOVMM area allocated since last pin */
|
| + } pgalloc;
|
| + struct {
|
| + struct nvmap_carveout *co_heap;
|
| + int block_idx;
|
| + unsigned long base;
|
| + unsigned int key; /* preserved by bootloader */
|
| + } carveout;
|
| + };
|
| + bool global;
|
| + bool secure; /* only allocated in IOVM space, zapped on unpin */
|
| + bool heap_pgalloc;
|
| + bool alloc;
|
| + void *kern_map; /* used for RM memmgr backwards compat */
|
| +};
|
| +
|
| +/* handle_ref objects are file-descriptor-local references to nvmap_handle
|
| + * objects. they track the number of references and pins performed by
|
| + * the specific caller (since nvmap_handle objects may be global), so that
|
| + * a client which terminates without properly unwinding all handles (or
|
| + * all nested pins) can be unwound by nvmap. */
|
| +struct nvmap_handle_ref {
|
| + struct nvmap_handle *h;
|
| + struct rb_node node;
|
| + atomic_t refs;
|
| + atomic_t pin;
|
| +};
|
| +
|
| +struct nvmap_file_priv {
|
| + struct rb_root handle_refs;
|
| + atomic_t iovm_commit;
|
| + size_t iovm_limit;
|
| + spinlock_t ref_lock;
|
| + bool su;
|
| +};
|
| +
|
| +struct nvmap_carveout_node {
|
| + struct device dev;
|
| + struct list_head heap_list;
|
| + unsigned int heap_bit;
|
| + struct nvmap_carveout carveout;
|
| +};
|
| +
|
| +/* the master structure for all nvmap-managed carveouts and all handle_ref
|
| + * objects allocated inside the kernel. heaps are sorted by their heap_bit
|
| + * (highest heap_bit first) so that carveout allocation will be first
|
| + * attempted by the heap with the highest heap_bit set in the allocation's
|
| + * heap mask */
|
| +static struct {
|
| + struct nvmap_file_priv init_data;
|
| + struct rw_semaphore list_sem;
|
| + struct list_head heaps;
|
| +} nvmap_context;
|
| +
|
| +static struct vm_operations_struct nvmap_vma_ops = {
|
| + .open = nvmap_vma_open,
|
| + .close = nvmap_vma_close,
|
| + .fault = nvmap_vma_fault,
|
| +};
|
| +
|
| +const struct file_operations nvmap_fops = {
|
| + .owner = THIS_MODULE,
|
| + .open = nvmap_open,
|
| + .release = nvmap_release,
|
| + .unlocked_ioctl = nvmap_ioctl,
|
| + .mmap = nvmap_mmap
|
| +};
|
| +
|
| +const struct file_operations knvmap_fops = {
|
| + .owner = THIS_MODULE,
|
| + .open = nvmap_open,
|
| + .release = nvmap_release,
|
| + .unlocked_ioctl = nvmap_ioctl,
|
| + .mmap = nvmap_mmap
|
| +};
|
| +
|
| +struct nvmap_vma_priv {
|
| + struct nvmap_handle *h;
|
| + size_t offs;
|
| + atomic_t ref;
|
| +};
|
| +
|
| +static struct proc_dir_entry *nvmap_procfs_root;
|
| +static struct proc_dir_entry *nvmap_procfs_proc;
|
| +
|
| +static void _nvmap_handle_free(struct nvmap_handle *h);
|
| +
|
| +#define NVMAP_CARVEOUT_ATTR_RO(_name) \
|
| + struct device_attribute nvmap_heap_attr_##_name = \
|
| + __ATTR(_name, S_IRUGO, _nvmap_sysfs_show_heap_##_name, NULL)
|
| +
|
| +#define NVMAP_CARVEOUT_ATTR_WO(_name, _mode) \
|
| + struct device_attribute nvmap_heap_attr_##_name = \
|
| + __ATTR(_name, _mode, NULL, _nvmap_sysfs_set_heap_##_name)
|
| +
|
| +
|
| +static ssize_t _nvmap_sysfs_show_heap_usage(struct device *d,
|
| + struct device_attribute *attr, char *buf)
|
| +{
|
| + struct nvmap_carveout_node *c = container_of(d,
|
| + struct nvmap_carveout_node, dev);
|
| + return sprintf(buf, "%08x\n", c->heap_bit);
|
| +}
|
| +
|
| +static ssize_t _nvmap_sysfs_show_heap_name(struct device *d,
|
| + struct device_attribute *attr, char *buf)
|
| +{
|
| + struct nvmap_carveout_node *c = container_of(d,
|
| + struct nvmap_carveout_node, dev);
|
| + return sprintf(buf, "%s\n", c->carveout.name);
|
| +}
|
| +
|
| +static ssize_t _nvmap_sysfs_show_heap_base(struct device *d,
|
| + struct device_attribute *attr, char *buf)
|
| +{
|
| + struct nvmap_carveout_node *c = container_of(d,
|
| + struct nvmap_carveout_node, dev);
|
| + return sprintf(buf, "%08lx\n",
|
| + _nvmap_carveout_blockstat(&c->carveout, CARVEOUT_STAT_BASE));
|
| +}
|
| +
|
| +static ssize_t _nvmap_sysfs_show_heap_free_size(struct device *d,
|
| + struct device_attribute *attr, char *buf)
|
| +{
|
| + struct nvmap_carveout_node *c = container_of(d,
|
| + struct nvmap_carveout_node, dev);
|
| + return sprintf(buf, "%lu\n",
|
| + _nvmap_carveout_blockstat(&c->carveout,
|
| + CARVEOUT_STAT_FREE_SIZE));
|
| +}
|
| +
|
| +static ssize_t _nvmap_sysfs_show_heap_free_count(struct device *d,
|
| + struct device_attribute *attr, char *buf)
|
| +{
|
| + struct nvmap_carveout_node *c = container_of(d,
|
| + struct nvmap_carveout_node, dev);
|
| + return sprintf(buf, "%lu\n",
|
| + _nvmap_carveout_blockstat(&c->carveout,
|
| + CARVEOUT_STAT_FREE_BLOCKS));
|
| +}
|
| +
|
| +static ssize_t _nvmap_sysfs_show_heap_free_max(struct device *d,
|
| + struct device_attribute *attr, char *buf)
|
| +{
|
| + struct nvmap_carveout_node *c = container_of(d,
|
| + struct nvmap_carveout_node, dev);
|
| + return sprintf(buf, "%lu\n",
|
| + _nvmap_carveout_blockstat(&c->carveout,
|
| + CARVEOUT_STAT_LARGEST_FREE));
|
| +}
|
| +
|
| +static ssize_t _nvmap_sysfs_show_heap_total_count(struct device *d,
|
| + struct device_attribute *attr, char *buf)
|
| +{
|
| + struct nvmap_carveout_node *c = container_of(d,
|
| + struct nvmap_carveout_node, dev);
|
| + return sprintf(buf, "%lu\n",
|
| + _nvmap_carveout_blockstat(&c->carveout,
|
| + CARVEOUT_STAT_NUM_BLOCKS));
|
| +}
|
| +
|
| +static ssize_t _nvmap_sysfs_show_heap_total_max(struct device *d,
|
| + struct device_attribute *attr, char *buf)
|
| +{
|
| + struct nvmap_carveout_node *c = container_of(d,
|
| + struct nvmap_carveout_node, dev);
|
| + return sprintf(buf, "%lu\n",
|
| + _nvmap_carveout_blockstat(&c->carveout,
|
| + CARVEOUT_STAT_LARGEST_BLOCK));
|
| +}
|
| +
|
| +static ssize_t _nvmap_sysfs_show_heap_total_size(struct device *d,
|
| + struct device_attribute *attr, char *buf)
|
| +{
|
| + struct nvmap_carveout_node *c = container_of(d,
|
| + struct nvmap_carveout_node, dev);
|
| + return sprintf(buf, "%lu\n",
|
| + _nvmap_carveout_blockstat(&c->carveout,
|
| + CARVEOUT_STAT_TOTAL_SIZE));
|
| +}
|
| +
|
| +static int nvmap_split_carveout_heap(struct nvmap_carveout *co, size_t size,
|
| + const char *name, unsigned int new_bitmask);
|
| +
|
| +static ssize_t _nvmap_sysfs_set_heap_split(struct device *d,
|
| + struct device_attribute *attr, const char * buf, size_t count)
|
| +{
|
| + struct nvmap_carveout_node *c = container_of(d,
|
| + struct nvmap_carveout_node, dev);
|
| + char *tmp, *local = kzalloc(count+1, GFP_KERNEL);
|
| + char *sizestr = NULL, *bitmaskstr = NULL, *name = NULL;
|
| + char **format[] = { &sizestr, &bitmaskstr, &name };
|
| + char ***f_iter = format;
|
| + unsigned int i;
|
| + unsigned long size, bitmask;
|
| + int err;
|
| +
|
| + if (!local) {
|
| + pr_err("%s: unable to read string\n", __func__);
|
| + return -ENOMEM;
|
| + }
|
| +
|
| + memcpy(local, buf, count);
|
| + tmp = local;
|
| + for (i=0, **f_iter = local; i<count &&
|
| + (f_iter - format)<ARRAY_SIZE(format)-1; i++) {
|
| + if (local[i]==',') {
|
| + local[i] = '\0';
|
| + f_iter++;
|
| + **f_iter = &local[i+1];
|
| + }
|
| + }
|
| +
|
| + if (!sizestr || !bitmaskstr || !name) {
|
| + pr_err("%s: format error\n", __func__);
|
| + kfree(tmp);
|
| + return -EINVAL;
|
| + }
|
| +
|
| + for (local=name; !isspace(*local); local++);
|
| +
|
| + if (local==name) {
|
| + pr_err("%s: invalid name %s\n", __func__, name);
|
| + kfree(tmp);
|
| + return -EINVAL;
|
| + }
|
| +
|
| + *local=0;
|
| +
|
| + size = memparse(sizestr, &sizestr);
|
| + if (!size) {
|
| + kfree(tmp);
|
| + return -EINVAL;
|
| + }
|
| +
|
| + if (strict_strtoul(bitmaskstr, 0, &bitmask)==-EINVAL) {
|
| + kfree(tmp);
|
| + return -EINVAL;
|
| + }
|
| +
|
| + err = nvmap_split_carveout_heap(&c->carveout, size, name, bitmask);
|
| +
|
| + if (err) pr_err("%s: failed to create split heap %s\n", __func__, name);
|
| + kfree(tmp);
|
| + return err ? err : count;
|
| +}
|
| +
|
| +static NVMAP_CARVEOUT_ATTR_RO(usage);
|
| +static NVMAP_CARVEOUT_ATTR_RO(name);
|
| +static NVMAP_CARVEOUT_ATTR_RO(base);
|
| +static NVMAP_CARVEOUT_ATTR_RO(free_size);
|
| +static NVMAP_CARVEOUT_ATTR_RO(free_count);
|
| +static NVMAP_CARVEOUT_ATTR_RO(free_max);
|
| +static NVMAP_CARVEOUT_ATTR_RO(total_size);
|
| +static NVMAP_CARVEOUT_ATTR_RO(total_count);
|
| +static NVMAP_CARVEOUT_ATTR_RO(total_max);
|
| +static NVMAP_CARVEOUT_ATTR_WO(split, (S_IWUSR | S_IWGRP));
|
| +
|
| +static struct attribute *nvmap_heap_default_attrs[] = {
|
| + &nvmap_heap_attr_usage.attr,
|
| + &nvmap_heap_attr_name.attr,
|
| + &nvmap_heap_attr_split.attr,
|
| + &nvmap_heap_attr_base.attr,
|
| + &nvmap_heap_attr_total_size.attr,
|
| + &nvmap_heap_attr_free_size.attr,
|
| + &nvmap_heap_attr_total_count.attr,
|
| + &nvmap_heap_attr_free_count.attr,
|
| + &nvmap_heap_attr_total_max.attr,
|
| + &nvmap_heap_attr_free_max.attr,
|
| + NULL
|
| +};
|
| +
|
| +static struct attribute_group nvmap_heap_defattr_group = {
|
| + .attrs = nvmap_heap_default_attrs
|
| +};
|
| +
|
| +static struct device *__nvmap_heap_parent_dev(void);
|
| +#define _nvmap_heap_parent_dev __nvmap_heap_parent_dev()
|
| +
|
| +/* unpinned I/O VMM areas may be reclaimed by nvmap to make room for
|
| + * new surfaces. unpinned surfaces are stored in segregated linked-lists
|
| + * sorted in most-recently-unpinned order (i.e., head insertion, head
|
| + * removal */
|
| +#ifdef CONFIG_DEVNVMAP_RECLAIM_UNPINNED_VM
|
| +static DEFINE_SPINLOCK(nvmap_mru_vma_lock);
|
| +static const size_t nvmap_mru_cutoff[] = {
|
| + 262144, 393216, 786432, 1048576, 1572864
|
| +};
|
| +
|
| +static struct list_head nvmap_mru_vma_lists[ARRAY_SIZE(nvmap_mru_cutoff)];
|
| +
|
| +static inline struct list_head *_nvmap_list(size_t size)
|
| +{
|
| + unsigned int i;
|
| +
|
| + for (i=0; i<ARRAY_SIZE(nvmap_mru_cutoff); i++)
|
| + if (size <= nvmap_mru_cutoff[i]) return &nvmap_mru_vma_lists[i];
|
| +
|
| + return &nvmap_mru_vma_lists[ARRAY_SIZE(nvmap_mru_cutoff)-1];
|
| +}
|
| +#endif
|
| +
|
| +static inline struct nvmap_handle *_nvmap_handle_get(struct nvmap_handle *h)
|
| +{
|
| + if (unlikely(h->poison!=NVDA_POISON)) {
|
| + pr_err("%s: %s getting poisoned handle\n", __func__,
|
| + current->group_leader->comm);
|
| + return NULL;
|
| + } else if (unlikely(atomic_inc_return(&h->ref)<=1)) {
|
| + pr_err("%s: %s getting a freed handle\n",
|
| + __func__, current->group_leader->comm);
|
| + return NULL;
|
| + }
|
| + return h;
|
| +}
|
| +
|
| +static inline void _nvmap_handle_put(struct nvmap_handle *h)
|
| +{
|
| + int cnt = atomic_dec_return(&h->ref);
|
| +
|
| + if (unlikely(cnt<0)) {
|
| + pr_err("%s: %s put to negative references\n",
|
| + __func__, current->comm);
|
| + dump_stack();
|
| + } else if (!cnt) _nvmap_handle_free(h);
|
| +}
|
| +
|
| +static struct nvmap_handle *_nvmap_claim_preserved(
|
| + struct task_struct *new_owner, unsigned long key)
|
| +{
|
| + struct rb_node *n;
|
| + struct nvmap_handle *b = NULL;
|
| +
|
| + if (!key) return NULL;
|
| +
|
| + spin_lock(&nvmap_handle_lock);
|
| + n = rb_first(&nvmap_handles);
|
| +
|
| + while (n) {
|
| + b = rb_entry(n, struct nvmap_handle, node);
|
| + if (b->alloc && !b->heap_pgalloc && b->carveout.key == key) {
|
| + b->carveout.key = 0;
|
| + b->owner = new_owner;
|
| + break;
|
| + }
|
| + b = NULL;
|
| + n = rb_next(n);
|
| + }
|
| +
|
| + spin_unlock(&nvmap_handle_lock);
|
| + return b;
|
| +}
|
| +
|
| +static struct nvmap_handle *_nvmap_validate_get(unsigned long handle, bool su)
|
| +{
|
| + struct nvmap_handle *b = NULL;
|
| +
|
| +#ifdef CONFIG_DEVNVMAP_PARANOID
|
| + struct rb_node *n;
|
| +
|
| + spin_lock(&nvmap_handle_lock);
|
| +
|
| + n = nvmap_handles.rb_node;
|
| +
|
| + while (n) {
|
| + b = rb_entry(n, struct nvmap_handle, node);
|
| + if ((unsigned long)b == handle) {
|
| + if (su || b->global || b->owner==current->group_leader)
|
| + b = _nvmap_handle_get(b);
|
| + else
|
| + b = NULL;
|
| + spin_unlock(&nvmap_handle_lock);
|
| + return b;
|
| + }
|
| + if (handle > (unsigned long)b) n = n->rb_right;
|
| + else n = n->rb_left;
|
| + }
|
| + spin_unlock(&nvmap_handle_lock);
|
| + return NULL;
|
| +#else
|
| + if (!handle) return NULL;
|
| + b = _nvmap_handle_get((struct nvmap_handle *)handle);
|
| + return b;
|
| +#endif
|
| +}
|
| +
|
| +static inline void _nvmap_insert_mru_vma(struct nvmap_handle *h)
|
| +{
|
| +#ifdef CONFIG_DEVNVMAP_RECLAIM_UNPINNED_VM
|
| + spin_lock(&nvmap_mru_vma_lock);
|
| + list_add(&h->pgalloc.mru_list, _nvmap_list(h->pgalloc.area->iovm_length));
|
| + spin_unlock(&nvmap_mru_vma_lock);
|
| +#endif
|
| +}
|
| +
|
| +static void _nvmap_remove_mru_vma(struct nvmap_handle *h)
|
| +{
|
| +#ifdef CONFIG_DEVNVMAP_RECLAIM_UNPINNED_VM
|
| + spin_lock(&nvmap_mru_vma_lock);
|
| + if (!list_empty(&h->pgalloc.mru_list))
|
| + list_del(&h->pgalloc.mru_list);
|
| + spin_unlock(&nvmap_mru_vma_lock);
|
| + INIT_LIST_HEAD(&h->pgalloc.mru_list);
|
| +#endif
|
| +}
|
| +
|
| +static struct tegra_iovmm_area *_nvmap_get_vm(struct nvmap_handle *h)
|
| +{
|
| +#ifndef CONFIG_DEVNVMAP_RECLAIM_UNPINNED_VM
|
| + BUG_ON(!h->pgalloc.area);
|
| + BUG_ON(h->size > h->pgalloc.area->iovm_length);
|
| + BUG_ON((h->size | h->pgalloc.area->iovm_length) & ~PAGE_MASK);
|
| + return h->pgalloc.area;
|
| +#else
|
| + struct list_head *mru;
|
| + struct nvmap_handle *evict = NULL;
|
| + struct tegra_iovmm_area *vm = NULL;
|
| + unsigned int i, idx;
|
| +
|
| + spin_lock(&nvmap_mru_vma_lock);
|
| +
|
| + if (h->pgalloc.area) {
|
| + BUG_ON(list_empty(&h->pgalloc.mru_list));
|
| + list_del(&h->pgalloc.mru_list);
|
| + INIT_LIST_HEAD(&h->pgalloc.mru_list);
|
| + spin_unlock(&nvmap_mru_vma_lock);
|
| + return h->pgalloc.area;
|
| + }
|
| +
|
| + vm = tegra_iovmm_create_vm(nvmap_vm_client, NULL, h->size,
|
| + _nvmap_flag_to_pgprot(h->flags, pgprot_kernel));
|
| +
|
| + if (vm) {
|
| + INIT_LIST_HEAD(&h->pgalloc.mru_list);
|
| + spin_unlock(&nvmap_mru_vma_lock);
|
| + return vm;
|
| + }
|
| + /* attempt to re-use the most recently unpinned IOVMM area in the
|
| + * same size bin as the current handle. If that fails, iteratively
|
| + * evict handles (starting from the current bin) until an allocation
|
| + * succeeds or no more areas can be evicted */
|
| +
|
| + mru = _nvmap_list(h->size);
|
| + if (!list_empty(mru))
|
| + evict = list_first_entry(mru, struct nvmap_handle,
|
| + pgalloc.mru_list);
|
| + if (evict && evict->pgalloc.area->iovm_length >= h->size) {
|
| + list_del(&evict->pgalloc.mru_list);
|
| + vm = evict->pgalloc.area;
|
| + evict->pgalloc.area = NULL;
|
| + INIT_LIST_HEAD(&evict->pgalloc.mru_list);
|
| + spin_unlock(&nvmap_mru_vma_lock);
|
| + return vm;
|
| + }
|
| +
|
| + idx = mru - nvmap_mru_vma_lists;
|
| +
|
| + for (i=0; i<ARRAY_SIZE(nvmap_mru_vma_lists) && !vm; i++, idx++) {
|
| + if (idx >= ARRAY_SIZE(nvmap_mru_vma_lists))
|
| + idx -= ARRAY_SIZE(nvmap_mru_vma_lists);
|
| + mru = &nvmap_mru_vma_lists[idx];
|
| + while (!list_empty(mru) && !vm) {
|
| + evict = list_first_entry(mru, struct nvmap_handle,
|
| + pgalloc.mru_list);
|
| +
|
| + BUG_ON(atomic_add_return(0, &evict->pin)!=0);
|
| + BUG_ON(!evict->pgalloc.area);
|
| + list_del(&evict->pgalloc.mru_list);
|
| + INIT_LIST_HEAD(&evict->pgalloc.mru_list);
|
| + tegra_iovmm_free_vm(evict->pgalloc.area);
|
| + evict->pgalloc.area = NULL;
|
| + vm = tegra_iovmm_create_vm(nvmap_vm_client,
|
| + NULL, h->size,
|
| + _nvmap_flag_to_pgprot(h->flags, pgprot_kernel));
|
| + }
|
| + }
|
| +
|
| + spin_unlock(&nvmap_mru_vma_lock);
|
| + return vm;
|
| +#endif
|
| +}
|
| +
|
| +static int _nvmap_do_cache_maint(struct nvmap_handle *h,
|
| + unsigned long start, unsigned long end, unsigned long op, bool get);
|
| +
|
| +void _nvmap_handle_free(struct nvmap_handle *h)
|
| +{
|
| + int e;
|
| + spin_lock(&nvmap_handle_lock);
|
| +
|
| + /* if 2 contexts call _get and _put simultaneously, the reference
|
| + * count may drop to 0 and then increase to 1 before the handle
|
| + * can be freed. */
|
| + if (atomic_add_return(0, &h->ref)>0) {
|
| + spin_unlock(&nvmap_handle_lock);
|
| + return;
|
| + }
|
| + smp_rmb();
|
| + BUG_ON(atomic_read(&h->ref)<0);
|
| + BUG_ON(atomic_read(&h->pin)!=0);
|
| +
|
| + rb_erase(&h->node, &nvmap_handles);
|
| +
|
| + spin_unlock(&nvmap_handle_lock);
|
| +
|
| + if (h->owner) put_task_struct(h->owner);
|
| +
|
| + /* remove when NvRmMemMgr compatibility is eliminated */
|
| + if (h->kern_map) {
|
| + BUG_ON(!h->alloc);
|
| + if (h->heap_pgalloc)
|
| + vm_unmap_ram(h->kern_map, h->size>>PAGE_SHIFT);
|
| + else {
|
| + unsigned long addr = (unsigned long)h->kern_map;
|
| + addr &= ~PAGE_MASK;
|
| + iounmap((void *)addr);
|
| + }
|
| + }
|
| +
|
| + /* ensure that no stale data remains in the cache for this handle */
|
| + e = _nvmap_do_cache_maint(h, 0, h->size, NVMEM_CACHE_OP_WB_INV, false);
|
| +
|
| + if (h->alloc && !h->heap_pgalloc)
|
| + nvmap_carveout_free(h->carveout.co_heap, h->carveout.block_idx);
|
| + else if (h->alloc) {
|
| + unsigned int i;
|
| + BUG_ON(h->size & ~PAGE_MASK);
|
| + BUG_ON(!h->pgalloc.pages);
|
| + _nvmap_remove_mru_vma(h);
|
| + if (h->pgalloc.area) tegra_iovmm_free_vm(h->pgalloc.area);
|
| + for (i=0; i<h->size>>PAGE_SHIFT; i++) {
|
| + ClearPageReserved(h->pgalloc.pages[i]);
|
| + __free_page(h->pgalloc.pages[i]);
|
| + }
|
| + if ((h->size>>PAGE_SHIFT)*sizeof(struct page*)>=PAGE_SIZE)
|
| + vfree(h->pgalloc.pages);
|
| + else
|
| + kfree(h->pgalloc.pages);
|
| + }
|
| + h->poison = 0xa5a5a5a5;
|
| + kfree(h);
|
| +}
|
| +
|
| +#define nvmap_gfp (GFP_KERNEL | __GFP_HIGHMEM | __GFP_NOWARN)
|
| +
|
| +static int _nvmap_alloc_do_coalloc(struct nvmap_handle *h,
|
| + struct nvmap_carveout *co, size_t align)
|
| +{
|
| + int idx;
|
| +
|
| + idx = nvmap_carveout_alloc(co, align, h->size);
|
| + if (idx != -1) {
|
| + h->alloc = true;
|
| + h->heap_pgalloc = false;
|
| + h->carveout.co_heap = co;
|
| + h->carveout.block_idx = idx;
|
| + spin_lock(&co->lock);
|
| + h->carveout.base = co->blocks[idx].base;
|
| + spin_unlock(&co->lock);
|
| + }
|
| +
|
| + return (idx==-1) ? -ENOMEM : 0;
|
| +}
|
| +
|
| +/* map the backing pages for a heap_pgalloc handle into its IOVMM area */
|
| +static void _nvmap_handle_iovmm_map(struct nvmap_handle *h)
|
| +{
|
| + tegra_iovmm_addr_t va;
|
| + unsigned long i;
|
| +
|
| + BUG_ON(!h->heap_pgalloc || !h->pgalloc.area);
|
| + BUG_ON(h->size & ~PAGE_MASK);
|
| + WARN_ON(!h->pgalloc.dirty);
|
| +
|
| + for (va = h->pgalloc.area->iovm_start, i=0;
|
| + va < (h->pgalloc.area->iovm_start + h->size);
|
| + i++, va+=PAGE_SIZE) {
|
| + BUG_ON(!pfn_valid(page_to_pfn(h->pgalloc.pages[i])));
|
| + tegra_iovmm_vm_insert_pfn(h->pgalloc.area, va,
|
| + page_to_pfn(h->pgalloc.pages[i]));
|
| + }
|
| + h->pgalloc.dirty = false;
|
| +}
|
| +
|
| +static int _nvmap_alloc_do_pgalloc(struct nvmap_handle *h,
|
| + bool contiguous, bool secure)
|
| +{
|
| + unsigned int i = 0, cnt = (h->size + PAGE_SIZE - 1) >> PAGE_SHIFT;
|
| + struct page **pages;
|
| +
|
| + if (cnt*sizeof(*pages)>=PAGE_SIZE)
|
| + pages = vmalloc(cnt*sizeof(*pages));
|
| + else
|
| + pages = kzalloc(sizeof(*pages)*cnt, GFP_KERNEL);
|
| +
|
| + if (!pages) return -ENOMEM;
|
| +
|
| + if (cnt==1 && !secure) contiguous = true;
|
| +
|
| + /* secure surfaces should only be allocated in discontiguous (IOVM-
|
| + * managed) space, so that the mapping can be zapped after it is
|
| + * unpinned */
|
| + WARN_ON(secure && contiguous);
|
| +
|
| + if (contiguous) {
|
| + size_t order = get_order(h->size);
|
| + struct page *compound_page;
|
| + compound_page = alloc_pages(nvmap_gfp, order);
|
| + if (!compound_page) goto fail;
|
| + split_page(compound_page, order);
|
| + for (i=0; i<cnt; i++)
|
| + pages[i] = nth_page(compound_page, i);
|
| + for (; i<(1<<order); i++)
|
| + __free_page(nth_page(compound_page, i));
|
| + } else {
|
| + for (i=0; i<cnt; i++) {
|
| + pages[i] = alloc_page(nvmap_gfp);
|
| + if (!pages[i]) {
|
| + pr_err("failed to allocate %u pages after %u entries\n",
|
| + cnt, i);
|
| + goto fail;
|
| + }
|
| + }
|
| + }
|
| +
|
| + h->pgalloc.area = NULL;
|
| +#ifndef CONFIG_DEVNVMAP_RECLAIM_UNPINNED_VM
|
| + if (!contiguous) {
|
| + h->pgalloc.area = tegra_iovmm_create_vm(nvmap_vm_client,
|
| + NULL, cnt << PAGE_SHIFT,
|
| + _nvmap_flag_to_pgprot(h->flags, pgprot_kernel));
|
| + if (!h->pgalloc.area) goto fail;
|
| + h->pgalloc.dirty = true;
|
| + }
|
| +#endif
|
| +
|
| + for (i=0; i<cnt; i++) {
|
| + void *km;
|
| + SetPageReserved(pages[i]);
|
| + km = kmap(pages[i]);
|
| + if (km) flush_dcache_page(pages[i]);
|
| + outer_flush_range(page_to_phys(pages[i]),
|
| + page_to_phys(pages[i])+PAGE_SIZE);
|
| + kunmap(pages[i]);
|
| + }
|
| +
|
| + h->size = cnt<<PAGE_SHIFT;
|
| + h->pgalloc.pages = pages;
|
| + h->heap_pgalloc = true;
|
| + h->pgalloc.contig = contiguous;
|
| + INIT_LIST_HEAD(&h->pgalloc.mru_list);
|
| + h->alloc = true;
|
| + return 0;
|
| +
|
| +fail:
|
| + while (i--) __free_page(pages[i]);
|
| + if (pages && (cnt*sizeof(*pages)>=PAGE_SIZE)) vfree(pages);
|
| + else if (pages) kfree(pages);
|
| + return -ENOMEM;
|
| +}
|
| +
|
| +static struct nvmap_handle *_nvmap_handle_create(
|
| + struct task_struct *owner, size_t size)
|
| +{
|
| + struct nvmap_handle *h = kzalloc(sizeof(*h), GFP_KERNEL);
|
| + struct nvmap_handle *b;
|
| + struct rb_node **p;
|
| + struct rb_node *parent = NULL;
|
| +
|
| + if (!h) return NULL;
|
| + atomic_set(&h->ref, 1);
|
| + atomic_set(&h->pin, 0);
|
| + h->owner = owner;
|
| + h->size = h->orig_size = size;
|
| + h->flags = NVMEM_HANDLE_WRITE_COMBINE;
|
| + h->poison = NVDA_POISON;
|
| +
|
| + spin_lock(&nvmap_handle_lock);
|
| + p = &nvmap_handles.rb_node;
|
| + while (*p) {
|
| + 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, &nvmap_handles);
|
| + spin_unlock(&nvmap_handle_lock);
|
| + if (owner) get_task_struct(owner);
|
| + return h;
|
| +}
|
| +
|
| +/* nvmap pte manager */
|
| +
|
| +static void _nvmap_set_pte_at(unsigned long addr, unsigned long pfn,
|
| + pgprot_t prot)
|
| +{
|
| + u32 off;
|
| + int idx;
|
| + pte_t *pte;
|
| +
|
| + BUG_ON(!addr);
|
| + idx = NVMAP_PTE_INDEX(addr);
|
| + off = NVMAP_PTE_OFFSET(addr) & (PTRS_PER_PTE-1);
|
| +
|
| + pte = nvmap_pte[idx] + off;
|
| + set_pte_ext(pte, pfn_pte(pfn, prot), 0);
|
| + flush_tlb_kernel_page(addr);
|
| +}
|
| +
|
| +static int _nvmap_map_pte(unsigned long pfn, pgprot_t prot, void **vaddr)
|
| +{
|
| + static unsigned int last_bit = 0;
|
| + unsigned long bit;
|
| + unsigned long addr;
|
| + unsigned long flags;
|
| +
|
| + spin_lock_irqsave(&nvmap_ptelock, flags);
|
| +
|
| + bit = find_next_zero_bit(nvmap_ptebits, NVMAP_PAGES, last_bit);
|
| + if (bit==NVMAP_PAGES) {
|
| + bit = find_first_zero_bit(nvmap_ptebits, last_bit);
|
| + if (bit == last_bit) bit = NVMAP_PAGES;
|
| + }
|
| +
|
| + if (bit==NVMAP_PAGES) {
|
| + spin_unlock_irqrestore(&nvmap_ptelock, flags);
|
| + return -ENOMEM;
|
| + }
|
| +
|
| + last_bit = bit;
|
| + set_bit(bit, nvmap_ptebits);
|
| + spin_unlock_irqrestore(&nvmap_ptelock, flags);
|
| +
|
| + addr = NVMAP_BASE + bit*PAGE_SIZE;
|
| +
|
| + _nvmap_set_pte_at(addr, pfn, prot);
|
| + *vaddr = (void *)addr;
|
| + return 0;
|
| +}
|
| +
|
| +static int nvmap_map_pte(unsigned long pfn, pgprot_t prot, void **addr)
|
| +{
|
| + int ret;
|
| + ret = wait_event_interruptible(nvmap_ptefull,
|
| + !_nvmap_map_pte(pfn, prot, addr));
|
| +
|
| + if (ret==-ERESTARTSYS) return -EINTR;
|
| + return ret;
|
| +}
|
| +
|
| +static void nvmap_unmap_pte(void *addr)
|
| +{
|
| + unsigned long bit = NVMAP_PTE_OFFSET(addr);
|
| + unsigned long flags;
|
| +
|
| + /* the ptes aren't cleared in this function, since the address isn't
|
| + * re-used until it is allocated again by nvmap_map_pte. */
|
| + BUG_ON(bit >= NVMAP_PAGES);
|
| + spin_lock_irqsave(&nvmap_ptelock, flags);
|
| + clear_bit(bit, nvmap_ptebits);
|
| + spin_unlock_irqrestore(&nvmap_ptelock, flags);
|
| + wake_up(&nvmap_ptefull);
|
| +}
|
| +
|
| +/* to ensure that the backing store for the VMA isn't freed while a fork'd
|
| + * reference still exists, nvmap_vma_open increments the reference count on
|
| + * the handle, and nvmap_vma_close decrements it. alternatively, we could
|
| + * disallow copying of the vma, or behave like pmem and zap the pages. FIXME.
|
| +*/
|
| +static void nvmap_vma_open(struct vm_area_struct *vma)
|
| +{
|
| + struct nvmap_vma_priv *priv;
|
| +
|
| + priv = vma->vm_private_data;
|
| +
|
| + BUG_ON(!priv);
|
| +
|
| + atomic_inc(&priv->ref);
|
| +}
|
| +
|
| +static void nvmap_vma_close(struct vm_area_struct *vma) {
|
| + struct nvmap_vma_priv *priv = vma->vm_private_data;
|
| +
|
| + if (priv && !atomic_dec_return(&priv->ref)) {
|
| + if (priv->h) _nvmap_handle_put(priv->h);
|
| + kfree(priv);
|
| + }
|
| + vma->vm_private_data = NULL;
|
| +}
|
| +
|
| +static int nvmap_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
|
| +{
|
| + struct nvmap_vma_priv *priv;
|
| + unsigned long offs;
|
| +
|
| + offs = (unsigned long)(vmf->virtual_address - vma->vm_start);
|
| + priv = vma->vm_private_data;
|
| + if (!priv || !priv->h || !priv->h->alloc)
|
| + return VM_FAULT_SIGBUS;
|
| +
|
| + offs += priv->offs;
|
| + /* if the VMA was split for some reason, vm_pgoff will be the VMA's
|
| + * offset from the original VMA */
|
| + offs += (vma->vm_pgoff << PAGE_SHIFT);
|
| +
|
| + if (offs >= priv->h->size)
|
| + return VM_FAULT_SIGBUS;
|
| +
|
| + if (!priv->h->heap_pgalloc) {
|
| + unsigned long pfn;
|
| + BUG_ON(priv->h->carveout.base & ~PAGE_MASK);
|
| + pfn = ((priv->h->carveout.base + offs) >> PAGE_SHIFT);
|
| + vm_insert_pfn(vma, (unsigned long)vmf->virtual_address, pfn);
|
| + return VM_FAULT_NOPAGE;
|
| + } else {
|
| + struct page *page;
|
| + offs >>= PAGE_SHIFT;
|
| + page = priv->h->pgalloc.pages[offs];
|
| + if (page) get_page(page);
|
| + vmf->page = page;
|
| + return (page) ? 0 : VM_FAULT_SIGBUS;
|
| + }
|
| +}
|
| +
|
| +static long nvmap_ioctl(struct file *filp,
|
| + unsigned int cmd, unsigned long arg)
|
| +{
|
| + int err = 0;
|
| + void __user *uarg = (void __user *)arg;
|
| +
|
| + if (_IOC_TYPE(cmd) != NVMEM_IOC_MAGIC)
|
| + return -ENOTTY;
|
| +
|
| + if (_IOC_NR(cmd) > NVMEM_IOC_MAXNR)
|
| + return -ENOTTY;
|
| +
|
| + if (_IOC_DIR(cmd) & _IOC_READ)
|
| + err = !access_ok(VERIFY_WRITE, uarg, _IOC_SIZE(cmd));
|
| + if (_IOC_DIR(cmd) & _IOC_WRITE)
|
| + err = !access_ok(VERIFY_READ, uarg, _IOC_SIZE(cmd));
|
| +
|
| + if (err)
|
| + return -EFAULT;
|
| +
|
| + switch (cmd) {
|
| + case NVMEM_IOC_CREATE:
|
| + case NVMEM_IOC_CLAIM:
|
| + case NVMEM_IOC_FROM_ID:
|
| + err = nvmap_ioctl_create(filp, cmd, uarg);
|
| + break;
|
| +
|
| + case NVMEM_IOC_GET_ID:
|
| + err = nvmap_ioctl_getid(filp, uarg);
|
| + break;
|
| +
|
| + case NVMEM_IOC_PARAM:
|
| + err = nvmap_ioctl_get_param(filp, uarg);
|
| + break;
|
| +
|
| + case NVMEM_IOC_UNPIN_MULT:
|
| + case NVMEM_IOC_PIN_MULT:
|
| + err = nvmap_ioctl_pinop(filp, cmd==NVMEM_IOC_PIN_MULT, uarg);
|
| + break;
|
| +
|
| + case NVMEM_IOC_ALLOC:
|
| + err = nvmap_ioctl_alloc(filp, uarg);
|
| + break;
|
| +
|
| + case NVMEM_IOC_FREE:
|
| + err = nvmap_ioctl_free(filp, arg);
|
| + break;
|
| +
|
| + case NVMEM_IOC_MMAP:
|
| + err = nvmap_map_into_caller_ptr(filp, uarg);
|
| + break;
|
| +
|
| + case NVMEM_IOC_WRITE:
|
| + case NVMEM_IOC_READ:
|
| + err = nvmap_ioctl_rw_handle(filp, cmd==NVMEM_IOC_READ, uarg);
|
| + break;
|
| +
|
| + case NVMEM_IOC_CACHE:
|
| + err = nvmap_ioctl_cache_maint(filp, uarg);
|
| + break;
|
| +
|
| + default:
|
| + return -ENOTTY;
|
| + }
|
| + return err;
|
| +}
|
| +
|
| +/* must be called with the ref_lock held - given a user-space handle ID
|
| + * ref, validate that the handle_ref object may be used by the caller */
|
| +struct nvmap_handle_ref *_nvmap_ref_lookup_locked(
|
| + struct nvmap_file_priv *priv, unsigned long ref)
|
| +{
|
| + struct rb_node *n = priv->handle_refs.rb_node;
|
| +
|
| + while (n) {
|
| + struct nvmap_handle_ref *r;
|
| + r = rb_entry(n, struct nvmap_handle_ref, node);
|
| + if ((unsigned long)r->h == ref) return r;
|
| + else if (ref > (unsigned long)r->h) n = n->rb_right;
|
| + else n = n->rb_left;
|
| + }
|
| +
|
| + return NULL;
|
| +}
|
| +
|
| +/* must be called inside nvmap_pin_lock, to ensure that an entire stream
|
| + * of pins will complete without competition from a second stream. returns
|
| + * 0 if the pin was successful, -ENOMEM on failure */
|
| +static int _nvmap_handle_pin_locked(struct nvmap_handle *h)
|
| +{
|
| + struct tegra_iovmm_area *area;
|
| + BUG_ON(!h->alloc);
|
| +
|
| + h = _nvmap_handle_get(h);
|
| + if (!h) return -ENOMEM;
|
| +
|
| + if (atomic_inc_return(&h->pin)==1) {
|
| + if (h->heap_pgalloc && !h->pgalloc.contig) {
|
| + area = _nvmap_get_vm(h);
|
| + if (!area) {
|
| + /* no race here, inside the pin mutex */
|
| + atomic_dec(&h->pin);
|
| + _nvmap_handle_put(h);
|
| + return -ENOMEM;
|
| + }
|
| + if (area != h->pgalloc.area)
|
| + h->pgalloc.dirty = true;
|
| + h->pgalloc.area = area;
|
| + }
|
| + }
|
| + return 0;
|
| +}
|
| +
|
| +/* doesn't need to be called inside nvmap_pin_lock, since this will only
|
| + * expand the available VM area */
|
| +static int _nvmap_handle_unpin(struct nvmap_handle *h)
|
| +{
|
| + int ret = 0;
|
| +
|
| + if (atomic_add_return(0, &h->pin)==0) {
|
| + pr_err("%s: %s attempting to unpin an unpinned handle\n",
|
| + __func__, current->comm);
|
| + dump_stack();
|
| + return 0;
|
| + }
|
| +
|
| + BUG_ON(!h->alloc);
|
| + if (!atomic_dec_return(&h->pin)) {
|
| + if (h->heap_pgalloc && h->pgalloc.area) {
|
| + /* if a secure handle is clean (i.e., mapped into
|
| + * IOVMM, it needs to be zapped on unpin. */
|
| + if (h->secure && !h->pgalloc.dirty) {
|
| + tegra_iovmm_zap_vm(h->pgalloc.area);
|
| + h->pgalloc.dirty = true;
|
| + }
|
| + _nvmap_insert_mru_vma(h);
|
| + ret=1;
|
| + }
|
| + }
|
| + _nvmap_handle_put(h);
|
| + return ret;
|
| +}
|
| +
|
| +/* pin a list of handles, mapping IOVMM areas if needed. may sleep, if
|
| + * a handle's IOVMM area has been reclaimed and insufficient IOVMM space
|
| + * is available to complete the list pin. no intervening pin operations
|
| + * will interrupt this, and no validation is performed on the handles
|
| + * that are provided. */
|
| +static int _nvmap_handle_pin_fast(unsigned int nr, struct nvmap_handle **h)
|
| +{
|
| + unsigned int i;
|
| + int ret = 0;
|
| +
|
| + mutex_lock(&nvmap_pin_lock);
|
| + for (i=0; i<nr && !ret; i++) {
|
| + ret = wait_event_interruptible(nvmap_pin_wait,
|
| + !_nvmap_handle_pin_locked(h[i]));
|
| + }
|
| + mutex_unlock(&nvmap_pin_lock);
|
| +
|
| + if (ret) {
|
| + int do_wake = 0;
|
| + while (i--) do_wake |= _nvmap_handle_unpin(h[i]);
|
| + if (do_wake) wake_up(&nvmap_pin_wait);
|
| + return -EINTR;
|
| + } else {
|
| + for (i=0; i<nr; i++)
|
| + if (h[i]->heap_pgalloc && h[i]->pgalloc.dirty)
|
| + _nvmap_handle_iovmm_map(h[i]);
|
| + }
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +static int _nvmap_do_global_unpin(unsigned long ref)
|
| +{
|
| + struct nvmap_handle *h;
|
| + int w;
|
| +
|
| + h = _nvmap_validate_get(ref, true);
|
| + if (unlikely(!h)) {
|
| + pr_err("%s: %s attempting to unpin non-existent handle\n",
|
| + __func__, current->group_leader->comm);
|
| + return 0;
|
| + }
|
| +
|
| + pr_err("%s: %s unpinning %s's %uB %s handle without local context\n",
|
| + __func__, current->group_leader->comm,
|
| + (h->owner) ? h->owner->comm : "kernel", h->orig_size,
|
| + (h->heap_pgalloc && !h->pgalloc.contig) ? "iovmm" :
|
| + (h->heap_pgalloc) ? "sysmem" : "carveout");
|
| +
|
| + w = _nvmap_handle_unpin(h);
|
| + _nvmap_handle_put(h);
|
| + return w;
|
| +}
|
| +
|
| +static void _nvmap_do_unpin(struct nvmap_file_priv *priv,
|
| + unsigned int nr, unsigned long *refs)
|
| +{
|
| + struct nvmap_handle_ref *r;
|
| + unsigned int i;
|
| + int do_wake = 0;
|
| +
|
| + spin_lock(&priv->ref_lock);
|
| + for (i=0; i<nr; i++) {
|
| + if (!refs[i]) continue;
|
| + r = _nvmap_ref_lookup_locked(priv, refs[i]);
|
| + if (unlikely(!r)) {
|
| + if (priv->su)
|
| + do_wake |= _nvmap_do_global_unpin(refs[i]);
|
| + else
|
| + pr_err("%s: %s unpinning invalid handle\n",
|
| + __func__, current->comm);
|
| + } else if (unlikely(!atomic_add_unless(&r->pin, -1, 0)))
|
| + pr_err("%s: %s unpinning unpinned handle\n",
|
| + __func__, current->comm);
|
| + else
|
| + do_wake |= _nvmap_handle_unpin(r->h);
|
| + }
|
| + spin_unlock(&priv->ref_lock);
|
| + if (do_wake) wake_up(&nvmap_pin_wait);
|
| +}
|
| +
|
| +/* pins a list of handle_ref objects; same conditions apply as to
|
| + * _nvmap_handle_pin, but also bumps the pin count of each handle_ref. */
|
| +static int _nvmap_do_pin(struct nvmap_file_priv *priv,
|
| + unsigned int nr, unsigned long *refs)
|
| +{
|
| + int ret = 0;
|
| + unsigned int i;
|
| + struct nvmap_handle **h = (struct nvmap_handle **)refs;
|
| + struct nvmap_handle_ref *r;
|
| +
|
| + /* to optimize for the common case (client provided valid handle
|
| + * references and the pin succeeds), increment the handle_ref pin
|
| + * count during validation. in error cases, the tree will need to
|
| + * be re-walked, since the handle_ref is discarded so that an
|
| + * allocation isn't required. if a handle_ref is not found,
|
| + * locally validate that the caller has permission to pin the handle;
|
| + * handle_refs are not created in this case, so it is possible that
|
| + * if the caller crashes after pinning a global handle, the handle
|
| + * will be permanently leaked. */
|
| + spin_lock(&priv->ref_lock);
|
| + for (i=0; i<nr && !ret; i++) {
|
| + r = _nvmap_ref_lookup_locked(priv, refs[i]);
|
| + if (!r && (!(priv->su || h[i]->global ||
|
| + current->group_leader == h[i]->owner)))
|
| + ret = -EPERM;
|
| + else if (r) atomic_inc(&r->pin);
|
| + else {
|
| + pr_err("%s: %s pinning %s's %uB handle without "
|
| + "local context\n", __func__,
|
| + current->group_leader->comm,
|
| + h[i]->owner->comm, h[i]->orig_size);
|
| + }
|
| + }
|
| +
|
| + while (ret && i--) {
|
| + r = _nvmap_ref_lookup_locked(priv, refs[i]);
|
| + if (r) atomic_dec(&r->pin);
|
| + }
|
| + spin_unlock(&priv->ref_lock);
|
| +
|
| + if (ret) return ret;
|
| +
|
| + mutex_lock(&nvmap_pin_lock);
|
| + for (i=0; i<nr && !ret; i++) {
|
| + ret = wait_event_interruptible_timeout(nvmap_pin_wait,
|
| + !_nvmap_handle_pin_locked(h[i]), 5);
|
| + if (ret >= 0) ret = !ret;
|
| + BUG_ON(ret > 0);
|
| +
|
| +
|
| + }
|
| + mutex_unlock(&nvmap_pin_lock);
|
| +
|
| + if (ret) {
|
| + int do_wake = 0;
|
| + spin_lock(&priv->ref_lock);
|
| + while (i--) {
|
| + r = _nvmap_ref_lookup_locked(priv, refs[i]);
|
| + do_wake |= _nvmap_handle_unpin(r->h);
|
| + if (r) atomic_dec(&r->pin);
|
| + }
|
| + spin_unlock(&priv->ref_lock);
|
| + if (do_wake) wake_up(&nvmap_pin_wait);
|
| + return -EINTR;
|
| + } else {
|
| + for (i=0; i<nr; i++) {
|
| + if (h[i]->heap_pgalloc && h[i]->pgalloc.dirty)
|
| + _nvmap_handle_iovmm_map(h[i]);
|
| + }
|
| + }
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +static int nvmap_ioctl_pinop(struct file *filp,
|
| + bool is_pin, void __user *arg)
|
| +{
|
| + struct nvmem_pin_handle op;
|
| + struct nvmap_handle *h;
|
| + unsigned long on_stack[16];
|
| + unsigned long *refs;
|
| + unsigned long __user *output;
|
| + unsigned int i;
|
| + int err;
|
| +
|
| + err = copy_from_user(&op, arg, sizeof(op));
|
| + if (err) return err;
|
| +
|
| + if (!op.count) return -EINVAL;
|
| +
|
| + if (op.count > 1) {
|
| + size_t bytes = op.count * sizeof(unsigned long *);
|
| + if (!access_ok(VERIFY_READ, (void *)op.handles, bytes))
|
| + return -EPERM;
|
| + if (is_pin && op.addr &&
|
| + !access_ok(VERIFY_WRITE, (void *)op.addr, bytes))
|
| + return -EPERM;
|
| +
|
| + if (op.count <= ARRAY_SIZE(on_stack)) refs = on_stack;
|
| + else refs = kzalloc(bytes, GFP_KERNEL);
|
| +
|
| + if (!refs) return -ENOMEM;
|
| + err = copy_from_user(refs, (void*)op.handles, bytes);
|
| + if (err) goto out;
|
| + } else {
|
| + refs = on_stack;
|
| + on_stack[0] = (unsigned long)op.handles;
|
| + }
|
| +
|
| + if (is_pin)
|
| + err = _nvmap_do_pin(filp->private_data, op.count, refs);
|
| + else
|
| + _nvmap_do_unpin(filp->private_data, op.count, refs);
|
| +
|
| + /* skip the output stage on unpin */
|
| + if (err || !is_pin) goto out;
|
| +
|
| + /* it is guaranteed that if _nvmap_do_pin returns 0 that
|
| + * all of the handle_ref objects are valid, so dereferencing directly
|
| + * here is safe */
|
| + if (op.count > 1)
|
| + output = (unsigned long __user *)op.addr;
|
| + else {
|
| + struct nvmem_pin_handle __user *tmp = arg;
|
| + output = (unsigned long __user *)&(tmp->addr);
|
| + }
|
| +
|
| + if (!output) goto out;
|
| +
|
| + for (i=0; i<op.count; i++) {
|
| + unsigned long addr;
|
| + h = (struct nvmap_handle *)refs[i];
|
| + if (h->heap_pgalloc && h->pgalloc.contig)
|
| + addr = page_to_phys(h->pgalloc.pages[0]);
|
| + else if (h->heap_pgalloc)
|
| + addr = h->pgalloc.area->iovm_start;
|
| + else
|
| + addr = h->carveout.base;
|
| +
|
| + __put_user(addr, &output[i]);
|
| + }
|
| +
|
| +out:
|
| + if (refs != on_stack) kfree(refs);
|
| + return err;
|
| +}
|
| +
|
| +static int nvmap_release(struct inode *inode, struct file *filp)
|
| +{
|
| + struct nvmap_file_priv *priv = filp->private_data;
|
| + struct rb_node *n;
|
| + struct nvmap_handle_ref *r;
|
| + int refs;
|
| + int do_wake = 0;
|
| + int pins;
|
| +
|
| + if (!priv) return 0;
|
| +
|
| + while ((n = rb_first(&priv->handle_refs))) {
|
| + r = rb_entry(n, struct nvmap_handle_ref, node);
|
| + rb_erase(&r->node, &priv->handle_refs);
|
| + smp_rmb();
|
| + pins = atomic_read(&r->pin);
|
| + atomic_set(&r->pin, 0);
|
| + while (pins--) do_wake |= _nvmap_handle_unpin(r->h);
|
| + refs = atomic_read(&r->refs);
|
| + if (r->h->alloc && r->h->heap_pgalloc && !r->h->pgalloc.contig)
|
| + atomic_sub(r->h->size, &priv->iovm_commit);
|
| + while (refs--) _nvmap_handle_put(r->h);
|
| + kfree(r);
|
| + }
|
| + if (do_wake) wake_up(&nvmap_pin_wait);
|
| + kfree(priv);
|
| + return 0;
|
| +}
|
| +
|
| +static int nvmap_open(struct inode *inode, struct file *filp)
|
| +{
|
| + /* eliminate read, write and llseek support on this node */
|
| + struct nvmap_file_priv *priv;
|
| + int ret;
|
| +
|
| + /* nvmap doesn't track total number of pinned references, so its
|
| + * IOVMM client is always locked. */
|
| + if (!nvmap_vm_client) {
|
| + mutex_lock(&nvmap_pin_lock);
|
| + if (!nvmap_vm_client) {
|
| + nvmap_vm_client = tegra_iovmm_alloc_client("gpu", NULL);
|
| + if (nvmap_vm_client)
|
| + tegra_iovmm_client_lock(nvmap_vm_client);
|
| + }
|
| + mutex_unlock(&nvmap_pin_lock);
|
| + }
|
| +
|
| + ret = nonseekable_open(inode, filp);
|
| + if (unlikely(ret))
|
| + return ret;
|
| +
|
| + priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
| + if (!priv) return -ENOMEM;
|
| + priv->handle_refs = RB_ROOT;
|
| + priv->su = (filp->f_op == &knvmap_fops);
|
| +
|
| + atomic_set(&priv->iovm_commit, 0);
|
| +
|
| + if (nvmap_vm_client)
|
| + priv->iovm_limit = tegra_iovmm_get_vm_size(nvmap_vm_client);
|
| +#ifdef CONFIG_DEVNVMAP_RECLAIM_UNPINNED_VM
|
| + /* to prevent fragmentation-caused deadlocks, derate the size of
|
| + * the IOVM space to 75% */
|
| + priv->iovm_limit >>= 2;
|
| + priv->iovm_limit *= 3;
|
| +#endif
|
| +
|
| + spin_lock_init(&priv->ref_lock);
|
| +
|
| + filp->f_mapping->backing_dev_info = &nvmap_bdi;
|
| +
|
| + filp->private_data = priv;
|
| + return 0;
|
| +}
|
| +
|
| +static int nvmap_ioctl_getid(struct file *filp, void __user *arg)
|
| +{
|
| + struct nvmem_create_handle op;
|
| + struct nvmap_handle *h = NULL;
|
| + int err;
|
| +
|
| + err = copy_from_user(&op, arg, sizeof(op));
|
| + if (err) return err;
|
| +
|
| + if (!op.handle) return -EINVAL;
|
| +
|
| + h = _nvmap_validate_get((unsigned long)op.handle,
|
| + filp->f_op==&knvmap_fops);
|
| +
|
| + if (h) {
|
| + op.id = (__u32)h;
|
| + /* when the owner of a handle gets its ID, this is treated
|
| + * as a granting of the handle for use by other processes.
|
| + * however, the super-user is not capable of promoting a
|
| + * handle to global status if it was created in another
|
| + * process. */
|
| + if (current->group_leader == h->owner) h->global = true;
|
| +
|
| + /* getid is not supposed to result in a ref count increase */
|
| + _nvmap_handle_put(h);
|
| +
|
| + return copy_to_user(arg, &op, sizeof(op));
|
| + }
|
| + return -EPERM;
|
| +}
|
| +
|
| +/* attempts to allocate from either contiguous system memory or IOVMM space */
|
| +static int _nvmap_do_page_alloc(struct nvmap_file_priv *priv,
|
| + struct nvmap_handle *h, unsigned int heap_mask,
|
| + size_t align, bool secure)
|
| +{
|
| + int ret = -ENOMEM;
|
| + size_t page_size = (h->size + PAGE_SIZE - 1) & ~(PAGE_SIZE-1);
|
| +#ifdef IOVMM_FIRST
|
| + unsigned int fallback[] = { NVMEM_HEAP_IOVMM, NVMEM_HEAP_SYSMEM, 0 };
|
| +#else
|
| + unsigned int fallback[] = { NVMEM_HEAP_SYSMEM, NVMEM_HEAP_IOVMM, 0 };
|
| +#endif
|
| + unsigned int *m = fallback;
|
| +
|
| + /* secure allocations must not be performed from sysmem */
|
| + if (secure) heap_mask &= ~NVMEM_HEAP_SYSMEM;
|
| +
|
| + if (align > PAGE_SIZE) return -EINVAL;
|
| +
|
| +
|
| + while (*m && ret) {
|
| + if (heap_mask & NVMEM_HEAP_SYSMEM & *m)
|
| + ret = _nvmap_alloc_do_pgalloc(h, true, secure);
|
| +
|
| + else if (heap_mask & NVMEM_HEAP_IOVMM & *m) {
|
| + /* increment the committed IOVM space prior to
|
| + * allocation, to avoid race conditions with other
|
| + * threads simultaneously allocating. this is
|
| + * conservative, but guaranteed to work */
|
| +
|
| + int oc;
|
| + oc = atomic_add_return(page_size, &priv->iovm_commit);
|
| +
|
| + if (oc <= priv->iovm_limit)
|
| + ret = _nvmap_alloc_do_pgalloc(h, false, secure);
|
| + else
|
| + ret = -ENOMEM;
|
| + /* on failure, or when do_pgalloc promotes a non-
|
| + * contiguous request into a contiguous request,
|
| + * release the commited iovm space */
|
| + if (ret || h->pgalloc.contig)
|
| + atomic_sub(page_size, &priv->iovm_commit);
|
| + }
|
| + m++;
|
| + }
|
| + return ret;
|
| +}
|
| +
|
| +/* attempts to allocate from the carveout heaps */
|
| +static int _nvmap_do_carveout_alloc(struct nvmap_handle *h,
|
| + unsigned int heap_mask, size_t align)
|
| +{
|
| + int ret = -ENOMEM;
|
| + struct nvmap_carveout_node *n;
|
| +
|
| + down_read(&nvmap_context.list_sem);
|
| + list_for_each_entry(n, &nvmap_context.heaps, heap_list) {
|
| + if (heap_mask & n->heap_bit)
|
| + ret = _nvmap_alloc_do_coalloc(h, &n->carveout, align);
|
| + if (!ret) break;
|
| + }
|
| + up_read(&nvmap_context.list_sem);
|
| + return ret;
|
| +}
|
| +
|
| +static int _nvmap_do_alloc(struct nvmap_file_priv *priv,
|
| + unsigned long href, unsigned int heap_mask, size_t align,
|
| + unsigned int flags, bool secure, bool carveout_first)
|
| +{
|
| + int ret = -ENOMEM;
|
| + struct nvmap_handle_ref *r;
|
| + struct nvmap_handle *h;
|
| +
|
| + if (!href) return -EINVAL;
|
| +
|
| + spin_lock(&priv->ref_lock);
|
| + r = _nvmap_ref_lookup_locked(priv, href);
|
| + spin_unlock(&priv->ref_lock);
|
| +
|
| + if (!r) return -EPERM;
|
| +
|
| + h = r->h;
|
| + if (h->alloc) return 0;
|
| + h->flags = flags;
|
| +
|
| + align = max_t(size_t, align, L1_CACHE_BYTES);
|
| +
|
| + if (secure) heap_mask &= ~NVMEM_HEAP_CARVEOUT_MASK;
|
| +
|
| + if (carveout_first || (heap_mask & NVMEM_HEAP_CARVEOUT_IRAM)) {
|
| + ret = _nvmap_do_carveout_alloc(h, heap_mask, align);
|
| + if (ret) ret = _nvmap_do_page_alloc(priv, h,
|
| + heap_mask, align, secure);
|
| + } else {
|
| + ret = _nvmap_do_page_alloc(priv, h, heap_mask, align, secure);
|
| + if (ret) ret = _nvmap_do_carveout_alloc(h, heap_mask, align);
|
| + }
|
| +
|
| + BUG_ON((!ret && !h->alloc) || (ret && h->alloc));
|
| + return ret;
|
| +}
|
| +
|
| +static int nvmap_ioctl_alloc(struct file *filp, void __user *arg)
|
| +{
|
| + struct nvmem_alloc_handle op;
|
| + struct nvmap_file_priv *priv = filp->private_data;
|
| + bool secure = false;
|
| +#ifdef IOVMM_FIRST
|
| + bool carveout_first = false;
|
| +#else
|
| + bool carveout_first = true;
|
| +#endif
|
| + int err;
|
| +
|
| + err = copy_from_user(&op, arg, sizeof(op));
|
| + if (err) return err;
|
| +
|
| + if (op.align & (op.align-1)) return -EINVAL;
|
| +
|
| + /* user-space handles are aligned to page boundaries, to prevent
|
| + * data leakage. */
|
| + op.align = max_t(size_t, op.align, PAGE_SIZE);
|
| +
|
| + if (op.flags & NVMEM_HANDLE_SECURE) secure = true;
|
| +
|
| + /* TODO: implement a way to specify carveout-first vs
|
| + * carveout-second */
|
| + return _nvmap_do_alloc(priv, op.handle, op.heap_mask,
|
| + op.align, (op.flags & 0x3), secure, carveout_first);
|
| +}
|
| +
|
| +static int _nvmap_do_free(struct nvmap_file_priv *priv, unsigned long href)
|
| +{
|
| + struct nvmap_handle_ref *r;
|
| + struct nvmap_handle *h;
|
| + int do_wake = 0;
|
| +
|
| + if (!href) return 0;
|
| +
|
| + spin_lock(&priv->ref_lock);
|
| + r = _nvmap_ref_lookup_locked(priv, href);
|
| +
|
| + if (!r) {
|
| + spin_unlock(&priv->ref_lock);
|
| + pr_err("%s attempting to free unrealized handle\n",
|
| + current->group_leader->comm);
|
| + return -EPERM;
|
| + }
|
| +
|
| + h = r->h;
|
| +
|
| + smp_rmb();
|
| + if (!atomic_dec_return(&r->refs)) {
|
| + int pins = atomic_read(&r->pin);
|
| + rb_erase(&r->node, &priv->handle_refs);
|
| + spin_unlock(&priv->ref_lock);
|
| + if (pins) pr_err("%s: %s freeing %s's pinned %s %s %uB handle\n",
|
| + __func__, current->comm,
|
| + (r->h->owner) ? r->h->owner->comm : "kernel",
|
| + (r->h->global) ? "global" : "private",
|
| + (r->h->alloc && r->h->heap_pgalloc)?"page-alloc" :
|
| + (r->h->alloc) ? "carveout" : "unallocated",
|
| + r->h->orig_size);
|
| + while (pins--) do_wake |= _nvmap_handle_unpin(r->h);
|
| + kfree(r);
|
| + if (h->alloc && h->heap_pgalloc && !h->pgalloc.contig)
|
| + atomic_sub(h->size, &priv->iovm_commit);
|
| + if (do_wake) wake_up(&nvmap_pin_wait);
|
| + } else
|
| + spin_unlock(&priv->ref_lock);
|
| +
|
| + BUG_ON(!atomic_read(&h->ref));
|
| + _nvmap_handle_put(h);
|
| + return 0;
|
| +}
|
| +
|
| +static int nvmap_ioctl_free(struct file *filp, unsigned long arg)
|
| +{
|
| + return _nvmap_do_free(filp->private_data, arg);
|
| +}
|
| +
|
| +/* given a size, pre-existing handle ID, or a preserved handle key, create
|
| + * a handle and a reference to the handle in the per-context data */
|
| +static int _nvmap_do_create(struct nvmap_file_priv *priv,
|
| + unsigned int cmd, unsigned long key, bool su,
|
| + struct nvmap_handle_ref **ref)
|
| +{
|
| + struct nvmap_handle_ref *r = NULL;
|
| + struct nvmap_handle *h = NULL;
|
| + struct rb_node **p, *parent = NULL;
|
| +
|
| + if (cmd == NVMEM_IOC_FROM_ID) {
|
| + /* only ugly corner case to handle with from ID:
|
| + *
|
| + * normally, if the handle that is being duplicated is IOVMM-
|
| + * backed, the handle should fail to duplicate if duping it
|
| + * would over-commit IOVMM space. however, if the handle is
|
| + * already duplicated in the client process (or the client
|
| + * is duplicating a handle it created originally), IOVMM space
|
| + * should not be doubly-reserved.
|
| + */
|
| + h = _nvmap_validate_get(key, priv->su);
|
| +
|
| + if (!h) {
|
| + pr_err("%s: %s duplicate handle failed\n", __func__,
|
| + current->group_leader->comm);
|
| + return -EPERM;
|
| + }
|
| +
|
| + if (!h->alloc) {
|
| + pr_err("%s: attempting to clone unallocated "
|
| + "handle\n", __func__);
|
| + _nvmap_handle_put(h);
|
| + h = NULL;
|
| + return -EINVAL;
|
| + }
|
| +
|
| + spin_lock(&priv->ref_lock);
|
| + r = _nvmap_ref_lookup_locked(priv, (unsigned long)h);
|
| + spin_unlock(&priv->ref_lock);
|
| + if (r) {
|
| + /* if the client does something strange, like calling CreateFromId
|
| + * when it was the original creator, avoid creating two handle refs
|
| + * for the same handle */
|
| + atomic_inc(&r->refs);
|
| + *ref = r;
|
| + return 0;
|
| + }
|
| +
|
| + /* verify that adding this handle to the process' access list
|
| + * won't exceed the IOVM limit */
|
| + /* TODO: [ahatala 2010-04-20] let the kernel over-commit for now */
|
| + if (h->heap_pgalloc && !h->pgalloc.contig && !su) {
|
| + int oc = atomic_add_return(h->size, &priv->iovm_commit);
|
| + if (oc > priv->iovm_limit) {
|
| + atomic_sub(h->size, &priv->iovm_commit);
|
| + _nvmap_handle_put(h);
|
| + h = NULL;
|
| + pr_err("%s: %s duplicating handle would "
|
| + "over-commit iovmm space (%dB / %dB)\n",
|
| + __func__, current->group_leader->comm,
|
| + oc, priv->iovm_limit);
|
| + return -ENOMEM;
|
| + }
|
| + }
|
| + } else if (cmd == NVMEM_IOC_CREATE) {
|
| + h = _nvmap_handle_create(current->group_leader, key);
|
| + if (!h) return -ENOMEM;
|
| + } else {
|
| + h = _nvmap_claim_preserved(current->group_leader, key);
|
| + if (!h) return -EINVAL;
|
| + }
|
| +
|
| + BUG_ON(!h);
|
| +
|
| + r = kzalloc(sizeof(*r), GFP_KERNEL);
|
| + spin_lock(&priv->ref_lock);
|
| + if (!r) {
|
| + spin_unlock(&priv->ref_lock);
|
| + if (h) _nvmap_handle_put(h);
|
| + return -ENOMEM;
|
| + }
|
| +
|
| + atomic_set(&r->refs, 1);
|
| + r->h = h;
|
| + atomic_set(&r->pin, 0);
|
| +
|
| + p = &priv->handle_refs.rb_node;
|
| + while (*p) {
|
| + struct nvmap_handle_ref *l;
|
| + parent = *p;
|
| + l = rb_entry(parent, struct nvmap_handle_ref, node);
|
| + if (r->h > l->h) p = &parent->rb_right;
|
| + else p = &parent->rb_left;
|
| + }
|
| + rb_link_node(&r->node, parent, p);
|
| + rb_insert_color(&r->node, &priv->handle_refs);
|
| +
|
| + spin_unlock(&priv->ref_lock);
|
| + *ref = r;
|
| + return 0;
|
| +}
|
| +
|
| +static int nvmap_ioctl_create(struct file *filp,
|
| + unsigned int cmd, void __user *arg)
|
| +{
|
| + struct nvmem_create_handle op;
|
| + struct nvmap_handle_ref *r = NULL;
|
| + struct nvmap_file_priv *priv = filp->private_data;
|
| + unsigned long key;
|
| + int err = 0;
|
| +
|
| + err = copy_from_user(&op, arg, sizeof(op));
|
| + if (err) return err;
|
| +
|
| + if (!priv) return -ENODEV;
|
| +
|
| + /* user-space-created handles are expanded to be page-aligned,
|
| + * so that mmap() will not accidentally leak a different allocation */
|
| + if (cmd==NVMEM_IOC_CREATE)
|
| + key = (op.size + PAGE_SIZE - 1) & ~(PAGE_SIZE-1);
|
| + else if (cmd==NVMEM_IOC_CLAIM)
|
| + key = op.key;
|
| + else if (cmd==NVMEM_IOC_FROM_ID)
|
| + key = op.id;
|
| +
|
| + err = _nvmap_do_create(priv, cmd, key, (filp->f_op==&knvmap_fops), &r);
|
| +
|
| + if (!err) {
|
| + op.handle = (uintptr_t)r->h;
|
| + /* since the size is spoofed to a page-multiple above,
|
| + * clobber the orig_size field back to the requested value for
|
| + * debugging. */
|
| + if (cmd == NVMEM_IOC_CREATE) r->h->orig_size = op.size;
|
| + err = copy_to_user(arg, &op, sizeof(op));
|
| + if (err) _nvmap_do_free(priv, op.handle);
|
| + }
|
| +
|
| + return err;
|
| +}
|
| +
|
| +static int nvmap_map_into_caller_ptr(struct file *filp, void __user *arg)
|
| +{
|
| + struct nvmem_map_caller op;
|
| + struct nvmap_vma_priv *vpriv;
|
| + struct vm_area_struct *vma;
|
| + struct nvmap_handle *h;
|
| + int err = 0;
|
| +
|
| + err = copy_from_user(&op, arg, sizeof(op));
|
| + if (err) return err;
|
| +
|
| + if (!op.handle) return -EINVAL;
|
| +
|
| + h = _nvmap_validate_get(op.handle, (filp->f_op==&knvmap_fops));
|
| + if (!h) return -EINVAL;
|
| +
|
| + down_read(¤t->mm->mmap_sem);
|
| +
|
| + vma = find_vma(current->mm, op.addr);
|
| + if (!vma || !vma->vm_private_data) {
|
| + err = -ENOMEM;
|
| + goto out;
|
| + }
|
| +
|
| + if (op.offset & ~PAGE_MASK) {
|
| + err = -EFAULT;
|
| + goto out;
|
| + }
|
| +
|
| + if ((op.offset + op.length) > h->size) {
|
| + err = -EADDRNOTAVAIL;
|
| + goto out;
|
| + }
|
| +
|
| + vpriv = vma->vm_private_data;
|
| + BUG_ON(!vpriv);
|
| +
|
| + /* the VMA must exactly match the requested mapping operation, and the
|
| + * VMA that is targetted must have been created originally by /dev/nvmap
|
| + */
|
| + if ((vma->vm_start != op.addr) || (vma->vm_ops != &nvmap_vma_ops) ||
|
| + (vma->vm_end-vma->vm_start != op.length)) {
|
| + err = -EPERM;
|
| + goto out;
|
| + }
|
| +
|
| + /* verify that each mmap() system call creates a unique VMA */
|
| +
|
| + if (vpriv->h && h==vpriv->h)
|
| + goto out;
|
| + else if (vpriv->h) {
|
| + err = -EADDRNOTAVAIL;
|
| + goto out;
|
| + }
|
| +
|
| + if (!h->heap_pgalloc && (h->carveout.base & ~PAGE_MASK)) {
|
| + err = -EFAULT;
|
| + goto out;
|
| + }
|
| +
|
| + vpriv->h = h;
|
| + vpriv->offs = op.offset;
|
| +
|
| + /* if the hmem is not writeback-cacheable, drop back to a page mapping
|
| + * which will guarantee DMA coherency
|
| + */
|
| + vma->vm_page_prot = _nvmap_flag_to_pgprot(h->flags,
|
| + vma->vm_page_prot);
|
| +
|
| +out:
|
| + up_read(¤t->mm->mmap_sem);
|
| + if (err) _nvmap_handle_put(h);
|
| + return err;
|
| +}
|
| +/* Initially, the nvmap mmap system call is used to allocate an inaccessible
|
| + * region of virtual-address space in the client. A subsequent
|
| + * NVMAP_IOC_MMAP ioctl will associate each
|
| + */
|
| +static int nvmap_mmap(struct file *filp, struct vm_area_struct *vma)
|
| +{
|
| + /* FIXME: drivers which do not support cow seem to be split down the
|
| + * middle whether to force the VM_SHARED flag, or to return an error
|
| + * when this flag isn't already set (i.e., MAP_PRIVATE).
|
| + */
|
| + struct nvmap_vma_priv *priv;
|
| +
|
| + vma->vm_private_data = NULL;
|
| +
|
| + priv = kzalloc(sizeof(*priv),GFP_KERNEL);
|
| + if (!priv)
|
| + return -ENOMEM;
|
| +
|
| + priv->offs = 0;
|
| + priv->h = NULL;
|
| + atomic_set(&priv->ref, 1);
|
| +
|
| + vma->vm_flags |= VM_SHARED;
|
| + vma->vm_flags |= (VM_IO | VM_DONTEXPAND | VM_MIXEDMAP | VM_RESERVED);
|
| + vma->vm_ops = &nvmap_vma_ops;
|
| + vma->vm_private_data = priv;
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +/* perform cache maintenance on a handle; caller's handle must be pre-
|
| + * validated. */
|
| +static int _nvmap_do_cache_maint(struct nvmap_handle *h,
|
| + unsigned long start, unsigned long end, unsigned long op, bool get)
|
| +{
|
| + pgprot_t prot;
|
| + void *addr = NULL;
|
| + void (*inner_maint)(const void*, const void*);
|
| + void (*outer_maint)(unsigned long, unsigned long);
|
| + int err = 0;
|
| +
|
| + if (get) h = _nvmap_handle_get(h);
|
| +
|
| + if (!h) return -EINVAL;
|
| +
|
| + /* don't waste time on cache maintenance if the handle isn't cached */
|
| + if (h->flags == NVMEM_HANDLE_UNCACHEABLE ||
|
| + h->flags == NVMEM_HANDLE_WRITE_COMBINE)
|
| + goto out;
|
| +
|
| + if (op == NVMEM_CACHE_OP_WB) {
|
| + inner_maint = smp_dma_clean_range;
|
| + if (h->flags == NVMEM_HANDLE_CACHEABLE)
|
| + outer_maint = outer_clean_range;
|
| + else
|
| + outer_maint = NULL;
|
| + } else if (op == NVMEM_CACHE_OP_WB_INV) {
|
| + inner_maint = smp_dma_flush_range;
|
| + if (h->flags == NVMEM_HANDLE_CACHEABLE)
|
| + outer_maint = outer_flush_range;
|
| + else
|
| + outer_maint = NULL;
|
| + } else {
|
| + inner_maint = smp_dma_inv_range;
|
| + if (h->flags == NVMEM_HANDLE_CACHEABLE)
|
| + outer_maint = outer_inv_range;
|
| + else
|
| + outer_maint = NULL;
|
| + }
|
| +
|
| + prot = _nvmap_flag_to_pgprot(h->flags, pgprot_kernel);
|
| +
|
| + /* for any write-back operation, it is safe to writeback the entire
|
| + * cache rather than just the requested region. for large regions, it
|
| + * is faster to do this than to iterate over every line.
|
| + * only implemented for L1-only cacheable handles currently */
|
| +#if 0
|
| + if (h->flags == NVMEM_HANDLE_INNER_CACHEABLE &&
|
| + end-start >= PAGE_SIZE*3 && op != NVMEM_CACHE_OP_INV) {
|
| + flush_cache_all();
|
| + goto out;
|
| + }
|
| +#endif
|
| +
|
| + while (start < end) {
|
| + struct page *page = NULL;
|
| + unsigned long phys;
|
| + void *src;
|
| + size_t count;
|
| +
|
| + if (h->heap_pgalloc) {
|
| + page = h->pgalloc.pages[start>>PAGE_SHIFT];
|
| + BUG_ON(!page);
|
| + get_page(page);
|
| + phys = page_to_phys(page) + (start & ~PAGE_MASK);
|
| + } else {
|
| + phys = h->carveout.base + start;
|
| + }
|
| +
|
| + if (!addr) {
|
| + err = nvmap_map_pte(__phys_to_pfn(phys), prot, &addr);
|
| + if (err) {
|
| + if (page) put_page(page);
|
| + break;
|
| + }
|
| + } else {
|
| + _nvmap_set_pte_at((unsigned long)addr,
|
| + __phys_to_pfn(phys), prot);
|
| + }
|
| +
|
| + src = addr + (phys & ~PAGE_MASK);
|
| + count = min_t(size_t, end-start, PAGE_SIZE-(phys&~PAGE_MASK));
|
| +
|
| + inner_maint(src, src+count);
|
| + if (outer_maint) outer_maint(phys, phys+count);
|
| + start += count;
|
| + if (page) put_page(page);
|
| + }
|
| +
|
| +out:
|
| + if (h->flags == NVMEM_HANDLE_INNER_CACHEABLE) outer_sync();
|
| + if (addr) nvmap_unmap_pte(addr);
|
| + if (get) _nvmap_handle_put(h);
|
| + return err;
|
| +}
|
| +
|
| +static int nvmap_ioctl_cache_maint(struct file *filp, void __user *arg)
|
| +{
|
| + struct nvmem_cache_op op;
|
| + int err = 0;
|
| + struct vm_area_struct *vma;
|
| + struct nvmap_vma_priv *vpriv;
|
| + unsigned long start;
|
| + unsigned long end;
|
| +
|
| + err = copy_from_user(&op, arg, sizeof(op));
|
| + if (err) return err;
|
| +
|
| + if (!op.handle || !op.addr || op.op<NVMEM_CACHE_OP_WB ||
|
| + op.op>NVMEM_CACHE_OP_WB_INV)
|
| + return -EINVAL;
|
| +
|
| + vma = find_vma(current->active_mm, (unsigned long)op.addr);
|
| + if (!vma || vma->vm_ops!=&nvmap_vma_ops ||
|
| + (unsigned long)op.addr + op.len > vma->vm_end)
|
| + return -EADDRNOTAVAIL;
|
| +
|
| + vpriv = (struct nvmap_vma_priv *)vma->vm_private_data;
|
| +
|
| + if ((unsigned long)vpriv->h != op.handle)
|
| + return -EFAULT;
|
| +
|
| + start = (unsigned long)op.addr - vma->vm_start;
|
| + end = start + op.len;
|
| +
|
| + return _nvmap_do_cache_maint(vpriv->h, start, end, op.op, true);
|
| +}
|
| +
|
| +/* copies a single element from the pre-get()'ed handle h, returns
|
| + * the number of bytes copied, and the address in the nvmap mapping range
|
| + * which was used (to eliminate re-allocation when copying multiple
|
| + * elements */
|
| +static ssize_t _nvmap_do_one_rw_handle(struct nvmap_handle *h, int is_read,
|
| + int is_user, unsigned long start, unsigned long rw_addr,
|
| + unsigned long bytes, void **nvmap_addr)
|
| +{
|
| + pgprot_t prot = _nvmap_flag_to_pgprot(h->flags, pgprot_kernel);
|
| + unsigned long end = start + bytes;
|
| + unsigned long orig_start = start;
|
| +
|
| + if (is_user) {
|
| + if (is_read && !access_ok(VERIFY_WRITE, (void*)rw_addr, bytes))
|
| + return -EPERM;
|
| + if (!is_read && !access_ok(VERIFY_READ, (void*)rw_addr, bytes))
|
| + return -EPERM;
|
| + }
|
| +
|
| + while (start < end) {
|
| + struct page *page = NULL;
|
| + unsigned long phys;
|
| + size_t count;
|
| + void *src;
|
| +
|
| + if (h->heap_pgalloc) {
|
| + page = h->pgalloc.pages[start >> PAGE_SHIFT];
|
| + BUG_ON(!page);
|
| + get_page(page);
|
| + phys = page_to_phys(page) + (start & ~PAGE_MASK);
|
| + } else {
|
| + phys = h->carveout.base + start;
|
| + }
|
| +
|
| + if (!*nvmap_addr) {
|
| + int err = nvmap_map_pte(__phys_to_pfn(phys),
|
| + prot, nvmap_addr);
|
| + if (err) {
|
| + if (page) put_page(page);
|
| + count = start - orig_start;
|
| + return (count) ? count : err;
|
| + }
|
| + } else {
|
| + _nvmap_set_pte_at((unsigned long)*nvmap_addr,
|
| + __phys_to_pfn(phys), prot);
|
| +
|
| + }
|
| +
|
| + src = *nvmap_addr + (phys & ~PAGE_MASK);
|
| + count = min_t(size_t, end-start, PAGE_SIZE-(phys&~PAGE_MASK));
|
| +
|
| + if (is_user && is_read)
|
| + copy_to_user((void*)rw_addr, src, count);
|
| + else if (is_user)
|
| + copy_from_user(src, (void*)rw_addr, count);
|
| + else if (is_read)
|
| + memcpy((void*)rw_addr, src, count);
|
| + else
|
| + memcpy(src, (void*)rw_addr, count);
|
| +
|
| + rw_addr += count;
|
| + start += count;
|
| + if (page) put_page(page);
|
| + }
|
| +
|
| + return (ssize_t)start - orig_start;
|
| +}
|
| +
|
| +static ssize_t _nvmap_do_rw_handle(struct nvmap_handle *h, int is_read,
|
| + int is_user, unsigned long h_offs, unsigned long sys_addr,
|
| + unsigned long h_stride, unsigned long sys_stride,
|
| + unsigned long elem_size, unsigned long count)
|
| +{
|
| + ssize_t bytes_copied = 0;
|
| + void *addr = NULL;
|
| +
|
| + h = _nvmap_handle_get(h);
|
| + if (!h) return -EINVAL;
|
| +
|
| + if (elem_size == h_stride &&
|
| + elem_size == sys_stride) {
|
| + elem_size *= count;
|
| + h_stride = elem_size;
|
| + sys_stride = elem_size;
|
| + count = 1;
|
| + }
|
| +
|
| + while (count--) {
|
| + size_t ret = _nvmap_do_one_rw_handle(h, is_read,
|
| + is_user, h_offs, sys_addr, elem_size, &addr);
|
| + if (ret < 0) {
|
| + if (!bytes_copied) bytes_copied = ret;
|
| + break;
|
| + }
|
| + bytes_copied += ret;
|
| + if (ret < elem_size) break;
|
| + sys_addr += sys_stride;
|
| + h_offs += h_stride;
|
| + }
|
| +
|
| + if (addr) nvmap_unmap_pte(addr);
|
| + _nvmap_handle_put(h);
|
| + return bytes_copied;
|
| +}
|
| +
|
| +static int nvmap_ioctl_rw_handle(struct file *filp,
|
| + int is_read, void __user* arg)
|
| +{
|
| + struct nvmem_rw_handle __user *uarg = arg;
|
| + struct nvmem_rw_handle op;
|
| + struct nvmap_handle *h;
|
| + ssize_t copied;
|
| + int err = 0;
|
| +
|
| + err = copy_from_user(&op, arg, sizeof(op));
|
| + if (err) return err;
|
| +
|
| + if (!op.handle || !op.addr || !op.count || !op.elem_size)
|
| + return -EINVAL;
|
| +
|
| + h = _nvmap_validate_get(op.handle, (filp->f_op == &knvmap_fops));
|
| + if (!h) return -EINVAL; /* -EPERM? */
|
| +
|
| + copied = _nvmap_do_rw_handle(h, is_read, 1, op.offset,
|
| + (unsigned long)op.addr, op.hmem_stride,
|
| + op.user_stride, op.elem_size, op.count);
|
| +
|
| + if (copied < 0) { err = copied; copied = 0; }
|
| + else if (copied < (op.count*op.elem_size)) err = -EINTR;
|
| +
|
| + __put_user(copied, &uarg->count);
|
| +
|
| + _nvmap_handle_put(h);
|
| +
|
| + return err;
|
| +}
|
| +
|
| +static unsigned int _nvmap_do_get_param(struct nvmap_handle *h,
|
| + unsigned int param)
|
| +{
|
| + if (param==NVMEM_HANDLE_PARAM_SIZE)
|
| + return h->orig_size;
|
| +
|
| + else if (param==NVMEM_HANDLE_PARAM_ALIGNMENT) {
|
| + if (!h->alloc) return 0;
|
| +
|
| + if (h->heap_pgalloc) return PAGE_SIZE;
|
| + else {
|
| + unsigned int i=1;
|
| + if (!h->carveout.base) return SZ_4M;
|
| + while (!(i & h->carveout.base)) i<<=1;
|
| + return i;
|
| + }
|
| + } else if (param==NVMEM_HANDLE_PARAM_BASE) {
|
| +
|
| + if (!h->alloc || !atomic_add_return(0, &h->pin)){
|
| + WARN_ON(1);
|
| + return ~0ul;
|
| + }
|
| +
|
| + if (!h->heap_pgalloc)
|
| + return h->carveout.base;
|
| +
|
| + if (h->pgalloc.contig)
|
| + return page_to_phys(h->pgalloc.pages[0]);
|
| +
|
| + if (h->pgalloc.area)
|
| + return h->pgalloc.area->iovm_start;
|
| +
|
| + return ~0ul;
|
| + } else if (param==NVMEM_HANDLE_PARAM_HEAP) {
|
| +
|
| + if (!h->alloc) return 0;
|
| +
|
| + if (!h->heap_pgalloc) {
|
| + /* FIXME: hard-coded physical address */
|
| + if ((h->carveout.base & 0xf0000000ul)==0x40000000ul)
|
| + return NVMEM_HEAP_CARVEOUT_IRAM;
|
| + else
|
| + return NVMEM_HEAP_CARVEOUT_GENERIC;
|
| + }
|
| +
|
| + if (!h->pgalloc.contig)
|
| + return NVMEM_HEAP_IOVMM;
|
| +
|
| + return NVMEM_HEAP_SYSMEM;
|
| + }
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +static int nvmap_ioctl_get_param(struct file *filp, void __user* arg)
|
| +{
|
| + struct nvmem_handle_param op;
|
| + struct nvmap_handle *h;
|
| + int err;
|
| +
|
| + err = copy_from_user(&op, arg, sizeof(op));
|
| + if (err) return err;
|
| +
|
| + if (op.param < NVMEM_HANDLE_PARAM_SIZE ||
|
| + op.param > NVMEM_HANDLE_PARAM_HEAP)
|
| + return -EINVAL;
|
| +
|
| + h = _nvmap_validate_get(op.handle, (filp->f_op==&knvmap_fops));
|
| + if (!h) return -EINVAL;
|
| +
|
| + op.result = _nvmap_do_get_param(h, op.param);
|
| + err = copy_to_user(arg, &op, sizeof(op));
|
| +
|
| + _nvmap_handle_put(h);
|
| + return err;
|
| +}
|
| +
|
| +static struct miscdevice misc_nvmap_dev = {
|
| + .minor = MISC_DYNAMIC_MINOR,
|
| + .name = "nvmap",
|
| + .fops = &nvmap_fops
|
| +};
|
| +
|
| +static struct miscdevice misc_knvmap_dev = {
|
| + .minor = MISC_DYNAMIC_MINOR,
|
| + .name = "knvmap",
|
| + .fops = &knvmap_fops
|
| +};
|
| +
|
| +static struct device *__nvmap_heap_parent_dev(void)
|
| +{
|
| + return misc_nvmap_dev.this_device;
|
| +}
|
| +
|
| +/* creates the sysfs attribute files for a carveout heap; if called
|
| + * before fs initialization, silently returns.
|
| + */
|
| +static void _nvmap_create_heap_attrs(struct nvmap_carveout_node *n)
|
| +{
|
| + if (!_nvmap_heap_parent_dev) return;
|
| + dev_set_name(&n->dev, "heap-%s", n->carveout.name);
|
| + n->dev.parent = _nvmap_heap_parent_dev;
|
| + n->dev.driver = NULL;
|
| + n->dev.release = NULL;
|
| + if (device_register(&n->dev)) {
|
| + pr_err("%s: failed to create heap-%s device\n",
|
| + __func__, n->carveout.name);
|
| + return;
|
| + }
|
| + if (sysfs_create_group(&n->dev.kobj, &nvmap_heap_defattr_group))
|
| + pr_err("%s: failed to create attribute group for heap-%s "
|
| + "device\n", __func__, n->carveout.name);
|
| +}
|
| +
|
| +static int __init nvmap_dev_init(void)
|
| +{
|
| + struct nvmap_carveout_node *n;
|
| +
|
| + if (misc_register(&misc_nvmap_dev))
|
| + pr_err("%s error registering %s\n", __func__,
|
| + misc_nvmap_dev.name);
|
| +
|
| + if (misc_register(&misc_knvmap_dev))
|
| + pr_err("%s error registering %s\n", __func__,
|
| + misc_knvmap_dev.name);
|
| +
|
| + /* create sysfs attribute entries for all the heaps which were
|
| + * created prior to nvmap_dev_init */
|
| + down_read(&nvmap_context.list_sem);
|
| + list_for_each_entry(n, &nvmap_context.heaps, heap_list) {
|
| + _nvmap_create_heap_attrs(n);
|
| + }
|
| + up_read(&nvmap_context.list_sem);
|
| +
|
| + nvmap_procfs_root = proc_mkdir("nvmap", NULL);
|
| + if (nvmap_procfs_root) {
|
| + nvmap_procfs_proc = proc_mkdir("proc", nvmap_procfs_root);
|
| + }
|
| + return 0;
|
| +}
|
| +fs_initcall(nvmap_dev_init);
|
| +
|
| +/* initialization of core data structures split out to earlier in the
|
| + * init sequence, to allow kernel drivers access to nvmap before devfs
|
| + * is initialized */
|
| +#define NR_CARVEOUTS 2
|
| +static unsigned int nvmap_carveout_cmds = 0;
|
| +static unsigned long nvmap_carveout_cmd_base[NR_CARVEOUTS];
|
| +static unsigned long nvmap_carveout_cmd_size[NR_CARVEOUTS];
|
| +
|
| +static int __init nvmap_core_init(void)
|
| +{
|
| + u32 base = NVMAP_BASE;
|
| + pgd_t *pgd;
|
| + pmd_t *pmd;
|
| + pte_t *pte;
|
| + unsigned int i;
|
| +
|
| + init_rwsem(&nvmap_context.list_sem);
|
| + nvmap_context.init_data.handle_refs = RB_ROOT;
|
| + atomic_set(&nvmap_context.init_data.iovm_commit, 0);
|
| + /* no IOVMM allocations for kernel-created handles */
|
| + spin_lock_init(&nvmap_context.init_data.ref_lock);
|
| + nvmap_context.init_data.su = true;
|
| + nvmap_context.init_data.iovm_limit = 0;
|
| + INIT_LIST_HEAD(&nvmap_context.heaps);
|
| +
|
| +#ifdef CONFIG_DEVNVMAP_RECLAIM_UNPINNED_VM
|
| + for (i=0; i<ARRAY_SIZE(nvmap_mru_cutoff); i++)
|
| + INIT_LIST_HEAD(&nvmap_mru_vma_lists[i]);
|
| +#endif
|
| +
|
| + i = 0;
|
| + do {
|
| + pgd = pgd_offset(&init_mm, base);
|
| + pmd = pmd_alloc(&init_mm, pgd, base);
|
| + if (!pmd) {
|
| + pr_err("%s: no pmd tables\n", __func__);
|
| + return -ENOMEM;
|
| + }
|
| + pte = pte_alloc_kernel(pmd, base);
|
| + if (!pte) {
|
| + pr_err("%s: no pte tables\n", __func__);
|
| + return -ENOMEM;
|
| + }
|
| + nvmap_pte[i++] = pte;
|
| + base += (1<<PGDIR_SHIFT);
|
| + } while (base < NVMAP_END);
|
| +
|
| + for (i=0; i<nvmap_carveout_cmds; i++) {
|
| + char tmp[16];
|
| + snprintf(tmp, sizeof(tmp), "generic-%u", i);
|
| + nvmap_add_carveout_heap(nvmap_carveout_cmd_base[i],
|
| + nvmap_carveout_cmd_size[i], tmp, 0x1);
|
| + }
|
| +
|
| + return 0;
|
| +}
|
| +core_initcall(nvmap_core_init);
|
| +
|
| +static int __init nvmap_heap_arg(char *options)
|
| +{
|
| + unsigned long start, size;
|
| + char *p = options;
|
| +
|
| + start = -1;
|
| + size = memparse(p, &p);
|
| + if (*p == '@')
|
| + start = memparse(p + 1, &p);
|
| +
|
| + if (nvmap_carveout_cmds < ARRAY_SIZE(nvmap_carveout_cmd_size)) {
|
| + nvmap_carveout_cmd_base[nvmap_carveout_cmds] = start;
|
| + nvmap_carveout_cmd_size[nvmap_carveout_cmds] = size;
|
| + nvmap_carveout_cmds++;
|
| + }
|
| + return 0;
|
| +}
|
| +__setup("nvmem=", nvmap_heap_arg);
|
| +
|
| +static int _nvmap_try_create_preserved(struct nvmap_carveout *co,
|
| + struct nvmap_handle *h, unsigned long base,
|
| + size_t size, unsigned int key)
|
| +{
|
| + unsigned long end = base + size;
|
| + short idx;
|
| +
|
| + h->carveout.base = ~0;
|
| + h->carveout.key = key;
|
| + h->carveout.co_heap = NULL;
|
| +
|
| + spin_lock(&co->lock);
|
| + idx = co->free_index;
|
| + while (idx != -1) {
|
| + struct nvmap_mem_block *b = BLOCK(co, idx);
|
| + unsigned long blk_end = b->base + b->size;
|
| + if (b->base <= base && blk_end >= end) {
|
| + nvmap_split_block(co, idx, base, size);
|
| + h->carveout.block_idx = idx;
|
| + h->carveout.base = co->blocks[idx].base;
|
| + h->carveout.co_heap = co;
|
| + h->alloc = true;
|
| + break;
|
| + }
|
| + idx = b->next_free;
|
| + }
|
| + spin_unlock(&co->lock);
|
| +
|
| + return (h->carveout.co_heap == NULL) ? -ENXIO : 0;
|
| +}
|
| +
|
| +static void _nvmap_create_nvos_preserved(struct nvmap_carveout *co)
|
| +{
|
| + unsigned int i, key;
|
| + NvBootArgsPreservedMemHandle mem;
|
| + static int was_created[NvBootArgKey_PreservedMemHandle_Num -
|
| + NvBootArgKey_PreservedMemHandle_0] = { 0 };
|
| +
|
| + for (i=0, key=NvBootArgKey_PreservedMemHandle_0;
|
| + i<ARRAY_SIZE(was_created); i++, key++) {
|
| + struct nvmap_handle *h;
|
| +
|
| + if (was_created[i]) continue;
|
| +
|
| + if (NvOsBootArgGet(key, &mem, sizeof(mem))!=NvSuccess) continue;
|
| + if (!mem.Address || !mem.Size) continue;
|
| +
|
| + h = _nvmap_handle_create(NULL, mem.Size);
|
| + if (!h) continue;
|
| +
|
| + if (!_nvmap_try_create_preserved(co, h, mem.Address,
|
| + mem.Size, key))
|
| + was_created[i] = 1;
|
| + else
|
| + _nvmap_handle_put(h);
|
| + }
|
| +}
|
| +
|
| +int nvmap_add_carveout_heap(unsigned long base, size_t size,
|
| + const char *name, unsigned int bitmask)
|
| +{
|
| + struct nvmap_carveout_node *n;
|
| + struct nvmap_carveout_node *l;
|
| +
|
| +
|
| + n = kzalloc(sizeof(*n), GFP_KERNEL);
|
| + if (!n) return -ENOMEM;
|
| +
|
| + BUG_ON(bitmask & ~NVMEM_HEAP_CARVEOUT_MASK);
|
| + n->heap_bit = bitmask;
|
| +
|
| + if (_nvmap_init_carveout(&n->carveout, name, base, size)) {
|
| + kfree(n);
|
| + return -ENOMEM;
|
| + }
|
| +
|
| + down_write(&nvmap_context.list_sem);
|
| +
|
| + /* called inside the list_sem lock to ensure that the was_created
|
| + * array is protected against simultaneous access */
|
| + _nvmap_create_nvos_preserved(&n->carveout);
|
| + _nvmap_create_heap_attrs(n);
|
| +
|
| + list_for_each_entry(l, &nvmap_context.heaps, heap_list) {
|
| + if (n->heap_bit > l->heap_bit) {
|
| + list_add_tail(&n->heap_list, &l->heap_list);
|
| + up_write(&nvmap_context.list_sem);
|
| + return 0;
|
| + }
|
| + }
|
| + list_add_tail(&n->heap_list, &nvmap_context.heaps);
|
| + up_write(&nvmap_context.list_sem);
|
| + return 0;
|
| +}
|
| +
|
| +int nvmap_create_preserved_handle(unsigned long base, size_t size,
|
| + unsigned int key)
|
| +{
|
| + struct nvmap_carveout_node *i;
|
| + struct nvmap_handle *h;
|
| +
|
| + h = _nvmap_handle_create(NULL, size);
|
| + if (!h) return -ENOMEM;
|
| +
|
| + down_read(&nvmap_context.list_sem);
|
| + list_for_each_entry(i, &nvmap_context.heaps, heap_list) {
|
| + struct nvmap_carveout *co = &i->carveout;
|
| + if (!_nvmap_try_create_preserved(co, h, base, size, key))
|
| + break;
|
| + }
|
| + up_read(&nvmap_context.list_sem);
|
| +
|
| + /* the base may not be correct if block splitting fails */
|
| + if (!h->carveout.co_heap || h->carveout.base != base) {
|
| + _nvmap_handle_put(h);
|
| + return -ENOMEM;
|
| + }
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +/* attempts to create a new carveout heap with a new usage bitmask by
|
| + * taking an allocation from a previous carveout with a different bitmask */
|
| +static int nvmap_split_carveout_heap(struct nvmap_carveout *co, size_t size,
|
| + const char *name, unsigned int new_bitmask)
|
| +{
|
| + struct nvmap_carveout_node *i, *n;
|
| + int idx = -1;
|
| + unsigned int blkbase, blksize;
|
| +
|
| +
|
| + n = kzalloc(sizeof(*n), GFP_KERNEL);
|
| + if (!n) return -ENOMEM;
|
| + n->heap_bit = new_bitmask;
|
| +
|
| + /* align split carveouts to 1M */
|
| + idx = nvmap_carveout_alloc(co, SZ_1M, size);
|
| + if (idx != -1) {
|
| + /* take the spin lock to avoid race conditions with
|
| + * intervening allocations triggering grow_block operations */
|
| + spin_lock(&co->lock);
|
| + blkbase = co->blocks[idx].base;
|
| + blksize = co->blocks[idx].size;
|
| + spin_unlock(&co->lock);
|
| +
|
| + if (_nvmap_init_carveout(&n->carveout,name, blkbase, blksize)) {
|
| + nvmap_carveout_free(&i->carveout, idx);
|
| + idx = -1;
|
| + } else {
|
| + spin_lock(&co->lock);
|
| + if (co->blocks[idx].prev) {
|
| + co->blocks[co->blocks[idx].prev].next =
|
| + co->blocks[idx].next;
|
| + }
|
| + if (co->blocks[idx].next) {
|
| + co->blocks[co->blocks[idx].next].prev =
|
| + co->blocks[idx].prev;
|
| + }
|
| + if (co->block_index==idx)
|
| + co->block_index = co->blocks[idx].next;
|
| + co->blocks[idx].next_free = -1;
|
| + co->blocks[idx].prev_free = -1;
|
| + co->blocks[idx].next = co->spare_index;
|
| + if (co->spare_index!=-1)
|
| + co->blocks[co->spare_index].prev = idx;
|
| + co->spare_index = idx;
|
| + spin_unlock(&co->lock);
|
| + }
|
| + }
|
| +
|
| + if (idx==-1) {
|
| + kfree(n);
|
| + return -ENOMEM;
|
| + }
|
| +
|
| + down_write(&nvmap_context.list_sem);
|
| + _nvmap_create_heap_attrs(n);
|
| + list_for_each_entry(i, &nvmap_context.heaps, heap_list) {
|
| + if (n->heap_bit > i->heap_bit) {
|
| + list_add_tail(&n->heap_list, &i->heap_list);
|
| + up_write(&nvmap_context.list_sem);
|
| + return 0;
|
| + }
|
| + }
|
| + list_add_tail(&n->heap_list, &nvmap_context.heaps);
|
| + up_write(&nvmap_context.list_sem);
|
| + return 0;
|
| +}
|
| +
|
| +/* NvRmMemMgr APIs implemented on top of nvmap */
|
| +
|
| +#include <linux/freezer.h>
|
| +
|
| +NvU32 NvRmMemGetAddress(NvRmMemHandle hMem, NvU32 Offset)
|
| +{
|
| + struct nvmap_handle *h = (struct nvmap_handle *)hMem;
|
| + unsigned long addr;
|
| +
|
| + if (unlikely(!atomic_add_return(0, &h->pin) || !h->alloc ||
|
| + Offset >= h->orig_size)) {
|
| + WARN_ON(1);
|
| + return ~0ul;
|
| + }
|
| +
|
| + if (h->heap_pgalloc && h->pgalloc.contig)
|
| + addr = page_to_phys(h->pgalloc.pages[0]);
|
| + else if (h->heap_pgalloc) {
|
| + BUG_ON(!h->pgalloc.area);
|
| + addr = h->pgalloc.area->iovm_start;
|
| + } else
|
| + addr = h->carveout.base;
|
| +
|
| + return (NvU32)addr+Offset;
|
| +
|
| +}
|
| +
|
| +void NvRmMemPinMult(NvRmMemHandle *hMems, NvU32 *addrs, NvU32 Count)
|
| +{
|
| + struct nvmap_handle **h = (struct nvmap_handle **)hMems;
|
| + unsigned int i;
|
| + int ret;
|
| +
|
| + do {
|
| + ret = _nvmap_handle_pin_fast(Count, h);
|
| + if (ret && !try_to_freeze()) {
|
| + pr_err("%s: failed to pin handles\n", __func__);
|
| + dump_stack();
|
| + }
|
| + } while (ret);
|
| +
|
| + for (i=0; i<Count; i++) {
|
| + addrs[i] = NvRmMemGetAddress(hMems[i], 0);
|
| + BUG_ON(addrs[i]==~0ul);
|
| + }
|
| +}
|
| +
|
| +void NvRmMemUnpinMult(NvRmMemHandle *hMems, NvU32 Count)
|
| +{
|
| + int do_wake = 0;
|
| + unsigned int i;
|
| +
|
| + for (i=0; i<Count; i++) {
|
| + struct nvmap_handle *h = (struct nvmap_handle *)hMems[i];
|
| + if (h) {
|
| + BUG_ON(atomic_add_return(0, &h->pin)==0);
|
| + do_wake |= _nvmap_handle_unpin(h);
|
| + }
|
| + }
|
| +
|
| + if (do_wake) wake_up(&nvmap_pin_wait);
|
| +}
|
| +
|
| +NvU32 NvRmMemPin(NvRmMemHandle hMem)
|
| +{
|
| + NvU32 addr;
|
| + NvRmMemPinMult(&hMem, &addr, 1);
|
| + return addr;
|
| +}
|
| +
|
| +void NvRmMemUnpin(NvRmMemHandle hMem)
|
| +{
|
| + NvRmMemUnpinMult(&hMem, 1);
|
| +}
|
| +
|
| +void NvRmMemHandleFree(NvRmMemHandle hMem)
|
| +{
|
| + _nvmap_do_free(&nvmap_context.init_data, (unsigned long)hMem);
|
| +}
|
| +
|
| +NvError NvRmMemMap(NvRmMemHandle hMem, NvU32 Offset, NvU32 Size,
|
| + NvU32 Flags, void **pVirtAddr)
|
| +{
|
| + struct nvmap_handle *h = (struct nvmap_handle *)hMem;
|
| + pgprot_t prot = _nvmap_flag_to_pgprot(h->flags, pgprot_kernel);
|
| +
|
| + BUG_ON(!h->alloc);
|
| +
|
| + if (Offset+Size > h->size)
|
| + return NvError_BadParameter;
|
| +
|
| + if (!h->kern_map && h->heap_pgalloc) {
|
| + BUG_ON(h->size & ~PAGE_MASK);
|
| + h->kern_map = vm_map_ram(h->pgalloc.pages,
|
| + h->size>>PAGE_SHIFT, -1, prot);
|
| + } else if (!h->kern_map) {
|
| + unsigned int size;
|
| + unsigned long addr;
|
| +
|
| + addr = h->carveout.base;
|
| + size = h->size + (addr & ~PAGE_MASK);
|
| + addr &= PAGE_MASK;
|
| + size = (size + PAGE_SIZE - 1) & PAGE_MASK;
|
| +
|
| + h->kern_map = ioremap_wc(addr, size);
|
| + if (h->kern_map) {
|
| + addr = h->carveout.base - addr;
|
| + h->kern_map += addr;
|
| + }
|
| + }
|
| +
|
| + if (h->kern_map) {
|
| + *pVirtAddr = (h->kern_map + Offset);
|
| + return NvSuccess;
|
| + }
|
| +
|
| + return NvError_InsufficientMemory;
|
| +}
|
| +
|
| +void NvRmMemUnmap(NvRmMemHandle hMem, void *pVirtAddr, NvU32 Size)
|
| +{
|
| + return;
|
| +}
|
| +
|
| +NvU32 NvRmMemGetId(NvRmMemHandle hMem)
|
| +{
|
| + struct nvmap_handle *h = (struct nvmap_handle *)hMem;
|
| + if (!h->owner) h->global = true;
|
| + return (NvU32)h;
|
| +}
|
| +
|
| +NvError NvRmMemHandleFromId(NvU32 id, NvRmMemHandle *hMem)
|
| +{
|
| + struct nvmap_handle_ref *r;
|
| +
|
| + int err = _nvmap_do_create(&nvmap_context.init_data,
|
| + NVMEM_IOC_FROM_ID, id, true, &r);
|
| +
|
| + if (err || !r) return NvError_NotInitialized;
|
| +
|
| + *hMem = (NvRmMemHandle)r->h;
|
| + return NvSuccess;
|
| +}
|
| +
|
| +NvError NvRmMemHandleClaimPreservedHandle(NvRmDeviceHandle hRm,
|
| + NvU32 Key, NvRmMemHandle *hMem)
|
| +{
|
| + struct nvmap_handle_ref *r;
|
| +
|
| + int err = _nvmap_do_create(&nvmap_context.init_data,
|
| + NVMEM_IOC_CLAIM, (unsigned long)Key, true, &r);
|
| +
|
| + if (err || !r) return NvError_NotInitialized;
|
| +
|
| + *hMem = (NvRmMemHandle)r->h;
|
| + return NvSuccess;
|
| +}
|
| +
|
| +NvError NvRmMemHandleCreate(NvRmDeviceHandle hRm,
|
| + NvRmMemHandle *hMem, NvU32 Size)
|
| +{
|
| + struct nvmap_handle_ref *r;
|
| + int err = _nvmap_do_create(&nvmap_context.init_data,
|
| + NVMEM_IOC_CREATE, (unsigned long)Size, true, &r);
|
| +
|
| + if (err || !r) return NvError_InsufficientMemory;
|
| + *hMem = (NvRmMemHandle)r->h;
|
| + return NvSuccess;
|
| +}
|
| +
|
| +NvError NvRmMemAlloc(NvRmMemHandle hMem, const NvRmHeap *Heaps,
|
| + NvU32 NumHeaps, NvU32 Alignment, NvOsMemAttribute Coherency)
|
| +{
|
| + unsigned int heap_mask = 0;
|
| + unsigned int flags = pgprot_kernel;
|
| + int err;
|
| +
|
| + BUG_ON(Alignment & (Alignment-1));
|
| +
|
| + if (Coherency == NvOsMemAttribute_WriteBack)
|
| + flags = NVMEM_HANDLE_INNER_CACHEABLE;
|
| + else
|
| + flags = NVMEM_HANDLE_WRITE_COMBINE;
|
| +
|
| + if (!NumHeaps || !Heaps)
|
| + heap_mask = (NVMEM_HEAP_SYSMEM | NVMEM_HEAP_CARVEOUT_GENERIC);
|
| + else {
|
| + unsigned int i;
|
| + for (i=0; i<NumHeaps; i++) {
|
| + switch (Heaps[i]) {
|
| + case NvRmHeap_GART:
|
| + heap_mask |= NVMEM_HEAP_IOVMM;
|
| + break;
|
| + case NvRmHeap_External:
|
| + heap_mask |= NVMEM_HEAP_SYSMEM;
|
| + break;
|
| + case NvRmHeap_ExternalCarveOut:
|
| + heap_mask |= NVMEM_HEAP_CARVEOUT_GENERIC;
|
| + break;
|
| + case NvRmHeap_IRam:
|
| + heap_mask |= NVMEM_HEAP_CARVEOUT_IRAM;
|
| + break;
|
| + default:
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + if (!heap_mask) return NvError_InsufficientMemory;
|
| +
|
| + err = _nvmap_do_alloc(&nvmap_context.init_data, (unsigned long)hMem,
|
| + heap_mask, (size_t)Alignment, flags, false, true);
|
| +
|
| + if (err) return NvError_InsufficientMemory;
|
| + return NvSuccess;
|
| +}
|
| +
|
| +void NvRmMemReadStrided(NvRmMemHandle hMem, NvU32 Offset, NvU32 SrcStride,
|
| + void *pDst, NvU32 DstStride, NvU32 ElementSize, NvU32 Count)
|
| +{
|
| + ssize_t bytes = 0;
|
| +
|
| + bytes = _nvmap_do_rw_handle((struct nvmap_handle *)hMem, true,
|
| + false, Offset, (unsigned long)pDst, SrcStride,
|
| + DstStride, ElementSize, Count);
|
| +
|
| + BUG_ON(bytes != (ssize_t)(Count*ElementSize));
|
| +}
|
| +
|
| +void NvRmMemWriteStrided(NvRmMemHandle hMem, NvU32 Offset, NvU32 DstStride,
|
| + const void *pSrc, NvU32 SrcStride, NvU32 ElementSize, NvU32 Count)
|
| +{
|
| + ssize_t bytes = 0;
|
| +
|
| + bytes = _nvmap_do_rw_handle((struct nvmap_handle *)hMem, false,
|
| + false, Offset, (unsigned long)pSrc, DstStride,
|
| + SrcStride, ElementSize, Count);
|
| +
|
| + BUG_ON(bytes != (ssize_t)(Count*ElementSize));
|
| +}
|
| +
|
| +NvU32 NvRmMemGetSize(NvRmMemHandle hMem)
|
| +{
|
| + struct nvmap_handle *h = (struct nvmap_handle *)hMem;
|
| + return h->orig_size;
|
| +}
|
| +
|
| +NvRmHeap NvRmMemGetHeapType(NvRmMemHandle hMem, NvU32 *BaseAddr)
|
| +{
|
| + struct nvmap_handle *h = (struct nvmap_handle *)hMem;
|
| + NvRmHeap heap;
|
| +
|
| + if (!h->alloc) {
|
| + *BaseAddr = ~0ul;
|
| + return (NvRmHeap)0;
|
| + }
|
| +
|
| + if (h->heap_pgalloc && !h->pgalloc.contig)
|
| + heap = NvRmHeap_GART;
|
| + else if (h->heap_pgalloc)
|
| + heap = NvRmHeap_External;
|
| + else if ((h->carveout.base & 0xf0000000ul) == 0x40000000ul)
|
| + heap = NvRmHeap_IRam;
|
| + else
|
| + heap = NvRmHeap_ExternalCarveOut;
|
| +
|
| + if (h->heap_pgalloc && h->pgalloc.contig)
|
| + *BaseAddr = (NvU32)page_to_phys(h->pgalloc.pages[0]);
|
| + else if (h->heap_pgalloc && atomic_add_return(0, &h->pin))
|
| + *BaseAddr = h->pgalloc.area->iovm_start;
|
| + else if (h->heap_pgalloc)
|
| + *BaseAddr = ~0ul;
|
| + else
|
| + *BaseAddr = (NvU32)h->carveout.base;
|
| +
|
| + return heap;
|
| +}
|
| +
|
| +void NvRmMemCacheMaint(NvRmMemHandle hMem, void *pMapping,
|
| + NvU32 Size, NvBool Writeback, NvBool Inv)
|
| +{
|
| + struct nvmap_handle *h = (struct nvmap_handle *)hMem;
|
| + unsigned long start;
|
| + unsigned int op;
|
| +
|
| + if (!h->kern_map || h->flags==NVMEM_HANDLE_UNCACHEABLE ||
|
| + h->flags==NVMEM_HANDLE_WRITE_COMBINE) return;
|
| +
|
| + if (!Writeback && !Inv) return;
|
| +
|
| + if (Writeback && Inv) op = NVMEM_CACHE_OP_WB_INV;
|
| + else if (Writeback) op = NVMEM_CACHE_OP_WB;
|
| + else op = NVMEM_CACHE_OP_INV;
|
| +
|
| + start = (unsigned long)pMapping - (unsigned long)h->kern_map;
|
| +
|
| + _nvmap_do_cache_maint(h, start, start+Size, op, true);
|
| + return;
|
| +}
|
| +
|
| +NvU32 NvRmMemGetAlignment(NvRmMemHandle hMem)
|
| +{
|
| + struct nvmap_handle *h = (struct nvmap_handle *)hMem;
|
| + return _nvmap_do_get_param(h, NVMEM_HANDLE_PARAM_ALIGNMENT);
|
| +}
|
| +
|
| +NvError NvRmMemGetStat(NvRmMemStat Stat, NvS32 *Result)
|
| +{
|
| + unsigned long total_co = 0;
|
| + unsigned long free_co = 0;
|
| + unsigned long max_free = 0;
|
| + struct nvmap_carveout_node *n;
|
| +
|
| + down_read(&nvmap_context.list_sem);
|
| + list_for_each_entry(n, &nvmap_context.heaps, heap_list) {
|
| +
|
| + if (!(n->heap_bit & NVMEM_HEAP_CARVEOUT_GENERIC)) continue;
|
| + total_co += _nvmap_carveout_blockstat(&n->carveout,
|
| + CARVEOUT_STAT_TOTAL_SIZE);
|
| + free_co += _nvmap_carveout_blockstat(&n->carveout,
|
| + CARVEOUT_STAT_FREE_SIZE);
|
| + max_free = max(max_free,
|
| + _nvmap_carveout_blockstat(&n->carveout,
|
| + CARVEOUT_STAT_LARGEST_FREE));
|
| + }
|
| + up_read(&nvmap_context.list_sem);
|
| +
|
| + if (Stat==NvRmMemStat_TotalCarveout) {
|
| + *Result = (NvU32)total_co;
|
| + return NvSuccess;
|
| + } else if (Stat==NvRmMemStat_UsedCarveout) {
|
| + *Result = (NvU32)total_co - (NvU32)free_co;
|
| + return NvSuccess;
|
| + } else if (Stat==NvRmMemStat_LargestFreeCarveoutBlock) {
|
| + *Result = (NvU32)max_free;
|
| + return NvSuccess;
|
| + }
|
| +
|
| + return NvError_BadParameter;
|
| +}
|
| +
|
| +NvU8 NvRmMemRd08(NvRmMemHandle hMem, NvU32 Offset)
|
| +{
|
| + NvU8 val;
|
| + NvRmMemRead(hMem, Offset, &val, sizeof(val));
|
| + return val;
|
| +}
|
| +
|
| +NvU16 NvRmMemRd16(NvRmMemHandle hMem, NvU32 Offset)
|
| +{
|
| + NvU16 val;
|
| + NvRmMemRead(hMem, Offset, &val, sizeof(val));
|
| + return val;
|
| +}
|
| +
|
| +NvU32 NvRmMemRd32(NvRmMemHandle hMem, NvU32 Offset)
|
| +{
|
| + NvU32 val;
|
| + NvRmMemRead(hMem, Offset, &val, sizeof(val));
|
| + return val;
|
| +}
|
| +
|
| +void NvRmMemWr08(NvRmMemHandle hMem, NvU32 Offset, NvU8 Data)
|
| +{
|
| + NvRmMemWrite(hMem, Offset, &Data, sizeof(Data));
|
| +}
|
| +
|
| +void NvRmMemWr16(NvRmMemHandle hMem, NvU32 Offset, NvU16 Data)
|
| +{
|
| + NvRmMemWrite(hMem, Offset, &Data, sizeof(Data));
|
| +}
|
| +
|
| +void NvRmMemWr32(NvRmMemHandle hMem, NvU32 Offset, NvU32 Data)
|
| +{
|
| + NvRmMemWrite(hMem, Offset, &Data, sizeof(Data));
|
| +}
|
| +
|
| +void NvRmMemRead(NvRmMemHandle hMem, NvU32 Offset, void *pDst, NvU32 Size)
|
| +{
|
| + NvRmMemReadStrided(hMem, Offset, Size, pDst, Size, Size, 1);
|
| +}
|
| +
|
| +void NvRmMemWrite(NvRmMemHandle hMem, NvU32 Offset,
|
| + const void *pSrc, NvU32 Size)
|
| +{
|
| + NvRmMemWriteStrided(hMem, Offset, Size, pSrc, Size, Size, 1);
|
| +}
|
| +
|
| +void NvRmMemMove(NvRmMemHandle dstHMem, NvU32 dstOffset,
|
| + NvRmMemHandle srcHMem, NvU32 srcOffset, NvU32 Size)
|
| +{
|
| + while (Size--) {
|
| + NvU8 tmp = NvRmMemRd08(srcHMem, srcOffset);
|
| + NvRmMemWr08(dstHMem, dstOffset, tmp);
|
| + dstOffset++;
|
| + srcOffset++;
|
| + }
|
| +}
|
| +
|
| +NvU32 NvRmMemGetCacheLineSize(void)
|
| +{
|
| + return 32;
|
| +}
|
| +
|
| +void *NvRmHostAlloc(size_t size)
|
| +{
|
| + return NvOsAlloc(size);
|
| +}
|
| +
|
| +void NvRmHostFree(void *ptr)
|
| +{
|
| + NvOsFree(ptr);
|
| +}
|
| +
|
| +NvError NvRmMemMapIntoCallerPtr(NvRmMemHandle hMem, void *pCallerPtr,
|
| + NvU32 Offset, NvU32 Size)
|
| +{
|
| + return NvError_NotSupported;
|
| +}
|
| +
|
| +NvError NvRmMemHandlePreserveHandle(NvRmMemHandle hMem, NvU32 *pKey)
|
| +{
|
| + return NvError_NotSupported;
|
| +}
|
|
|