xref: /openbmc/linux/drivers/mtd/parsers/redboot.c (revision f91ca89e924eb287915522664a31afc71a49c05b)
1fd534e9bSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
243f1fd01SLinus Walleij /*
343f1fd01SLinus Walleij  * Parse RedBoot-style Flash Image System (FIS) tables and
443f1fd01SLinus Walleij  * produce a Linux partition array to match.
543f1fd01SLinus Walleij  *
643f1fd01SLinus Walleij  * Copyright © 2001      Red Hat UK Limited
743f1fd01SLinus Walleij  * Copyright © 2001-2010 David Woodhouse <dwmw2@infradead.org>
843f1fd01SLinus Walleij  */
943f1fd01SLinus Walleij 
1043f1fd01SLinus Walleij #include <linux/kernel.h>
1143f1fd01SLinus Walleij #include <linux/slab.h>
1243f1fd01SLinus Walleij #include <linux/init.h>
1343f1fd01SLinus Walleij #include <linux/vmalloc.h>
14c0e118c8SLinus Walleij #include <linux/of.h>
1543f1fd01SLinus Walleij #include <linux/mtd/mtd.h>
1643f1fd01SLinus Walleij #include <linux/mtd/partitions.h>
1743f1fd01SLinus Walleij #include <linux/module.h>
1843f1fd01SLinus Walleij 
1943f1fd01SLinus Walleij struct fis_image_desc {
2043f1fd01SLinus Walleij 	unsigned char name[16];      // Null terminated name
21eb1765c4SCorentin Labbe 	u32	  flash_base;    // Address within FLASH of image
22eb1765c4SCorentin Labbe 	u32	  mem_base;      // Address in memory where it executes
23eb1765c4SCorentin Labbe 	u32	  size;          // Length of image
24eb1765c4SCorentin Labbe 	u32	  entry_point;   // Execution entry point
25eb1765c4SCorentin Labbe 	u32	  data_length;   // Length of actual data
26eb1765c4SCorentin Labbe 	unsigned char _pad[256 - (16 + 7 * sizeof(u32))];
27eb1765c4SCorentin Labbe 	u32	  desc_cksum;    // Checksum over image descriptor
28eb1765c4SCorentin Labbe 	u32	  file_cksum;    // Checksum over image data
2943f1fd01SLinus Walleij };
3043f1fd01SLinus Walleij 
3143f1fd01SLinus Walleij struct fis_list {
3243f1fd01SLinus Walleij 	struct fis_image_desc *img;
3343f1fd01SLinus Walleij 	struct fis_list *next;
3443f1fd01SLinus Walleij };
3543f1fd01SLinus Walleij 
3643f1fd01SLinus Walleij static int directory = CONFIG_MTD_REDBOOT_DIRECTORY_BLOCK;
3743f1fd01SLinus Walleij module_param(directory, int, 0);
3843f1fd01SLinus Walleij 
redboot_checksum(struct fis_image_desc * img)3943f1fd01SLinus Walleij static inline int redboot_checksum(struct fis_image_desc *img)
4043f1fd01SLinus Walleij {
4143f1fd01SLinus Walleij 	/* RedBoot doesn't actually write the desc_cksum field yet AFAICT */
4243f1fd01SLinus Walleij 	return 1;
4343f1fd01SLinus Walleij }
4443f1fd01SLinus Walleij 
parse_redboot_of(struct mtd_info * master)45c0e118c8SLinus Walleij static void parse_redboot_of(struct mtd_info *master)
46c0e118c8SLinus Walleij {
47c0e118c8SLinus Walleij 	struct device_node *np;
4823796088SCorentin Labbe 	struct device_node *npart;
49c0e118c8SLinus Walleij 	u32 dirblock;
50c0e118c8SLinus Walleij 	int ret;
51c0e118c8SLinus Walleij 
52c0e118c8SLinus Walleij 	np = mtd_get_of_node(master);
53c0e118c8SLinus Walleij 	if (!np)
54c0e118c8SLinus Walleij 		return;
55c0e118c8SLinus Walleij 
5623796088SCorentin Labbe 	npart = of_get_child_by_name(np, "partitions");
5723796088SCorentin Labbe 	if (!npart)
5823796088SCorentin Labbe 		return;
5923796088SCorentin Labbe 
6023796088SCorentin Labbe 	ret = of_property_read_u32(npart, "fis-index-block", &dirblock);
619f7e6281SMiaoqian Lin 	of_node_put(npart);
62c0e118c8SLinus Walleij 	if (ret)
63c0e118c8SLinus Walleij 		return;
64c0e118c8SLinus Walleij 
65c0e118c8SLinus Walleij 	/*
66c0e118c8SLinus Walleij 	 * Assign the block found in the device tree to the local
67c0e118c8SLinus Walleij 	 * directory block pointer.
68c0e118c8SLinus Walleij 	 */
69c0e118c8SLinus Walleij 	directory = dirblock;
70c0e118c8SLinus Walleij }
71c0e118c8SLinus Walleij 
parse_redboot_partitions(struct mtd_info * master,const struct mtd_partition ** pparts,struct mtd_part_parser_data * data)7243f1fd01SLinus Walleij static int parse_redboot_partitions(struct mtd_info *master,
7343f1fd01SLinus Walleij 				    const struct mtd_partition **pparts,
7443f1fd01SLinus Walleij 				    struct mtd_part_parser_data *data)
7543f1fd01SLinus Walleij {
7643f1fd01SLinus Walleij 	int nrparts = 0;
7743f1fd01SLinus Walleij 	struct fis_image_desc *buf;
7843f1fd01SLinus Walleij 	struct mtd_partition *parts;
7943f1fd01SLinus Walleij 	struct fis_list *fl = NULL, *tmp_fl;
8043f1fd01SLinus Walleij 	int ret, i;
8143f1fd01SLinus Walleij 	size_t retlen;
8243f1fd01SLinus Walleij 	char *names;
8343f1fd01SLinus Walleij 	char *nullname;
8443f1fd01SLinus Walleij 	int namelen = 0;
8543f1fd01SLinus Walleij 	int nulllen = 0;
8643f1fd01SLinus Walleij 	int numslots;
8743f1fd01SLinus Walleij 	unsigned long offset;
8843f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
8943f1fd01SLinus Walleij 	static char nullstring[] = "unallocated";
9043f1fd01SLinus Walleij #endif
9143f1fd01SLinus Walleij 
92c0e118c8SLinus Walleij 	parse_redboot_of(master);
93c0e118c8SLinus Walleij 
9443f1fd01SLinus Walleij 	if (directory < 0) {
9543f1fd01SLinus Walleij 		offset = master->size + directory * master->erasesize;
9643f1fd01SLinus Walleij 		while (mtd_block_isbad(master, offset)) {
9743f1fd01SLinus Walleij 			if (!offset) {
9843f1fd01SLinus Walleij nogood:
99eb1765c4SCorentin Labbe 				pr_notice("Failed to find a non-bad block to check for RedBoot partition table\n");
10043f1fd01SLinus Walleij 				return -EIO;
10143f1fd01SLinus Walleij 			}
10243f1fd01SLinus Walleij 			offset -= master->erasesize;
10343f1fd01SLinus Walleij 		}
10443f1fd01SLinus Walleij 	} else {
105*5266cbcfSDenis Arefev 		offset = (unsigned long) directory * master->erasesize;
10643f1fd01SLinus Walleij 		while (mtd_block_isbad(master, offset)) {
10743f1fd01SLinus Walleij 			offset += master->erasesize;
10843f1fd01SLinus Walleij 			if (offset == master->size)
10943f1fd01SLinus Walleij 				goto nogood;
11043f1fd01SLinus Walleij 		}
11143f1fd01SLinus Walleij 	}
11243f1fd01SLinus Walleij 	buf = vmalloc(master->erasesize);
11343f1fd01SLinus Walleij 
11443f1fd01SLinus Walleij 	if (!buf)
11543f1fd01SLinus Walleij 		return -ENOMEM;
11643f1fd01SLinus Walleij 
117eb1765c4SCorentin Labbe 	pr_notice("Searching for RedBoot partition table in %s at offset 0x%lx\n",
11843f1fd01SLinus Walleij 		  master->name, offset);
11943f1fd01SLinus Walleij 
12043f1fd01SLinus Walleij 	ret = mtd_read(master, offset, master->erasesize, &retlen,
12143f1fd01SLinus Walleij 		       (void *)buf);
12243f1fd01SLinus Walleij 
12343f1fd01SLinus Walleij 	if (ret)
12443f1fd01SLinus Walleij 		goto out;
12543f1fd01SLinus Walleij 
12643f1fd01SLinus Walleij 	if (retlen != master->erasesize) {
12743f1fd01SLinus Walleij 		ret = -EIO;
12843f1fd01SLinus Walleij 		goto out;
12943f1fd01SLinus Walleij 	}
13043f1fd01SLinus Walleij 
13143f1fd01SLinus Walleij 	numslots = (master->erasesize / sizeof(struct fis_image_desc));
13243f1fd01SLinus Walleij 	for (i = 0; i < numslots; i++) {
13343f1fd01SLinus Walleij 		if (!memcmp(buf[i].name, "FIS directory", 14)) {
13443f1fd01SLinus Walleij 			/* This is apparently the FIS directory entry for the
13543f1fd01SLinus Walleij 			 * FIS directory itself.  The FIS directory size is
13643f1fd01SLinus Walleij 			 * one erase block; if the buf[i].size field is
13743f1fd01SLinus Walleij 			 * swab32(erasesize) then we know we are looking at
13843f1fd01SLinus Walleij 			 * a byte swapped FIS directory - swap all the entries!
13943f1fd01SLinus Walleij 			 * (NOTE: this is 'size' not 'data_length'; size is
14043f1fd01SLinus Walleij 			 * the full size of the entry.)
14143f1fd01SLinus Walleij 			 */
14243f1fd01SLinus Walleij 
14343f1fd01SLinus Walleij 			/* RedBoot can combine the FIS directory and
14443f1fd01SLinus Walleij 			   config partitions into a single eraseblock;
14543f1fd01SLinus Walleij 			   we assume wrong-endian if either the swapped
14643f1fd01SLinus Walleij 			   'size' matches the eraseblock size precisely,
14743f1fd01SLinus Walleij 			   or if the swapped size actually fits in an
14843f1fd01SLinus Walleij 			   eraseblock while the unswapped size doesn't. */
14943f1fd01SLinus Walleij 			if (swab32(buf[i].size) == master->erasesize ||
15043f1fd01SLinus Walleij 			    (buf[i].size > master->erasesize
15143f1fd01SLinus Walleij 			     && swab32(buf[i].size) < master->erasesize)) {
15243f1fd01SLinus Walleij 				int j;
15343f1fd01SLinus Walleij 				/* Update numslots based on actual FIS directory size */
15443f1fd01SLinus Walleij 				numslots = swab32(buf[i].size) / sizeof(struct fis_image_desc);
15543f1fd01SLinus Walleij 				for (j = 0; j < numslots; ++j) {
15643f1fd01SLinus Walleij 					/* A single 0xff denotes a deleted entry.
15743f1fd01SLinus Walleij 					 * Two of them in a row is the end of the table.
15843f1fd01SLinus Walleij 					 */
15943f1fd01SLinus Walleij 					if (buf[j].name[0] == 0xff) {
16043f1fd01SLinus Walleij 						if (buf[j].name[1] == 0xff) {
16143f1fd01SLinus Walleij 							break;
16243f1fd01SLinus Walleij 						} else {
16343f1fd01SLinus Walleij 							continue;
16443f1fd01SLinus Walleij 						}
16543f1fd01SLinus Walleij 					}
16643f1fd01SLinus Walleij 
16743f1fd01SLinus Walleij 					/* The unsigned long fields were written with the
16843f1fd01SLinus Walleij 					 * wrong byte sex, name and pad have no byte sex.
16943f1fd01SLinus Walleij 					 */
17043f1fd01SLinus Walleij 					swab32s(&buf[j].flash_base);
17143f1fd01SLinus Walleij 					swab32s(&buf[j].mem_base);
17243f1fd01SLinus Walleij 					swab32s(&buf[j].size);
17343f1fd01SLinus Walleij 					swab32s(&buf[j].entry_point);
17443f1fd01SLinus Walleij 					swab32s(&buf[j].data_length);
17543f1fd01SLinus Walleij 					swab32s(&buf[j].desc_cksum);
17643f1fd01SLinus Walleij 					swab32s(&buf[j].file_cksum);
17743f1fd01SLinus Walleij 				}
17843f1fd01SLinus Walleij 			} else if (buf[i].size < master->erasesize) {
17943f1fd01SLinus Walleij 				/* Update numslots based on actual FIS directory size */
18043f1fd01SLinus Walleij 				numslots = buf[i].size / sizeof(struct fis_image_desc);
18143f1fd01SLinus Walleij 			}
18243f1fd01SLinus Walleij 			break;
18343f1fd01SLinus Walleij 		}
18443f1fd01SLinus Walleij 	}
18543f1fd01SLinus Walleij 	if (i == numslots) {
18643f1fd01SLinus Walleij 		/* Didn't find it */
187eb1765c4SCorentin Labbe 		pr_notice("No RedBoot partition table detected in %s\n",
18843f1fd01SLinus Walleij 			  master->name);
18943f1fd01SLinus Walleij 		ret = 0;
19043f1fd01SLinus Walleij 		goto out;
19143f1fd01SLinus Walleij 	}
19243f1fd01SLinus Walleij 
19343f1fd01SLinus Walleij 	for (i = 0; i < numslots; i++) {
19443f1fd01SLinus Walleij 		struct fis_list *new_fl, **prev;
19543f1fd01SLinus Walleij 
19643f1fd01SLinus Walleij 		if (buf[i].name[0] == 0xff) {
19743f1fd01SLinus Walleij 			if (buf[i].name[1] == 0xff) {
19843f1fd01SLinus Walleij 				break;
19943f1fd01SLinus Walleij 			} else {
20043f1fd01SLinus Walleij 				continue;
20143f1fd01SLinus Walleij 			}
20243f1fd01SLinus Walleij 		}
20343f1fd01SLinus Walleij 		if (!redboot_checksum(&buf[i]))
20443f1fd01SLinus Walleij 			break;
20543f1fd01SLinus Walleij 
20643f1fd01SLinus Walleij 		new_fl = kmalloc(sizeof(struct fis_list), GFP_KERNEL);
20743f1fd01SLinus Walleij 		namelen += strlen(buf[i].name) + 1;
20843f1fd01SLinus Walleij 		if (!new_fl) {
20943f1fd01SLinus Walleij 			ret = -ENOMEM;
21043f1fd01SLinus Walleij 			goto out;
21143f1fd01SLinus Walleij 		}
21243f1fd01SLinus Walleij 		new_fl->img = &buf[i];
21343f1fd01SLinus Walleij 		if (data && data->origin)
21443f1fd01SLinus Walleij 			buf[i].flash_base -= data->origin;
21543f1fd01SLinus Walleij 		else
21643f1fd01SLinus Walleij 			buf[i].flash_base &= master->size - 1;
21743f1fd01SLinus Walleij 
21843f1fd01SLinus Walleij 		/* I'm sure the JFFS2 code has done me permanent damage.
21943f1fd01SLinus Walleij 		 * I now think the following is _normal_
22043f1fd01SLinus Walleij 		 */
22143f1fd01SLinus Walleij 		prev = &fl;
22243f1fd01SLinus Walleij 		while (*prev && (*prev)->img->flash_base < new_fl->img->flash_base)
22343f1fd01SLinus Walleij 			prev = &(*prev)->next;
22443f1fd01SLinus Walleij 		new_fl->next = *prev;
22543f1fd01SLinus Walleij 		*prev = new_fl;
22643f1fd01SLinus Walleij 
22743f1fd01SLinus Walleij 		nrparts++;
22843f1fd01SLinus Walleij 	}
22943f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
23043f1fd01SLinus Walleij 	if (fl->img->flash_base) {
23143f1fd01SLinus Walleij 		nrparts++;
23243f1fd01SLinus Walleij 		nulllen = sizeof(nullstring);
23343f1fd01SLinus Walleij 	}
23443f1fd01SLinus Walleij 
23543f1fd01SLinus Walleij 	for (tmp_fl = fl; tmp_fl->next; tmp_fl = tmp_fl->next) {
23643f1fd01SLinus Walleij 		if (tmp_fl->img->flash_base + tmp_fl->img->size + master->erasesize <= tmp_fl->next->img->flash_base) {
23743f1fd01SLinus Walleij 			nrparts++;
23843f1fd01SLinus Walleij 			nulllen = sizeof(nullstring);
23943f1fd01SLinus Walleij 		}
24043f1fd01SLinus Walleij 	}
24143f1fd01SLinus Walleij #endif
24243f1fd01SLinus Walleij 	parts = kzalloc(sizeof(*parts) * nrparts + nulllen + namelen, GFP_KERNEL);
24343f1fd01SLinus Walleij 
24443f1fd01SLinus Walleij 	if (!parts) {
24543f1fd01SLinus Walleij 		ret = -ENOMEM;
24643f1fd01SLinus Walleij 		goto out;
24743f1fd01SLinus Walleij 	}
24843f1fd01SLinus Walleij 
24943f1fd01SLinus Walleij 	nullname = (char *)&parts[nrparts];
25043f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
251eb1765c4SCorentin Labbe 	if (nulllen > 0)
25243f1fd01SLinus Walleij 		strcpy(nullname, nullstring);
25343f1fd01SLinus Walleij #endif
25443f1fd01SLinus Walleij 	names = nullname + nulllen;
25543f1fd01SLinus Walleij 
25643f1fd01SLinus Walleij 	i = 0;
25743f1fd01SLinus Walleij 
25843f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
25943f1fd01SLinus Walleij 	if (fl->img->flash_base) {
26043f1fd01SLinus Walleij 		parts[0].name = nullname;
26143f1fd01SLinus Walleij 		parts[0].size = fl->img->flash_base;
26243f1fd01SLinus Walleij 		parts[0].offset = 0;
26343f1fd01SLinus Walleij 		i++;
26443f1fd01SLinus Walleij 	}
26543f1fd01SLinus Walleij #endif
26643f1fd01SLinus Walleij 	for ( ; i < nrparts; i++) {
26743f1fd01SLinus Walleij 		parts[i].size = fl->img->size;
26843f1fd01SLinus Walleij 		parts[i].offset = fl->img->flash_base;
26943f1fd01SLinus Walleij 		parts[i].name = names;
27043f1fd01SLinus Walleij 
27143f1fd01SLinus Walleij 		strcpy(names, fl->img->name);
27243f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_READONLY
27343f1fd01SLinus Walleij 		if (!memcmp(names, "RedBoot", 8) ||
27443f1fd01SLinus Walleij 		    !memcmp(names, "RedBoot config", 15) ||
27543f1fd01SLinus Walleij 		    !memcmp(names, "FIS directory", 14)) {
27643f1fd01SLinus Walleij 			parts[i].mask_flags = MTD_WRITEABLE;
27743f1fd01SLinus Walleij 		}
27843f1fd01SLinus Walleij #endif
27943f1fd01SLinus Walleij 		names += strlen(names) + 1;
28043f1fd01SLinus Walleij 
28143f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
28243f1fd01SLinus Walleij 		if (fl->next && fl->img->flash_base + fl->img->size + master->erasesize <= fl->next->img->flash_base) {
28343f1fd01SLinus Walleij 			i++;
28443f1fd01SLinus Walleij 			parts[i].offset = parts[i - 1].size + parts[i - 1].offset;
28543f1fd01SLinus Walleij 			parts[i].size = fl->next->img->flash_base - parts[i].offset;
28643f1fd01SLinus Walleij 			parts[i].name = nullname;
28743f1fd01SLinus Walleij 		}
28843f1fd01SLinus Walleij #endif
28943f1fd01SLinus Walleij 		tmp_fl = fl;
29043f1fd01SLinus Walleij 		fl = fl->next;
29143f1fd01SLinus Walleij 		kfree(tmp_fl);
29243f1fd01SLinus Walleij 	}
29343f1fd01SLinus Walleij 	ret = nrparts;
29443f1fd01SLinus Walleij 	*pparts = parts;
29543f1fd01SLinus Walleij  out:
29643f1fd01SLinus Walleij 	while (fl) {
29743f1fd01SLinus Walleij 		struct fis_list *old = fl;
298eb1765c4SCorentin Labbe 
29943f1fd01SLinus Walleij 		fl = fl->next;
30043f1fd01SLinus Walleij 		kfree(old);
30143f1fd01SLinus Walleij 	}
30243f1fd01SLinus Walleij 	vfree(buf);
30343f1fd01SLinus Walleij 	return ret;
30443f1fd01SLinus Walleij }
30543f1fd01SLinus Walleij 
306c0e118c8SLinus Walleij static const struct of_device_id mtd_parser_redboot_of_match_table[] = {
307c0e118c8SLinus Walleij 	{ .compatible = "redboot-fis" },
308c0e118c8SLinus Walleij 	{},
309c0e118c8SLinus Walleij };
310c0e118c8SLinus Walleij MODULE_DEVICE_TABLE(of, mtd_parser_redboot_of_match_table);
311c0e118c8SLinus Walleij 
31243f1fd01SLinus Walleij static struct mtd_part_parser redboot_parser = {
31343f1fd01SLinus Walleij 	.parse_fn = parse_redboot_partitions,
31443f1fd01SLinus Walleij 	.name = "RedBoot",
315c0e118c8SLinus Walleij 	.of_match_table = mtd_parser_redboot_of_match_table,
31643f1fd01SLinus Walleij };
31743f1fd01SLinus Walleij module_mtd_part_parser(redboot_parser);
31843f1fd01SLinus Walleij 
31943f1fd01SLinus Walleij /* mtd parsers will request the module by parser name */
32043f1fd01SLinus Walleij MODULE_ALIAS("RedBoot");
32143f1fd01SLinus Walleij MODULE_LICENSE("GPL");
32243f1fd01SLinus Walleij MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
32343f1fd01SLinus Walleij MODULE_DESCRIPTION("Parsing code for RedBoot Flash Image System (FIS) tables");
324