/*------------------------------------------------------------------------------------------*\ * * Copyright (C) 2014- AVM GmbH * * 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 option) any later version. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA \*------------------------------------------------------------------------------------------*/ /* * qcom_mtd.c * * Created on: 18 Nov 2014 * Author: tklaassen */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * tables mapping bootloader mtd names to runtime mtd names, depending * on value of envvariable linux_fs_start */ struct mtd_entry { char *urlader_name; char *runtime_name_0; char *runtime_name_1; }; struct mtd_entry mtd_names_nand[] = { { "mtd1", "kernel", "reserved-kernel" }, { "mtd2", "urlader", "urlader" }, { "mtd3", "nand-tffs", "nand-tffs" }, { "mtd4", "reserved-kernel", "kernel" }, { "mtd5", "ubi", "ubi" }, { NULL, NULL, NULL }, }; struct mtd_entry mtd_names_ubi[] = { { "avm_filesys_0", "filesystem", "reserved-filesystem" }, { "avm_filesys_1", "reserved-filesystem", "filesystem" }, { "avm_config", "config", "config" }, { "avm_userdata", "nand-filesystem", "nand-filesystem" }, { NULL, NULL, NULL }, }; /* * forward declarations */ static int __init qcom_mtd_parser_function(struct mtd_info *mtd, struct mtd_partition **p_mtd_pat, struct mtd_part_parser_data *parse_data); /* * AVM partition parser */ static struct mtd_part_parser qcom_mtd_parser __initdata = { .owner = THIS_MODULE, .parse_fn = qcom_mtd_parser_function, .name = "avmpart", }; /* * NAND mtd data */ #define NAND_NAME "1ac00000.nand" #define NUM_NAND_MTD (ARRAY_SIZE(mtd_names_nand) - 1) static struct mtd_partition qcom_nand_partitions[NUM_NAND_MTD]; static int mtdram_root_dev_detected = 0; /*------------------------------------------------------------------------------------------*\ * plat-ram mapping data \*------------------------------------------------------------------------------------------*/ static struct mtd_partition qcom_ram_partitions[2]; static unsigned int qcom_ram_part_cnt = 0; static struct resource qcom_ram_resource[] = { { /* for RAM loaded filesystem */ .start = 0, // set by mtdram_setup() .end = 0, // dito .flags = IORESOURCE_MEM, } }; static struct platdata_mtd_ram qcom_ram_data = { .mapname = "ram-filesystem", .bankwidth = 4, // .partitions = qcom_ram_partitions, // .nr_partitions = 0, // will be set by mtdram_setup() }; struct platform_device qcom_ram_device = { .name = "mtd-ram", .id = -1, .dev = { .platform_data = &qcom_ram_data }, .num_resources = 1, .resource = &qcom_ram_resource[0], }; /* * search a name table for runtime MTD name. Returns original name if no * substitution is found */ static char* qcom_mtd_get_name(struct mtd_entry *nametable, const char *name) { unsigned int i; unsigned long linux_fs_start; char *p, *new_name; int res; linux_fs_start = 0; new_name = (char *) name; p = prom_getenv("linux_fs_start"); if(p != NULL){ res = kstrtoul(p, 0, &linux_fs_start); if(res == 0){ switch(linux_fs_start){ case 0: case 1: break; default: linux_fs_start = 0; } } } i = 0; while(nametable[i].urlader_name != NULL){ if(!strcmp(name, nametable[i].urlader_name)){ new_name = (linux_fs_start == 0) ? nametable[i].runtime_name_0 : nametable[i].runtime_name_1; break; } ++i; } return new_name; } /*------------------------------------------------------------------------------------------*\ * partition parser callback. Sets up and returns matching partition tables * for our NAND and RAM MTDs \*------------------------------------------------------------------------------------------*/ static int qcom_mtd_parser_function(struct mtd_info *mtd, struct mtd_partition **p_mtd_pat, struct mtd_part_parser_data *parse_data) { int result, part_cnt; struct mtd_partition *kernel_part, *fs_part; uint32_t magic; size_t readlen; loff_t offset; pr_debug("[%s] mtd_info->name=%s, mtd_info->index=%u, p_mtd_pat=0x%x\n", __func__, mtd->name, mtd->index, (unsigned int )*p_mtd_pat); result = 0; part_cnt = 0; if(mtd->name != NULL && !strcmp(mtd->name, NAND_NAME)){ pr_info("[%s] Detected NAND with %d partitions\n", __func__, ARRAY_SIZE(qcom_nand_partitions)); *p_mtd_pat = kmemdup(qcom_nand_partitions, sizeof(qcom_nand_partitions), GFP_KERNEL); return (*p_mtd_pat != NULL) ? ARRAY_SIZE(qcom_nand_partitions) : -ENOMEM; } // only go on if this is our ram loaded system and the plat-ram mtd // has been initialised if(qcom_ram_part_cnt == 0 || strcmp(mtd->name, "ram-filesystem") != 0){ return 0; } fs_part = &qcom_ram_partitions[0]; kernel_part = &qcom_ram_partitions[1]; /* * this MTD contains a squashfs and possibly a kernel image prepended to it. * The squashfs will be aligned on a 256 byte boundary. We will start on * the first aligned offset inside the mtd and look for the squashfs- * signature every 256 bytes. */ offset = ALIGN(kernel_part->offset, 256); while(offset < (kernel_part->offset + kernel_part->size)){ magic = 0; result = mtd_read(mtd, offset, sizeof(magic), &readlen, (u_char *) &magic); if(result != 0 || readlen != sizeof(magic)){ pr_err("[%s] Error scanning RAM-MTD for squashfs\n", __func__); part_cnt = 0; break; } /* * we found the squashfs, now adjust sizes and offsets for the filesys * and kernel partitions */ // FIXME: are we big or little endian? if((magic == SQUASHFS_MAGIC) || (magic == __swab32(SQUASHFS_MAGIC))){ fs_part->offset = offset; fs_part->size = kernel_part->offset + kernel_part->size - offset; kernel_part->size = offset - kernel_part->offset; pr_info("[%s] squashfs magic found @0x%llx\n" , __func__, offset); part_cnt = qcom_ram_part_cnt; break; } offset += 256; } if(part_cnt > 0){ /* mtd_device_parse_register() expects this function to return an * array of partition entries that will be released by kfree() after * setting up the partitions. */ *p_mtd_pat = kmemdup(qcom_ram_partitions, sizeof(qcom_ram_partitions), GFP_KERNEL); if(*p_mtd_pat == NULL){ return -ENOMEM; } } return part_cnt; } /*------------------------------------------------------------------------------------------*\ * MTD add notifier. Looks for MTDs created from specially named UBI volumes * and substitutes the correct runtime names \*------------------------------------------------------------------------------------------*/ void qcom_ubimtd_add_notifier(struct mtd_info *mtd) { char *mtdname, *oldname; if(!mtd->name){ pr_info("[%s] Called for unnamed mtd", __func__); return; } if(mtd->type != MTD_UBIVOLUME){ pr_debug("[%s] skipping mtd %s. Not an UBI volume\n", __func__, mtd->name); return; } mtdname = qcom_mtd_get_name(&mtd_names_ubi[0], mtd->name); if(mtdname != mtd->name){ // gluebi allocates name strings dynamiccaly and frees them on // volume removal oldname = (char *) mtd->name; mtd->name = kstrdup(mtdname, GFP_KERNEL); if(mtd->name != NULL){ pr_debug("[%s] renamed mtd %s -> %s\n", __func__, oldname, mtd->name); // FIXME: is it safe to free the string or should we just drop it? kfree(oldname); } else { mtd->name = oldname; pr_warning("[%s] Unable to rename mtd %s.\n", __func__, mtd->name); } } } void qcom_ubimtd_rm_notifier(struct mtd_info *mtd) { pr_err("ignore %s", mtd->name); } struct mtd_notifier qcom_ubimtd_notifier_ops = { add: qcom_ubimtd_add_notifier, remove: qcom_ubimtd_rm_notifier }; extern int __init ubi_mtd_param_parse(const char *val, struct kernel_param *kp); extern int __init root_dev_setup(char *line); struct mtd_info *urlader_mtd; /*------------------------------------------------------------------------------------------*\ * General MTD notfiers. * First calls UBI notfier above to fix MTD names, then looks for special * partitions (rootfs, tffs, UBI) and calls necessary setup functions \*------------------------------------------------------------------------------------------*/ void __init qcom_mtd_add_notifier(struct mtd_info *mtd) { char root_dev[64]; int ret; if(!mtd->name){ pr_info("[%s] Called for unnamed mtd", __func__); return; } pr_err("[%s] name %s\n", __func__, mtd->name); // change UBI volume names to canonical MTD names qcom_ubimtd_add_notifier(mtd); // check for ram-loaded rootfs and register it with the kernel. // Set a flag to ignore any rootfs found in flash later if(mtdram_root_dev_detected == 0 && !strcmp(mtd->name, "rootfs_ram")){ ret = snprintf(root_dev, sizeof(root_dev), "/dev/mtdblock%d", mtd->index); if(ret >= sizeof(root_dev)){ pr_emerg("%s: Unable to generate root device name!\n", mtd->name); return; } pr_info("%s: %s will be used as root device\n", mtd->name, root_dev); root_dev_setup(root_dev); mtdram_root_dev_detected = 1; }else if(!strcmp(mtd->name, "nand-tffs")){ if(mtd->size > 0){ TFFS3_Register_NAND(mtd); pr_err("[%s] tffs3 on MTD %s\n", __func__, mtd->name); } }else if(!strcmp(mtd->name, "urlader")){ urlader_mtd = mtd; }else if(!strcmp(mtd->name, "ubi")){ ubi_mtd_param_parse(mtd->name, NULL); pr_err("[%s] UBI on MTD %s\n", __func__, mtd->name); }else{ pr_err("skip %s\n", mtd->name); } } void __init qcom_mtd_rm_notifier(struct mtd_info *mtd) { pr_err("[%s] ignore %s\n", __func__, mtd->name); } struct mtd_notifier qcom_mtd_notifier_ops = { add: qcom_mtd_add_notifier, remove: qcom_mtd_rm_notifier }; /*------------------------------------------------------------------------------------------*\ * UBI volume notifier * Check volume name against runtime name and if it contains the current * root filesysten, register the associated ubiblock-dev as kernel root-dev \*------------------------------------------------------------------------------------------*/ void __init qcom_ubi_add_notifier(struct ubi_device_info *di, struct ubi_volume_info *vi) { int ret; char root_dev[64]; char *name; // skip check if a RAM MTD has already been registered if(mtdram_root_dev_detected != 0){ return; } if(!vi->name){ pr_info("[%s] Called for unnamed volume", __func__); return; } // just a sanity check. There should not be a volume called "filesystem" if(!strcmp(vi->name, "filesystem")){ pr_warn("[%s] found UBI volume named \"filesystem\". Potential problems ahead!\n", __func__); return; } // register ubiblock-dev as kernel root-dev if this volume's runtime // name will be "filesystem" name = qcom_mtd_get_name(&mtd_names_ubi[0], vi->name); if(!strcmp(name, "filesystem")){ ret = snprintf(root_dev, sizeof(root_dev), "/dev/ubiblock%d_%d", vi->ubi_num, vi->vol_id); if(ret >= sizeof(root_dev)){ pr_emerg("[%s] Unable to generate root device name /dev/ubiblock%d_%d!\n", name, vi->ubi_num, vi->vol_id); return; } pr_info("[%s] %s(%s) will be used as root device\n", __func__, root_dev, vi->name); root_dev_setup(root_dev); }else{ pr_err("skip %s\n", vi->name); } } static int __init ubi_notify(struct notifier_block *nb, unsigned long l, void *ns_ptr) { struct ubi_notification *nt = ns_ptr; switch(l){ case UBI_VOLUME_ADDED: qcom_ubi_add_notifier(&nt->di, &nt->vi); break; case UBI_VOLUME_REMOVED: case UBI_VOLUME_RESIZED: case UBI_VOLUME_UPDATED: break; default: break; } return NOTIFY_OK; } static struct notifier_block ubi_notifier __initdata = { .notifier_call = ubi_notify, }; /*------------------------------------------------------------------------------------------*\ * unregister parser callback to be used after system init has completed \*------------------------------------------------------------------------------------------*/ void __ref qcom_unregister_notifiers(void) { // UBI filesystem volumes might be created later from userspace. // We have to register a notifier that will update the new volume's // MTD name register_mtd_user(&qcom_ubimtd_notifier_ops); // de-register all other notifiers deregister_mtd_parser(&qcom_mtd_parser); unregister_mtd_user(&qcom_mtd_notifier_ops); ubi_unregister_volume_notifier(&ubi_notifier); pr_info("Unregistered mtd and ubi notifiers\n"); } /*------------------------------------------------------------------------------------------*\ * Set up the NAND mtd partitions from bootloader environment and register platform devices, * notifiers and partition parser \*------------------------------------------------------------------------------------------*/ static int __init qcom_mtd_init(void) { unsigned int i; unsigned long linux_fs_start; uint64_t start, end, size; loff_t offset; char *p, *np, *name; int res; offset = 0; linux_fs_start = 0; // find out which of the two images is to be used p = prom_getenv("linux_fs_start"); if(p != NULL){ res = kstrtoul(p, 0, &linux_fs_start); if(res == 0 && linux_fs_start != 0){ linux_fs_start = 1; } } // set up NAND partitions from mtdx=... env vars i = 0; while(mtd_names_nand[i].urlader_name != NULL){ p = prom_getenv(mtd_names_nand[i].urlader_name); name = linux_fs_start == 0 ? mtd_names_nand[i].runtime_name_0 : mtd_names_nand[i].runtime_name_1; if(p == NULL || name == NULL){ continue; } pr_info("mtd[%u] = %s\n", i, p); np = strchr(p, ','); if(np == NULL){ start = 0; continue; } *np = '\0'; ++np; res = kstrtoull(p, 0, &start); if(res != 0){ pr_err("[%s] error parsing start for %s\n", __func__, name); continue; } res = kstrtoull(np, 0, &end); if(res != 0){ pr_err("[%s] error parsing end for %s\n", __func__, name); continue; } size = end - start; qcom_nand_partitions[i].name = (linux_fs_start == 0) ? mtd_names_nand[i].runtime_name_0 : mtd_names_nand[i].runtime_name_1; qcom_nand_partitions[i].offset = start; qcom_nand_partitions[i].size = size; qcom_nand_partitions[i].mask_flags = 0; offset += size; ++i; } // TODO: check for partitions with size 0 and move their offsets to end of // device // only register the plat-ram device if it has been initialised // by mtdram_setup() if(qcom_ram_part_cnt > 0){ platform_device_register(&qcom_ram_device); } // register mtd parser and notification callbacks for mtd and ubi volumes register_mtd_parser(&qcom_mtd_parser); register_mtd_user(&qcom_mtd_notifier_ops); ubi_register_volume_notifier(&ubi_notifier, 1); return 0; } subsys_initcall(qcom_mtd_init); /*------------------------------------------------------------------------------------------*\ * Called when "mtdram1=..." is found in the kernel cmdline. Scan the start and end addresses * and set up resource and partition data for the plat-ram mtd. \*------------------------------------------------------------------------------------------*/ static int __init mtdram_setup(char *p) { char *np; unsigned long start, end; int result; if(p == NULL){ return 0; } pr_debug("[%s] [mtdram1] %s\n", __func__, p); np = strchr(p, ','); if(np == NULL){ goto err_out; } *np = '\0'; ++np; result = kstrtoul(p, 0, &start); if(result != 0){ goto err_out; } result = kstrtoul(np, 0, &end); if(result != 0){ goto err_out; } if(start >= end){ goto err_out; } // FIXME: do the addresses have to be translated to kernel address space? qcom_ram_resource[0].start = start; qcom_ram_resource[0].end = end - 1; qcom_ram_resource[0].flags = IORESOURCE_MEM; pr_info("[%s] mtdram1 0x%08x-0x%08x\n", __func__, qcom_ram_resource[0].start, qcom_ram_resource[0].end); /* * set up two partition covering the whole MTD. Offsets and sizes will * be adjusted by the partition scanner */ qcom_ram_partitions[0].name = "rootfs_ram"; qcom_ram_partitions[0].offset = 0; qcom_ram_partitions[0].size = end - start; qcom_ram_partitions[0].mask_flags = MTD_ROM; qcom_ram_partitions[1].name = "kernel_ram"; qcom_ram_partitions[1].offset = 0; qcom_ram_partitions[1].size = end - start; qcom_ram_partitions[1].mask_flags = MTD_ROM; qcom_ram_part_cnt = 2; return 0; err_out: pr_err("[%s] error parsing setup string: %s\n", __func__, p); return 0; } __setup("mtdram1=", mtdram_setup);