18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * BCM63XX CFE image tag parser
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright © 2006-2008  Florian Fainelli <florian@openwrt.org>
68c2ecf20Sopenharmony_ci *			  Mike Albon <malbon@openwrt.org>
78c2ecf20Sopenharmony_ci * Copyright © 2009-2010  Daniel Dickinson <openwrt@cshore.neomailbox.net>
88c2ecf20Sopenharmony_ci * Copyright © 2011-2013  Jonas Gorski <jonas.gorski@gmail.com>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include <linux/bcm963xx_nvram.h>
148c2ecf20Sopenharmony_ci#include <linux/bcm963xx_tag.h>
158c2ecf20Sopenharmony_ci#include <linux/crc32.h>
168c2ecf20Sopenharmony_ci#include <linux/module.h>
178c2ecf20Sopenharmony_ci#include <linux/kernel.h>
188c2ecf20Sopenharmony_ci#include <linux/sizes.h>
198c2ecf20Sopenharmony_ci#include <linux/slab.h>
208c2ecf20Sopenharmony_ci#include <linux/vmalloc.h>
218c2ecf20Sopenharmony_ci#include <linux/mtd/mtd.h>
228c2ecf20Sopenharmony_ci#include <linux/mtd/partitions.h>
238c2ecf20Sopenharmony_ci#include <linux/of.h>
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#ifdef CONFIG_MIPS
268c2ecf20Sopenharmony_ci#include <asm/bootinfo.h>
278c2ecf20Sopenharmony_ci#include <asm/fw/cfe/cfe_api.h>
288c2ecf20Sopenharmony_ci#endif /* CONFIG_MIPS */
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#define BCM963XX_CFE_BLOCK_SIZE		SZ_64K	/* always at least 64KiB */
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci#define BCM963XX_CFE_MAGIC_OFFSET	0x4e0
338c2ecf20Sopenharmony_ci#define BCM963XX_CFE_VERSION_OFFSET	0x570
348c2ecf20Sopenharmony_ci#define BCM963XX_NVRAM_OFFSET		0x580
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci/* Ensure strings read from flash structs are null terminated */
378c2ecf20Sopenharmony_ci#define STR_NULL_TERMINATE(x) \
388c2ecf20Sopenharmony_ci	do { char *_str = (x); _str[sizeof(x) - 1] = 0; } while (0)
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic inline int bcm63xx_detect_cfe(void)
418c2ecf20Sopenharmony_ci{
428c2ecf20Sopenharmony_ci	int ret = 0;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci#ifdef CONFIG_MIPS
458c2ecf20Sopenharmony_ci	ret = (fw_arg3 == CFE_EPTSEAL);
468c2ecf20Sopenharmony_ci#endif /* CONFIG_MIPS */
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	return ret;
498c2ecf20Sopenharmony_ci}
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistatic int bcm63xx_read_nvram(struct mtd_info *master,
528c2ecf20Sopenharmony_ci	struct bcm963xx_nvram *nvram)
538c2ecf20Sopenharmony_ci{
548c2ecf20Sopenharmony_ci	u32 actual_crc, expected_crc;
558c2ecf20Sopenharmony_ci	size_t retlen;
568c2ecf20Sopenharmony_ci	int ret;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	/* extract nvram data */
598c2ecf20Sopenharmony_ci	ret = mtd_read(master, BCM963XX_NVRAM_OFFSET, BCM963XX_NVRAM_V5_SIZE,
608c2ecf20Sopenharmony_ci			&retlen, (void *)nvram);
618c2ecf20Sopenharmony_ci	if (ret)
628c2ecf20Sopenharmony_ci		return ret;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	ret = bcm963xx_nvram_checksum(nvram, &expected_crc, &actual_crc);
658c2ecf20Sopenharmony_ci	if (ret)
668c2ecf20Sopenharmony_ci		pr_warn("nvram checksum failed, contents may be invalid (expected %08x, got %08x)\n",
678c2ecf20Sopenharmony_ci			expected_crc, actual_crc);
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	if (!nvram->psi_size)
708c2ecf20Sopenharmony_ci		nvram->psi_size = BCM963XX_DEFAULT_PSI_SIZE;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	return 0;
738c2ecf20Sopenharmony_ci}
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_cistatic const char * const bcm63xx_cfe_part_types[] = {
768c2ecf20Sopenharmony_ci	"bcm963xx-imagetag",
778c2ecf20Sopenharmony_ci	NULL,
788c2ecf20Sopenharmony_ci};
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_cistatic int bcm63xx_parse_cfe_nor_partitions(struct mtd_info *master,
818c2ecf20Sopenharmony_ci	const struct mtd_partition **pparts, struct bcm963xx_nvram *nvram)
828c2ecf20Sopenharmony_ci{
838c2ecf20Sopenharmony_ci	struct mtd_partition *parts;
848c2ecf20Sopenharmony_ci	int nrparts = 3, curpart = 0;
858c2ecf20Sopenharmony_ci	unsigned int cfelen, nvramlen;
868c2ecf20Sopenharmony_ci	unsigned int cfe_erasesize;
878c2ecf20Sopenharmony_ci	int i;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	cfe_erasesize = max_t(uint32_t, master->erasesize,
908c2ecf20Sopenharmony_ci			      BCM963XX_CFE_BLOCK_SIZE);
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	cfelen = cfe_erasesize;
938c2ecf20Sopenharmony_ci	nvramlen = nvram->psi_size * SZ_1K;
948c2ecf20Sopenharmony_ci	nvramlen = roundup(nvramlen, cfe_erasesize);
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	parts = kzalloc(sizeof(*parts) * nrparts + 10 * nrparts, GFP_KERNEL);
978c2ecf20Sopenharmony_ci	if (!parts)
988c2ecf20Sopenharmony_ci		return -ENOMEM;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	/* Start building partition list */
1018c2ecf20Sopenharmony_ci	parts[curpart].name = "CFE";
1028c2ecf20Sopenharmony_ci	parts[curpart].offset = 0;
1038c2ecf20Sopenharmony_ci	parts[curpart].size = cfelen;
1048c2ecf20Sopenharmony_ci	curpart++;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	parts[curpart].name = "nvram";
1078c2ecf20Sopenharmony_ci	parts[curpart].offset = master->size - nvramlen;
1088c2ecf20Sopenharmony_ci	parts[curpart].size = nvramlen;
1098c2ecf20Sopenharmony_ci	curpart++;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	/* Global partition "linux" to make easy firmware upgrade */
1128c2ecf20Sopenharmony_ci	parts[curpart].name = "linux";
1138c2ecf20Sopenharmony_ci	parts[curpart].offset = cfelen;
1148c2ecf20Sopenharmony_ci	parts[curpart].size = master->size - cfelen - nvramlen;
1158c2ecf20Sopenharmony_ci	parts[curpart].types = bcm63xx_cfe_part_types;
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	for (i = 0; i < nrparts; i++)
1188c2ecf20Sopenharmony_ci		pr_info("Partition %d is %s offset %llx and length %llx\n", i,
1198c2ecf20Sopenharmony_ci			parts[i].name, parts[i].offset,	parts[i].size);
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	*pparts = parts;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	return nrparts;
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_cistatic int bcm63xx_parse_cfe_partitions(struct mtd_info *master,
1278c2ecf20Sopenharmony_ci					const struct mtd_partition **pparts,
1288c2ecf20Sopenharmony_ci					struct mtd_part_parser_data *data)
1298c2ecf20Sopenharmony_ci{
1308c2ecf20Sopenharmony_ci	struct bcm963xx_nvram *nvram = NULL;
1318c2ecf20Sopenharmony_ci	int ret;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	if (!bcm63xx_detect_cfe())
1348c2ecf20Sopenharmony_ci		return -EINVAL;
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	nvram = vzalloc(sizeof(*nvram));
1378c2ecf20Sopenharmony_ci	if (!nvram)
1388c2ecf20Sopenharmony_ci		return -ENOMEM;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	ret = bcm63xx_read_nvram(master, nvram);
1418c2ecf20Sopenharmony_ci	if (ret)
1428c2ecf20Sopenharmony_ci		goto out;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	if (!mtd_type_is_nand(master))
1458c2ecf20Sopenharmony_ci		ret = bcm63xx_parse_cfe_nor_partitions(master, pparts, nvram);
1468c2ecf20Sopenharmony_ci	else
1478c2ecf20Sopenharmony_ci		ret = -EINVAL;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ciout:
1508c2ecf20Sopenharmony_ci	vfree(nvram);
1518c2ecf20Sopenharmony_ci	return ret;
1528c2ecf20Sopenharmony_ci};
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_cistatic const struct of_device_id parse_bcm63xx_cfe_match_table[] = {
1558c2ecf20Sopenharmony_ci	{ .compatible = "brcm,bcm963xx-cfe-nor-partitions" },
1568c2ecf20Sopenharmony_ci	{},
1578c2ecf20Sopenharmony_ci};
1588c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, parse_bcm63xx_cfe_match_table);
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_cistatic struct mtd_part_parser bcm63xx_cfe_parser = {
1618c2ecf20Sopenharmony_ci	.parse_fn = bcm63xx_parse_cfe_partitions,
1628c2ecf20Sopenharmony_ci	.name = "bcm63xxpart",
1638c2ecf20Sopenharmony_ci	.of_match_table = parse_bcm63xx_cfe_match_table,
1648c2ecf20Sopenharmony_ci};
1658c2ecf20Sopenharmony_cimodule_mtd_part_parser(bcm63xx_cfe_parser);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
1688c2ecf20Sopenharmony_ciMODULE_AUTHOR("Daniel Dickinson <openwrt@cshore.neomailbox.net>");
1698c2ecf20Sopenharmony_ciMODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
1708c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mike Albon <malbon@openwrt.org>");
1718c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jonas Gorski <jonas.gorski@gmail.com");
1728c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MTD partitioning for BCM63XX CFE bootloaders");
173