diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/base/dma-contiguous.c | 119 | ||||
-rw-r--r-- | drivers/of/Kconfig | 6 | ||||
-rw-r--r-- | drivers/of/Makefile | 1 | ||||
-rw-r--r-- | drivers/of/fdt.c | 76 | ||||
-rw-r--r-- | drivers/of/of_reserved_mem.c | 175 | ||||
-rw-r--r-- | drivers/of/platform.c | 4 |
6 files changed, 306 insertions, 75 deletions
diff --git a/drivers/base/dma-contiguous.c b/drivers/base/dma-contiguous.c index 6c9cdaa9200d..99802d6f3c60 100644 --- a/drivers/base/dma-contiguous.c +++ b/drivers/base/dma-contiguous.c @@ -96,7 +96,7 @@ static inline __maybe_unused phys_addr_t cma_early_percent_memory(void) #endif /** - * dma_contiguous_reserve() - reserve area for contiguous memory handling + * dma_contiguous_reserve() - reserve area(s) for contiguous memory handling * @limit: End address of the reserved memory (optional, 0 for any). * * This function reserves memory from early allocator. It should be @@ -124,22 +124,29 @@ void __init dma_contiguous_reserve(phys_addr_t limit) #endif } - if (selected_size) { + if (selected_size && !dma_contiguous_default_area) { pr_debug("%s: reserving %ld MiB for global area\n", __func__, (unsigned long)selected_size / SZ_1M); - dma_declare_contiguous(NULL, selected_size, 0, limit); + dma_contiguous_reserve_area(selected_size, 0, limit, + &dma_contiguous_default_area); } }; static DEFINE_MUTEX(cma_mutex); -static int __init cma_activate_area(unsigned long base_pfn, unsigned long count) +static int __init cma_activate_area(struct cma *cma) { - unsigned long pfn = base_pfn; - unsigned i = count >> pageblock_order; + int bitmap_size = BITS_TO_LONGS(cma->count) * sizeof(long); + unsigned long base_pfn = cma->base_pfn, pfn = base_pfn; + unsigned i = cma->count >> pageblock_order; struct zone *zone; + cma->bitmap = kzalloc(bitmap_size, GFP_KERNEL); + + if (!cma->bitmap) + return -ENOMEM; + WARN_ON_ONCE(!pfn_valid(pfn)); zone = page_zone(pfn_to_page(pfn)); @@ -153,92 +160,53 @@ static int __init cma_activate_area(unsigned long base_pfn, unsigned long count) } init_cma_reserved_pageblock(pfn_to_page(base_pfn)); } while (--i); - return 0; -} - -static struct cma * __init cma_create_area(unsigned long base_pfn, - unsigned long count) -{ - int bitmap_size = BITS_TO_LONGS(count) * sizeof(long); - struct cma *cma; - int ret = -ENOMEM; - - pr_debug("%s(base %08lx, count %lx)\n", __func__, base_pfn, count); - - cma = kmalloc(sizeof *cma, GFP_KERNEL); - if (!cma) - return ERR_PTR(-ENOMEM); - - cma->base_pfn = base_pfn; - cma->count = count; - cma->bitmap = kzalloc(bitmap_size, GFP_KERNEL); - if (!cma->bitmap) - goto no_mem; - - ret = cma_activate_area(base_pfn, count); - if (ret) - goto error; - - pr_debug("%s: returned %p\n", __func__, (void *)cma); - return cma; - -error: - kfree(cma->bitmap); -no_mem: - kfree(cma); - return ERR_PTR(ret); + return 0; } -static struct cma_reserved { - phys_addr_t start; - unsigned long size; - struct device *dev; -} cma_reserved[MAX_CMA_AREAS] __initdata; -static unsigned cma_reserved_count __initdata; +static struct cma cma_areas[MAX_CMA_AREAS]; +static unsigned cma_area_count; static int __init cma_init_reserved_areas(void) { - struct cma_reserved *r = cma_reserved; - unsigned i = cma_reserved_count; - - pr_debug("%s()\n", __func__); + int i; - for (; i; --i, ++r) { - struct cma *cma; - cma = cma_create_area(PFN_DOWN(r->start), - r->size >> PAGE_SHIFT); - if (!IS_ERR(cma)) - dev_set_cma_area(r->dev, cma); + for (i = 0; i < cma_area_count; i++) { + int ret = cma_activate_area(&cma_areas[i]); + if (ret) + return ret; } + return 0; } core_initcall(cma_init_reserved_areas); /** - * dma_declare_contiguous() - reserve area for contiguous memory handling - * for particular device - * @dev: Pointer to device structure. - * @size: Size of the reserved memory. - * @base: Start address of the reserved memory (optional, 0 for any). + * dma_contiguous_reserve_area() - reserve custom contiguous area + * @size: Size of the reserved area (in bytes), + * @base: Base address of the reserved area optional, use 0 for any * @limit: End address of the reserved memory (optional, 0 for any). + * @res_cma: Pointer to store the created cma region. * - * This function reserves memory for specified device. It should be - * called by board specific code when early allocator (memblock or bootmem) - * is still activate. + * This function reserves memory from early allocator. It should be + * called by arch specific code once the early allocator (memblock or bootmem) + * has been activated and all other subsystems have already allocated/reserved + * memory. This function allows to create custom reserved areas for specific + * devices. */ -int __init dma_declare_contiguous(struct device *dev, phys_addr_t size, - phys_addr_t base, phys_addr_t limit) +int __init dma_contiguous_reserve_area(phys_addr_t size, phys_addr_t base, + phys_addr_t limit, struct cma **res_cma) { - struct cma_reserved *r = &cma_reserved[cma_reserved_count]; + struct cma *cma = &cma_areas[cma_area_count]; phys_addr_t alignment; + int ret = 0; pr_debug("%s(size %lx, base %08lx, limit %08lx)\n", __func__, (unsigned long)size, (unsigned long)base, (unsigned long)limit); /* Sanity checks */ - if (cma_reserved_count == ARRAY_SIZE(cma_reserved)) { + if (cma_area_count == ARRAY_SIZE(cma_areas)) { pr_err("Not enough slots for CMA reserved regions!\n"); return -ENOSPC; } @@ -256,7 +224,7 @@ int __init dma_declare_contiguous(struct device *dev, phys_addr_t size, if (base) { if (memblock_is_region_reserved(base, size) || memblock_reserve(base, size) < 0) { - base = -EBUSY; + ret = -EBUSY; goto err; } } else { @@ -266,7 +234,7 @@ int __init dma_declare_contiguous(struct device *dev, phys_addr_t size, */ phys_addr_t addr = __memblock_alloc_base(size, alignment, limit); if (!addr) { - base = -ENOMEM; + ret = -ENOMEM; goto err; } else { base = addr; @@ -277,10 +245,11 @@ int __init dma_declare_contiguous(struct device *dev, phys_addr_t size, * Each reserved area must be initialised later, when more kernel * subsystems (like slab allocator) are available. */ - r->start = base; - r->size = size; - r->dev = dev; - cma_reserved_count++; + cma->base_pfn = PFN_DOWN(base); + cma->count = size >> PAGE_SHIFT; + *res_cma = cma; + cma_area_count++; + pr_info("CMA: reserved %ld MiB at %08lx\n", (unsigned long)size / SZ_1M, (unsigned long)base); @@ -289,7 +258,7 @@ int __init dma_declare_contiguous(struct device *dev, phys_addr_t size, return 0; err: pr_err("CMA: failed to reserve %ld MiB\n", (unsigned long)size / SZ_1M); - return base; + return ret; } /** diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index 78cc76053328..9d2009a9004d 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -74,4 +74,10 @@ config OF_MTD depends on MTD def_bool y +config OF_RESERVED_MEM + depends on OF_FLATTREE && (DMA_CMA || (HAVE_GENERIC_DMA_COHERENT && HAVE_MEMBLOCK)) + def_bool y + help + Initialization code for DMA reserved memory + endmenu # OF diff --git a/drivers/of/Makefile b/drivers/of/Makefile index efd05102c405..ed9660adad77 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_OF_MDIO) += of_mdio.o obj-$(CONFIG_OF_PCI) += of_pci.o obj-$(CONFIG_OF_PCI_IRQ) += of_pci_irq.o obj-$(CONFIG_OF_MTD) += of_mtd.o +obj-$(CONFIG_OF_RESERVED_MEM) += of_reserved_mem.o diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index b10ba00cc3e6..4fb06f3e7b3c 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -545,6 +545,82 @@ int __init of_flat_dt_match(unsigned long node, const char *const *compat) return of_fdt_match(initial_boot_params, node, compat); } +struct fdt_scan_status { + const char *name; + int namelen; + int depth; + int found; + int (*iterator)(unsigned long node, const char *uname, int depth, void *data); + void *data; +}; + +/** + * fdt_scan_node_by_path - iterator for of_scan_flat_dt_by_path function + */ +static int __init fdt_scan_node_by_path(unsigned long node, const char *uname, + int depth, void *data) +{ + struct fdt_scan_status *st = data; + + /* + * if scan at the requested fdt node has been completed, + * return -ENXIO to abort further scanning + */ + if (depth <= st->depth) + return -ENXIO; + + /* requested fdt node has been found, so call iterator function */ + if (st->found) + return st->iterator(node, uname, depth, st->data); + + /* check if scanning automata is entering next level of fdt nodes */ + if (depth == st->depth + 1 && + strncmp(st->name, uname, st->namelen) == 0 && + uname[st->namelen] == 0) { + st->depth += 1; + if (st->name[st->namelen] == 0) { + st->found = 1; + } else { + const char *next = st->name + st->namelen + 1; + st->name = next; + st->namelen = strcspn(next, "/"); + } + return 0; + } + + /* scan next fdt node */ + return 0; +} + +/** + * of_scan_flat_dt_by_path - scan flattened tree blob and call callback on each + * child of the given path. + * @path: path to start searching for children + * @it: callback function + * @data: context data pointer + * + * This function is used to scan the flattened device-tree starting from the + * node given by path. It is used to extract information (like reserved + * memory), which is required on ealy boot before we can unflatten the tree. + */ +int __init of_scan_flat_dt_by_path(const char *path, + int (*it)(unsigned long node, const char *name, int depth, void *data), + void *data) +{ + struct fdt_scan_status st = {path, 0, -1, 0, it, data}; + int ret = 0; + + if (initial_boot_params) + ret = of_scan_flat_dt(fdt_scan_node_by_path, &st); + + if (!st.found) + return -ENOENT; + else if (ret == -ENXIO) /* scan has been completed */ + return 0; + else + return ret; +} + #ifdef CONFIG_BLK_DEV_INITRD /** * early_init_dt_check_for_initrd - Decode initrd location from flat tree diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c new file mode 100644 index 000000000000..a754b84ba016 --- /dev/null +++ b/drivers/of/of_reserved_mem.c @@ -0,0 +1,175 @@ +/* + * Device tree based initialization code for reserved memory. + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * Author: Marek Szyprowski <m.szyprowski@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. + */ + +#include <asm/dma-contiguous.h> + +#include <linux/memblock.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/of_fdt.h> +#include <linux/of_platform.h> +#include <linux/mm.h> +#include <linux/sizes.h> +#include <linux/mm_types.h> +#include <linux/dma-contiguous.h> +#include <linux/dma-mapping.h> +#include <linux/of_reserved_mem.h> + +#define MAX_RESERVED_REGIONS 16 +struct reserved_mem { + phys_addr_t base; + unsigned long size; + struct cma *cma; + char name[32]; +}; +static struct reserved_mem reserved_mem[MAX_RESERVED_REGIONS]; +static int reserved_mem_count; + +static int __init fdt_scan_reserved_mem(unsigned long node, const char *uname, + int depth, void *data) +{ + struct reserved_mem *rmem = &reserved_mem[reserved_mem_count]; + phys_addr_t base, size; + int is_cma, is_reserved; + unsigned long len; + const char *status; + __be32 *prop; + + is_cma = IS_ENABLED(CONFIG_DMA_CMA) && + of_flat_dt_is_compatible(node, "linux,contiguous-memory-region"); + is_reserved = of_flat_dt_is_compatible(node, "reserved-memory-region"); + + if (!is_reserved && !is_cma) { + /* ignore node and scan next one */ + return 0; + } + + status = of_get_flat_dt_prop(node, "status", &len); + if (status && strcmp(status, "okay") != 0) { + /* ignore disabled node nad scan next one */ + return 0; + } + + prop = of_get_flat_dt_prop(node, "reg", &len); + if (!prop || (len < (dt_root_size_cells + dt_root_addr_cells) * + sizeof(__be32))) { + pr_err("Reserved mem: node %s, incorrect \"reg\" property\n", + uname); + /* ignore node and scan next one */ + return 0; + } + base = dt_mem_next_cell(dt_root_addr_cells, &prop); + size = dt_mem_next_cell(dt_root_size_cells, &prop); + + if (!size) { + /* ignore node and scan next one */ + return 0; + } + + pr_info("Reserved mem: found %s, memory base %lx, size %ld MiB\n", + uname, (unsigned long)base, (unsigned long)size / SZ_1M); + + if (reserved_mem_count == ARRAY_SIZE(reserved_mem)) + return -ENOSPC; + + rmem->base = base; + rmem->size = size; + strlcpy(rmem->name, uname, sizeof(rmem->name)); + + if (is_cma) { + struct cma *cma; + if (dma_contiguous_reserve_area(size, base, 0, &cma) == 0) { + rmem->cma = cma; + reserved_mem_count++; + if (of_get_flat_dt_prop(node, + "linux,default-contiguous-region", + NULL)) + dma_contiguous_set_default(cma); + } + } else if (is_reserved) { + if (memblock_remove(base, size) == 0) + reserved_mem_count++; + else + pr_err("Failed to reserve memory for %s\n", uname); + } + + return 0; +} + +static struct reserved_mem *get_dma_memory_region(struct device *dev) +{ + struct device_node *node; + const char *name; + int i; + + node = of_parse_phandle(dev->of_node, "memory-region", 0); + if (!node) + return NULL; + + name = kbasename(node->full_name); + for (i = 0; i < reserved_mem_count; i++) + if (strcmp(name, reserved_mem[i].name) == 0) + return &reserved_mem[i]; + return NULL; +} + +/** + * of_reserved_mem_device_init() - assign reserved memory region to given device + * + * This function assign memory region pointed by "memory-region" device tree + * property to the given device. + */ +void of_reserved_mem_device_init(struct device *dev) +{ + struct reserved_mem *region = get_dma_memory_region(dev); + if (!region) + return; + + if (region->cma) { + dev_set_cma_area(dev, region->cma); + pr_info("Assigned CMA %s to %s device\n", region->name, + dev_name(dev)); + } else { + if (dma_declare_coherent_memory(dev, region->base, region->base, + region->size, DMA_MEMORY_MAP | DMA_MEMORY_EXCLUSIVE) != 0) + pr_info("Declared reserved memory %s to %s device\n", + region->name, dev_name(dev)); + } +} + +/** + * of_reserved_mem_device_release() - release reserved memory device structures + * + * This function releases structures allocated for memory region handling for + * the given device. + */ +void of_reserved_mem_device_release(struct device *dev) +{ + struct reserved_mem *region = get_dma_memory_region(dev); + if (!region && !region->cma) + dma_release_declared_memory(dev); +} + +/** + * early_init_dt_scan_reserved_mem() - create reserved memory regions + * + * This function grabs memory from early allocator for device exclusive use + * defined in device tree structures. It should be called by arch specific code + * once the early allocator (memblock) has been activated and all other + * subsystems have already allocated/reserved memory. + */ +void __init early_init_dt_scan_reserved_mem(void) +{ + of_scan_flat_dt_by_path("/memory/reserved-memory", + fdt_scan_reserved_mem, NULL); +} diff --git a/drivers/of/platform.c b/drivers/of/platform.c index e0a6514ab46c..eeca8a596973 100644 --- a/drivers/of/platform.c +++ b/drivers/of/platform.c @@ -21,6 +21,7 @@ #include <linux/of_device.h> #include <linux/of_irq.h> #include <linux/of_platform.h> +#include <linux/of_reserved_mem.h> #include <linux/platform_device.h> const struct of_device_id of_default_bus_match_table[] = { @@ -218,6 +219,8 @@ struct platform_device *of_platform_device_create_pdata( dev->dev.bus = &platform_bus_type; dev->dev.platform_data = platform_data; + of_reserved_mem_device_init(&dev->dev); + /* We do not fill the DMA ops for platform devices by default. * This is currently the responsibility of the platform code * to do such, possibly using a device notifier @@ -225,6 +228,7 @@ struct platform_device *of_platform_device_create_pdata( if (of_device_add(dev) != 0) { platform_device_put(dev); + of_reserved_mem_device_release(&dev->dev); return NULL; } |