18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * BCM47XX MTD partitioning 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright © 2012 Rafał Miłecki <zajec5@gmail.com> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/bcm47xx_nvram.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/slab.h> 128c2ecf20Sopenharmony_ci#include <linux/mtd/mtd.h> 138c2ecf20Sopenharmony_ci#include <linux/mtd/partitions.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <uapi/linux/magic.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci/* 188c2ecf20Sopenharmony_ci * NAND flash on Netgear R6250 was verified to contain 15 partitions. 198c2ecf20Sopenharmony_ci * This will result in allocating too big array for some old devices, but the 208c2ecf20Sopenharmony_ci * memory will be freed soon anyway (see mtd_device_parse_register). 218c2ecf20Sopenharmony_ci */ 228c2ecf20Sopenharmony_ci#define BCM47XXPART_MAX_PARTS 20 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* 258c2ecf20Sopenharmony_ci * Amount of bytes we read when analyzing each block of flash memory. 268c2ecf20Sopenharmony_ci * Set it big enough to allow detecting partition and reading important data. 278c2ecf20Sopenharmony_ci */ 288c2ecf20Sopenharmony_ci#define BCM47XXPART_BYTES_TO_READ 0x4e8 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* Magics */ 318c2ecf20Sopenharmony_ci#define BOARD_DATA_MAGIC 0x5246504D /* MPFR */ 328c2ecf20Sopenharmony_ci#define BOARD_DATA_MAGIC2 0xBD0D0BBD 338c2ecf20Sopenharmony_ci#define CFE_MAGIC 0x43464531 /* 1EFC */ 348c2ecf20Sopenharmony_ci#define FACTORY_MAGIC 0x59544346 /* FCTY */ 358c2ecf20Sopenharmony_ci#define NVRAM_HEADER 0x48534C46 /* FLSH */ 368c2ecf20Sopenharmony_ci#define POT_MAGIC1 0x54544f50 /* POTT */ 378c2ecf20Sopenharmony_ci#define POT_MAGIC2 0x504f /* OP */ 388c2ecf20Sopenharmony_ci#define ML_MAGIC1 0x39685a42 398c2ecf20Sopenharmony_ci#define ML_MAGIC2 0x26594131 408c2ecf20Sopenharmony_ci#define TRX_MAGIC 0x30524448 418c2ecf20Sopenharmony_ci#define SHSQ_MAGIC 0x71736873 /* shsq (weird ZTE H218N endianness) */ 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic const char * const trx_types[] = { "trx", NULL }; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistruct trx_header { 468c2ecf20Sopenharmony_ci uint32_t magic; 478c2ecf20Sopenharmony_ci uint32_t length; 488c2ecf20Sopenharmony_ci uint32_t crc32; 498c2ecf20Sopenharmony_ci uint16_t flags; 508c2ecf20Sopenharmony_ci uint16_t version; 518c2ecf20Sopenharmony_ci uint32_t offset[3]; 528c2ecf20Sopenharmony_ci} __packed; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistatic void bcm47xxpart_add_part(struct mtd_partition *part, const char *name, 558c2ecf20Sopenharmony_ci u64 offset, uint32_t mask_flags) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci part->name = name; 588c2ecf20Sopenharmony_ci part->offset = offset; 598c2ecf20Sopenharmony_ci part->mask_flags = mask_flags; 608c2ecf20Sopenharmony_ci} 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci/** 638c2ecf20Sopenharmony_ci * bcm47xxpart_bootpartition - gets index of TRX partition used by bootloader 648c2ecf20Sopenharmony_ci * 658c2ecf20Sopenharmony_ci * Some devices may have more than one TRX partition. In such case one of them 668c2ecf20Sopenharmony_ci * is the main one and another a failsafe one. Bootloader may fallback to the 678c2ecf20Sopenharmony_ci * failsafe firmware if it detects corruption of the main image. 688c2ecf20Sopenharmony_ci * 698c2ecf20Sopenharmony_ci * This function provides info about currently used TRX partition. It's the one 708c2ecf20Sopenharmony_ci * containing kernel started by the bootloader. 718c2ecf20Sopenharmony_ci */ 728c2ecf20Sopenharmony_cistatic int bcm47xxpart_bootpartition(void) 738c2ecf20Sopenharmony_ci{ 748c2ecf20Sopenharmony_ci char buf[4]; 758c2ecf20Sopenharmony_ci int bootpartition; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci /* Check CFE environment variable */ 788c2ecf20Sopenharmony_ci if (bcm47xx_nvram_getenv("bootpartition", buf, sizeof(buf)) > 0) { 798c2ecf20Sopenharmony_ci if (!kstrtoint(buf, 0, &bootpartition)) 808c2ecf20Sopenharmony_ci return bootpartition; 818c2ecf20Sopenharmony_ci } 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci return 0; 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic int bcm47xxpart_parse(struct mtd_info *master, 878c2ecf20Sopenharmony_ci const struct mtd_partition **pparts, 888c2ecf20Sopenharmony_ci struct mtd_part_parser_data *data) 898c2ecf20Sopenharmony_ci{ 908c2ecf20Sopenharmony_ci struct mtd_partition *parts; 918c2ecf20Sopenharmony_ci uint8_t i, curr_part = 0; 928c2ecf20Sopenharmony_ci uint32_t *buf; 938c2ecf20Sopenharmony_ci size_t bytes_read; 948c2ecf20Sopenharmony_ci uint32_t offset; 958c2ecf20Sopenharmony_ci uint32_t blocksize = master->erasesize; 968c2ecf20Sopenharmony_ci int trx_parts[2]; /* Array with indexes of TRX partitions */ 978c2ecf20Sopenharmony_ci int trx_num = 0; /* Number of found TRX partitions */ 988c2ecf20Sopenharmony_ci int possible_nvram_sizes[] = { 0x8000, 0xF000, 0x10000, }; 998c2ecf20Sopenharmony_ci int err; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci /* 1028c2ecf20Sopenharmony_ci * Some really old flashes (like AT45DB*) had smaller erasesize-s, but 1038c2ecf20Sopenharmony_ci * partitions were aligned to at least 0x1000 anyway. 1048c2ecf20Sopenharmony_ci */ 1058c2ecf20Sopenharmony_ci if (blocksize < 0x1000) 1068c2ecf20Sopenharmony_ci blocksize = 0x1000; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci /* Alloc */ 1098c2ecf20Sopenharmony_ci parts = kcalloc(BCM47XXPART_MAX_PARTS, sizeof(struct mtd_partition), 1108c2ecf20Sopenharmony_ci GFP_KERNEL); 1118c2ecf20Sopenharmony_ci if (!parts) 1128c2ecf20Sopenharmony_ci return -ENOMEM; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci buf = kzalloc(BCM47XXPART_BYTES_TO_READ, GFP_KERNEL); 1158c2ecf20Sopenharmony_ci if (!buf) { 1168c2ecf20Sopenharmony_ci kfree(parts); 1178c2ecf20Sopenharmony_ci return -ENOMEM; 1188c2ecf20Sopenharmony_ci } 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci /* Parse block by block looking for magics */ 1218c2ecf20Sopenharmony_ci for (offset = 0; offset <= master->size - blocksize; 1228c2ecf20Sopenharmony_ci offset += blocksize) { 1238c2ecf20Sopenharmony_ci /* Nothing more in higher memory on BCM47XX (MIPS) */ 1248c2ecf20Sopenharmony_ci if (IS_ENABLED(CONFIG_BCM47XX) && offset >= 0x2000000) 1258c2ecf20Sopenharmony_ci break; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci if (curr_part >= BCM47XXPART_MAX_PARTS) { 1288c2ecf20Sopenharmony_ci pr_warn("Reached maximum number of partitions, scanning stopped!\n"); 1298c2ecf20Sopenharmony_ci break; 1308c2ecf20Sopenharmony_ci } 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci /* Read beginning of the block */ 1338c2ecf20Sopenharmony_ci err = mtd_read(master, offset, BCM47XXPART_BYTES_TO_READ, 1348c2ecf20Sopenharmony_ci &bytes_read, (uint8_t *)buf); 1358c2ecf20Sopenharmony_ci if (err && !mtd_is_bitflip(err)) { 1368c2ecf20Sopenharmony_ci pr_err("mtd_read error while parsing (offset: 0x%X): %d\n", 1378c2ecf20Sopenharmony_ci offset, err); 1388c2ecf20Sopenharmony_ci continue; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci /* Magic or small NVRAM at 0x400 */ 1428c2ecf20Sopenharmony_ci if ((buf[0x4e0 / 4] == CFE_MAGIC && buf[0x4e4 / 4] == CFE_MAGIC) || 1438c2ecf20Sopenharmony_ci (buf[0x400 / 4] == NVRAM_HEADER)) { 1448c2ecf20Sopenharmony_ci bcm47xxpart_add_part(&parts[curr_part++], "boot", 1458c2ecf20Sopenharmony_ci offset, MTD_WRITEABLE); 1468c2ecf20Sopenharmony_ci continue; 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci /* 1508c2ecf20Sopenharmony_ci * board_data starts with board_id which differs across boards, 1518c2ecf20Sopenharmony_ci * but we can use 'MPFR' (hopefully) magic at 0x100 1528c2ecf20Sopenharmony_ci */ 1538c2ecf20Sopenharmony_ci if (buf[0x100 / 4] == BOARD_DATA_MAGIC) { 1548c2ecf20Sopenharmony_ci bcm47xxpart_add_part(&parts[curr_part++], "board_data", 1558c2ecf20Sopenharmony_ci offset, MTD_WRITEABLE); 1568c2ecf20Sopenharmony_ci continue; 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci /* Found on Huawei E970 */ 1608c2ecf20Sopenharmony_ci if (buf[0x000 / 4] == FACTORY_MAGIC) { 1618c2ecf20Sopenharmony_ci bcm47xxpart_add_part(&parts[curr_part++], "factory", 1628c2ecf20Sopenharmony_ci offset, MTD_WRITEABLE); 1638c2ecf20Sopenharmony_ci continue; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci /* POT(TOP) */ 1678c2ecf20Sopenharmony_ci if (buf[0x000 / 4] == POT_MAGIC1 && 1688c2ecf20Sopenharmony_ci (buf[0x004 / 4] & 0xFFFF) == POT_MAGIC2) { 1698c2ecf20Sopenharmony_ci bcm47xxpart_add_part(&parts[curr_part++], "POT", offset, 1708c2ecf20Sopenharmony_ci MTD_WRITEABLE); 1718c2ecf20Sopenharmony_ci continue; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci /* ML */ 1758c2ecf20Sopenharmony_ci if (buf[0x010 / 4] == ML_MAGIC1 && 1768c2ecf20Sopenharmony_ci buf[0x014 / 4] == ML_MAGIC2) { 1778c2ecf20Sopenharmony_ci bcm47xxpart_add_part(&parts[curr_part++], "ML", offset, 1788c2ecf20Sopenharmony_ci MTD_WRITEABLE); 1798c2ecf20Sopenharmony_ci continue; 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci /* TRX */ 1838c2ecf20Sopenharmony_ci if (buf[0x000 / 4] == TRX_MAGIC) { 1848c2ecf20Sopenharmony_ci struct trx_header *trx; 1858c2ecf20Sopenharmony_ci uint32_t last_subpart; 1868c2ecf20Sopenharmony_ci uint32_t trx_size; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci if (trx_num >= ARRAY_SIZE(trx_parts)) 1898c2ecf20Sopenharmony_ci pr_warn("No enough space to store another TRX found at 0x%X\n", 1908c2ecf20Sopenharmony_ci offset); 1918c2ecf20Sopenharmony_ci else 1928c2ecf20Sopenharmony_ci trx_parts[trx_num++] = curr_part; 1938c2ecf20Sopenharmony_ci bcm47xxpart_add_part(&parts[curr_part++], "firmware", 1948c2ecf20Sopenharmony_ci offset, 0); 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci /* 1978c2ecf20Sopenharmony_ci * Try to find TRX size. The "length" field isn't fully 1988c2ecf20Sopenharmony_ci * reliable as it could be decreased to make CRC32 cover 1998c2ecf20Sopenharmony_ci * only part of TRX data. It's commonly used as checksum 2008c2ecf20Sopenharmony_ci * can't cover e.g. ever-changing rootfs partition. 2018c2ecf20Sopenharmony_ci * Use offsets as helpers for assuming min TRX size. 2028c2ecf20Sopenharmony_ci */ 2038c2ecf20Sopenharmony_ci trx = (struct trx_header *)buf; 2048c2ecf20Sopenharmony_ci last_subpart = max3(trx->offset[0], trx->offset[1], 2058c2ecf20Sopenharmony_ci trx->offset[2]); 2068c2ecf20Sopenharmony_ci trx_size = max(trx->length, last_subpart + blocksize); 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci /* 2098c2ecf20Sopenharmony_ci * Skip the TRX data. Decrease offset by block size as 2108c2ecf20Sopenharmony_ci * the next loop iteration will increase it. 2118c2ecf20Sopenharmony_ci */ 2128c2ecf20Sopenharmony_ci offset += roundup(trx_size, blocksize) - blocksize; 2138c2ecf20Sopenharmony_ci continue; 2148c2ecf20Sopenharmony_ci } 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci /* Squashfs on devices not using TRX */ 2178c2ecf20Sopenharmony_ci if (le32_to_cpu(buf[0x000 / 4]) == SQUASHFS_MAGIC || 2188c2ecf20Sopenharmony_ci buf[0x000 / 4] == SHSQ_MAGIC) { 2198c2ecf20Sopenharmony_ci bcm47xxpart_add_part(&parts[curr_part++], "rootfs", 2208c2ecf20Sopenharmony_ci offset, 0); 2218c2ecf20Sopenharmony_ci continue; 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci /* 2258c2ecf20Sopenharmony_ci * New (ARM?) devices may have NVRAM in some middle block. Last 2268c2ecf20Sopenharmony_ci * block will be checked later, so skip it. 2278c2ecf20Sopenharmony_ci */ 2288c2ecf20Sopenharmony_ci if (offset != master->size - blocksize && 2298c2ecf20Sopenharmony_ci buf[0x000 / 4] == NVRAM_HEADER) { 2308c2ecf20Sopenharmony_ci bcm47xxpart_add_part(&parts[curr_part++], "nvram", 2318c2ecf20Sopenharmony_ci offset, 0); 2328c2ecf20Sopenharmony_ci continue; 2338c2ecf20Sopenharmony_ci } 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci /* Read middle of the block */ 2368c2ecf20Sopenharmony_ci err = mtd_read(master, offset + (blocksize / 2), 0x4, &bytes_read, 2378c2ecf20Sopenharmony_ci (uint8_t *)buf); 2388c2ecf20Sopenharmony_ci if (err && !mtd_is_bitflip(err)) { 2398c2ecf20Sopenharmony_ci pr_err("mtd_read error while parsing (offset: 0x%X): %d\n", 2408c2ecf20Sopenharmony_ci offset + (blocksize / 2), err); 2418c2ecf20Sopenharmony_ci continue; 2428c2ecf20Sopenharmony_ci } 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci /* Some devices (ex. WNDR3700v3) don't have a standard 'MPFR' */ 2458c2ecf20Sopenharmony_ci if (buf[0x000 / 4] == BOARD_DATA_MAGIC2) { 2468c2ecf20Sopenharmony_ci bcm47xxpart_add_part(&parts[curr_part++], "board_data", 2478c2ecf20Sopenharmony_ci offset, MTD_WRITEABLE); 2488c2ecf20Sopenharmony_ci continue; 2498c2ecf20Sopenharmony_ci } 2508c2ecf20Sopenharmony_ci } 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci /* Look for NVRAM at the end of the last block. */ 2538c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(possible_nvram_sizes); i++) { 2548c2ecf20Sopenharmony_ci if (curr_part >= BCM47XXPART_MAX_PARTS) { 2558c2ecf20Sopenharmony_ci pr_warn("Reached maximum number of partitions, scanning stopped!\n"); 2568c2ecf20Sopenharmony_ci break; 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci offset = master->size - possible_nvram_sizes[i]; 2608c2ecf20Sopenharmony_ci err = mtd_read(master, offset, 0x4, &bytes_read, 2618c2ecf20Sopenharmony_ci (uint8_t *)buf); 2628c2ecf20Sopenharmony_ci if (err && !mtd_is_bitflip(err)) { 2638c2ecf20Sopenharmony_ci pr_err("mtd_read error while reading (offset 0x%X): %d\n", 2648c2ecf20Sopenharmony_ci offset, err); 2658c2ecf20Sopenharmony_ci continue; 2668c2ecf20Sopenharmony_ci } 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci /* Standard NVRAM */ 2698c2ecf20Sopenharmony_ci if (buf[0] == NVRAM_HEADER) { 2708c2ecf20Sopenharmony_ci bcm47xxpart_add_part(&parts[curr_part++], "nvram", 2718c2ecf20Sopenharmony_ci master->size - blocksize, 0); 2728c2ecf20Sopenharmony_ci break; 2738c2ecf20Sopenharmony_ci } 2748c2ecf20Sopenharmony_ci } 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci kfree(buf); 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci /* 2798c2ecf20Sopenharmony_ci * Assume that partitions end at the beginning of the one they are 2808c2ecf20Sopenharmony_ci * followed by. 2818c2ecf20Sopenharmony_ci */ 2828c2ecf20Sopenharmony_ci for (i = 0; i < curr_part; i++) { 2838c2ecf20Sopenharmony_ci u64 next_part_offset = (i < curr_part - 1) ? 2848c2ecf20Sopenharmony_ci parts[i + 1].offset : master->size; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci parts[i].size = next_part_offset - parts[i].offset; 2878c2ecf20Sopenharmony_ci } 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci /* If there was TRX parse it now */ 2908c2ecf20Sopenharmony_ci for (i = 0; i < trx_num; i++) { 2918c2ecf20Sopenharmony_ci struct mtd_partition *trx = &parts[trx_parts[i]]; 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci if (i == bcm47xxpart_bootpartition()) 2948c2ecf20Sopenharmony_ci trx->types = trx_types; 2958c2ecf20Sopenharmony_ci else 2968c2ecf20Sopenharmony_ci trx->name = "failsafe"; 2978c2ecf20Sopenharmony_ci } 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci *pparts = parts; 3008c2ecf20Sopenharmony_ci return curr_part; 3018c2ecf20Sopenharmony_ci}; 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_cistatic const struct of_device_id bcm47xxpart_of_match_table[] = { 3048c2ecf20Sopenharmony_ci { .compatible = "brcm,bcm947xx-cfe-partitions" }, 3058c2ecf20Sopenharmony_ci {}, 3068c2ecf20Sopenharmony_ci}; 3078c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, bcm47xxpart_of_match_table); 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_cistatic struct mtd_part_parser bcm47xxpart_mtd_parser = { 3108c2ecf20Sopenharmony_ci .parse_fn = bcm47xxpart_parse, 3118c2ecf20Sopenharmony_ci .name = "bcm47xxpart", 3128c2ecf20Sopenharmony_ci .of_match_table = bcm47xxpart_of_match_table, 3138c2ecf20Sopenharmony_ci}; 3148c2ecf20Sopenharmony_cimodule_mtd_part_parser(bcm47xxpart_mtd_parser); 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3178c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MTD partitioning for BCM47XX flash memories"); 318