/* 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 #include #include #include #include #include #include #include #include #include #include #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);