// SPDX-License-Identifier: GPL-2.0 #include #include #include #include #include #include #include #include #include #define HAVE_BOOTMEM \ (LINUX_VERSION_CODE < KERNEL_VERSION(4, 20, 0)) #define USE_MEMBLOCK \ (!HAVE_BOOTMEM || IS_ENABLED(CONFIG_NO_BOOTMEM)) #include #if HAVE_BOOTMEM # include #endif #define MAX_AVM_MTD_NAME_LENGTH 64 #define AVM_MTDRAM1_NAME "update-image.0" #define NUM_AVM_MTDRAMS 11 static unsigned int next_mtdram_slot __initdata; static struct avm_mtdram_param { const char *name; phys_addr_t base; phys_addr_t size; } avm_mtdram_params[NUM_AVM_MTDRAMS] __initdata; struct avm_mtdram_info { struct map_info map; struct mtd_info *mtd; }; #ifdef __mips__ /* ar7-manager builds virtual mtdram addresses assuming a fixed KSEG0 membase */ #define AVM_QUIRK_MEMBASE_KSEG0 0x80000000 #define QUIRKY_CPHYSADDR(a) \ ((a) - AVM_QUIRK_MEMBASE_KSEG0 + PHYS_OFFSET) #endif struct mtdram_check_overlap_query { phys_addr_t base; phys_addr_t size; bool overlap_mtdram; }; static void __init _mtdram_setup(char *p, char *name, struct mtdram_check_overlap_query *query) { struct avm_mtdram_param *param; unsigned long start, end; char *s; if (!p || !name) return; if (next_mtdram_slot >= NUM_AVM_MTDRAMS) { pr_err("%s: too many mtdram or mtdram1 parameter!\n", __func__); return; } param = &avm_mtdram_params[next_mtdram_slot]; /* * Note, early param parsing in do_early_param() matches prefixes. * Should "mtdram1" ever become an early param like "mtdram", the * "mtdram1" parser will be called for "mtdram" as well. * The following code then will silently return. */ /* parse start and end addresses */ s = strsep(&p, ","); if (!p) return; if (kstrtoul(s, 0, &start)) return; if (kstrtoul(p, 0, &end)) return; #ifdef CONFIG_MTD_AVM_MTDRAM_KSEG0 start = QUIRKY_CPHYSADDR(start); end = QUIRKY_CPHYSADDR(end); #endif if (start >= end) { pr_err("%s: ignoring mtdram '%s' with end address before start address\n", __func__, name); return; } if (query) { if (!(start > query->base + query->size || end < query->base)) query->overlap_mtdram = true; return; } param->base = start; param->size = end - start; param->name = name; next_mtdram_slot++; } #if USE_MEMBLOCK # define __memreserve(addr, size) memblock_reserve(addr, size) #else # define __memreserve(addr, size) reserve_bootmem(addr, size, BOOTMEM_EXCLUSIVE) #endif int __init avm_mtdram_mem_reserve(void) { unsigned int i; for (i = 0; i < next_mtdram_slot; ++i) { struct avm_mtdram_param *param; int ret; param = &avm_mtdram_params[i]; ret = __memreserve(param->base, param->size); if (ret) { pr_err("%s: Error reserving memory for mtdram %s: %d\n", __func__, param->name, ret); return ret; } pr_info("Reserved %pa bytes at %pa for mtdram \"%s\"\n", ¶m->size, ¶m->base, param->name); } return 0; } /* "mtdram1" argument parsing. mtdram memory is already reserved by bootloader. */ static int __init mtdram1_setup_or_query(char *p, struct mtdram_check_overlap_query *query) { _mtdram_setup(p, AVM_MTDRAM1_NAME, query); return 0; } static int __init mtdram1_setup(char *p) { return !mtdram1_setup_or_query(p, NULL); } __setup("mtdram1=", mtdram1_setup); /* "mtdram" argument parsing. Needs to reserve memory, so it must happen early. */ static int __init mtdram_setup_or_query(char *p, struct mtdram_check_overlap_query *query) { char *name; name = strsep(&p, ","); if (!p) return -EINVAL; _mtdram_setup(p, name, query); /* Early param parsers return zero if successful */ return 0; } static int __init mtdram_setup(char *p) { return mtdram_setup_or_query(p, NULL); } early_param("mtdram", mtdram_setup); static int __init mtdram_check_overlap(char *param, char *val, const char *doing, void *query) { if (!strcmp(param, "mtdram1")) return mtdram1_setup_or_query(val, query); else if (!strcmp(param, "mtdram")) return mtdram_setup_or_query(val, query); else return 0; } bool __init avm_mtdram_check_overlap(const char *cmdline, phys_addr_t base, phys_addr_t size) { static char static_cmdline[COMMAND_LINE_SIZE]; static DEFINE_SPINLOCK(static_cmdline_lock); struct mtdram_check_overlap_query query = { .base = base, .size = size, .overlap_mtdram = false, }; spin_lock(&static_cmdline_lock); strlcpy(static_cmdline, cmdline, sizeof(static_cmdline)); parse_args("avm mtdram options", static_cmdline, NULL, 0, 0, 0, &query, mtdram_check_overlap); spin_unlock(&static_cmdline_lock); return query.overlap_mtdram; } static int __init create_avm_mtdram(const char *name, phys_addr_t base, phys_addr_t size) { struct avm_mtdram_info *info; int err = 0; info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; pr_err("%s: creating mtdram '%s' (phys %pap@%pap)\n", __func__, name, &size, &base); info->map.virt = memremap(base, size, MEMREMAP_WB); if (!info->map.virt) { pr_err("%s: mtdram '%s': failed to remap memory\n", __func__, name); err = -ENOMEM; goto err1; } info->map.bankwidth = 4; info->map.phys = base; info->map.size = size; info->map.name = kstrdup(name, GFP_KERNEL); if (!info->map.name) { err = -ENOMEM; goto err2; } simple_map_init(&info->map); info->mtd = do_map_probe("map_ram", &info->map); if (!info->mtd) { pr_err("%s: mtdram '%s': failed to probe for map_ram\n", __func__, name); err = -ENOMEM; goto err3; } err = mtd_device_register(info->mtd, NULL, 0); if (err) { pr_err("%s: mtdram '%s': failed to register mtd device\n", __func__, name); goto err4; } pr_err("%s: mtd device '%s' registered successful\n", __func__, name); return 0; err4: map_destroy(info->mtd); err3: kfree(info->map.name); err2: memunmap(info->map.virt); err1: kfree(info); return err; } static int __init avm_mtdram_init(void) { unsigned int i; int result = 0; for (i = 0 ; i < next_mtdram_slot; i++) { struct avm_mtdram_param *param = &avm_mtdram_params[i]; if (create_avm_mtdram(param->name, param->base, param->size)) pr_err("%s: failed to create mtdram '%s'\n", __func__, param->name); } return result; } late_initcall(avm_mtdram_init);