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