162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * BCM63XX CFE image tag parser 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright © 2006-2008 Florian Fainelli <florian@openwrt.org> 662306a36Sopenharmony_ci * Mike Albon <malbon@openwrt.org> 762306a36Sopenharmony_ci * Copyright © 2009-2010 Daniel Dickinson <openwrt@cshore.neomailbox.net> 862306a36Sopenharmony_ci * Copyright © 2011-2013 Jonas Gorski <jonas.gorski@gmail.com> 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/bcm963xx_nvram.h> 1462306a36Sopenharmony_ci#include <linux/bcm963xx_tag.h> 1562306a36Sopenharmony_ci#include <linux/crc32.h> 1662306a36Sopenharmony_ci#include <linux/module.h> 1762306a36Sopenharmony_ci#include <linux/kernel.h> 1862306a36Sopenharmony_ci#include <linux/sizes.h> 1962306a36Sopenharmony_ci#include <linux/slab.h> 2062306a36Sopenharmony_ci#include <linux/vmalloc.h> 2162306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 2262306a36Sopenharmony_ci#include <linux/mtd/partitions.h> 2362306a36Sopenharmony_ci#include <linux/of.h> 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#ifdef CONFIG_MIPS 2662306a36Sopenharmony_ci#include <asm/bootinfo.h> 2762306a36Sopenharmony_ci#include <asm/fw/cfe/cfe_api.h> 2862306a36Sopenharmony_ci#endif /* CONFIG_MIPS */ 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#define BCM963XX_CFE_BLOCK_SIZE SZ_64K /* always at least 64KiB */ 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#define BCM963XX_CFE_MAGIC_OFFSET 0x4e0 3362306a36Sopenharmony_ci#define BCM963XX_CFE_VERSION_OFFSET 0x570 3462306a36Sopenharmony_ci#define BCM963XX_NVRAM_OFFSET 0x580 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci/* Ensure strings read from flash structs are null terminated */ 3762306a36Sopenharmony_ci#define STR_NULL_TERMINATE(x) \ 3862306a36Sopenharmony_ci do { char *_str = (x); _str[sizeof(x) - 1] = 0; } while (0) 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic inline int bcm63xx_detect_cfe(void) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci int ret = 0; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci#ifdef CONFIG_MIPS 4562306a36Sopenharmony_ci ret = (fw_arg3 == CFE_EPTSEAL); 4662306a36Sopenharmony_ci#endif /* CONFIG_MIPS */ 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci return ret; 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic int bcm63xx_read_nvram(struct mtd_info *master, 5262306a36Sopenharmony_ci struct bcm963xx_nvram *nvram) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci u32 actual_crc, expected_crc; 5562306a36Sopenharmony_ci size_t retlen; 5662306a36Sopenharmony_ci int ret; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci /* extract nvram data */ 5962306a36Sopenharmony_ci ret = mtd_read(master, BCM963XX_NVRAM_OFFSET, BCM963XX_NVRAM_V5_SIZE, 6062306a36Sopenharmony_ci &retlen, (void *)nvram); 6162306a36Sopenharmony_ci if (ret) 6262306a36Sopenharmony_ci return ret; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci ret = bcm963xx_nvram_checksum(nvram, &expected_crc, &actual_crc); 6562306a36Sopenharmony_ci if (ret) 6662306a36Sopenharmony_ci pr_warn("nvram checksum failed, contents may be invalid (expected %08x, got %08x)\n", 6762306a36Sopenharmony_ci expected_crc, actual_crc); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci if (!nvram->psi_size) 7062306a36Sopenharmony_ci nvram->psi_size = BCM963XX_DEFAULT_PSI_SIZE; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci return 0; 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic const char * const bcm63xx_cfe_part_types[] = { 7662306a36Sopenharmony_ci "bcm963xx-imagetag", 7762306a36Sopenharmony_ci NULL, 7862306a36Sopenharmony_ci}; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic int bcm63xx_parse_cfe_nor_partitions(struct mtd_info *master, 8162306a36Sopenharmony_ci const struct mtd_partition **pparts, struct bcm963xx_nvram *nvram) 8262306a36Sopenharmony_ci{ 8362306a36Sopenharmony_ci struct mtd_partition *parts; 8462306a36Sopenharmony_ci int nrparts = 3, curpart = 0; 8562306a36Sopenharmony_ci unsigned int cfelen, nvramlen; 8662306a36Sopenharmony_ci unsigned int cfe_erasesize; 8762306a36Sopenharmony_ci int i; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci cfe_erasesize = max_t(uint32_t, master->erasesize, 9062306a36Sopenharmony_ci BCM963XX_CFE_BLOCK_SIZE); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci cfelen = cfe_erasesize; 9362306a36Sopenharmony_ci nvramlen = nvram->psi_size * SZ_1K; 9462306a36Sopenharmony_ci nvramlen = roundup(nvramlen, cfe_erasesize); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci parts = kzalloc(sizeof(*parts) * nrparts + 10 * nrparts, GFP_KERNEL); 9762306a36Sopenharmony_ci if (!parts) 9862306a36Sopenharmony_ci return -ENOMEM; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci /* Start building partition list */ 10162306a36Sopenharmony_ci parts[curpart].name = "CFE"; 10262306a36Sopenharmony_ci parts[curpart].offset = 0; 10362306a36Sopenharmony_ci parts[curpart].size = cfelen; 10462306a36Sopenharmony_ci curpart++; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci parts[curpart].name = "nvram"; 10762306a36Sopenharmony_ci parts[curpart].offset = master->size - nvramlen; 10862306a36Sopenharmony_ci parts[curpart].size = nvramlen; 10962306a36Sopenharmony_ci curpart++; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci /* Global partition "linux" to make easy firmware upgrade */ 11262306a36Sopenharmony_ci parts[curpart].name = "linux"; 11362306a36Sopenharmony_ci parts[curpart].offset = cfelen; 11462306a36Sopenharmony_ci parts[curpart].size = master->size - cfelen - nvramlen; 11562306a36Sopenharmony_ci parts[curpart].types = bcm63xx_cfe_part_types; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci for (i = 0; i < nrparts; i++) 11862306a36Sopenharmony_ci pr_info("Partition %d is %s offset %llx and length %llx\n", i, 11962306a36Sopenharmony_ci parts[i].name, parts[i].offset, parts[i].size); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci *pparts = parts; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci return nrparts; 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cistatic int bcm63xx_parse_cfe_partitions(struct mtd_info *master, 12762306a36Sopenharmony_ci const struct mtd_partition **pparts, 12862306a36Sopenharmony_ci struct mtd_part_parser_data *data) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci struct bcm963xx_nvram *nvram = NULL; 13162306a36Sopenharmony_ci int ret; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci if (!bcm63xx_detect_cfe()) 13462306a36Sopenharmony_ci return -EINVAL; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci nvram = vzalloc(sizeof(*nvram)); 13762306a36Sopenharmony_ci if (!nvram) 13862306a36Sopenharmony_ci return -ENOMEM; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci ret = bcm63xx_read_nvram(master, nvram); 14162306a36Sopenharmony_ci if (ret) 14262306a36Sopenharmony_ci goto out; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci if (!mtd_type_is_nand(master)) 14562306a36Sopenharmony_ci ret = bcm63xx_parse_cfe_nor_partitions(master, pparts, nvram); 14662306a36Sopenharmony_ci else 14762306a36Sopenharmony_ci ret = -EINVAL; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ciout: 15062306a36Sopenharmony_ci vfree(nvram); 15162306a36Sopenharmony_ci return ret; 15262306a36Sopenharmony_ci}; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic const struct of_device_id parse_bcm63xx_cfe_match_table[] = { 15562306a36Sopenharmony_ci { .compatible = "brcm,bcm963xx-cfe-nor-partitions" }, 15662306a36Sopenharmony_ci {}, 15762306a36Sopenharmony_ci}; 15862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, parse_bcm63xx_cfe_match_table); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistatic struct mtd_part_parser bcm63xx_cfe_parser = { 16162306a36Sopenharmony_ci .parse_fn = bcm63xx_parse_cfe_partitions, 16262306a36Sopenharmony_ci .name = "bcm63xxpart", 16362306a36Sopenharmony_ci .of_match_table = parse_bcm63xx_cfe_match_table, 16462306a36Sopenharmony_ci}; 16562306a36Sopenharmony_cimodule_mtd_part_parser(bcm63xx_cfe_parser); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ciMODULE_AUTHOR("Daniel Dickinson <openwrt@cshore.neomailbox.net>"); 16862306a36Sopenharmony_ciMODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); 16962306a36Sopenharmony_ciMODULE_AUTHOR("Mike Albon <malbon@openwrt.org>"); 17062306a36Sopenharmony_ciMODULE_AUTHOR("Jonas Gorski <jonas.gorski@gmail.com"); 17162306a36Sopenharmony_ciMODULE_DESCRIPTION("MTD partitioning for BCM63XX CFE bootloaders"); 172