| /* Copyright (c) 2011, Code Aurora Forum. All rights reserved. | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License version 2 and | 
 |  * only version 2 as published by the Free Software Foundation. | 
 |  * | 
 |  * This program is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |  * GNU General Public License for more details. | 
 |  */ | 
 |  | 
 | #include <asm/page.h> | 
 | #include <linux/io.h> | 
 | #include <linux/memory_alloc.h> | 
 | #include <linux/mm.h> | 
 | #include <linux/vmalloc.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/module.h> | 
 | #include <linux/err.h> | 
 | #include <linux/log2.h> | 
 | #include <linux/debugfs.h> | 
 | #include <linux/seq_file.h> | 
 |  | 
 |  | 
 | #define MAX_MEMPOOLS 8 | 
 |  | 
 | struct mem_pool mpools[MAX_MEMPOOLS]; | 
 |  | 
 | /* The tree contains all allocations over all memory pools */ | 
 | static struct rb_root alloc_root; | 
 | static struct mutex alloc_mutex; | 
 |  | 
 | static void *s_start(struct seq_file *m, loff_t *pos) | 
 | 	__acquires(&alloc_mutex) | 
 | { | 
 | 	loff_t n = *pos; | 
 | 	struct rb_node *r; | 
 |  | 
 | 	mutex_lock(&alloc_mutex); | 
 | 	r = rb_first(&alloc_root); | 
 |  | 
 | 	while (n > 0 && r) { | 
 | 		n--; | 
 | 		r = rb_next(r); | 
 | 	} | 
 | 	if (!n) | 
 | 		return r; | 
 | 	return NULL; | 
 | } | 
 |  | 
 | static void *s_next(struct seq_file *m, void *p, loff_t *pos) | 
 | { | 
 | 	struct rb_node *r = p; | 
 | 	++*pos; | 
 | 	return rb_next(r); | 
 | } | 
 |  | 
 | static void s_stop(struct seq_file *m, void *p) | 
 | 	__releases(&alloc_mutex) | 
 | { | 
 | 	mutex_unlock(&alloc_mutex); | 
 | } | 
 |  | 
 | static int s_show(struct seq_file *m, void *p) | 
 | { | 
 | 	struct rb_node *r = p; | 
 | 	struct alloc *node = rb_entry(r, struct alloc, rb_node); | 
 |  | 
 | 	seq_printf(m, "0x%lx 0x%p %ld %u %pS\n", node->paddr, node->vaddr, | 
 | 		   node->len, node->mpool->id, node->caller); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct seq_operations mempool_op = { | 
 | 	.start = s_start, | 
 | 	.next = s_next, | 
 | 	.stop = s_stop, | 
 | 	.show = s_show, | 
 | }; | 
 |  | 
 | static int mempool_open(struct inode *inode, struct file *file) | 
 | { | 
 | 	return seq_open(file, &mempool_op); | 
 | } | 
 |  | 
 | static struct alloc *find_alloc(void *addr) | 
 | { | 
 | 	struct rb_root *root = &alloc_root; | 
 | 	struct rb_node *p = root->rb_node; | 
 |  | 
 | 	mutex_lock(&alloc_mutex); | 
 |  | 
 | 	while (p) { | 
 | 		struct alloc *node; | 
 |  | 
 | 		node = rb_entry(p, struct alloc, rb_node); | 
 | 		if (addr < node->vaddr) | 
 | 			p = p->rb_left; | 
 | 		else if (addr > node->vaddr) | 
 | 			p = p->rb_right; | 
 | 		else { | 
 | 			mutex_unlock(&alloc_mutex); | 
 | 			return node; | 
 | 		} | 
 | 	} | 
 | 	mutex_unlock(&alloc_mutex); | 
 | 	return NULL; | 
 | } | 
 |  | 
 | static int add_alloc(struct alloc *node) | 
 | { | 
 | 	struct rb_root *root = &alloc_root; | 
 | 	struct rb_node **p = &root->rb_node; | 
 | 	struct rb_node *parent = NULL; | 
 |  | 
 | 	mutex_lock(&alloc_mutex); | 
 | 	while (*p) { | 
 | 		struct alloc *tmp; | 
 | 		parent = *p; | 
 |  | 
 | 		tmp = rb_entry(parent, struct alloc, rb_node); | 
 |  | 
 | 		if (node->vaddr < tmp->vaddr) | 
 | 			p = &(*p)->rb_left; | 
 | 		else if (node->vaddr > tmp->vaddr) | 
 | 			p = &(*p)->rb_right; | 
 | 		else { | 
 | 			WARN(1, "memory at %p already allocated", tmp->vaddr); | 
 | 			mutex_unlock(&alloc_mutex); | 
 | 			return -EINVAL; | 
 | 		} | 
 | 	} | 
 | 	rb_link_node(&node->rb_node, parent, p); | 
 | 	rb_insert_color(&node->rb_node, root); | 
 | 	mutex_unlock(&alloc_mutex); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int remove_alloc(struct alloc *victim_node) | 
 | { | 
 | 	struct rb_root *root = &alloc_root; | 
 | 	if (!victim_node) | 
 | 		return -EINVAL; | 
 |  | 
 | 	mutex_lock(&alloc_mutex); | 
 | 	rb_erase(&victim_node->rb_node, root); | 
 | 	mutex_unlock(&alloc_mutex); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct gen_pool *initialize_gpool(unsigned long start, | 
 | 	unsigned long size) | 
 | { | 
 | 	struct gen_pool *gpool; | 
 |  | 
 | 	gpool = gen_pool_create(PAGE_SHIFT, -1); | 
 |  | 
 | 	if (!gpool) | 
 | 		return NULL; | 
 | 	if (gen_pool_add(gpool, start, size, -1)) { | 
 | 		gen_pool_destroy(gpool); | 
 | 		return NULL; | 
 | 	} | 
 |  | 
 | 	return gpool; | 
 | } | 
 |  | 
 | static void *__alloc(struct mem_pool *mpool, unsigned long size, | 
 | 	unsigned long align, int cached, void *caller) | 
 | { | 
 | 	unsigned long paddr; | 
 | 	void __iomem *vaddr; | 
 |  | 
 | 	unsigned long aligned_size; | 
 | 	int log_align = ilog2(align); | 
 |  | 
 | 	struct alloc *node; | 
 |  | 
 | 	aligned_size = PFN_ALIGN(size); | 
 | 	paddr = gen_pool_alloc_aligned(mpool->gpool, aligned_size, log_align); | 
 | 	if (!paddr) | 
 | 		return NULL; | 
 |  | 
 | 	node = kmalloc(sizeof(struct alloc), GFP_KERNEL); | 
 | 	if (!node) | 
 | 		goto out; | 
 |  | 
 | 	if (cached) | 
 | 		vaddr = ioremap_cached(paddr, aligned_size); | 
 | 	else | 
 | 		vaddr = ioremap(paddr, aligned_size); | 
 |  | 
 | 	if (!vaddr) | 
 | 		goto out_kfree; | 
 |  | 
 | 	node->vaddr = vaddr; | 
 | 	node->paddr = paddr; | 
 | 	node->len = aligned_size; | 
 | 	node->mpool = mpool; | 
 | 	node->caller = caller; | 
 | 	if (add_alloc(node)) | 
 | 		goto out_kfree; | 
 |  | 
 | 	mpool->free -= aligned_size; | 
 |  | 
 | 	return vaddr; | 
 | out_kfree: | 
 | 	if (vaddr) | 
 | 		iounmap(vaddr); | 
 | 	kfree(node); | 
 | out: | 
 | 	gen_pool_free(mpool->gpool, paddr, aligned_size); | 
 | 	return NULL; | 
 | } | 
 |  | 
 | static void __free(void *vaddr, bool unmap) | 
 | { | 
 | 	struct alloc *node = find_alloc(vaddr); | 
 |  | 
 | 	if (!node) | 
 | 		return; | 
 |  | 
 | 	if (unmap) | 
 | 		iounmap(node->vaddr); | 
 |  | 
 | 	gen_pool_free(node->mpool->gpool, node->paddr, node->len); | 
 | 	node->mpool->free += node->len; | 
 |  | 
 | 	remove_alloc(node); | 
 | 	kfree(node); | 
 | } | 
 |  | 
 | static struct mem_pool *mem_type_to_memory_pool(int mem_type) | 
 | { | 
 | 	struct mem_pool *mpool = &mpools[mem_type]; | 
 |  | 
 | 	if (!mpool->size) | 
 | 		return NULL; | 
 |  | 
 | 	mutex_lock(&mpool->pool_mutex); | 
 | 	if (!mpool->gpool) | 
 | 		mpool->gpool = initialize_gpool(mpool->paddr, mpool->size); | 
 | 	mutex_unlock(&mpool->pool_mutex); | 
 | 	if (!mpool->gpool) | 
 | 		return NULL; | 
 |  | 
 | 	return mpool; | 
 | } | 
 |  | 
 | struct mem_pool *initialize_memory_pool(unsigned long start, | 
 | 	unsigned long size, int mem_type) | 
 | { | 
 | 	int id = mem_type; | 
 |  | 
 | 	if (id >= MAX_MEMPOOLS || size <= PAGE_SIZE || size % PAGE_SIZE) | 
 | 		return NULL; | 
 |  | 
 | 	mutex_lock(&mpools[id].pool_mutex); | 
 |  | 
 | 	mpools[id].paddr = start; | 
 | 	mpools[id].size = size; | 
 | 	mpools[id].free = size; | 
 | 	mpools[id].id = id; | 
 | 	mutex_unlock(&mpools[id].pool_mutex); | 
 |  | 
 | 	pr_info("memory pool %d (start %lx size %lx) initialized\n", | 
 | 		id, start, size); | 
 | 	return &mpools[id]; | 
 | } | 
 | EXPORT_SYMBOL_GPL(initialize_memory_pool); | 
 |  | 
 | void *allocate_contiguous_memory(unsigned long size, | 
 | 	int mem_type, unsigned long align, int cached) | 
 | { | 
 | 	unsigned long aligned_size = PFN_ALIGN(size); | 
 | 	struct mem_pool *mpool; | 
 |  | 
 | 	mpool = mem_type_to_memory_pool(mem_type); | 
 | 	if (!mpool) | 
 | 		return NULL; | 
 | 	return __alloc(mpool, aligned_size, align, cached, | 
 | 		__builtin_return_address(0)); | 
 |  | 
 | } | 
 | EXPORT_SYMBOL_GPL(allocate_contiguous_memory); | 
 |  | 
 | unsigned long _allocate_contiguous_memory_nomap(unsigned long size, | 
 | 	int mem_type, unsigned long align, void *caller) | 
 | { | 
 | 	unsigned long paddr; | 
 | 	unsigned long aligned_size; | 
 |  | 
 | 	struct alloc *node; | 
 | 	struct mem_pool *mpool; | 
 | 	int log_align = ilog2(align); | 
 |  | 
 | 	mpool = mem_type_to_memory_pool(mem_type); | 
 | 	if (!mpool || !mpool->gpool) | 
 | 		return 0; | 
 |  | 
 | 	aligned_size = PFN_ALIGN(size); | 
 | 	paddr = gen_pool_alloc_aligned(mpool->gpool, aligned_size, log_align); | 
 | 	if (!paddr) | 
 | 		return 0; | 
 |  | 
 | 	node = kmalloc(sizeof(struct alloc), GFP_KERNEL); | 
 | 	if (!node) | 
 | 		goto out; | 
 |  | 
 | 	node->paddr = paddr; | 
 |  | 
 | 	/* We search the tree using node->vaddr, so set | 
 | 	 * it to something unique even though we don't | 
 | 	 * use it for physical allocation nodes. | 
 | 	 * The virtual and physical address ranges | 
 | 	 * are disjoint, so there won't be any chance of | 
 | 	 * a duplicate node->vaddr value. | 
 | 	 */ | 
 | 	node->vaddr = (void *)paddr; | 
 | 	node->len = aligned_size; | 
 | 	node->mpool = mpool; | 
 | 	node->caller = caller; | 
 | 	if (add_alloc(node)) | 
 | 		goto out_kfree; | 
 |  | 
 | 	mpool->free -= aligned_size; | 
 | 	return paddr; | 
 | out_kfree: | 
 | 	kfree(node); | 
 | out: | 
 | 	gen_pool_free(mpool->gpool, paddr, aligned_size); | 
 | 	return 0; | 
 | } | 
 | EXPORT_SYMBOL_GPL(_allocate_contiguous_memory_nomap); | 
 |  | 
 | unsigned long allocate_contiguous_memory_nomap(unsigned long size, | 
 | 	int mem_type, unsigned long align) | 
 | { | 
 | 	return _allocate_contiguous_memory_nomap(size, mem_type, align, | 
 | 		__builtin_return_address(0)); | 
 | } | 
 | EXPORT_SYMBOL_GPL(allocate_contiguous_memory_nomap); | 
 |  | 
 | void free_contiguous_memory(void *addr) | 
 | { | 
 | 	if (!addr) | 
 | 		return; | 
 | 	__free(addr, true); | 
 | 	return; | 
 | } | 
 | EXPORT_SYMBOL_GPL(free_contiguous_memory); | 
 |  | 
 | void free_contiguous_memory_by_paddr(unsigned long paddr) | 
 | { | 
 | 	if (!paddr) | 
 | 		return; | 
 | 	__free((void *)paddr, false); | 
 | 	return; | 
 | } | 
 | EXPORT_SYMBOL_GPL(free_contiguous_memory_by_paddr); | 
 |  | 
 | unsigned long memory_pool_node_paddr(void *vaddr) | 
 | { | 
 | 	struct alloc *node = find_alloc(vaddr); | 
 |  | 
 | 	if (!node) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return node->paddr; | 
 | } | 
 | EXPORT_SYMBOL_GPL(memory_pool_node_paddr); | 
 |  | 
 | unsigned long memory_pool_node_len(void *vaddr) | 
 | { | 
 | 	struct alloc *node = find_alloc(vaddr); | 
 |  | 
 | 	if (!node) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return node->len; | 
 | } | 
 | EXPORT_SYMBOL_GPL(memory_pool_node_len); | 
 |  | 
 | static const struct file_operations mempool_operations = { | 
 | 	.owner		= THIS_MODULE, | 
 | 	.open           = mempool_open, | 
 | 	.read           = seq_read, | 
 | 	.llseek         = seq_lseek, | 
 | 	.release        = seq_release_private, | 
 | }; | 
 |  | 
 | int __init memory_pool_init(void) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	alloc_root = RB_ROOT; | 
 | 	mutex_init(&alloc_mutex); | 
 | 	for (i = 0; i < ARRAY_SIZE(mpools); i++) { | 
 | 		mutex_init(&mpools[i].pool_mutex); | 
 | 		mpools[i].gpool = NULL; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int __init debugfs_mempool_init(void) | 
 | { | 
 | 	struct dentry *entry, *dir = debugfs_create_dir("mempool", NULL); | 
 |  | 
 | 	if (!dir) { | 
 | 		pr_err("Cannot create /sys/kernel/debug/mempool"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	entry = debugfs_create_file("map", S_IRUSR, dir, | 
 | 		NULL, &mempool_operations); | 
 |  | 
 | 	if (!entry) | 
 | 		pr_err("Cannot create /sys/kernel/debug/mempool/map"); | 
 |  | 
 | 	return entry ? 0 : -EINVAL; | 
 | } | 
 |  | 
 | module_init(debugfs_mempool_init); |