From e8d9d1f5485b52ec3c4d7af839e6914438f6c285 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Fri, 28 Feb 2014 14:42:47 +0100 Subject: drivers: of: add initialization code for static reserved memory This patch adds support for static (defined by 'reg' property) reserved memory regions declared in device tree. Memory blocks can be reliably reserved only during early boot. This must happen before the whole memory management subsystem is initialized, because we need to ensure that the given contiguous blocks are not yet allocated by kernel. Also it must happen before kernel mappings for the whole low memory are created, to ensure that there will be no mappings (for reserved blocks). Typically, all this happens before device tree structures are unflattened, so we need to get reserved memory layout directly from fdt. Based on previous code provided by Josh Cartwright Signed-off-by: Marek Szyprowski Signed-off-by: Grant Likely --- drivers/of/fdt.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) (limited to 'drivers') diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index 758b4f8b30b7..819e11209718 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -439,6 +440,118 @@ struct boot_param_header *initial_boot_params; #ifdef CONFIG_OF_EARLY_FLATTREE +/** + * res_mem_reserve_reg() - reserve all memory described in 'reg' property + */ +static int __init __reserved_mem_reserve_reg(unsigned long node, + const char *uname) +{ + int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32); + phys_addr_t base, size; + unsigned long len; + __be32 *prop; + int nomap; + + prop = of_get_flat_dt_prop(node, "reg", &len); + if (!prop) + return -ENOENT; + + if (len && len % t_len != 0) { + pr_err("Reserved memory: invalid reg property in '%s', skipping node.\n", + uname); + return -EINVAL; + } + + nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL; + + while (len >= t_len) { + base = dt_mem_next_cell(dt_root_addr_cells, &prop); + size = dt_mem_next_cell(dt_root_size_cells, &prop); + + if (base && size && + early_init_dt_reserve_memory_arch(base, size, nomap) == 0) + pr_debug("Reserved memory: reserved region for node '%s': base %pa, size %ld MiB\n", + uname, &base, (unsigned long)size / SZ_1M); + else + pr_info("Reserved memory: failed to reserve memory for node '%s': base %pa, size %ld MiB\n", + uname, &base, (unsigned long)size / SZ_1M); + + len -= t_len; + } + return 0; +} + +/** + * __reserved_mem_check_root() - check if #size-cells, #address-cells provided + * in /reserved-memory matches the values supported by the current implementation, + * also check if ranges property has been provided + */ +static int __reserved_mem_check_root(unsigned long node) +{ + __be32 *prop; + + prop = of_get_flat_dt_prop(node, "#size-cells", NULL); + if (!prop || be32_to_cpup(prop) != dt_root_size_cells) + return -EINVAL; + + prop = of_get_flat_dt_prop(node, "#address-cells", NULL); + if (!prop || be32_to_cpup(prop) != dt_root_addr_cells) + return -EINVAL; + + prop = of_get_flat_dt_prop(node, "ranges", NULL); + if (!prop) + return -EINVAL; + return 0; +} + +/** + * fdt_scan_reserved_mem() - scan a single FDT node for reserved memory + */ +static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname, + int depth, void *data) +{ + static int found; + const char *status; + + if (!found && depth == 1 && strcmp(uname, "reserved-memory") == 0) { + if (__reserved_mem_check_root(node) != 0) { + pr_err("Reserved memory: unsupported node format, ignoring\n"); + /* break scan */ + return 1; + } + found = 1; + /* scan next node */ + return 0; + } else if (!found) { + /* scan next node */ + return 0; + } else if (found && depth < 2) { + /* scanning of /reserved-memory has been finished */ + return 1; + } + + status = of_get_flat_dt_prop(node, "status", NULL); + if (status && strcmp(status, "okay") != 0 && strcmp(status, "ok") != 0) + return 0; + + __reserved_mem_reserve_reg(node, uname); + + /* scan next node */ + return 0; +} + +/** + * early_init_fdt_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 (i.e. memblock) has been fully activated. + */ +void __init early_init_fdt_scan_reserved_mem(void) +{ + of_scan_flat_dt(__fdt_scan_reserved_mem, NULL); +} + /** * of_scan_flat_dt - scan flattened tree blob and call callback on each. * @it: callback function @@ -856,6 +969,16 @@ void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size) memblock_add(base, size); } +int __init __weak early_init_dt_reserve_memory_arch(phys_addr_t base, + phys_addr_t size, bool nomap) +{ + if (memblock_is_region_reserved(base, size)) + return -EBUSY; + if (nomap) + return memblock_remove(base, size); + return memblock_reserve(base, size); +} + /* * called from unflatten_device_tree() to bootstrap devicetree itself * Architectures can override this definition if memblock isn't used @@ -864,6 +987,14 @@ void * __init __weak early_init_dt_alloc_memory_arch(u64 size, u64 align) { return __va(memblock_alloc(size, align)); } +#else +int __init __weak early_init_dt_reserve_memory_arch(phys_addr_t base, + phys_addr_t size, bool nomap) +{ + pr_err("Reserved memory not supported, ignoring range 0x%llx - 0x%llx%s\n", + base, size, nomap ? " (nomap)" : ""); + return -ENOSYS; +} #endif bool __init early_init_dt_scan(void *params) -- cgit v1.2.3 From 3f0c8206644836e4f10a6b9fc47cda6a9a372f9b Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Fri, 28 Feb 2014 14:42:48 +0100 Subject: drivers: of: add initialization code for dynamic reserved memory This patch adds support for dynamically allocated reserved memory regions declared in device tree. Such regions are defined by 'size', 'alignment' and 'alloc-ranges' properties. Based on previous code provided by Josh Cartwright Signed-off-by: Marek Szyprowski Signed-off-by: Grant Likely --- drivers/of/Kconfig | 6 ++ drivers/of/Makefile | 1 + drivers/of/fdt.c | 13 ++- drivers/of/of_reserved_mem.c | 188 ++++++++++++++++++++++++++++++++++++++++ include/linux/of_reserved_mem.h | 21 +++++ 5 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 drivers/of/of_reserved_mem.c create mode 100644 include/linux/of_reserved_mem.h (limited to 'drivers') diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index ffdcb11f75fb..c144b8f990ff 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -79,4 +79,10 @@ config OF_MTD depends on MTD def_bool y +config OF_RESERVED_MEM + depends on OF_EARLY_FLATTREE + bool + help + Helpers to allow for reservation of memory regions + 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 819e11209718..510c0d8de8a0 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -450,7 +451,7 @@ static int __init __reserved_mem_reserve_reg(unsigned long node, phys_addr_t base, size; unsigned long len; __be32 *prop; - int nomap; + int nomap, first = 1; prop = of_get_flat_dt_prop(node, "reg", &len); if (!prop) @@ -477,6 +478,10 @@ static int __init __reserved_mem_reserve_reg(unsigned long node, uname, &base, (unsigned long)size / SZ_1M); len -= t_len; + if (first) { + fdt_reserved_mem_save_node(node, uname, base, size); + first = 0; + } } return 0; } @@ -512,6 +517,7 @@ static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname, { static int found; const char *status; + int err; if (!found && depth == 1 && strcmp(uname, "reserved-memory") == 0) { if (__reserved_mem_check_root(node) != 0) { @@ -534,7 +540,9 @@ static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname, if (status && strcmp(status, "okay") != 0 && strcmp(status, "ok") != 0) return 0; - __reserved_mem_reserve_reg(node, uname); + err = __reserved_mem_reserve_reg(node, uname); + if (err == -ENOENT && of_get_flat_dt_prop(node, "size", NULL)) + fdt_reserved_mem_save_node(node, uname, 0, 0); /* scan next node */ return 0; @@ -550,6 +558,7 @@ static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname, void __init early_init_fdt_scan_reserved_mem(void) { of_scan_flat_dt(__fdt_scan_reserved_mem, NULL); + fdt_init_reserved_mem(); } /** diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c new file mode 100644 index 000000000000..69b811779585 --- /dev/null +++ b/drivers/of/of_reserved_mem.c @@ -0,0 +1,188 @@ +/* + * Device tree based initialization code for reserved memory. + * + * Copyright (c) 2013, The Linux Foundation. All Rights Reserved. + * Copyright (c) 2013,2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * Author: Marek Szyprowski + * Author: Josh Cartwright + * + * 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 +#include +#include +#include +#include +#include +#include + +#define MAX_RESERVED_REGIONS 16 +static struct reserved_mem reserved_mem[MAX_RESERVED_REGIONS]; +static int reserved_mem_count; + +#if defined(CONFIG_HAVE_MEMBLOCK) +#include +int __init __weak early_init_dt_alloc_reserved_memory_arch(phys_addr_t size, + phys_addr_t align, phys_addr_t start, phys_addr_t end, bool nomap, + phys_addr_t *res_base) +{ + /* + * We use __memblock_alloc_base() because memblock_alloc_base() + * panic()s on allocation failure. + */ + phys_addr_t base = __memblock_alloc_base(size, align, end); + if (!base) + return -ENOMEM; + + /* + * Check if the allocated region fits in to start..end window + */ + if (base < start) { + memblock_free(base, size); + return -ENOMEM; + } + + *res_base = base; + if (nomap) + return memblock_remove(base, size); + return 0; +} +#else +int __init __weak early_init_dt_alloc_reserved_memory_arch(phys_addr_t size, + phys_addr_t align, phys_addr_t start, phys_addr_t end, bool nomap, + phys_addr_t *res_base) +{ + pr_err("Reserved memory not supported, ignoring region 0x%llx%s\n", + size, nomap ? " (nomap)" : ""); + return -ENOSYS; +} +#endif + +/** + * res_mem_save_node() - save fdt node for second pass initialization + */ +void __init fdt_reserved_mem_save_node(unsigned long node, const char *uname, + phys_addr_t base, phys_addr_t size) +{ + struct reserved_mem *rmem = &reserved_mem[reserved_mem_count]; + + if (reserved_mem_count == ARRAY_SIZE(reserved_mem)) { + pr_err("Reserved memory: not enough space all defined regions.\n"); + return; + } + + rmem->fdt_node = node; + rmem->name = uname; + rmem->base = base; + rmem->size = size; + + reserved_mem_count++; + return; +} + +/** + * res_mem_alloc_size() - allocate reserved memory described by 'size', 'align' + * and 'alloc-ranges' properties + */ +static int __init __reserved_mem_alloc_size(unsigned long node, + const char *uname, phys_addr_t *res_base, phys_addr_t *res_size) +{ + int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32); + phys_addr_t start = 0, end = 0; + phys_addr_t base = 0, align = 0, size; + unsigned long len; + __be32 *prop; + int nomap; + int ret; + + prop = of_get_flat_dt_prop(node, "size", &len); + if (!prop) + return -EINVAL; + + if (len != dt_root_size_cells * sizeof(__be32)) { + pr_err("Reserved memory: invalid size property in '%s' node.\n", + uname); + return -EINVAL; + } + size = dt_mem_next_cell(dt_root_size_cells, &prop); + + nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL; + + prop = of_get_flat_dt_prop(node, "alignment", &len); + if (prop) { + if (len != dt_root_addr_cells * sizeof(__be32)) { + pr_err("Reserved memory: invalid alignment property in '%s' node.\n", + uname); + return -EINVAL; + } + align = dt_mem_next_cell(dt_root_addr_cells, &prop); + } + + prop = of_get_flat_dt_prop(node, "alloc-ranges", &len); + if (prop) { + + if (len % t_len != 0) { + pr_err("Reserved memory: invalid alloc-ranges property in '%s', skipping node.\n", + uname); + return -EINVAL; + } + + base = 0; + + while (len > 0) { + start = dt_mem_next_cell(dt_root_addr_cells, &prop); + end = start + dt_mem_next_cell(dt_root_size_cells, + &prop); + + ret = early_init_dt_alloc_reserved_memory_arch(size, + align, start, end, nomap, &base); + if (ret == 0) { + pr_debug("Reserved memory: allocated memory for '%s' node: base %pa, size %ld MiB\n", + uname, &base, + (unsigned long)size / SZ_1M); + break; + } + len -= t_len; + } + + } else { + ret = early_init_dt_alloc_reserved_memory_arch(size, align, + 0, 0, nomap, &base); + if (ret == 0) + pr_debug("Reserved memory: allocated memory for '%s' node: base %pa, size %ld MiB\n", + uname, &base, (unsigned long)size / SZ_1M); + } + + if (base == 0) { + pr_info("Reserved memory: failed to allocate memory for node '%s'\n", + uname); + return -ENOMEM; + } + + *res_base = base; + *res_size = size; + + return 0; +} + +/** + * fdt_init_reserved_mem - allocate and init all saved reserved memory regions + */ +void __init fdt_init_reserved_mem(void) +{ + int i; + for (i = 0; i < reserved_mem_count; i++) { + struct reserved_mem *rmem = &reserved_mem[i]; + unsigned long node = rmem->fdt_node; + int err = 0; + + if (rmem->size == 0) + err = __reserved_mem_alloc_size(node, rmem->name, + &rmem->base, &rmem->size); + } +} diff --git a/include/linux/of_reserved_mem.h b/include/linux/of_reserved_mem.h new file mode 100644 index 000000000000..89226ed7d954 --- /dev/null +++ b/include/linux/of_reserved_mem.h @@ -0,0 +1,21 @@ +#ifndef __OF_RESERVED_MEM_H +#define __OF_RESERVED_MEM_H + +struct reserved_mem { + const char *name; + unsigned long fdt_node; + phys_addr_t base; + phys_addr_t size; +}; + +#ifdef CONFIG_OF_RESERVED_MEM +void fdt_init_reserved_mem(void); +void fdt_reserved_mem_save_node(unsigned long node, const char *uname, + phys_addr_t base, phys_addr_t size); +#else +static inline void fdt_init_reserved_mem(void) { } +static inline void fdt_reserved_mem_save_node(unsigned long node, + const char *uname, phys_addr_t base, phys_addr_t size) { } +#endif + +#endif /* __OF_RESERVED_MEM_H */ -- cgit v1.2.3 From f618c4703a14672d27bc2ca5d132a844363d6f5f Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Fri, 28 Feb 2014 14:42:49 +0100 Subject: drivers: of: add support for custom reserved memory drivers Add support for custom reserved memory drivers. Call their init() function for each reserved region and prepare for using operations provided by them with by the reserved_mem->ops array. Based on previous code provided by Josh Cartwright Signed-off-by: Marek Szyprowski Signed-off-by: Grant Likely --- drivers/of/of_reserved_mem.c | 29 +++++++++++++++++++++++++++++ include/asm-generic/vmlinux.lds.h | 11 +++++++++++ include/linux/of_reserved_mem.h | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) (limited to 'drivers') diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c index 69b811779585..daaaf935911d 100644 --- a/drivers/of/of_reserved_mem.c +++ b/drivers/of/of_reserved_mem.c @@ -170,6 +170,33 @@ static int __init __reserved_mem_alloc_size(unsigned long node, return 0; } +static const struct of_device_id __rmem_of_table_sentinel + __used __section(__reservedmem_of_table_end); + +/** + * res_mem_init_node() - call region specific reserved memory init code + */ +static int __init __reserved_mem_init_node(struct reserved_mem *rmem) +{ + extern const struct of_device_id __reservedmem_of_table[]; + const struct of_device_id *i; + + for (i = __reservedmem_of_table; i < &__rmem_of_table_sentinel; i++) { + reservedmem_of_init_fn initfn = i->data; + const char *compat = i->compatible; + + if (!of_flat_dt_is_compatible(rmem->fdt_node, compat)) + continue; + + if (initfn(rmem, rmem->fdt_node, rmem->name) == 0) { + pr_info("Reserved memory: initialized node %s, compatible id %s\n", + rmem->name, compat); + return 0; + } + } + return -ENOENT; +} + /** * fdt_init_reserved_mem - allocate and init all saved reserved memory regions */ @@ -184,5 +211,7 @@ void __init fdt_init_reserved_mem(void) if (rmem->size == 0) err = __reserved_mem_alloc_size(node, rmem->name, &rmem->base, &rmem->size); + if (err == 0) + __reserved_mem_init_node(rmem); } } diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index bc2121fa9132..f10f64fcc815 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -167,6 +167,16 @@ #define CLK_OF_TABLES() #endif +#ifdef CONFIG_OF_RESERVED_MEM +#define RESERVEDMEM_OF_TABLES() \ + . = ALIGN(8); \ + VMLINUX_SYMBOL(__reservedmem_of_table) = .; \ + *(__reservedmem_of_table) \ + *(__reservedmem_of_table_end) +#else +#define RESERVEDMEM_OF_TABLES() +#endif + #define KERNEL_DTB() \ STRUCT_ALIGN(); \ VMLINUX_SYMBOL(__dtb_start) = .; \ @@ -490,6 +500,7 @@ TRACE_SYSCALLS() \ MEM_DISCARD(init.rodata) \ CLK_OF_TABLES() \ + RESERVEDMEM_OF_TABLES() \ CLKSRC_OF_TABLES() \ KERNEL_DTB() \ IRQCHIP_OF_MATCH_TABLE() diff --git a/include/linux/of_reserved_mem.h b/include/linux/of_reserved_mem.h index 89226ed7d954..9b1fbb7f29fc 100644 --- a/include/linux/of_reserved_mem.h +++ b/include/linux/of_reserved_mem.h @@ -1,21 +1,53 @@ #ifndef __OF_RESERVED_MEM_H #define __OF_RESERVED_MEM_H +struct device; +struct of_phandle_args; +struct reserved_mem_ops; + struct reserved_mem { const char *name; unsigned long fdt_node; + const struct reserved_mem_ops *ops; phys_addr_t base; phys_addr_t size; + void *priv; +}; + +struct reserved_mem_ops { + void (*device_init)(struct reserved_mem *rmem, + struct device *dev); + void (*device_release)(struct reserved_mem *rmem, + struct device *dev); }; +typedef int (*reservedmem_of_init_fn)(struct reserved_mem *rmem, + unsigned long node, const char *uname); + #ifdef CONFIG_OF_RESERVED_MEM void fdt_init_reserved_mem(void); void fdt_reserved_mem_save_node(unsigned long node, const char *uname, phys_addr_t base, phys_addr_t size); + +#define RESERVEDMEM_OF_DECLARE(name, compat, init) \ + static const struct of_device_id __reservedmem_of_table_##name \ + __used __section(__reservedmem_of_table) \ + = { .compatible = compat, \ + .data = (init == (reservedmem_of_init_fn)NULL) ? \ + init : init } + #else static inline void fdt_init_reserved_mem(void) { } static inline void fdt_reserved_mem_save_node(unsigned long node, const char *uname, phys_addr_t base, phys_addr_t size) { } + +#define RESERVEDMEM_OF_DECLARE(name, compat, init) \ + static const struct of_device_id __reservedmem_of_table_##name \ + __attribute__((unused)) \ + = { .compatible = compat, \ + .data = (init == (reservedmem_of_init_fn)NULL) ? \ + init : init } + #endif #endif /* __OF_RESERVED_MEM_H */ -- cgit v1.2.3 From 2040b52768ebab6e7bd73af0dc63703269c62f17 Mon Sep 17 00:00:00 2001 From: Josh Cartwright Date: Thu, 13 Mar 2014 16:36:36 -0500 Subject: of: only scan for reserved mem when fdt present When the reserved memory patches hit -next, several legacy (non-DT) boot failures were detected and bisected down to that commit. There needs to be some sanity checking whether a DT is even present before parsing the reserved ranges. Reported-by: Kevin Hilman Signed-off-by: Josh Cartwright Tested-by: Kevin Hilman Signed-off-by: Grant Likely --- drivers/of/fdt.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index 510c0d8de8a0..501bc83f8cdf 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -557,6 +557,9 @@ static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname, */ void __init early_init_fdt_scan_reserved_mem(void) { + if (!initial_boot_params) + return; + of_scan_flat_dt(__fdt_scan_reserved_mem, NULL); fdt_init_reserved_mem(); } -- cgit v1.2.3