xref: /openbmc/linux/drivers/mtd/parsers/redboot.c (revision c0e118c8a1a32eda2a9c66174930afaf304753b4)
143f1fd01SLinus Walleij /*
243f1fd01SLinus Walleij  * Parse RedBoot-style Flash Image System (FIS) tables and
343f1fd01SLinus Walleij  * produce a Linux partition array to match.
443f1fd01SLinus Walleij  *
543f1fd01SLinus Walleij  * Copyright © 2001      Red Hat UK Limited
643f1fd01SLinus Walleij  * Copyright © 2001-2010 David Woodhouse <dwmw2@infradead.org>
743f1fd01SLinus Walleij  *
843f1fd01SLinus Walleij  * This program is free software; you can redistribute it and/or modify
943f1fd01SLinus Walleij  * it under the terms of the GNU General Public License as published by
1043f1fd01SLinus Walleij  * the Free Software Foundation; either version 2 of the License, or
1143f1fd01SLinus Walleij  * (at your option) any later version.
1243f1fd01SLinus Walleij  *
1343f1fd01SLinus Walleij  * This program is distributed in the hope that it will be useful,
1443f1fd01SLinus Walleij  * but WITHOUT ANY WARRANTY; without even the implied warranty of
1543f1fd01SLinus Walleij  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1643f1fd01SLinus Walleij  * GNU General Public License for more details.
1743f1fd01SLinus Walleij  *
1843f1fd01SLinus Walleij  * You should have received a copy of the GNU General Public License
1943f1fd01SLinus Walleij  * along with this program; if not, write to the Free Software
2043f1fd01SLinus Walleij  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
2143f1fd01SLinus Walleij  *
2243f1fd01SLinus Walleij  */
2343f1fd01SLinus Walleij 
2443f1fd01SLinus Walleij #include <linux/kernel.h>
2543f1fd01SLinus Walleij #include <linux/slab.h>
2643f1fd01SLinus Walleij #include <linux/init.h>
2743f1fd01SLinus Walleij #include <linux/vmalloc.h>
28*c0e118c8SLinus Walleij #include <linux/of.h>
2943f1fd01SLinus Walleij #include <linux/mtd/mtd.h>
3043f1fd01SLinus Walleij #include <linux/mtd/partitions.h>
3143f1fd01SLinus Walleij #include <linux/module.h>
3243f1fd01SLinus Walleij 
3343f1fd01SLinus Walleij struct fis_image_desc {
3443f1fd01SLinus Walleij     unsigned char name[16];      // Null terminated name
3543f1fd01SLinus Walleij     uint32_t	  flash_base;    // Address within FLASH of image
3643f1fd01SLinus Walleij     uint32_t	  mem_base;      // Address in memory where it executes
3743f1fd01SLinus Walleij     uint32_t	  size;          // Length of image
3843f1fd01SLinus Walleij     uint32_t	  entry_point;   // Execution entry point
3943f1fd01SLinus Walleij     uint32_t	  data_length;   // Length of actual data
4043f1fd01SLinus Walleij     unsigned char _pad[256-(16+7*sizeof(uint32_t))];
4143f1fd01SLinus Walleij     uint32_t	  desc_cksum;    // Checksum over image descriptor
4243f1fd01SLinus Walleij     uint32_t	  file_cksum;    // Checksum over image data
4343f1fd01SLinus Walleij };
4443f1fd01SLinus Walleij 
4543f1fd01SLinus Walleij struct fis_list {
4643f1fd01SLinus Walleij 	struct fis_image_desc *img;
4743f1fd01SLinus Walleij 	struct fis_list *next;
4843f1fd01SLinus Walleij };
4943f1fd01SLinus Walleij 
5043f1fd01SLinus Walleij static int directory = CONFIG_MTD_REDBOOT_DIRECTORY_BLOCK;
5143f1fd01SLinus Walleij module_param(directory, int, 0);
5243f1fd01SLinus Walleij 
5343f1fd01SLinus Walleij static inline int redboot_checksum(struct fis_image_desc *img)
5443f1fd01SLinus Walleij {
5543f1fd01SLinus Walleij 	/* RedBoot doesn't actually write the desc_cksum field yet AFAICT */
5643f1fd01SLinus Walleij 	return 1;
5743f1fd01SLinus Walleij }
5843f1fd01SLinus Walleij 
59*c0e118c8SLinus Walleij static void parse_redboot_of(struct mtd_info *master)
60*c0e118c8SLinus Walleij {
61*c0e118c8SLinus Walleij 	struct device_node *np;
62*c0e118c8SLinus Walleij 	u32 dirblock;
63*c0e118c8SLinus Walleij 	int ret;
64*c0e118c8SLinus Walleij 
65*c0e118c8SLinus Walleij 	np = mtd_get_of_node(master);
66*c0e118c8SLinus Walleij 	if (!np)
67*c0e118c8SLinus Walleij 		return;
68*c0e118c8SLinus Walleij 
69*c0e118c8SLinus Walleij 	ret = of_property_read_u32(np, "fis-index-block", &dirblock);
70*c0e118c8SLinus Walleij 	if (ret)
71*c0e118c8SLinus Walleij 		return;
72*c0e118c8SLinus Walleij 
73*c0e118c8SLinus Walleij 	/*
74*c0e118c8SLinus Walleij 	 * Assign the block found in the device tree to the local
75*c0e118c8SLinus Walleij 	 * directory block pointer.
76*c0e118c8SLinus Walleij 	 */
77*c0e118c8SLinus Walleij 	directory = dirblock;
78*c0e118c8SLinus Walleij }
79*c0e118c8SLinus Walleij 
8043f1fd01SLinus Walleij static int parse_redboot_partitions(struct mtd_info *master,
8143f1fd01SLinus Walleij 				    const struct mtd_partition **pparts,
8243f1fd01SLinus Walleij 				    struct mtd_part_parser_data *data)
8343f1fd01SLinus Walleij {
8443f1fd01SLinus Walleij 	int nrparts = 0;
8543f1fd01SLinus Walleij 	struct fis_image_desc *buf;
8643f1fd01SLinus Walleij 	struct mtd_partition *parts;
8743f1fd01SLinus Walleij 	struct fis_list *fl = NULL, *tmp_fl;
8843f1fd01SLinus Walleij 	int ret, i;
8943f1fd01SLinus Walleij 	size_t retlen;
9043f1fd01SLinus Walleij 	char *names;
9143f1fd01SLinus Walleij 	char *nullname;
9243f1fd01SLinus Walleij 	int namelen = 0;
9343f1fd01SLinus Walleij 	int nulllen = 0;
9443f1fd01SLinus Walleij 	int numslots;
9543f1fd01SLinus Walleij 	unsigned long offset;
9643f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
9743f1fd01SLinus Walleij 	static char nullstring[] = "unallocated";
9843f1fd01SLinus Walleij #endif
9943f1fd01SLinus Walleij 
100*c0e118c8SLinus Walleij 	parse_redboot_of(master);
101*c0e118c8SLinus Walleij 
10243f1fd01SLinus Walleij 	if ( directory < 0 ) {
10343f1fd01SLinus Walleij 		offset = master->size + directory * master->erasesize;
10443f1fd01SLinus Walleij 		while (mtd_block_isbad(master, offset)) {
10543f1fd01SLinus Walleij 			if (!offset) {
10643f1fd01SLinus Walleij 			nogood:
10743f1fd01SLinus Walleij 				printk(KERN_NOTICE "Failed to find a non-bad block to check for RedBoot partition table\n");
10843f1fd01SLinus Walleij 				return -EIO;
10943f1fd01SLinus Walleij 			}
11043f1fd01SLinus Walleij 			offset -= master->erasesize;
11143f1fd01SLinus Walleij 		}
11243f1fd01SLinus Walleij 	} else {
11343f1fd01SLinus Walleij 		offset = directory * master->erasesize;
11443f1fd01SLinus Walleij 		while (mtd_block_isbad(master, offset)) {
11543f1fd01SLinus Walleij 			offset += master->erasesize;
11643f1fd01SLinus Walleij 			if (offset == master->size)
11743f1fd01SLinus Walleij 				goto nogood;
11843f1fd01SLinus Walleij 		}
11943f1fd01SLinus Walleij 	}
12043f1fd01SLinus Walleij 	buf = vmalloc(master->erasesize);
12143f1fd01SLinus Walleij 
12243f1fd01SLinus Walleij 	if (!buf)
12343f1fd01SLinus Walleij 		return -ENOMEM;
12443f1fd01SLinus Walleij 
12543f1fd01SLinus Walleij 	printk(KERN_NOTICE "Searching for RedBoot partition table in %s at offset 0x%lx\n",
12643f1fd01SLinus Walleij 	       master->name, offset);
12743f1fd01SLinus Walleij 
12843f1fd01SLinus Walleij 	ret = mtd_read(master, offset, master->erasesize, &retlen,
12943f1fd01SLinus Walleij 		       (void *)buf);
13043f1fd01SLinus Walleij 
13143f1fd01SLinus Walleij 	if (ret)
13243f1fd01SLinus Walleij 		goto out;
13343f1fd01SLinus Walleij 
13443f1fd01SLinus Walleij 	if (retlen != master->erasesize) {
13543f1fd01SLinus Walleij 		ret = -EIO;
13643f1fd01SLinus Walleij 		goto out;
13743f1fd01SLinus Walleij 	}
13843f1fd01SLinus Walleij 
13943f1fd01SLinus Walleij 	numslots = (master->erasesize / sizeof(struct fis_image_desc));
14043f1fd01SLinus Walleij 	for (i = 0; i < numslots; i++) {
14143f1fd01SLinus Walleij 		if (!memcmp(buf[i].name, "FIS directory", 14)) {
14243f1fd01SLinus Walleij 			/* This is apparently the FIS directory entry for the
14343f1fd01SLinus Walleij 			 * FIS directory itself.  The FIS directory size is
14443f1fd01SLinus Walleij 			 * one erase block; if the buf[i].size field is
14543f1fd01SLinus Walleij 			 * swab32(erasesize) then we know we are looking at
14643f1fd01SLinus Walleij 			 * a byte swapped FIS directory - swap all the entries!
14743f1fd01SLinus Walleij 			 * (NOTE: this is 'size' not 'data_length'; size is
14843f1fd01SLinus Walleij 			 * the full size of the entry.)
14943f1fd01SLinus Walleij 			 */
15043f1fd01SLinus Walleij 
15143f1fd01SLinus Walleij 			/* RedBoot can combine the FIS directory and
15243f1fd01SLinus Walleij 			   config partitions into a single eraseblock;
15343f1fd01SLinus Walleij 			   we assume wrong-endian if either the swapped
15443f1fd01SLinus Walleij 			   'size' matches the eraseblock size precisely,
15543f1fd01SLinus Walleij 			   or if the swapped size actually fits in an
15643f1fd01SLinus Walleij 			   eraseblock while the unswapped size doesn't. */
15743f1fd01SLinus Walleij 			if (swab32(buf[i].size) == master->erasesize ||
15843f1fd01SLinus Walleij 			    (buf[i].size > master->erasesize
15943f1fd01SLinus Walleij 			     && swab32(buf[i].size) < master->erasesize)) {
16043f1fd01SLinus Walleij 				int j;
16143f1fd01SLinus Walleij 				/* Update numslots based on actual FIS directory size */
16243f1fd01SLinus Walleij 				numslots = swab32(buf[i].size) / sizeof (struct fis_image_desc);
16343f1fd01SLinus Walleij 				for (j = 0; j < numslots; ++j) {
16443f1fd01SLinus Walleij 
16543f1fd01SLinus Walleij 					/* A single 0xff denotes a deleted entry.
16643f1fd01SLinus Walleij 					 * Two of them in a row is the end of the table.
16743f1fd01SLinus Walleij 					 */
16843f1fd01SLinus Walleij 					if (buf[j].name[0] == 0xff) {
16943f1fd01SLinus Walleij 				  		if (buf[j].name[1] == 0xff) {
17043f1fd01SLinus Walleij 							break;
17143f1fd01SLinus Walleij 						} else {
17243f1fd01SLinus Walleij 							continue;
17343f1fd01SLinus Walleij 						}
17443f1fd01SLinus Walleij 					}
17543f1fd01SLinus Walleij 
17643f1fd01SLinus Walleij 					/* The unsigned long fields were written with the
17743f1fd01SLinus Walleij 					 * wrong byte sex, name and pad have no byte sex.
17843f1fd01SLinus Walleij 					 */
17943f1fd01SLinus Walleij 					swab32s(&buf[j].flash_base);
18043f1fd01SLinus Walleij 					swab32s(&buf[j].mem_base);
18143f1fd01SLinus Walleij 					swab32s(&buf[j].size);
18243f1fd01SLinus Walleij 					swab32s(&buf[j].entry_point);
18343f1fd01SLinus Walleij 					swab32s(&buf[j].data_length);
18443f1fd01SLinus Walleij 					swab32s(&buf[j].desc_cksum);
18543f1fd01SLinus Walleij 					swab32s(&buf[j].file_cksum);
18643f1fd01SLinus Walleij 				}
18743f1fd01SLinus Walleij 			} else if (buf[i].size < master->erasesize) {
18843f1fd01SLinus Walleij 				/* Update numslots based on actual FIS directory size */
18943f1fd01SLinus Walleij 				numslots = buf[i].size / sizeof(struct fis_image_desc);
19043f1fd01SLinus Walleij 			}
19143f1fd01SLinus Walleij 			break;
19243f1fd01SLinus Walleij 		}
19343f1fd01SLinus Walleij 	}
19443f1fd01SLinus Walleij 	if (i == numslots) {
19543f1fd01SLinus Walleij 		/* Didn't find it */
19643f1fd01SLinus Walleij 		printk(KERN_NOTICE "No RedBoot partition table detected in %s\n",
19743f1fd01SLinus Walleij 		       master->name);
19843f1fd01SLinus Walleij 		ret = 0;
19943f1fd01SLinus Walleij 		goto out;
20043f1fd01SLinus Walleij 	}
20143f1fd01SLinus Walleij 
20243f1fd01SLinus Walleij 	for (i = 0; i < numslots; i++) {
20343f1fd01SLinus Walleij 		struct fis_list *new_fl, **prev;
20443f1fd01SLinus Walleij 
20543f1fd01SLinus Walleij 		if (buf[i].name[0] == 0xff) {
20643f1fd01SLinus Walleij 			if (buf[i].name[1] == 0xff) {
20743f1fd01SLinus Walleij 				break;
20843f1fd01SLinus Walleij 			} else {
20943f1fd01SLinus Walleij 				continue;
21043f1fd01SLinus Walleij 			}
21143f1fd01SLinus Walleij 		}
21243f1fd01SLinus Walleij 		if (!redboot_checksum(&buf[i]))
21343f1fd01SLinus Walleij 			break;
21443f1fd01SLinus Walleij 
21543f1fd01SLinus Walleij 		new_fl = kmalloc(sizeof(struct fis_list), GFP_KERNEL);
21643f1fd01SLinus Walleij 		namelen += strlen(buf[i].name)+1;
21743f1fd01SLinus Walleij 		if (!new_fl) {
21843f1fd01SLinus Walleij 			ret = -ENOMEM;
21943f1fd01SLinus Walleij 			goto out;
22043f1fd01SLinus Walleij 		}
22143f1fd01SLinus Walleij 		new_fl->img = &buf[i];
22243f1fd01SLinus Walleij 		if (data && data->origin)
22343f1fd01SLinus Walleij 			buf[i].flash_base -= data->origin;
22443f1fd01SLinus Walleij 		else
22543f1fd01SLinus Walleij 			buf[i].flash_base &= master->size-1;
22643f1fd01SLinus Walleij 
22743f1fd01SLinus Walleij 		/* I'm sure the JFFS2 code has done me permanent damage.
22843f1fd01SLinus Walleij 		 * I now think the following is _normal_
22943f1fd01SLinus Walleij 		 */
23043f1fd01SLinus Walleij 		prev = &fl;
23143f1fd01SLinus Walleij 		while(*prev && (*prev)->img->flash_base < new_fl->img->flash_base)
23243f1fd01SLinus Walleij 			prev = &(*prev)->next;
23343f1fd01SLinus Walleij 		new_fl->next = *prev;
23443f1fd01SLinus Walleij 		*prev = new_fl;
23543f1fd01SLinus Walleij 
23643f1fd01SLinus Walleij 		nrparts++;
23743f1fd01SLinus Walleij 	}
23843f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
23943f1fd01SLinus Walleij 	if (fl->img->flash_base) {
24043f1fd01SLinus Walleij 		nrparts++;
24143f1fd01SLinus Walleij 		nulllen = sizeof(nullstring);
24243f1fd01SLinus Walleij 	}
24343f1fd01SLinus Walleij 
24443f1fd01SLinus Walleij 	for (tmp_fl = fl; tmp_fl->next; tmp_fl = tmp_fl->next) {
24543f1fd01SLinus Walleij 		if (tmp_fl->img->flash_base + tmp_fl->img->size + master->erasesize <= tmp_fl->next->img->flash_base) {
24643f1fd01SLinus Walleij 			nrparts++;
24743f1fd01SLinus Walleij 			nulllen = sizeof(nullstring);
24843f1fd01SLinus Walleij 		}
24943f1fd01SLinus Walleij 	}
25043f1fd01SLinus Walleij #endif
25143f1fd01SLinus Walleij 	parts = kzalloc(sizeof(*parts)*nrparts + nulllen + namelen, GFP_KERNEL);
25243f1fd01SLinus Walleij 
25343f1fd01SLinus Walleij 	if (!parts) {
25443f1fd01SLinus Walleij 		ret = -ENOMEM;
25543f1fd01SLinus Walleij 		goto out;
25643f1fd01SLinus Walleij 	}
25743f1fd01SLinus Walleij 
25843f1fd01SLinus Walleij 	nullname = (char *)&parts[nrparts];
25943f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
26043f1fd01SLinus Walleij 	if (nulllen > 0) {
26143f1fd01SLinus Walleij 		strcpy(nullname, nullstring);
26243f1fd01SLinus Walleij 	}
26343f1fd01SLinus Walleij #endif
26443f1fd01SLinus Walleij 	names = nullname + nulllen;
26543f1fd01SLinus Walleij 
26643f1fd01SLinus Walleij 	i=0;
26743f1fd01SLinus Walleij 
26843f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
26943f1fd01SLinus Walleij 	if (fl->img->flash_base) {
27043f1fd01SLinus Walleij 	       parts[0].name = nullname;
27143f1fd01SLinus Walleij 	       parts[0].size = fl->img->flash_base;
27243f1fd01SLinus Walleij 	       parts[0].offset = 0;
27343f1fd01SLinus Walleij 		i++;
27443f1fd01SLinus Walleij 	}
27543f1fd01SLinus Walleij #endif
27643f1fd01SLinus Walleij 	for ( ; i<nrparts; i++) {
27743f1fd01SLinus Walleij 		parts[i].size = fl->img->size;
27843f1fd01SLinus Walleij 		parts[i].offset = fl->img->flash_base;
27943f1fd01SLinus Walleij 		parts[i].name = names;
28043f1fd01SLinus Walleij 
28143f1fd01SLinus Walleij 		strcpy(names, fl->img->name);
28243f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_READONLY
28343f1fd01SLinus Walleij 		if (!memcmp(names, "RedBoot", 8) ||
28443f1fd01SLinus Walleij 				!memcmp(names, "RedBoot config", 15) ||
28543f1fd01SLinus Walleij 				!memcmp(names, "FIS directory", 14)) {
28643f1fd01SLinus Walleij 			parts[i].mask_flags = MTD_WRITEABLE;
28743f1fd01SLinus Walleij 		}
28843f1fd01SLinus Walleij #endif
28943f1fd01SLinus Walleij 		names += strlen(names)+1;
29043f1fd01SLinus Walleij 
29143f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
29243f1fd01SLinus Walleij 		if(fl->next && fl->img->flash_base + fl->img->size + master->erasesize <= fl->next->img->flash_base) {
29343f1fd01SLinus Walleij 			i++;
29443f1fd01SLinus Walleij 			parts[i].offset = parts[i-1].size + parts[i-1].offset;
29543f1fd01SLinus Walleij 			parts[i].size = fl->next->img->flash_base - parts[i].offset;
29643f1fd01SLinus Walleij 			parts[i].name = nullname;
29743f1fd01SLinus Walleij 		}
29843f1fd01SLinus Walleij #endif
29943f1fd01SLinus Walleij 		tmp_fl = fl;
30043f1fd01SLinus Walleij 		fl = fl->next;
30143f1fd01SLinus Walleij 		kfree(tmp_fl);
30243f1fd01SLinus Walleij 	}
30343f1fd01SLinus Walleij 	ret = nrparts;
30443f1fd01SLinus Walleij 	*pparts = parts;
30543f1fd01SLinus Walleij  out:
30643f1fd01SLinus Walleij 	while (fl) {
30743f1fd01SLinus Walleij 		struct fis_list *old = fl;
30843f1fd01SLinus Walleij 		fl = fl->next;
30943f1fd01SLinus Walleij 		kfree(old);
31043f1fd01SLinus Walleij 	}
31143f1fd01SLinus Walleij 	vfree(buf);
31243f1fd01SLinus Walleij 	return ret;
31343f1fd01SLinus Walleij }
31443f1fd01SLinus Walleij 
315*c0e118c8SLinus Walleij static const struct of_device_id mtd_parser_redboot_of_match_table[] = {
316*c0e118c8SLinus Walleij 	{ .compatible = "redboot-fis" },
317*c0e118c8SLinus Walleij 	{},
318*c0e118c8SLinus Walleij };
319*c0e118c8SLinus Walleij MODULE_DEVICE_TABLE(of, mtd_parser_redboot_of_match_table);
320*c0e118c8SLinus Walleij 
32143f1fd01SLinus Walleij static struct mtd_part_parser redboot_parser = {
32243f1fd01SLinus Walleij 	.parse_fn = parse_redboot_partitions,
32343f1fd01SLinus Walleij 	.name = "RedBoot",
324*c0e118c8SLinus Walleij 	.of_match_table = mtd_parser_redboot_of_match_table,
32543f1fd01SLinus Walleij };
32643f1fd01SLinus Walleij module_mtd_part_parser(redboot_parser);
32743f1fd01SLinus Walleij 
32843f1fd01SLinus Walleij /* mtd parsers will request the module by parser name */
32943f1fd01SLinus Walleij MODULE_ALIAS("RedBoot");
33043f1fd01SLinus Walleij MODULE_LICENSE("GPL");
33143f1fd01SLinus Walleij MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
33243f1fd01SLinus Walleij MODULE_DESCRIPTION("Parsing code for RedBoot Flash Image System (FIS) tables");
333