| /* |
| * Contiguous Memory Allocator framework |
| * Copyright (c) 2010 by Samsung Electronics. |
| * Written by Michal Nazarewicz (m.nazarewicz@samsung.com) |
| * |
| * 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 optional) any later version of the license. |
| */ |
| |
| /* |
| * See Documentation/contiguous-memory.txt for details. |
| */ |
| |
| #define pr_fmt(fmt) "cma: " fmt |
| |
| #ifdef CONFIG_CMA_DEBUG |
| # define DEBUG |
| #endif |
| |
| #ifndef CONFIG_NO_BOOTMEM |
| # include <linux/bootmem.h> /* alloc_bootmem_pages_nopanic() */ |
| #endif |
| #ifdef CONFIG_HAVE_MEMBLOCK |
| # include <linux/memblock.h> /* memblock*() */ |
| #endif |
| #include <linux/device.h> /* struct device, dev_name() */ |
| #include <linux/errno.h> /* Error numbers */ |
| #include <linux/err.h> /* IS_ERR, PTR_ERR, etc. */ |
| #include <linux/mm.h> /* PAGE_ALIGN() */ |
| #include <linux/module.h> /* EXPORT_SYMBOL_GPL() */ |
| #include <linux/mutex.h> /* mutex */ |
| #include <linux/slab.h> /* kmalloc() */ |
| #include <linux/string.h> /* str*() */ |
| |
| #include <linux/cma.h> |
| #include <linux/vmalloc.h> |
| |
| /* |
| * Protects cma_regions, cma_allocators, cma_map, cma_map_length, |
| * cma_kobj, cma_sysfs_regions and cma_chunks_by_start. |
| */ |
| static DEFINE_MUTEX(cma_mutex); |
| |
| |
| |
| /************************* Map attribute *************************/ |
| |
| static const char *cma_map; |
| static size_t cma_map_length; |
| |
| /* |
| * map-attr ::= [ rules [ ';' ] ] |
| * rules ::= rule [ ';' rules ] |
| * rule ::= patterns '=' regions |
| * patterns ::= pattern [ ',' patterns ] |
| * regions ::= REG-NAME [ ',' regions ] |
| * pattern ::= dev-pattern [ '/' TYPE-NAME ] | '/' TYPE-NAME |
| * |
| * See Documentation/contiguous-memory.txt for details. |
| */ |
| static ssize_t cma_map_validate(const char *param) |
| { |
| const char *ch = param; |
| |
| if (*ch == '\0' || *ch == '\n') |
| return 0; |
| |
| for (;;) { |
| const char *start = ch; |
| |
| while (*ch && *ch != '\n' && *ch != ';' && *ch != '=') |
| ++ch; |
| |
| if (*ch != '=' || start == ch) { |
| pr_err("map: expecting \"<patterns>=<regions>\" near %s\n", |
| start); |
| return -EINVAL; |
| } |
| |
| while (*++ch != ';') |
| if (*ch == '\0' || *ch == '\n') |
| return ch - param; |
| if (ch[1] == '\0' || ch[1] == '\n') |
| return ch - param; |
| ++ch; |
| } |
| } |
| |
| static int __init cma_map_param(char *param) |
| { |
| ssize_t len; |
| |
| pr_debug("param: map: %s\n", param); |
| |
| len = cma_map_validate(param); |
| if (len < 0) |
| return len; |
| |
| cma_map = param; |
| cma_map_length = len; |
| return 0; |
| } |
| |
| #if defined CONFIG_CMA_CMDLINE |
| |
| early_param("cma.map", cma_map_param); |
| |
| #endif |
| |
| |
| |
| /************************* Early regions *************************/ |
| |
| struct list_head cma_early_regions __initdata = |
| LIST_HEAD_INIT(cma_early_regions); |
| |
| #ifdef CONFIG_CMA_CMDLINE |
| |
| /* |
| * regions-attr ::= [ regions [ ';' ] ] |
| * regions ::= region [ ';' regions ] |
| * |
| * region ::= [ '-' ] reg-name |
| * '=' size |
| * [ '@' start ] |
| * [ '/' alignment ] |
| * [ ':' alloc-name ] |
| * |
| * See Documentation/contiguous-memory.txt for details. |
| * |
| * Example: |
| * cma=reg1=64M:bf;reg2=32M@0x100000:bf;reg3=64M/1M:bf |
| * |
| * If allocator is ommited the first available allocater will be used. |
| */ |
| |
| #define NUMPARSE(cond_ch, type, cond) ({ \ |
| unsigned long long v = 0; \ |
| if (*param == (cond_ch)) { \ |
| const char *const msg = param + 1; \ |
| v = memparse(msg, ¶m); \ |
| if (!v || v > ~(type)0 || !(cond)) { \ |
| pr_err("param: invalid value near %s\n", msg); \ |
| ret = -EINVAL; \ |
| break; \ |
| } \ |
| } \ |
| v; \ |
| }) |
| |
| static int __init cma_param_parse(char *param) |
| { |
| static struct cma_region regions[16]; |
| |
| size_t left = ARRAY_SIZE(regions); |
| struct cma_region *reg = regions; |
| int ret = 0; |
| |
| pr_debug("param: %s\n", param); |
| |
| for (; *param; ++reg) { |
| dma_addr_t start, alignment; |
| size_t size; |
| |
| if (unlikely(!--left)) { |
| pr_err("param: too many early regions\n"); |
| return -ENOSPC; |
| } |
| |
| /* Parse name */ |
| reg->name = param; |
| param = strchr(param, '='); |
| if (!param || param == reg->name) { |
| pr_err("param: expected \"<name>=\" near %s\n", |
| reg->name); |
| ret = -EINVAL; |
| break; |
| } |
| *param = '\0'; |
| |
| /* Parse numbers */ |
| size = NUMPARSE('\0', size_t, true); |
| start = NUMPARSE('@', dma_addr_t, true); |
| alignment = NUMPARSE('/', dma_addr_t, (v & (v - 1)) == 0); |
| |
| alignment = max(alignment, (dma_addr_t)PAGE_SIZE); |
| start = ALIGN(start, alignment); |
| size = PAGE_ALIGN(size); |
| if (start + size < start) { |
| pr_err("param: invalid start, size combination\n"); |
| ret = -EINVAL; |
| break; |
| } |
| |
| /* Parse allocator */ |
| if (*param == ':') { |
| reg->alloc_name = ++param; |
| while (*param && *param != ';') |
| ++param; |
| if (param == reg->alloc_name) |
| reg->alloc_name = NULL; |
| } |
| |
| /* Go to next */ |
| if (*param == ';') { |
| *param = '\0'; |
| ++param; |
| } else if (*param) { |
| pr_err("param: expecting ';' or end of parameter near %s\n", |
| param); |
| ret = -EINVAL; |
| break; |
| } |
| |
| /* Add */ |
| reg->size = size; |
| reg->start = start; |
| reg->alignment = alignment; |
| reg->copy_name = 1; |
| |
| list_add_tail(®->list, &cma_early_regions); |
| |
| pr_debug("param: registering early region %s (%p@%p/%p)\n", |
| reg->name, (void *)reg->size, (void *)reg->start, |
| (void *)reg->alignment); |
| } |
| |
| return ret; |
| } |
| early_param("cma", cma_param_parse); |
| |
| #undef NUMPARSE |
| |
| #endif |
| |
| |
| int __init __must_check cma_early_region_register(struct cma_region *reg) |
| { |
| dma_addr_t start, alignment; |
| size_t size; |
| |
| if (reg->alignment & (reg->alignment - 1)) |
| return -EINVAL; |
| |
| alignment = max(reg->alignment, (dma_addr_t)PAGE_SIZE); |
| start = ALIGN(reg->start, alignment); |
| size = PAGE_ALIGN(reg->size); |
| |
| if (start + size < start) |
| return -EINVAL; |
| |
| reg->size = size; |
| reg->start = start; |
| reg->alignment = alignment; |
| |
| list_add_tail(®->list, &cma_early_regions); |
| |
| pr_debug("param: registering early region %s (%p@%p/%p)\n", |
| reg->name, (void *)reg->size, (void *)reg->start, |
| (void *)reg->alignment); |
| |
| return 0; |
| } |
| |
| |
| |
| /************************* Regions & Allocators *************************/ |
| |
| static void __cma_sysfs_region_add(struct cma_region *reg); |
| |
| static int __cma_region_attach_alloc(struct cma_region *reg); |
| static void __maybe_unused __cma_region_detach_alloc(struct cma_region *reg); |
| |
| |
| /* List of all regions. Named regions are kept before unnamed. */ |
| static LIST_HEAD(cma_regions); |
| |
| #define cma_foreach_region(reg) \ |
| list_for_each_entry(reg, &cma_regions, list) |
| |
| int __must_check cma_region_register(struct cma_region *reg) |
| { |
| const char *name, *alloc_name; |
| struct cma_region *r; |
| char *ch = NULL; |
| int ret = 0; |
| |
| if (!reg->size || reg->start + reg->size < reg->start) |
| return -EINVAL; |
| |
| reg->users = 0; |
| reg->used = 0; |
| reg->private_data = NULL; |
| reg->registered = 0; |
| reg->free_space = reg->size; |
| |
| /* Copy name and alloc_name */ |
| name = reg->name; |
| alloc_name = reg->alloc_name; |
| if (reg->copy_name && (reg->name || reg->alloc_name)) { |
| size_t name_size, alloc_size; |
| |
| name_size = reg->name ? strlen(reg->name) + 1 : 0; |
| alloc_size = reg->alloc_name ? strlen(reg->alloc_name) + 1 : 0; |
| |
| ch = kmalloc(name_size + alloc_size, GFP_KERNEL); |
| if (!ch) { |
| pr_err("%s: not enough memory to allocate name\n", |
| reg->name ?: "(private)"); |
| return -ENOMEM; |
| } |
| |
| if (name_size) { |
| memcpy(ch, reg->name, name_size); |
| name = ch; |
| ch += name_size; |
| } |
| |
| if (alloc_size) { |
| memcpy(ch, reg->alloc_name, alloc_size); |
| alloc_name = ch; |
| } |
| } |
| |
| mutex_lock(&cma_mutex); |
| |
| /* Don't let regions overlap */ |
| cma_foreach_region(r) |
| if (r->start + r->size > reg->start && |
| r->start < reg->start + reg->size) { |
| ret = -EADDRINUSE; |
| goto done; |
| } |
| |
| if (reg->alloc) { |
| ret = __cma_region_attach_alloc(reg); |
| if (unlikely(ret < 0)) |
| goto done; |
| } |
| |
| reg->name = name; |
| reg->alloc_name = alloc_name; |
| reg->registered = 1; |
| ch = NULL; |
| |
| /* |
| * Keep named at the beginning and unnamed (private) at the |
| * end. This helps in traversal when named region is looked |
| * for. |
| */ |
| if (name) |
| list_add(®->list, &cma_regions); |
| else |
| list_add_tail(®->list, &cma_regions); |
| |
| __cma_sysfs_region_add(reg); |
| |
| done: |
| mutex_unlock(&cma_mutex); |
| |
| pr_debug("%s: region %sregistered\n", |
| reg->name ?: "(private)", ret ? "not " : ""); |
| kfree(ch); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(cma_region_register); |
| |
| static struct cma_region *__must_check |
| __cma_region_find(const char **namep) |
| { |
| struct cma_region *reg; |
| const char *ch, *name; |
| size_t n; |
| |
| ch = *namep; |
| while (*ch && *ch != ',' && *ch != ';') |
| ++ch; |
| name = *namep; |
| *namep = *ch == ',' ? ch + 1 : ch; |
| n = ch - name; |
| |
| /* |
| * Named regions are kept in front of unnamed so if we |
| * encounter unnamed region we can stop. |
| */ |
| cma_foreach_region(reg) |
| if (!reg->name) |
| break; |
| else if (!strncmp(name, reg->name, n) && !reg->name[n]) |
| return reg; |
| |
| return NULL; |
| } |
| |
| |
| /* List of all allocators. */ |
| static LIST_HEAD(cma_allocators); |
| |
| #define cma_foreach_allocator(alloc) \ |
| list_for_each_entry(alloc, &cma_allocators, list) |
| |
| int cma_allocator_register(struct cma_allocator *alloc) |
| { |
| struct cma_region *reg; |
| int first; |
| |
| if (!alloc->alloc || !alloc->free) |
| return -EINVAL; |
| |
| mutex_lock(&cma_mutex); |
| |
| first = list_empty(&cma_allocators); |
| |
| list_add_tail(&alloc->list, &cma_allocators); |
| |
| /* |
| * Attach this allocator to all allocator-less regions that |
| * request this particular allocator (reg->alloc_name equals |
| * alloc->name) or if region wants the first available |
| * allocator and we are the first. |
| */ |
| cma_foreach_region(reg) { |
| if (reg->alloc) |
| continue; |
| if (reg->alloc_name |
| ? alloc->name && !strcmp(alloc->name, reg->alloc_name) |
| : (!reg->used && first)) |
| continue; |
| |
| reg->alloc = alloc; |
| __cma_region_attach_alloc(reg); |
| } |
| |
| mutex_unlock(&cma_mutex); |
| |
| pr_debug("%s: allocator registered\n", alloc->name ?: "(unnamed)"); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(cma_allocator_register); |
| |
| static struct cma_allocator *__must_check |
| __cma_allocator_find(const char *name) |
| { |
| struct cma_allocator *alloc; |
| |
| if (!name) |
| return list_empty(&cma_allocators) |
| ? NULL |
| : list_entry(cma_allocators.next, |
| struct cma_allocator, list); |
| |
| cma_foreach_allocator(alloc) |
| if (alloc->name && !strcmp(name, alloc->name)) |
| return alloc; |
| |
| return NULL; |
| } |
| |
| |
| |
| /************************* Initialise CMA *************************/ |
| |
| int __init cma_set_defaults(struct cma_region *regions, const char *map) |
| { |
| if (map) { |
| int ret = cma_map_param((char *)map); |
| if (unlikely(ret < 0)) |
| return ret; |
| } |
| |
| if (!regions) |
| return 0; |
| |
| for (; regions->size; ++regions) { |
| int ret = cma_early_region_register(regions); |
| if (unlikely(ret < 0)) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| |
| int __init cma_early_region_reserve(struct cma_region *reg) |
| { |
| int tried = 0; |
| |
| if (!reg->size || (reg->alignment & (reg->alignment - 1)) || |
| reg->reserved) |
| return -EINVAL; |
| |
| #ifndef CONFIG_NO_BOOTMEM |
| |
| tried = 1; |
| |
| { |
| void *ptr = __alloc_bootmem_nopanic(reg->size, reg->alignment, |
| reg->start); |
| if (ptr) { |
| reg->start = virt_to_phys(ptr); |
| reg->reserved = 1; |
| return 0; |
| } |
| } |
| |
| #endif |
| |
| #ifdef CONFIG_HAVE_MEMBLOCK |
| |
| tried = 1; |
| |
| if (reg->start) { |
| if (!memblock_is_region_reserved(reg->start, reg->size) && |
| memblock_reserve(reg->start, reg->size) >= 0) { |
| reg->reserved = 1; |
| return 0; |
| } |
| } else { |
| /* |
| * Use __memblock_alloc_base() since |
| * memblock_alloc_base() panic()s. |
| */ |
| u64 ret = __memblock_alloc_base(reg->size, reg->alignment, 0); |
| if (ret && |
| ret < ~(dma_addr_t)0 && |
| ret + reg->size < ~(dma_addr_t)0 && |
| ret + reg->size > ret) { |
| reg->start = ret; |
| reg->reserved = 1; |
| return 0; |
| } |
| |
| if (ret) |
| memblock_free(ret, reg->size); |
| } |
| |
| #endif |
| |
| return tried ? -ENOMEM : -EOPNOTSUPP; |
| } |
| |
| void __init cma_early_regions_reserve(int (*reserve)(struct cma_region *reg)) |
| { |
| struct cma_region *reg; |
| |
| pr_debug("init: reserving early regions\n"); |
| |
| if (!reserve) |
| reserve = cma_early_region_reserve; |
| |
| list_for_each_entry(reg, &cma_early_regions, list) { |
| if (reg->reserved) { |
| /* nothing */ |
| } else if (reserve(reg) >= 0) { |
| pr_debug("init: %s: reserved %p@%p\n", |
| reg->name ?: "(private)", |
| (void *)reg->size, (void *)reg->start); |
| reg->reserved = 1; |
| } else { |
| pr_warn("init: %s: unable to reserve %p@%p/%p\n", |
| reg->name ?: "(private)", |
| (void *)reg->size, (void *)reg->start, |
| (void *)reg->alignment); |
| } |
| } |
| } |
| |
| |
| static int __init cma_init(void) |
| { |
| struct cma_region *reg, *n; |
| |
| pr_debug("init: initialising\n"); |
| |
| if (cma_map) { |
| char *val = kmemdup(cma_map, cma_map_length + 1, GFP_KERNEL); |
| cma_map = val; |
| if (!val) |
| return -ENOMEM; |
| val[cma_map_length] = '\0'; |
| } |
| |
| list_for_each_entry_safe(reg, n, &cma_early_regions, list) { |
| INIT_LIST_HEAD(®->list); |
| /* |
| * We don't care if there was an error. It's a pity |
| * but there's not much we can do about it any way. |
| * If the error is on a region that was parsed from |
| * command line then it will stay and waste a bit of |
| * space; if it was registered using |
| * cma_early_region_register() it's caller's |
| * responsibility to do something about it. |
| */ |
| if (reg->reserved && cma_region_register(reg) < 0) |
| /* ignore error */; |
| } |
| |
| INIT_LIST_HEAD(&cma_early_regions); |
| |
| return 0; |
| } |
| /* |
| * We want to be initialised earlier than module_init/__initcall so |
| * that drivers that want to grab memory at boot time will get CMA |
| * ready. subsys_initcall() seems early enough and not too early at |
| * the same time. |
| */ |
| subsys_initcall(cma_init); |
| |
| |
| |
| /************************* SysFS *************************/ |
| |
| #if defined CONFIG_CMA_SYSFS |
| |
| static struct kobject cma_sysfs_regions; |
| static int cma_sysfs_regions_ready; |
| |
| |
| #define CMA_ATTR_INLINE(_type, _name) \ |
| (&((struct cma_ ## _type ## _attribute){ \ |
| .attr = { \ |
| .name = __stringify(_name), \ |
| .mode = 0644, \ |
| }, \ |
| .show = cma_sysfs_ ## _type ## _ ## _name ## _show, \ |
| .store = cma_sysfs_ ## _type ## _ ## _name ## _store, \ |
| }).attr) |
| |
| #define CMA_ATTR_RO_INLINE(_type, _name) \ |
| (&((struct cma_ ## _type ## _attribute){ \ |
| .attr = { \ |
| .name = __stringify(_name), \ |
| .mode = 0444, \ |
| }, \ |
| .show = cma_sysfs_ ## _type ## _ ## _name ## _show, \ |
| }).attr) |
| |
| |
| struct cma_root_attribute { |
| struct attribute attr; |
| ssize_t (*show)(char *buf); |
| int (*store)(const char *buf); |
| }; |
| |
| static ssize_t cma_sysfs_root_map_show(char *page) |
| { |
| ssize_t len; |
| |
| len = cma_map_length; |
| if (!len) { |
| *page = 0; |
| len = 0; |
| } else { |
| if (len > (size_t)PAGE_SIZE - 1) |
| len = (size_t)PAGE_SIZE - 1; |
| memcpy(page, cma_map, len); |
| page[len++] = '\n'; |
| } |
| |
| return len; |
| } |
| |
| static int cma_sysfs_root_map_store(const char *page) |
| { |
| ssize_t len = cma_map_validate(page); |
| char *val = NULL; |
| |
| if (len < 0) |
| return len; |
| |
| if (len) { |
| val = kmemdup(page, len + 1, GFP_KERNEL); |
| if (!val) |
| return -ENOMEM; |
| val[len] = '\0'; |
| } |
| |
| kfree(cma_map); |
| cma_map = val; |
| cma_map_length = len; |
| |
| return 0; |
| } |
| |
| static ssize_t cma_sysfs_root_allocators_show(char *page) |
| { |
| struct cma_allocator *alloc; |
| size_t left = PAGE_SIZE; |
| char *ch = page; |
| |
| cma_foreach_allocator(alloc) { |
| ssize_t l = snprintf(ch, left, "%s ", alloc->name ?: "-"); |
| ch += l; |
| left -= l; |
| } |
| |
| if (ch != page) |
| ch[-1] = '\n'; |
| return ch - page; |
| } |
| |
| static ssize_t |
| cma_sysfs_root_show(struct kobject *kobj, struct attribute *attr, char *buf) |
| { |
| struct cma_root_attribute *rattr = |
| container_of(attr, struct cma_root_attribute, attr); |
| ssize_t ret; |
| |
| mutex_lock(&cma_mutex); |
| ret = rattr->show(buf); |
| mutex_unlock(&cma_mutex); |
| |
| return ret; |
| } |
| |
| static ssize_t |
| cma_sysfs_root_store(struct kobject *kobj, struct attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct cma_root_attribute *rattr = |
| container_of(attr, struct cma_root_attribute, attr); |
| int ret; |
| |
| mutex_lock(&cma_mutex); |
| ret = rattr->store(buf); |
| mutex_unlock(&cma_mutex); |
| |
| return ret < 0 ? ret : count; |
| } |
| |
| static struct kobj_type cma_sysfs_root_type = { |
| .sysfs_ops = &(const struct sysfs_ops){ |
| .show = cma_sysfs_root_show, |
| .store = cma_sysfs_root_store, |
| }, |
| .default_attrs = (struct attribute * []) { |
| CMA_ATTR_INLINE(root, map), |
| CMA_ATTR_RO_INLINE(root, allocators), |
| NULL |
| }, |
| }; |
| |
| static int __init cma_sysfs_init(void) |
| { |
| static struct kobject root; |
| static struct kobj_type fake_type; |
| |
| struct cma_region *reg; |
| int ret; |
| |
| /* Root */ |
| ret = kobject_init_and_add(&root, &cma_sysfs_root_type, |
| mm_kobj, "contiguous"); |
| if (unlikely(ret < 0)) { |
| pr_err("init: unable to add root kobject: %d\n", ret); |
| return ret; |
| } |
| |
| /* Regions */ |
| ret = kobject_init_and_add(&cma_sysfs_regions, &fake_type, |
| &root, "regions"); |
| if (unlikely(ret < 0)) { |
| pr_err("init: unable to add regions kobject: %d\n", ret); |
| return ret; |
| } |
| |
| mutex_lock(&cma_mutex); |
| cma_sysfs_regions_ready = 1; |
| cma_foreach_region(reg) |
| __cma_sysfs_region_add(reg); |
| mutex_unlock(&cma_mutex); |
| |
| return 0; |
| } |
| device_initcall(cma_sysfs_init); |
| |
| |
| |
| struct cma_region_attribute { |
| struct attribute attr; |
| ssize_t (*show)(struct cma_region *reg, char *buf); |
| int (*store)(struct cma_region *reg, const char *buf); |
| }; |
| |
| |
| static ssize_t cma_sysfs_region_name_show(struct cma_region *reg, char *page) |
| { |
| return reg->name ? snprintf(page, PAGE_SIZE, "%s\n", reg->name) : 0; |
| } |
| |
| static ssize_t cma_sysfs_region_start_show(struct cma_region *reg, char *page) |
| { |
| return snprintf(page, PAGE_SIZE, "%p\n", (void *)reg->start); |
| } |
| |
| static ssize_t cma_sysfs_region_size_show(struct cma_region *reg, char *page) |
| { |
| return snprintf(page, PAGE_SIZE, "%zu\n", reg->size); |
| } |
| |
| static ssize_t cma_sysfs_region_free_show(struct cma_region *reg, char *page) |
| { |
| return snprintf(page, PAGE_SIZE, "%zu\n", reg->free_space); |
| } |
| |
| static ssize_t cma_sysfs_region_users_show(struct cma_region *reg, char *page) |
| { |
| return snprintf(page, PAGE_SIZE, "%u\n", reg->users); |
| } |
| |
| static ssize_t cma_sysfs_region_alloc_show(struct cma_region *reg, char *page) |
| { |
| if (reg->alloc) |
| return snprintf(page, PAGE_SIZE, "%s\n", |
| reg->alloc->name ?: "-"); |
| else if (reg->alloc_name) |
| return snprintf(page, PAGE_SIZE, "[%s]\n", reg->alloc_name); |
| else |
| return 0; |
| } |
| |
| static int |
| cma_sysfs_region_alloc_store(struct cma_region *reg, const char *page) |
| { |
| char *s; |
| |
| if (reg->alloc && reg->users) |
| return -EBUSY; |
| |
| if (!*page || *page == '\n') { |
| s = NULL; |
| } else { |
| size_t len; |
| |
| for (s = (char *)page; *++s && *s != '\n'; ) |
| /* nop */; |
| |
| len = s - page; |
| s = kmemdup(page, len + 1, GFP_KERNEL); |
| if (!s) |
| return -ENOMEM; |
| s[len] = '\0'; |
| } |
| |
| if (reg->alloc) |
| __cma_region_detach_alloc(reg); |
| |
| if (reg->free_alloc_name) |
| kfree(reg->alloc_name); |
| |
| reg->alloc_name = s; |
| reg->free_alloc_name = !!s; |
| |
| return 0; |
| } |
| |
| |
| static ssize_t |
| cma_sysfs_region_show(struct kobject *kobj, struct attribute *attr, |
| char *buf) |
| { |
| struct cma_region *reg = container_of(kobj, struct cma_region, kobj); |
| struct cma_region_attribute *rattr = |
| container_of(attr, struct cma_region_attribute, attr); |
| ssize_t ret; |
| |
| mutex_lock(&cma_mutex); |
| ret = rattr->show(reg, buf); |
| mutex_unlock(&cma_mutex); |
| |
| return ret; |
| } |
| |
| static int |
| cma_sysfs_region_store(struct kobject *kobj, struct attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct cma_region *reg = container_of(kobj, struct cma_region, kobj); |
| struct cma_region_attribute *rattr = |
| container_of(attr, struct cma_region_attribute, attr); |
| int ret; |
| |
| mutex_lock(&cma_mutex); |
| ret = rattr->store(reg, buf); |
| mutex_unlock(&cma_mutex); |
| |
| return ret < 0 ? ret : count; |
| } |
| |
| static struct kobj_type cma_sysfs_region_type = { |
| .sysfs_ops = &(const struct sysfs_ops){ |
| .show = cma_sysfs_region_show, |
| .store = cma_sysfs_region_store, |
| }, |
| .default_attrs = (struct attribute * []) { |
| CMA_ATTR_RO_INLINE(region, name), |
| CMA_ATTR_RO_INLINE(region, start), |
| CMA_ATTR_RO_INLINE(region, size), |
| CMA_ATTR_RO_INLINE(region, free), |
| CMA_ATTR_RO_INLINE(region, users), |
| CMA_ATTR_INLINE(region, alloc), |
| NULL |
| }, |
| }; |
| |
| static void __cma_sysfs_region_add(struct cma_region *reg) |
| { |
| int ret; |
| |
| if (!cma_sysfs_regions_ready) |
| return; |
| |
| memset(®->kobj, 0, sizeof reg->kobj); |
| |
| ret = kobject_init_and_add(®->kobj, &cma_sysfs_region_type, |
| &cma_sysfs_regions, |
| "%p", (void *)reg->start); |
| |
| if (reg->name && |
| sysfs_create_link(&cma_sysfs_regions, ®->kobj, reg->name) < 0) |
| /* Ignore any errors. */; |
| } |
| |
| #else |
| |
| static void __cma_sysfs_region_add(struct cma_region *reg) |
| { |
| /* nop */ |
| } |
| |
| #endif |
| |
| |
| /************************* Chunks *************************/ |
| |
| /* All chunks sorted by start address. */ |
| static struct rb_root cma_chunks_by_start; |
| |
| static struct cma_chunk *__must_check __cma_chunk_find(dma_addr_t addr) |
| { |
| struct cma_chunk *chunk; |
| struct rb_node *n; |
| |
| for (n = cma_chunks_by_start.rb_node; n; ) { |
| chunk = rb_entry(n, struct cma_chunk, by_start); |
| if (addr < chunk->start) |
| n = n->rb_left; |
| else if (addr > chunk->start) |
| n = n->rb_right; |
| else |
| return chunk; |
| } |
| WARN(1, KERN_WARNING "no chunk starting at %p\n", (void *)addr); |
| return NULL; |
| } |
| |
| static int __must_check __cma_chunk_insert(struct cma_chunk *chunk) |
| { |
| struct rb_node **new, *parent = NULL; |
| typeof(chunk->start) addr = chunk->start; |
| |
| for (new = &cma_chunks_by_start.rb_node; *new; ) { |
| struct cma_chunk *c = |
| container_of(*new, struct cma_chunk, by_start); |
| |
| parent = *new; |
| if (addr < c->start) { |
| new = &(*new)->rb_left; |
| } else if (addr > c->start) { |
| new = &(*new)->rb_right; |
| } else { |
| /* |
| * We should never be here. If we are it |
| * means allocator gave us an invalid chunk |
| * (one that has already been allocated) so we |
| * refuse to accept it. Our caller will |
| * recover by freeing the chunk. |
| */ |
| WARN_ON(1); |
| return -EADDRINUSE; |
| } |
| } |
| |
| rb_link_node(&chunk->by_start, parent, new); |
| rb_insert_color(&chunk->by_start, &cma_chunks_by_start); |
| |
| return 0; |
| } |
| |
| static void __cma_chunk_free(struct cma_chunk *chunk) |
| { |
| rb_erase(&chunk->by_start, &cma_chunks_by_start); |
| |
| chunk->reg->free_space += chunk->size; |
| --chunk->reg->users; |
| |
| chunk->reg->alloc->free(chunk); |
| } |
| |
| |
| /************************* The Device API *************************/ |
| |
| static const char *__must_check |
| __cma_where_from(const struct device *dev, const char *type); |
| |
| |
| /* Allocate. */ |
| |
| static dma_addr_t __must_check |
| __cma_alloc_from_region(struct cma_region *reg, |
| size_t size, dma_addr_t alignment) |
| { |
| struct cma_chunk *chunk; |
| |
| pr_debug("allocate %p/%p from %s\n", |
| (void *)size, (void *)alignment, |
| reg ? reg->name ?: "(private)" : "(null)"); |
| |
| if (!reg || reg->free_space < size) |
| return -ENOMEM; |
| |
| if (!reg->alloc) { |
| if (!reg->used) |
| __cma_region_attach_alloc(reg); |
| if (!reg->alloc) |
| return -ENOMEM; |
| } |
| |
| chunk = reg->alloc->alloc(reg, size, alignment); |
| if (!chunk) |
| return -ENOMEM; |
| |
| if (unlikely(__cma_chunk_insert(chunk) < 0)) { |
| /* We should *never* be here. */ |
| chunk->reg->alloc->free(chunk); |
| kfree(chunk); |
| return -EADDRINUSE; |
| } |
| |
| chunk->reg = reg; |
| ++reg->users; |
| reg->free_space -= chunk->size; |
| pr_debug("allocated at %p\n", (void *)chunk->start); |
| return chunk->start; |
| } |
| |
| dma_addr_t __must_check |
| cma_alloc_from_region(struct cma_region *reg, |
| size_t size, dma_addr_t alignment) |
| { |
| dma_addr_t addr; |
| |
| pr_debug("allocate %p/%p from %s\n", |
| (void *)size, (void *)alignment, |
| reg ? reg->name ?: "(private)" : "(null)"); |
| |
| if (!size || alignment & (alignment - 1) || !reg) |
| return -EINVAL; |
| |
| mutex_lock(&cma_mutex); |
| |
| addr = reg->registered ? |
| __cma_alloc_from_region(reg, PAGE_ALIGN(size), |
| max(alignment, (dma_addr_t)PAGE_SIZE)) : |
| -EINVAL; |
| |
| mutex_unlock(&cma_mutex); |
| |
| return addr; |
| } |
| EXPORT_SYMBOL_GPL(cma_alloc_from_region); |
| |
| dma_addr_t __must_check |
| __cma_alloc(const struct device *dev, const char *type, |
| dma_addr_t size, dma_addr_t alignment) |
| { |
| struct cma_region *reg; |
| const char *from; |
| dma_addr_t addr; |
| |
| if (dev) |
| pr_debug("allocate %p/%p for %s/%s\n", |
| (void *)size, (void *)alignment, |
| dev_name(dev), type ?: ""); |
| |
| if (!size || (alignment & ~alignment)) |
| return -EINVAL; |
| |
| if (alignment < PAGE_SIZE) |
| alignment = PAGE_SIZE; |
| |
| if (!IS_ALIGNED(size, alignment)) |
| size = ALIGN(size, alignment); |
| |
| mutex_lock(&cma_mutex); |
| |
| from = __cma_where_from(dev, type); |
| if (unlikely(IS_ERR(from))) { |
| addr = PTR_ERR(from); |
| goto done; |
| } |
| |
| pr_debug("allocate %p/%p from one of %s\n", |
| (void *)size, (void *)alignment, from); |
| |
| while (*from && *from != ';') { |
| reg = __cma_region_find(&from); |
| addr = __cma_alloc_from_region(reg, size, alignment); |
| if (!IS_ERR_VALUE(addr)) |
| goto done; |
| } |
| |
| pr_debug("not enough memory\n"); |
| addr = -ENOMEM; |
| |
| done: |
| mutex_unlock(&cma_mutex); |
| |
| return addr; |
| } |
| EXPORT_SYMBOL_GPL(__cma_alloc); |
| |
| |
| void *cma_get_virt(dma_addr_t phys, dma_addr_t size, int noncached) |
| { |
| unsigned long num_pages, i; |
| struct page **pages; |
| void *virt; |
| |
| if (noncached) { |
| num_pages = size >> PAGE_SHIFT; |
| pages = kmalloc(num_pages * sizeof(struct page *), GFP_KERNEL); |
| |
| if (!pages) |
| return ERR_PTR(-ENOMEM); |
| |
| for (i = 0; i < num_pages; i++) |
| pages[i] = pfn_to_page((phys >> PAGE_SHIFT) + i); |
| |
| virt = vmap(pages, num_pages, VM_MAP, |
| pgprot_writecombine(PAGE_KERNEL)); |
| |
| if (!virt) { |
| kfree(pages); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| kfree(pages); |
| } else { |
| virt = phys_to_virt((unsigned long)phys); |
| } |
| |
| return virt; |
| } |
| EXPORT_SYMBOL_GPL(cma_get_virt); |
| |
| /* Query information about regions. */ |
| static void __cma_info_add(struct cma_info *infop, struct cma_region *reg) |
| { |
| infop->total_size += reg->size; |
| infop->free_size += reg->free_space; |
| if (infop->lower_bound > reg->start) |
| infop->lower_bound = reg->start; |
| if (infop->upper_bound < reg->start + reg->size) |
| infop->upper_bound = reg->start + reg->size; |
| ++infop->count; |
| } |
| |
| int |
| __cma_info(struct cma_info *infop, const struct device *dev, const char *type) |
| { |
| struct cma_info info = { ~(dma_addr_t)0, 0, 0, 0, 0 }; |
| struct cma_region *reg; |
| const char *from; |
| int ret; |
| |
| if (unlikely(!infop)) |
| return -EINVAL; |
| |
| mutex_lock(&cma_mutex); |
| |
| from = __cma_where_from(dev, type); |
| if (IS_ERR(from)) { |
| ret = PTR_ERR(from); |
| info.lower_bound = 0; |
| goto done; |
| } |
| |
| while (*from && *from != ';') { |
| reg = __cma_region_find(&from); |
| if (reg) |
| __cma_info_add(&info, reg); |
| } |
| |
| ret = 0; |
| done: |
| mutex_unlock(&cma_mutex); |
| |
| memcpy(infop, &info, sizeof info); |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(__cma_info); |
| |
| |
| /* Freeing. */ |
| int cma_free(dma_addr_t addr) |
| { |
| struct cma_chunk *c; |
| int ret; |
| |
| mutex_lock(&cma_mutex); |
| |
| c = __cma_chunk_find(addr); |
| |
| if (c) { |
| __cma_chunk_free(c); |
| ret = 0; |
| } else { |
| ret = -ENOENT; |
| } |
| |
| mutex_unlock(&cma_mutex); |
| |
| if (c) |
| pr_debug("free(%p): freed\n", (void *)addr); |
| else |
| pr_err("free(%p): not found\n", (void *)addr); |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(cma_free); |
| |
| |
| /************************* Miscellaneous *************************/ |
| |
| static int __cma_region_attach_alloc(struct cma_region *reg) |
| { |
| struct cma_allocator *alloc; |
| int ret; |
| |
| /* |
| * If reg->alloc is set then caller wants us to use this |
| * allocator. Otherwise we need to find one by name. |
| */ |
| if (reg->alloc) { |
| alloc = reg->alloc; |
| } else { |
| alloc = __cma_allocator_find(reg->alloc_name); |
| if (!alloc) { |
| pr_warn("init: %s: %s: no such allocator\n", |
| reg->name ?: "(private)", |
| reg->alloc_name ?: "(default)"); |
| reg->used = 1; |
| return -ENOENT; |
| } |
| } |
| |
| /* Try to initialise the allocator. */ |
| reg->private_data = NULL; |
| ret = alloc->init ? alloc->init(reg) : 0; |
| if (unlikely(ret < 0)) { |
| pr_err("init: %s: %s: unable to initialise allocator\n", |
| reg->name ?: "(private)", alloc->name ?: "(unnamed)"); |
| reg->alloc = NULL; |
| reg->used = 1; |
| } else { |
| reg->alloc = alloc; |
| pr_debug("init: %s: %s: initialised allocator\n", |
| reg->name ?: "(private)", alloc->name ?: "(unnamed)"); |
| } |
| return ret; |
| } |
| |
| static void __cma_region_detach_alloc(struct cma_region *reg) |
| { |
| if (!reg->alloc) |
| return; |
| |
| if (reg->alloc->cleanup) |
| reg->alloc->cleanup(reg); |
| |
| reg->alloc = NULL; |
| reg->used = 1; |
| } |
| |
| |
| /* |
| * s ::= rules |
| * rules ::= rule [ ';' rules ] |
| * rule ::= patterns '=' regions |
| * patterns ::= pattern [ ',' patterns ] |
| * regions ::= REG-NAME [ ',' regions ] |
| * pattern ::= dev-pattern [ '/' TYPE-NAME ] | '/' TYPE-NAME |
| */ |
| static const char *__must_check |
| __cma_where_from(const struct device *dev, const char *type) |
| { |
| /* |
| * This function matches the pattern from the map attribute |
| * agains given device name and type. Type may be of course |
| * NULL or an emtpy string. |
| */ |
| |
| const char *s, *name; |
| int name_matched = 0; |
| |
| /* |
| * If dev is NULL we were called in alternative form where |
| * type is the from string. All we have to do is return it. |
| */ |
| if (!dev) |
| return type ?: ERR_PTR(-EINVAL); |
| |
| if (!cma_map) |
| return ERR_PTR(-ENOENT); |
| |
| name = dev_name(dev); |
| if (WARN_ON(!name || !*name)) |
| return ERR_PTR(-EINVAL); |
| |
| if (!type) |
| type = "common"; |
| |
| /* |
| * Now we go throught the cma_map attribute. |
| */ |
| for (s = cma_map; *s; ++s) { |
| const char *c; |
| |
| /* |
| * If the pattern starts with a slash, the device part of the |
| * pattern matches if it matched previously. |
| */ |
| if (*s == '/') { |
| if (!name_matched) |
| goto look_for_next; |
| goto match_type; |
| } |
| |
| /* |
| * We are now trying to match the device name. This also |
| * updates the name_matched variable. If, while reading the |
| * spec, we ecnounter comma it means that the pattern does not |
| * match and we need to start over with another pattern (the |
| * one afther the comma). If we encounter equal sign we need |
| * to start over with another rule. If there is a character |
| * that does not match, we neet to look for a comma (to get |
| * another pattern) or semicolon (to get another rule) and try |
| * again if there is one somewhere. |
| */ |
| |
| name_matched = 0; |
| |
| for (c = name; *s != '*' && *c; ++c, ++s) |
| if (*s == '=') |
| goto next_rule; |
| else if (*s == ',') |
| goto next_pattern; |
| else if (*s != '?' && *c != *s) |
| goto look_for_next; |
| if (*s == '*') |
| ++s; |
| |
| name_matched = 1; |
| |
| /* |
| * Now we need to match the type part of the pattern. If the |
| * pattern is missing it we match only if type points to an |
| * empty string. Otherwise wy try to match it just like name. |
| */ |
| if (*s == '/') { |
| match_type: /* s points to '/' */ |
| ++s; |
| |
| for (c = type; *s && *c; ++c, ++s) |
| if (*s == '=') |
| goto next_rule; |
| else if (*s == ',') |
| goto next_pattern; |
| else if (*c != *s) |
| goto look_for_next; |
| } |
| |
| /* Return the string behind the '=' sign of the rule. */ |
| if (*s == '=') |
| return s + 1; |
| else if (*s == ',') |
| return strchr(s, '=') + 1; |
| |
| /* Pattern did not match */ |
| |
| look_for_next: |
| do { |
| ++s; |
| } while (*s != ',' && *s != '='); |
| if (*s == ',') |
| continue; |
| |
| next_rule: /* s points to '=' */ |
| s = strchr(s, ';'); |
| if (!s) |
| break; |
| |
| next_pattern: |
| continue; |
| } |
| |
| return ERR_PTR(-ENOENT); |
| } |