diff options
Diffstat (limited to 'drivers/mtd/parsers')
-rw-r--r-- | drivers/mtd/parsers/Kconfig | 68 | ||||
-rw-r--r-- | drivers/mtd/parsers/Makefile | 5 | ||||
-rw-r--r-- | drivers/mtd/parsers/ar7part.c | 129 | ||||
-rw-r--r-- | drivers/mtd/parsers/bcm47xxpart.c | 317 | ||||
-rw-r--r-- | drivers/mtd/parsers/bcm63xxpart.c | 180 | ||||
-rw-r--r-- | drivers/mtd/parsers/cmdlinepart.c | 400 | ||||
-rw-r--r-- | drivers/mtd/parsers/ofpart.c | 236 |
7 files changed, 1335 insertions, 0 deletions
diff --git a/drivers/mtd/parsers/Kconfig b/drivers/mtd/parsers/Kconfig index 176b75a375b1..f98363c9b363 100644 --- a/drivers/mtd/parsers/Kconfig +++ b/drivers/mtd/parsers/Kconfig @@ -1,4 +1,72 @@ # SPDX-License-Identifier: GPL-2.0-only +config MTD_AR7_PARTS + tristate "TI AR7 partitioning parser" + help + TI AR7 partitioning parser support + +config MTD_BCM47XX_PARTS + tristate "BCM47XX partitioning parser" + depends on BCM47XX || ARCH_BCM_5301X + help + This provides partitions parser for devices based on BCM47xx + boards. + +config MTD_BCM63XX_PARTS + tristate "BCM63XX CFE partitioning parser" + depends on BCM63XX || BMIPS_GENERIC || COMPILE_TEST + select CRC32 + select MTD_PARSER_IMAGETAG + help + This provides partition parsing for BCM63xx devices with CFE + bootloaders. + +config MTD_CMDLINE_PARTS + tristate "Command line partition table parsing" + depends on MTD + help + Allow generic configuration of the MTD partition tables via the kernel + command line. Multiple flash resources are supported for hardware where + different kinds of flash memory are available. + + You will still need the parsing functions to be called by the driver + for your particular device. It won't happen automatically. The + SA1100 map driver (CONFIG_MTD_SA1100) has an option for this, for + example. + + The format for the command line is as follows: + + mtdparts=<mtddef>[;<mtddef] + <mtddef> := <mtd-id>:<partdef>[,<partdef>] + <partdef> := <size>[@offset][<name>][ro] + <mtd-id> := unique id used in mapping driver/device + <size> := standard linux memsize OR "-" to denote all + remaining space + <name> := (NAME) + + Due to the way Linux handles the command line, no spaces are + allowed in the partition definition, including mtd id's and partition + names. + + Examples: + + 1 flash resource (mtd-id "sa1100"), with 1 single writable partition: + mtdparts=sa1100:- + + Same flash, but 2 named partitions, the first one being read-only: + mtdparts=sa1100:256k(ARMboot)ro,-(root) + + If unsure, say 'N'. + +config MTD_OF_PARTS + tristate "OpenFirmware (device tree) partitioning parser" + default y + depends on OF + help + This provides a open firmware device tree partition parser + which derives the partition map from the children of the + flash memory node, as described in + Documentation/devicetree/bindings/mtd/partition.txt. + config MTD_PARSER_IMAGETAG tristate "Parser for BCM963XX Image Tag format partitions" depends on BCM63XX || BMIPS_GENERIC || COMPILE_TEST diff --git a/drivers/mtd/parsers/Makefile b/drivers/mtd/parsers/Makefile index dd566bdd16e2..b0c5f62f9e85 100644 --- a/drivers/mtd/parsers/Makefile +++ b/drivers/mtd/parsers/Makefile @@ -1,4 +1,9 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_MTD_AR7_PARTS) += ar7part.o +obj-$(CONFIG_MTD_BCM47XX_PARTS) += bcm47xxpart.o +obj-$(CONFIG_MTD_BCM63XX_PARTS) += bcm63xxpart.o +obj-$(CONFIG_MTD_CMDLINE_PARTS) += cmdlinepart.o +obj-$(CONFIG_MTD_OF_PARTS) += ofpart.o obj-$(CONFIG_MTD_PARSER_IMAGETAG) += parser_imagetag.o obj-$(CONFIG_MTD_AFS_PARTS) += afs.o obj-$(CONFIG_MTD_PARSER_TRX) += parser_trx.o diff --git a/drivers/mtd/parsers/ar7part.c b/drivers/mtd/parsers/ar7part.c new file mode 100644 index 000000000000..8cd683711ac6 --- /dev/null +++ b/drivers/mtd/parsers/ar7part.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright © 2007 Eugene Konev <ejka@openwrt.org> + * + * TI AR7 flash partition table. + * Based on ar7 map by Felix Fietkau <nbd@openwrt.org> + */ + +#include <linux/kernel.h> +#include <linux/slab.h> + +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/memblock.h> +#include <linux/module.h> + +#include <uapi/linux/magic.h> + +#define AR7_PARTS 4 +#define ROOT_OFFSET 0xe0000 + +#define LOADER_MAGIC1 le32_to_cpu(0xfeedfa42) +#define LOADER_MAGIC2 le32_to_cpu(0xfeed1281) + +struct ar7_bin_rec { + unsigned int checksum; + unsigned int length; + unsigned int address; +}; + +static int create_mtd_partitions(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct ar7_bin_rec header; + unsigned int offset; + size_t len; + unsigned int pre_size = master->erasesize, post_size = 0; + unsigned int root_offset = ROOT_OFFSET; + + int retries = 10; + struct mtd_partition *ar7_parts; + + ar7_parts = kcalloc(AR7_PARTS, sizeof(*ar7_parts), GFP_KERNEL); + if (!ar7_parts) + return -ENOMEM; + ar7_parts[0].name = "loader"; + ar7_parts[0].offset = 0; + ar7_parts[0].size = master->erasesize; + ar7_parts[0].mask_flags = MTD_WRITEABLE; + + ar7_parts[1].name = "config"; + ar7_parts[1].offset = 0; + ar7_parts[1].size = master->erasesize; + ar7_parts[1].mask_flags = 0; + + do { /* Try 10 blocks starting from master->erasesize */ + offset = pre_size; + mtd_read(master, offset, sizeof(header), &len, + (uint8_t *)&header); + if (!strncmp((char *)&header, "TIENV0.8", 8)) + ar7_parts[1].offset = pre_size; + if (header.checksum == LOADER_MAGIC1) + break; + if (header.checksum == LOADER_MAGIC2) + break; + pre_size += master->erasesize; + } while (retries--); + + pre_size = offset; + + if (!ar7_parts[1].offset) { + ar7_parts[1].offset = master->size - master->erasesize; + post_size = master->erasesize; + } + + switch (header.checksum) { + case LOADER_MAGIC1: + while (header.length) { + offset += sizeof(header) + header.length; + mtd_read(master, offset, sizeof(header), &len, + (uint8_t *)&header); + } + root_offset = offset + sizeof(header) + 4; + break; + case LOADER_MAGIC2: + while (header.length) { + offset += sizeof(header) + header.length; + mtd_read(master, offset, sizeof(header), &len, + (uint8_t *)&header); + } + root_offset = offset + sizeof(header) + 4 + 0xff; + root_offset &= ~(uint32_t)0xff; + break; + default: + printk(KERN_WARNING "Unknown magic: %08x\n", header.checksum); + break; + } + + mtd_read(master, root_offset, sizeof(header), &len, (u8 *)&header); + if (header.checksum != SQUASHFS_MAGIC) { + root_offset += master->erasesize - 1; + root_offset &= ~(master->erasesize - 1); + } + + ar7_parts[2].name = "linux"; + ar7_parts[2].offset = pre_size; + ar7_parts[2].size = master->size - pre_size - post_size; + ar7_parts[2].mask_flags = 0; + + ar7_parts[3].name = "rootfs"; + ar7_parts[3].offset = root_offset; + ar7_parts[3].size = master->size - root_offset - post_size; + ar7_parts[3].mask_flags = 0; + + *pparts = ar7_parts; + return AR7_PARTS; +} + +static struct mtd_part_parser ar7_parser = { + .parse_fn = create_mtd_partitions, + .name = "ar7part", +}; +module_mtd_part_parser(ar7_parser); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR( "Felix Fietkau <nbd@openwrt.org>, " + "Eugene Konev <ejka@openwrt.org>"); +MODULE_DESCRIPTION("MTD partitioning for TI AR7"); diff --git a/drivers/mtd/parsers/bcm47xxpart.c b/drivers/mtd/parsers/bcm47xxpart.c new file mode 100644 index 000000000000..6012a10f10c8 --- /dev/null +++ b/drivers/mtd/parsers/bcm47xxpart.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BCM47XX MTD partitioning + * + * Copyright © 2012 Rafał Miłecki <zajec5@gmail.com> + */ + +#include <linux/bcm47xx_nvram.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> + +#include <uapi/linux/magic.h> + +/* + * NAND flash on Netgear R6250 was verified to contain 15 partitions. + * This will result in allocating too big array for some old devices, but the + * memory will be freed soon anyway (see mtd_device_parse_register). + */ +#define BCM47XXPART_MAX_PARTS 20 + +/* + * Amount of bytes we read when analyzing each block of flash memory. + * Set it big enough to allow detecting partition and reading important data. + */ +#define BCM47XXPART_BYTES_TO_READ 0x4e8 + +/* Magics */ +#define BOARD_DATA_MAGIC 0x5246504D /* MPFR */ +#define BOARD_DATA_MAGIC2 0xBD0D0BBD +#define CFE_MAGIC 0x43464531 /* 1EFC */ +#define FACTORY_MAGIC 0x59544346 /* FCTY */ +#define NVRAM_HEADER 0x48534C46 /* FLSH */ +#define POT_MAGIC1 0x54544f50 /* POTT */ +#define POT_MAGIC2 0x504f /* OP */ +#define ML_MAGIC1 0x39685a42 +#define ML_MAGIC2 0x26594131 +#define TRX_MAGIC 0x30524448 +#define SHSQ_MAGIC 0x71736873 /* shsq (weird ZTE H218N endianness) */ + +static const char * const trx_types[] = { "trx", NULL }; + +struct trx_header { + uint32_t magic; + uint32_t length; + uint32_t crc32; + uint16_t flags; + uint16_t version; + uint32_t offset[3]; +} __packed; + +static void bcm47xxpart_add_part(struct mtd_partition *part, const char *name, + u64 offset, uint32_t mask_flags) +{ + part->name = name; + part->offset = offset; + part->mask_flags = mask_flags; +} + +/** + * bcm47xxpart_bootpartition - gets index of TRX partition used by bootloader + * + * Some devices may have more than one TRX partition. In such case one of them + * is the main one and another a failsafe one. Bootloader may fallback to the + * failsafe firmware if it detects corruption of the main image. + * + * This function provides info about currently used TRX partition. It's the one + * containing kernel started by the bootloader. + */ +static int bcm47xxpart_bootpartition(void) +{ + char buf[4]; + int bootpartition; + + /* Check CFE environment variable */ + if (bcm47xx_nvram_getenv("bootpartition", buf, sizeof(buf)) > 0) { + if (!kstrtoint(buf, 0, &bootpartition)) + return bootpartition; + } + + return 0; +} + +static int bcm47xxpart_parse(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + uint8_t i, curr_part = 0; + uint32_t *buf; + size_t bytes_read; + uint32_t offset; + uint32_t blocksize = master->erasesize; + int trx_parts[2]; /* Array with indexes of TRX partitions */ + int trx_num = 0; /* Number of found TRX partitions */ + int possible_nvram_sizes[] = { 0x8000, 0xF000, 0x10000, }; + int err; + + /* + * Some really old flashes (like AT45DB*) had smaller erasesize-s, but + * partitions were aligned to at least 0x1000 anyway. + */ + if (blocksize < 0x1000) + blocksize = 0x1000; + + /* Alloc */ + parts = kcalloc(BCM47XXPART_MAX_PARTS, sizeof(struct mtd_partition), + GFP_KERNEL); + if (!parts) + return -ENOMEM; + + buf = kzalloc(BCM47XXPART_BYTES_TO_READ, GFP_KERNEL); + if (!buf) { + kfree(parts); + return -ENOMEM; + } + + /* Parse block by block looking for magics */ + for (offset = 0; offset <= master->size - blocksize; + offset += blocksize) { + /* Nothing more in higher memory on BCM47XX (MIPS) */ + if (IS_ENABLED(CONFIG_BCM47XX) && offset >= 0x2000000) + break; + + if (curr_part >= BCM47XXPART_MAX_PARTS) { + pr_warn("Reached maximum number of partitions, scanning stopped!\n"); + break; + } + + /* Read beginning of the block */ + err = mtd_read(master, offset, BCM47XXPART_BYTES_TO_READ, + &bytes_read, (uint8_t *)buf); + if (err && !mtd_is_bitflip(err)) { + pr_err("mtd_read error while parsing (offset: 0x%X): %d\n", + offset, err); + continue; + } + + /* Magic or small NVRAM at 0x400 */ + if ((buf[0x4e0 / 4] == CFE_MAGIC && buf[0x4e4 / 4] == CFE_MAGIC) || + (buf[0x400 / 4] == NVRAM_HEADER)) { + bcm47xxpart_add_part(&parts[curr_part++], "boot", + offset, MTD_WRITEABLE); + continue; + } + + /* + * board_data starts with board_id which differs across boards, + * but we can use 'MPFR' (hopefully) magic at 0x100 + */ + if (buf[0x100 / 4] == BOARD_DATA_MAGIC) { + bcm47xxpart_add_part(&parts[curr_part++], "board_data", + offset, MTD_WRITEABLE); + continue; + } + + /* Found on Huawei E970 */ + if (buf[0x000 / 4] == FACTORY_MAGIC) { + bcm47xxpart_add_part(&parts[curr_part++], "factory", + offset, MTD_WRITEABLE); + continue; + } + + /* POT(TOP) */ + if (buf[0x000 / 4] == POT_MAGIC1 && + (buf[0x004 / 4] & 0xFFFF) == POT_MAGIC2) { + bcm47xxpart_add_part(&parts[curr_part++], "POT", offset, + MTD_WRITEABLE); + continue; + } + + /* ML */ + if (buf[0x010 / 4] == ML_MAGIC1 && + buf[0x014 / 4] == ML_MAGIC2) { + bcm47xxpart_add_part(&parts[curr_part++], "ML", offset, + MTD_WRITEABLE); + continue; + } + + /* TRX */ + if (buf[0x000 / 4] == TRX_MAGIC) { + struct trx_header *trx; + uint32_t last_subpart; + uint32_t trx_size; + + if (trx_num >= ARRAY_SIZE(trx_parts)) + pr_warn("No enough space to store another TRX found at 0x%X\n", + offset); + else + trx_parts[trx_num++] = curr_part; + bcm47xxpart_add_part(&parts[curr_part++], "firmware", + offset, 0); + + /* + * Try to find TRX size. The "length" field isn't fully + * reliable as it could be decreased to make CRC32 cover + * only part of TRX data. It's commonly used as checksum + * can't cover e.g. ever-changing rootfs partition. + * Use offsets as helpers for assuming min TRX size. + */ + trx = (struct trx_header *)buf; + last_subpart = max3(trx->offset[0], trx->offset[1], + trx->offset[2]); + trx_size = max(trx->length, last_subpart + blocksize); + + /* + * Skip the TRX data. Decrease offset by block size as + * the next loop iteration will increase it. + */ + offset += roundup(trx_size, blocksize) - blocksize; + continue; + } + + /* Squashfs on devices not using TRX */ + if (le32_to_cpu(buf[0x000 / 4]) == SQUASHFS_MAGIC || + buf[0x000 / 4] == SHSQ_MAGIC) { + bcm47xxpart_add_part(&parts[curr_part++], "rootfs", + offset, 0); + continue; + } + + /* + * New (ARM?) devices may have NVRAM in some middle block. Last + * block will be checked later, so skip it. + */ + if (offset != master->size - blocksize && + buf[0x000 / 4] == NVRAM_HEADER) { + bcm47xxpart_add_part(&parts[curr_part++], "nvram", + offset, 0); + continue; + } + + /* Read middle of the block */ + err = mtd_read(master, offset + 0x8000, 0x4, &bytes_read, + (uint8_t *)buf); + if (err && !mtd_is_bitflip(err)) { + pr_err("mtd_read error while parsing (offset: 0x%X): %d\n", + offset, err); + continue; + } + + /* Some devices (ex. WNDR3700v3) don't have a standard 'MPFR' */ + if (buf[0x000 / 4] == BOARD_DATA_MAGIC2) { + bcm47xxpart_add_part(&parts[curr_part++], "board_data", + offset, MTD_WRITEABLE); + continue; + } + } + + /* Look for NVRAM at the end of the last block. */ + for (i = 0; i < ARRAY_SIZE(possible_nvram_sizes); i++) { + if (curr_part >= BCM47XXPART_MAX_PARTS) { + pr_warn("Reached maximum number of partitions, scanning stopped!\n"); + break; + } + + offset = master->size - possible_nvram_sizes[i]; + err = mtd_read(master, offset, 0x4, &bytes_read, + (uint8_t *)buf); + if (err && !mtd_is_bitflip(err)) { + pr_err("mtd_read error while reading (offset 0x%X): %d\n", + offset, err); + continue; + } + + /* Standard NVRAM */ + if (buf[0] == NVRAM_HEADER) { + bcm47xxpart_add_part(&parts[curr_part++], "nvram", + master->size - blocksize, 0); + break; + } + } + + kfree(buf); + + /* + * Assume that partitions end at the beginning of the one they are + * followed by. + */ + for (i = 0; i < curr_part; i++) { + u64 next_part_offset = (i < curr_part - 1) ? + parts[i + 1].offset : master->size; + + parts[i].size = next_part_offset - parts[i].offset; + } + + /* If there was TRX parse it now */ + for (i = 0; i < trx_num; i++) { + struct mtd_partition *trx = &parts[trx_parts[i]]; + + if (i == bcm47xxpart_bootpartition()) + trx->types = trx_types; + else + trx->name = "failsafe"; + } + + *pparts = parts; + return curr_part; +}; + +static const struct of_device_id bcm47xxpart_of_match_table[] = { + { .compatible = "brcm,bcm947xx-cfe-partitions" }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm47xxpart_of_match_table); + +static struct mtd_part_parser bcm47xxpart_mtd_parser = { + .parse_fn = bcm47xxpart_parse, + .name = "bcm47xxpart", + .of_match_table = bcm47xxpart_of_match_table, +}; +module_mtd_part_parser(bcm47xxpart_mtd_parser); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MTD partitioning for BCM47XX flash memories"); diff --git a/drivers/mtd/parsers/bcm63xxpart.c b/drivers/mtd/parsers/bcm63xxpart.c new file mode 100644 index 000000000000..78f90c6c18fd --- /dev/null +++ b/drivers/mtd/parsers/bcm63xxpart.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BCM63XX CFE image tag parser + * + * Copyright © 2006-2008 Florian Fainelli <florian@openwrt.org> + * Mike Albon <malbon@openwrt.org> + * Copyright © 2009-2010 Daniel Dickinson <openwrt@cshore.neomailbox.net> + * Copyright © 2011-2013 Jonas Gorski <jonas.gorski@gmail.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/bcm963xx_nvram.h> +#include <linux/bcm963xx_tag.h> +#include <linux/crc32.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/of.h> + +#define BCM963XX_CFE_BLOCK_SIZE SZ_64K /* always at least 64KiB */ + +#define BCM963XX_CFE_MAGIC_OFFSET 0x4e0 +#define BCM963XX_CFE_VERSION_OFFSET 0x570 +#define BCM963XX_NVRAM_OFFSET 0x580 + +/* Ensure strings read from flash structs are null terminated */ +#define STR_NULL_TERMINATE(x) \ + do { char *_str = (x); _str[sizeof(x) - 1] = 0; } while (0) + +static int bcm63xx_detect_cfe(struct mtd_info *master) +{ + char buf[9]; + int ret; + size_t retlen; + + ret = mtd_read(master, BCM963XX_CFE_VERSION_OFFSET, 5, &retlen, + (void *)buf); + buf[retlen] = 0; + + if (ret) + return ret; + + if (strncmp("cfe-v", buf, 5) == 0) + return 0; + + /* very old CFE's do not have the cfe-v string, so check for magic */ + ret = mtd_read(master, BCM963XX_CFE_MAGIC_OFFSET, 8, &retlen, + (void *)buf); + buf[retlen] = 0; + + return strncmp("CFE1CFE1", buf, 8); +} + +static int bcm63xx_read_nvram(struct mtd_info *master, + struct bcm963xx_nvram *nvram) +{ + u32 actual_crc, expected_crc; + size_t retlen; + int ret; + + /* extract nvram data */ + ret = mtd_read(master, BCM963XX_NVRAM_OFFSET, BCM963XX_NVRAM_V5_SIZE, + &retlen, (void *)nvram); + if (ret) + return ret; + + ret = bcm963xx_nvram_checksum(nvram, &expected_crc, &actual_crc); + if (ret) + pr_warn("nvram checksum failed, contents may be invalid (expected %08x, got %08x)\n", + expected_crc, actual_crc); + + if (!nvram->psi_size) + nvram->psi_size = BCM963XX_DEFAULT_PSI_SIZE; + + return 0; +} + +static const char * const bcm63xx_cfe_part_types[] = { + "bcm963xx-imagetag", + NULL, +}; + +static int bcm63xx_parse_cfe_nor_partitions(struct mtd_info *master, + const struct mtd_partition **pparts, struct bcm963xx_nvram *nvram) +{ + struct mtd_partition *parts; + int nrparts = 3, curpart = 0; + unsigned int cfelen, nvramlen; + unsigned int cfe_erasesize; + int i; + + cfe_erasesize = max_t(uint32_t, master->erasesize, + BCM963XX_CFE_BLOCK_SIZE); + + cfelen = cfe_erasesize; + nvramlen = nvram->psi_size * SZ_1K; + nvramlen = roundup(nvramlen, cfe_erasesize); + + parts = kzalloc(sizeof(*parts) * nrparts + 10 * nrparts, GFP_KERNEL); + if (!parts) + return -ENOMEM; + + /* Start building partition list */ + parts[curpart].name = "CFE"; + parts[curpart].offset = 0; + parts[curpart].size = cfelen; + curpart++; + + parts[curpart].name = "nvram"; + parts[curpart].offset = master->size - nvramlen; + parts[curpart].size = nvramlen; + curpart++; + + /* Global partition "linux" to make easy firmware upgrade */ + parts[curpart].name = "linux"; + parts[curpart].offset = cfelen; + parts[curpart].size = master->size - cfelen - nvramlen; + parts[curpart].types = bcm63xx_cfe_part_types; + + for (i = 0; i < nrparts; i++) + pr_info("Partition %d is %s offset %llx and length %llx\n", i, + parts[i].name, parts[i].offset, parts[i].size); + + *pparts = parts; + + return nrparts; +} + +static int bcm63xx_parse_cfe_partitions(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct bcm963xx_nvram *nvram = NULL; + int ret; + + if (bcm63xx_detect_cfe(master)) + return -EINVAL; + + nvram = vzalloc(sizeof(*nvram)); + if (!nvram) + return -ENOMEM; + + ret = bcm63xx_read_nvram(master, nvram); + if (ret) + goto out; + + if (!mtd_type_is_nand(master)) + ret = bcm63xx_parse_cfe_nor_partitions(master, pparts, nvram); + else + ret = -EINVAL; + +out: + vfree(nvram); + return ret; +}; + +static const struct of_device_id parse_bcm63xx_cfe_match_table[] = { + { .compatible = "brcm,bcm963xx-cfe-nor-partitions" }, + {}, +}; +MODULE_DEVICE_TABLE(of, parse_bcm63xx_cfe_match_table); + +static struct mtd_part_parser bcm63xx_cfe_parser = { + .parse_fn = bcm63xx_parse_cfe_partitions, + .name = "bcm63xxpart", + .of_match_table = parse_bcm63xx_cfe_match_table, +}; +module_mtd_part_parser(bcm63xx_cfe_parser); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Daniel Dickinson <openwrt@cshore.neomailbox.net>"); +MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); +MODULE_AUTHOR("Mike Albon <malbon@openwrt.org>"); +MODULE_AUTHOR("Jonas Gorski <jonas.gorski@gmail.com"); +MODULE_DESCRIPTION("MTD partitioning for BCM63XX CFE bootloaders"); diff --git a/drivers/mtd/parsers/cmdlinepart.c b/drivers/mtd/parsers/cmdlinepart.c new file mode 100644 index 000000000000..c86f2db8c882 --- /dev/null +++ b/drivers/mtd/parsers/cmdlinepart.c @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Read flash partition table from command line + * + * Copyright © 2002 SYSGO Real-Time Solutions GmbH + * Copyright © 2002-2010 David Woodhouse <dwmw2@infradead.org> + * + * The format for the command line is as follows: + * + * mtdparts=<mtddef>[;<mtddef] + * <mtddef> := <mtd-id>:<partdef>[,<partdef>] + * <partdef> := <size>[@<offset>][<name>][ro][lk] + * <mtd-id> := unique name used in mapping driver/device (mtd->name) + * <size> := standard linux memsize OR "-" to denote all remaining space + * size is automatically truncated at end of device + * if specified or truncated size is 0 the part is skipped + * <offset> := standard linux memsize + * if omitted the part will immediately follow the previous part + * or 0 if the first part + * <name> := '(' NAME ')' + * NAME will appear in /proc/mtd + * + * <size> and <offset> can be specified such that the parts are out of order + * in physical memory and may even overlap. + * + * The parts are assigned MTD numbers in the order they are specified in the + * command line regardless of their order in physical memory. + * + * Examples: + * + * 1 NOR Flash, with 1 single writable partition: + * edb7312-nor:- + * + * 1 NOR Flash with 2 partitions, 1 NAND with one + * edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home) + */ + +#define pr_fmt(fmt) "mtd: " fmt + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/module.h> +#include <linux/err.h> + +/* debug macro */ +#if 0 +#define dbg(x) do { printk("DEBUG-CMDLINE-PART: "); printk x; } while(0) +#else +#define dbg(x) +#endif + + +/* special size referring to all the remaining space in a partition */ +#define SIZE_REMAINING ULLONG_MAX +#define OFFSET_CONTINUOUS ULLONG_MAX + +struct cmdline_mtd_partition { + struct cmdline_mtd_partition *next; + char *mtd_id; + int num_parts; + struct mtd_partition *parts; +}; + +/* mtdpart_setup() parses into here */ +static struct cmdline_mtd_partition *partitions; + +/* the command line passed to mtdpart_setup() */ +static char *mtdparts; +static char *cmdline; +static int cmdline_parsed; + +/* + * Parse one partition definition for an MTD. Since there can be many + * comma separated partition definitions, this function calls itself + * recursively until no more partition definitions are found. Nice side + * effect: the memory to keep the mtd_partition structs and the names + * is allocated upon the last definition being found. At that point the + * syntax has been verified ok. + */ +static struct mtd_partition * newpart(char *s, + char **retptr, + int *num_parts, + int this_part, + unsigned char **extra_mem_ptr, + int extra_mem_size) +{ + struct mtd_partition *parts; + unsigned long long size, offset = OFFSET_CONTINUOUS; + char *name; + int name_len; + unsigned char *extra_mem; + char delim; + unsigned int mask_flags; + + /* fetch the partition size */ + if (*s == '-') { + /* assign all remaining space to this partition */ + size = SIZE_REMAINING; + s++; + } else { + size = memparse(s, &s); + if (!size) { + pr_err("partition has size 0\n"); + return ERR_PTR(-EINVAL); + } + } + + /* fetch partition name and flags */ + mask_flags = 0; /* this is going to be a regular partition */ + delim = 0; + + /* check for offset */ + if (*s == '@') { + s++; + offset = memparse(s, &s); + } + + /* now look for name */ + if (*s == '(') + delim = ')'; + + if (delim) { + char *p; + + name = ++s; + p = strchr(name, delim); + if (!p) { + pr_err("no closing %c found in partition name\n", delim); + return ERR_PTR(-EINVAL); + } + name_len = p - name; + s = p + 1; + } else { + name = NULL; + name_len = 13; /* Partition_000 */ + } + + /* record name length for memory allocation later */ + extra_mem_size += name_len + 1; + + /* test for options */ + if (strncmp(s, "ro", 2) == 0) { + mask_flags |= MTD_WRITEABLE; + s += 2; + } + + /* if lk is found do NOT unlock the MTD partition*/ + if (strncmp(s, "lk", 2) == 0) { + mask_flags |= MTD_POWERUP_LOCK; + s += 2; + } + + /* test if more partitions are following */ + if (*s == ',') { + if (size == SIZE_REMAINING) { + pr_err("no partitions allowed after a fill-up partition\n"); + return ERR_PTR(-EINVAL); + } + /* more partitions follow, parse them */ + parts = newpart(s + 1, &s, num_parts, this_part + 1, + &extra_mem, extra_mem_size); + if (IS_ERR(parts)) + return parts; + } else { + /* this is the last partition: allocate space for all */ + int alloc_size; + + *num_parts = this_part + 1; + alloc_size = *num_parts * sizeof(struct mtd_partition) + + extra_mem_size; + + parts = kzalloc(alloc_size, GFP_KERNEL); + if (!parts) + return ERR_PTR(-ENOMEM); + extra_mem = (unsigned char *)(parts + *num_parts); + } + + /* + * enter this partition (offset will be calculated later if it is + * OFFSET_CONTINUOUS at this point) + */ + parts[this_part].size = size; + parts[this_part].offset = offset; + parts[this_part].mask_flags = mask_flags; + if (name) + strlcpy(extra_mem, name, name_len + 1); + else + sprintf(extra_mem, "Partition_%03d", this_part); + parts[this_part].name = extra_mem; + extra_mem += name_len + 1; + + dbg(("partition %d: name <%s>, offset %llx, size %llx, mask flags %x\n", + this_part, parts[this_part].name, parts[this_part].offset, + parts[this_part].size, parts[this_part].mask_flags)); + + /* return (updated) pointer to extra_mem memory */ + if (extra_mem_ptr) + *extra_mem_ptr = extra_mem; + + /* return (updated) pointer command line string */ + *retptr = s; + + /* return partition table */ + return parts; +} + +/* + * Parse the command line. + */ +static int mtdpart_setup_real(char *s) +{ + cmdline_parsed = 1; + + for( ; s != NULL; ) + { + struct cmdline_mtd_partition *this_mtd; + struct mtd_partition *parts; + int mtd_id_len, num_parts; + char *p, *mtd_id; + + mtd_id = s; + + /* fetch <mtd-id> */ + p = strchr(s, ':'); + if (!p) { + pr_err("no mtd-id\n"); + return -EINVAL; + } + mtd_id_len = p - mtd_id; + + dbg(("parsing <%s>\n", p+1)); + + /* + * parse one mtd. have it reserve memory for the + * struct cmdline_mtd_partition and the mtd-id string. + */ + parts = newpart(p + 1, /* cmdline */ + &s, /* out: updated cmdline ptr */ + &num_parts, /* out: number of parts */ + 0, /* first partition */ + (unsigned char**)&this_mtd, /* out: extra mem */ + mtd_id_len + 1 + sizeof(*this_mtd) + + sizeof(void*)-1 /*alignment*/); + if (IS_ERR(parts)) { + /* + * An error occurred. We're either: + * a) out of memory, or + * b) in the middle of the partition spec + * Either way, this mtd is hosed and we're + * unlikely to succeed in parsing any more + */ + return PTR_ERR(parts); + } + + /* align this_mtd */ + this_mtd = (struct cmdline_mtd_partition *) + ALIGN((unsigned long)this_mtd, sizeof(void *)); + /* enter results */ + this_mtd->parts = parts; + this_mtd->num_parts = num_parts; + this_mtd->mtd_id = (char*)(this_mtd + 1); + strlcpy(this_mtd->mtd_id, mtd_id, mtd_id_len + 1); + + /* link into chain */ + this_mtd->next = partitions; + partitions = this_mtd; + + dbg(("mtdid=<%s> num_parts=<%d>\n", + this_mtd->mtd_id, this_mtd->num_parts)); + + + /* EOS - we're done */ + if (*s == 0) + break; + + /* does another spec follow? */ + if (*s != ';') { + pr_err("bad character after partition (%c)\n", *s); + return -EINVAL; + } + s++; + } + + return 0; +} + +/* + * Main function to be called from the MTD mapping driver/device to + * obtain the partitioning information. At this point the command line + * arguments will actually be parsed and turned to struct mtd_partition + * information. It returns partitions for the requested mtd device, or + * the first one in the chain if a NULL mtd_id is passed in. + */ +static int parse_cmdline_partitions(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + unsigned long long offset; + int i, err; + struct cmdline_mtd_partition *part; + const char *mtd_id = master->name; + + /* parse command line */ + if (!cmdline_parsed) { + err = mtdpart_setup_real(cmdline); + if (err) + return err; + } + + /* + * Search for the partition definition matching master->name. + * If master->name is not set, stop at first partition definition. + */ + for (part = partitions; part; part = part->next) { + if ((!mtd_id) || (!strcmp(part->mtd_id, mtd_id))) + break; + } + + if (!part) + return 0; + + for (i = 0, offset = 0; i < part->num_parts; i++) { + if (part->parts[i].offset == OFFSET_CONTINUOUS) + part->parts[i].offset = offset; + else + offset = part->parts[i].offset; + + if (part->parts[i].size == SIZE_REMAINING) + part->parts[i].size = master->size - offset; + + if (offset + part->parts[i].size > master->size) { + pr_warn("%s: partitioning exceeds flash size, truncating\n", + part->mtd_id); + part->parts[i].size = master->size - offset; + } + offset += part->parts[i].size; + + if (part->parts[i].size == 0) { + pr_warn("%s: skipping zero sized partition\n", + part->mtd_id); + part->num_parts--; + memmove(&part->parts[i], &part->parts[i + 1], + sizeof(*part->parts) * (part->num_parts - i)); + i--; + } + } + + *pparts = kmemdup(part->parts, sizeof(*part->parts) * part->num_parts, + GFP_KERNEL); + if (!*pparts) + return -ENOMEM; + + return part->num_parts; +} + + +/* + * This is the handler for our kernel parameter, called from + * main.c::checksetup(). Note that we can not yet kmalloc() anything, + * so we only save the commandline for later processing. + * + * This function needs to be visible for bootloaders. + */ +static int __init mtdpart_setup(char *s) +{ + cmdline = s; + return 1; +} + +__setup("mtdparts=", mtdpart_setup); + +static struct mtd_part_parser cmdline_parser = { + .parse_fn = parse_cmdline_partitions, + .name = "cmdlinepart", +}; + +static int __init cmdline_parser_init(void) +{ + if (mtdparts) + mtdpart_setup(mtdparts); + register_mtd_parser(&cmdline_parser); + return 0; +} + +static void __exit cmdline_parser_exit(void) +{ + deregister_mtd_parser(&cmdline_parser); +} + +module_init(cmdline_parser_init); +module_exit(cmdline_parser_exit); + +MODULE_PARM_DESC(mtdparts, "Partitioning specification"); +module_param(mtdparts, charp, 0); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Marius Groeger <mag@sysgo.de>"); +MODULE_DESCRIPTION("Command line configuration of MTD partitions"); diff --git a/drivers/mtd/parsers/ofpart.c b/drivers/mtd/parsers/ofpart.c new file mode 100644 index 000000000000..3caeabf27987 --- /dev/null +++ b/drivers/mtd/parsers/ofpart.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Flash partitions described by the OF (or flattened) device tree + * + * Copyright © 2006 MontaVista Software Inc. + * Author: Vitaly Wool <vwool@ru.mvista.com> + * + * Revised to handle newer style flash binding by: + * Copyright © 2007 David Gibson, IBM Corporation. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/mtd/mtd.h> +#include <linux/slab.h> +#include <linux/mtd/partitions.h> + +static bool node_has_compatible(struct device_node *pp) +{ + return of_get_property(pp, "compatible", NULL); +} + +static int parse_fixed_partitions(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + struct device_node *mtd_node; + struct device_node *ofpart_node; + const char *partname; + struct device_node *pp; + int nr_parts, i, ret = 0; + bool dedicated = true; + + + /* Pull of_node from the master device node */ + mtd_node = mtd_get_of_node(master); + if (!mtd_node) + return 0; + + ofpart_node = of_get_child_by_name(mtd_node, "partitions"); + if (!ofpart_node) { + /* + * We might get here even when ofpart isn't used at all (e.g., + * when using another parser), so don't be louder than + * KERN_DEBUG + */ + pr_debug("%s: 'partitions' subnode not found on %pOF. Trying to parse direct subnodes as partitions.\n", + master->name, mtd_node); + ofpart_node = mtd_node; + dedicated = false; + } else if (!of_device_is_compatible(ofpart_node, "fixed-partitions")) { + /* The 'partitions' subnode might be used by another parser */ + return 0; + } + + /* First count the subnodes */ + nr_parts = 0; + for_each_child_of_node(ofpart_node, pp) { + if (!dedicated && node_has_compatible(pp)) + continue; + + nr_parts++; + } + + if (nr_parts == 0) + return 0; + + parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + i = 0; + for_each_child_of_node(ofpart_node, pp) { + const __be32 *reg; + int len; + int a_cells, s_cells; + + if (!dedicated && node_has_compatible(pp)) + continue; + + reg = of_get_property(pp, "reg", &len); + if (!reg) { + if (dedicated) { + pr_debug("%s: ofpart partition %pOF (%pOF) missing reg property.\n", + master->name, pp, + mtd_node); + goto ofpart_fail; + } else { + nr_parts--; + continue; + } + } + + a_cells = of_n_addr_cells(pp); + s_cells = of_n_size_cells(pp); + if (len / 4 != a_cells + s_cells) { + pr_debug("%s: ofpart partition %pOF (%pOF) error parsing reg property.\n", + master->name, pp, + mtd_node); + goto ofpart_fail; + } + + parts[i].offset = of_read_number(reg, a_cells); + parts[i].size = of_read_number(reg + a_cells, s_cells); + parts[i].of_node = pp; + + partname = of_get_property(pp, "label", &len); + if (!partname) + partname = of_get_property(pp, "name", &len); + parts[i].name = partname; + + if (of_get_property(pp, "read-only", &len)) + parts[i].mask_flags |= MTD_WRITEABLE; + + if (of_get_property(pp, "lock", &len)) + parts[i].mask_flags |= MTD_POWERUP_LOCK; + + i++; + } + + if (!nr_parts) + goto ofpart_none; + + *pparts = parts; + return nr_parts; + +ofpart_fail: + pr_err("%s: error parsing ofpart partition %pOF (%pOF)\n", + master->name, pp, mtd_node); + ret = -EINVAL; +ofpart_none: + of_node_put(pp); + kfree(parts); + return ret; +} + +static const struct of_device_id parse_ofpart_match_table[] = { + { .compatible = "fixed-partitions" }, + {}, +}; +MODULE_DEVICE_TABLE(of, parse_ofpart_match_table); + +static struct mtd_part_parser ofpart_parser = { + .parse_fn = parse_fixed_partitions, + .name = "fixed-partitions", + .of_match_table = parse_ofpart_match_table, +}; + +static int parse_ofoldpart_partitions(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + struct device_node *dp; + int i, plen, nr_parts; + const struct { + __be32 offset, len; + } *part; + const char *names; + + /* Pull of_node from the master device node */ + dp = mtd_get_of_node(master); + if (!dp) + return 0; + + part = of_get_property(dp, "partitions", &plen); + if (!part) + return 0; /* No partitions found */ + + pr_warn("Device tree uses obsolete partition map binding: %pOF\n", dp); + + nr_parts = plen / sizeof(part[0]); + + parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + names = of_get_property(dp, "partition-names", &plen); + + for (i = 0; i < nr_parts; i++) { + parts[i].offset = be32_to_cpu(part->offset); + parts[i].size = be32_to_cpu(part->len) & ~1; + /* bit 0 set signifies read only partition */ + if (be32_to_cpu(part->len) & 1) + parts[i].mask_flags = MTD_WRITEABLE; + + if (names && (plen > 0)) { + int len = strlen(names) + 1; + + parts[i].name = names; + plen -= len; + names += len; + } else { + parts[i].name = "unnamed"; + } + + part++; + } + + *pparts = parts; + return nr_parts; +} + +static struct mtd_part_parser ofoldpart_parser = { + .parse_fn = parse_ofoldpart_partitions, + .name = "ofoldpart", +}; + +static int __init ofpart_parser_init(void) +{ + register_mtd_parser(&ofpart_parser); + register_mtd_parser(&ofoldpart_parser); + return 0; +} + +static void __exit ofpart_parser_exit(void) +{ + deregister_mtd_parser(&ofpart_parser); + deregister_mtd_parser(&ofoldpart_parser); +} + +module_init(ofpart_parser_init); +module_exit(ofpart_parser_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parser for MTD partitioning information in device tree"); +MODULE_AUTHOR("Vitaly Wool, David Gibson"); +/* + * When MTD core cannot find the requested parser, it tries to load the module + * with the same name. Since we provide the ofoldpart parser, we should have + * the corresponding alias. + */ +MODULE_ALIAS("fixed-partitions"); +MODULE_ALIAS("ofoldpart"); |