1*fd534e9bSThomas 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 2143f1fd01SLinus Walleij uint32_t flash_base; // Address within FLASH of image 2243f1fd01SLinus Walleij uint32_t mem_base; // Address in memory where it executes 2343f1fd01SLinus Walleij uint32_t size; // Length of image 2443f1fd01SLinus Walleij uint32_t entry_point; // Execution entry point 2543f1fd01SLinus Walleij uint32_t data_length; // Length of actual data 2643f1fd01SLinus Walleij unsigned char _pad[256-(16+7*sizeof(uint32_t))]; 2743f1fd01SLinus Walleij uint32_t desc_cksum; // Checksum over image descriptor 2843f1fd01SLinus Walleij uint32_t 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 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 45c0e118c8SLinus Walleij static void parse_redboot_of(struct mtd_info *master) 46c0e118c8SLinus Walleij { 47c0e118c8SLinus Walleij struct device_node *np; 48c0e118c8SLinus Walleij u32 dirblock; 49c0e118c8SLinus Walleij int ret; 50c0e118c8SLinus Walleij 51c0e118c8SLinus Walleij np = mtd_get_of_node(master); 52c0e118c8SLinus Walleij if (!np) 53c0e118c8SLinus Walleij return; 54c0e118c8SLinus Walleij 55c0e118c8SLinus Walleij ret = of_property_read_u32(np, "fis-index-block", &dirblock); 56c0e118c8SLinus Walleij if (ret) 57c0e118c8SLinus Walleij return; 58c0e118c8SLinus Walleij 59c0e118c8SLinus Walleij /* 60c0e118c8SLinus Walleij * Assign the block found in the device tree to the local 61c0e118c8SLinus Walleij * directory block pointer. 62c0e118c8SLinus Walleij */ 63c0e118c8SLinus Walleij directory = dirblock; 64c0e118c8SLinus Walleij } 65c0e118c8SLinus Walleij 6643f1fd01SLinus Walleij static int parse_redboot_partitions(struct mtd_info *master, 6743f1fd01SLinus Walleij const struct mtd_partition **pparts, 6843f1fd01SLinus Walleij struct mtd_part_parser_data *data) 6943f1fd01SLinus Walleij { 7043f1fd01SLinus Walleij int nrparts = 0; 7143f1fd01SLinus Walleij struct fis_image_desc *buf; 7243f1fd01SLinus Walleij struct mtd_partition *parts; 7343f1fd01SLinus Walleij struct fis_list *fl = NULL, *tmp_fl; 7443f1fd01SLinus Walleij int ret, i; 7543f1fd01SLinus Walleij size_t retlen; 7643f1fd01SLinus Walleij char *names; 7743f1fd01SLinus Walleij char *nullname; 7843f1fd01SLinus Walleij int namelen = 0; 7943f1fd01SLinus Walleij int nulllen = 0; 8043f1fd01SLinus Walleij int numslots; 8143f1fd01SLinus Walleij unsigned long offset; 8243f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED 8343f1fd01SLinus Walleij static char nullstring[] = "unallocated"; 8443f1fd01SLinus Walleij #endif 8543f1fd01SLinus Walleij 86c0e118c8SLinus Walleij parse_redboot_of(master); 87c0e118c8SLinus Walleij 8843f1fd01SLinus Walleij if ( directory < 0 ) { 8943f1fd01SLinus Walleij offset = master->size + directory * master->erasesize; 9043f1fd01SLinus Walleij while (mtd_block_isbad(master, offset)) { 9143f1fd01SLinus Walleij if (!offset) { 9243f1fd01SLinus Walleij nogood: 9343f1fd01SLinus Walleij printk(KERN_NOTICE "Failed to find a non-bad block to check for RedBoot partition table\n"); 9443f1fd01SLinus Walleij return -EIO; 9543f1fd01SLinus Walleij } 9643f1fd01SLinus Walleij offset -= master->erasesize; 9743f1fd01SLinus Walleij } 9843f1fd01SLinus Walleij } else { 9943f1fd01SLinus Walleij offset = directory * master->erasesize; 10043f1fd01SLinus Walleij while (mtd_block_isbad(master, offset)) { 10143f1fd01SLinus Walleij offset += master->erasesize; 10243f1fd01SLinus Walleij if (offset == master->size) 10343f1fd01SLinus Walleij goto nogood; 10443f1fd01SLinus Walleij } 10543f1fd01SLinus Walleij } 10643f1fd01SLinus Walleij buf = vmalloc(master->erasesize); 10743f1fd01SLinus Walleij 10843f1fd01SLinus Walleij if (!buf) 10943f1fd01SLinus Walleij return -ENOMEM; 11043f1fd01SLinus Walleij 11143f1fd01SLinus Walleij printk(KERN_NOTICE "Searching for RedBoot partition table in %s at offset 0x%lx\n", 11243f1fd01SLinus Walleij master->name, offset); 11343f1fd01SLinus Walleij 11443f1fd01SLinus Walleij ret = mtd_read(master, offset, master->erasesize, &retlen, 11543f1fd01SLinus Walleij (void *)buf); 11643f1fd01SLinus Walleij 11743f1fd01SLinus Walleij if (ret) 11843f1fd01SLinus Walleij goto out; 11943f1fd01SLinus Walleij 12043f1fd01SLinus Walleij if (retlen != master->erasesize) { 12143f1fd01SLinus Walleij ret = -EIO; 12243f1fd01SLinus Walleij goto out; 12343f1fd01SLinus Walleij } 12443f1fd01SLinus Walleij 12543f1fd01SLinus Walleij numslots = (master->erasesize / sizeof(struct fis_image_desc)); 12643f1fd01SLinus Walleij for (i = 0; i < numslots; i++) { 12743f1fd01SLinus Walleij if (!memcmp(buf[i].name, "FIS directory", 14)) { 12843f1fd01SLinus Walleij /* This is apparently the FIS directory entry for the 12943f1fd01SLinus Walleij * FIS directory itself. The FIS directory size is 13043f1fd01SLinus Walleij * one erase block; if the buf[i].size field is 13143f1fd01SLinus Walleij * swab32(erasesize) then we know we are looking at 13243f1fd01SLinus Walleij * a byte swapped FIS directory - swap all the entries! 13343f1fd01SLinus Walleij * (NOTE: this is 'size' not 'data_length'; size is 13443f1fd01SLinus Walleij * the full size of the entry.) 13543f1fd01SLinus Walleij */ 13643f1fd01SLinus Walleij 13743f1fd01SLinus Walleij /* RedBoot can combine the FIS directory and 13843f1fd01SLinus Walleij config partitions into a single eraseblock; 13943f1fd01SLinus Walleij we assume wrong-endian if either the swapped 14043f1fd01SLinus Walleij 'size' matches the eraseblock size precisely, 14143f1fd01SLinus Walleij or if the swapped size actually fits in an 14243f1fd01SLinus Walleij eraseblock while the unswapped size doesn't. */ 14343f1fd01SLinus Walleij if (swab32(buf[i].size) == master->erasesize || 14443f1fd01SLinus Walleij (buf[i].size > master->erasesize 14543f1fd01SLinus Walleij && swab32(buf[i].size) < master->erasesize)) { 14643f1fd01SLinus Walleij int j; 14743f1fd01SLinus Walleij /* Update numslots based on actual FIS directory size */ 14843f1fd01SLinus Walleij numslots = swab32(buf[i].size) / sizeof (struct fis_image_desc); 14943f1fd01SLinus Walleij for (j = 0; j < numslots; ++j) { 15043f1fd01SLinus Walleij 15143f1fd01SLinus Walleij /* A single 0xff denotes a deleted entry. 15243f1fd01SLinus Walleij * Two of them in a row is the end of the table. 15343f1fd01SLinus Walleij */ 15443f1fd01SLinus Walleij if (buf[j].name[0] == 0xff) { 15543f1fd01SLinus Walleij if (buf[j].name[1] == 0xff) { 15643f1fd01SLinus Walleij break; 15743f1fd01SLinus Walleij } else { 15843f1fd01SLinus Walleij continue; 15943f1fd01SLinus Walleij } 16043f1fd01SLinus Walleij } 16143f1fd01SLinus Walleij 16243f1fd01SLinus Walleij /* The unsigned long fields were written with the 16343f1fd01SLinus Walleij * wrong byte sex, name and pad have no byte sex. 16443f1fd01SLinus Walleij */ 16543f1fd01SLinus Walleij swab32s(&buf[j].flash_base); 16643f1fd01SLinus Walleij swab32s(&buf[j].mem_base); 16743f1fd01SLinus Walleij swab32s(&buf[j].size); 16843f1fd01SLinus Walleij swab32s(&buf[j].entry_point); 16943f1fd01SLinus Walleij swab32s(&buf[j].data_length); 17043f1fd01SLinus Walleij swab32s(&buf[j].desc_cksum); 17143f1fd01SLinus Walleij swab32s(&buf[j].file_cksum); 17243f1fd01SLinus Walleij } 17343f1fd01SLinus Walleij } else if (buf[i].size < master->erasesize) { 17443f1fd01SLinus Walleij /* Update numslots based on actual FIS directory size */ 17543f1fd01SLinus Walleij numslots = buf[i].size / sizeof(struct fis_image_desc); 17643f1fd01SLinus Walleij } 17743f1fd01SLinus Walleij break; 17843f1fd01SLinus Walleij } 17943f1fd01SLinus Walleij } 18043f1fd01SLinus Walleij if (i == numslots) { 18143f1fd01SLinus Walleij /* Didn't find it */ 18243f1fd01SLinus Walleij printk(KERN_NOTICE "No RedBoot partition table detected in %s\n", 18343f1fd01SLinus Walleij master->name); 18443f1fd01SLinus Walleij ret = 0; 18543f1fd01SLinus Walleij goto out; 18643f1fd01SLinus Walleij } 18743f1fd01SLinus Walleij 18843f1fd01SLinus Walleij for (i = 0; i < numslots; i++) { 18943f1fd01SLinus Walleij struct fis_list *new_fl, **prev; 19043f1fd01SLinus Walleij 19143f1fd01SLinus Walleij if (buf[i].name[0] == 0xff) { 19243f1fd01SLinus Walleij if (buf[i].name[1] == 0xff) { 19343f1fd01SLinus Walleij break; 19443f1fd01SLinus Walleij } else { 19543f1fd01SLinus Walleij continue; 19643f1fd01SLinus Walleij } 19743f1fd01SLinus Walleij } 19843f1fd01SLinus Walleij if (!redboot_checksum(&buf[i])) 19943f1fd01SLinus Walleij break; 20043f1fd01SLinus Walleij 20143f1fd01SLinus Walleij new_fl = kmalloc(sizeof(struct fis_list), GFP_KERNEL); 20243f1fd01SLinus Walleij namelen += strlen(buf[i].name)+1; 20343f1fd01SLinus Walleij if (!new_fl) { 20443f1fd01SLinus Walleij ret = -ENOMEM; 20543f1fd01SLinus Walleij goto out; 20643f1fd01SLinus Walleij } 20743f1fd01SLinus Walleij new_fl->img = &buf[i]; 20843f1fd01SLinus Walleij if (data && data->origin) 20943f1fd01SLinus Walleij buf[i].flash_base -= data->origin; 21043f1fd01SLinus Walleij else 21143f1fd01SLinus Walleij buf[i].flash_base &= master->size-1; 21243f1fd01SLinus Walleij 21343f1fd01SLinus Walleij /* I'm sure the JFFS2 code has done me permanent damage. 21443f1fd01SLinus Walleij * I now think the following is _normal_ 21543f1fd01SLinus Walleij */ 21643f1fd01SLinus Walleij prev = &fl; 21743f1fd01SLinus Walleij while(*prev && (*prev)->img->flash_base < new_fl->img->flash_base) 21843f1fd01SLinus Walleij prev = &(*prev)->next; 21943f1fd01SLinus Walleij new_fl->next = *prev; 22043f1fd01SLinus Walleij *prev = new_fl; 22143f1fd01SLinus Walleij 22243f1fd01SLinus Walleij nrparts++; 22343f1fd01SLinus Walleij } 22443f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED 22543f1fd01SLinus Walleij if (fl->img->flash_base) { 22643f1fd01SLinus Walleij nrparts++; 22743f1fd01SLinus Walleij nulllen = sizeof(nullstring); 22843f1fd01SLinus Walleij } 22943f1fd01SLinus Walleij 23043f1fd01SLinus Walleij for (tmp_fl = fl; tmp_fl->next; tmp_fl = tmp_fl->next) { 23143f1fd01SLinus Walleij if (tmp_fl->img->flash_base + tmp_fl->img->size + master->erasesize <= tmp_fl->next->img->flash_base) { 23243f1fd01SLinus Walleij nrparts++; 23343f1fd01SLinus Walleij nulllen = sizeof(nullstring); 23443f1fd01SLinus Walleij } 23543f1fd01SLinus Walleij } 23643f1fd01SLinus Walleij #endif 23743f1fd01SLinus Walleij parts = kzalloc(sizeof(*parts)*nrparts + nulllen + namelen, GFP_KERNEL); 23843f1fd01SLinus Walleij 23943f1fd01SLinus Walleij if (!parts) { 24043f1fd01SLinus Walleij ret = -ENOMEM; 24143f1fd01SLinus Walleij goto out; 24243f1fd01SLinus Walleij } 24343f1fd01SLinus Walleij 24443f1fd01SLinus Walleij nullname = (char *)&parts[nrparts]; 24543f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED 24643f1fd01SLinus Walleij if (nulllen > 0) { 24743f1fd01SLinus Walleij strcpy(nullname, nullstring); 24843f1fd01SLinus Walleij } 24943f1fd01SLinus Walleij #endif 25043f1fd01SLinus Walleij names = nullname + nulllen; 25143f1fd01SLinus Walleij 25243f1fd01SLinus Walleij i=0; 25343f1fd01SLinus Walleij 25443f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED 25543f1fd01SLinus Walleij if (fl->img->flash_base) { 25643f1fd01SLinus Walleij parts[0].name = nullname; 25743f1fd01SLinus Walleij parts[0].size = fl->img->flash_base; 25843f1fd01SLinus Walleij parts[0].offset = 0; 25943f1fd01SLinus Walleij i++; 26043f1fd01SLinus Walleij } 26143f1fd01SLinus Walleij #endif 26243f1fd01SLinus Walleij for ( ; i<nrparts; i++) { 26343f1fd01SLinus Walleij parts[i].size = fl->img->size; 26443f1fd01SLinus Walleij parts[i].offset = fl->img->flash_base; 26543f1fd01SLinus Walleij parts[i].name = names; 26643f1fd01SLinus Walleij 26743f1fd01SLinus Walleij strcpy(names, fl->img->name); 26843f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_READONLY 26943f1fd01SLinus Walleij if (!memcmp(names, "RedBoot", 8) || 27043f1fd01SLinus Walleij !memcmp(names, "RedBoot config", 15) || 27143f1fd01SLinus Walleij !memcmp(names, "FIS directory", 14)) { 27243f1fd01SLinus Walleij parts[i].mask_flags = MTD_WRITEABLE; 27343f1fd01SLinus Walleij } 27443f1fd01SLinus Walleij #endif 27543f1fd01SLinus Walleij names += strlen(names)+1; 27643f1fd01SLinus Walleij 27743f1fd01SLinus Walleij #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED 27843f1fd01SLinus Walleij if(fl->next && fl->img->flash_base + fl->img->size + master->erasesize <= fl->next->img->flash_base) { 27943f1fd01SLinus Walleij i++; 28043f1fd01SLinus Walleij parts[i].offset = parts[i-1].size + parts[i-1].offset; 28143f1fd01SLinus Walleij parts[i].size = fl->next->img->flash_base - parts[i].offset; 28243f1fd01SLinus Walleij parts[i].name = nullname; 28343f1fd01SLinus Walleij } 28443f1fd01SLinus Walleij #endif 28543f1fd01SLinus Walleij tmp_fl = fl; 28643f1fd01SLinus Walleij fl = fl->next; 28743f1fd01SLinus Walleij kfree(tmp_fl); 28843f1fd01SLinus Walleij } 28943f1fd01SLinus Walleij ret = nrparts; 29043f1fd01SLinus Walleij *pparts = parts; 29143f1fd01SLinus Walleij out: 29243f1fd01SLinus Walleij while (fl) { 29343f1fd01SLinus Walleij struct fis_list *old = fl; 29443f1fd01SLinus Walleij fl = fl->next; 29543f1fd01SLinus Walleij kfree(old); 29643f1fd01SLinus Walleij } 29743f1fd01SLinus Walleij vfree(buf); 29843f1fd01SLinus Walleij return ret; 29943f1fd01SLinus Walleij } 30043f1fd01SLinus Walleij 301c0e118c8SLinus Walleij static const struct of_device_id mtd_parser_redboot_of_match_table[] = { 302c0e118c8SLinus Walleij { .compatible = "redboot-fis" }, 303c0e118c8SLinus Walleij {}, 304c0e118c8SLinus Walleij }; 305c0e118c8SLinus Walleij MODULE_DEVICE_TABLE(of, mtd_parser_redboot_of_match_table); 306c0e118c8SLinus Walleij 30743f1fd01SLinus Walleij static struct mtd_part_parser redboot_parser = { 30843f1fd01SLinus Walleij .parse_fn = parse_redboot_partitions, 30943f1fd01SLinus Walleij .name = "RedBoot", 310c0e118c8SLinus Walleij .of_match_table = mtd_parser_redboot_of_match_table, 31143f1fd01SLinus Walleij }; 31243f1fd01SLinus Walleij module_mtd_part_parser(redboot_parser); 31343f1fd01SLinus Walleij 31443f1fd01SLinus Walleij /* mtd parsers will request the module by parser name */ 31543f1fd01SLinus Walleij MODULE_ALIAS("RedBoot"); 31643f1fd01SLinus Walleij MODULE_LICENSE("GPL"); 31743f1fd01SLinus Walleij MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 31843f1fd01SLinus Walleij MODULE_DESCRIPTION("Parsing code for RedBoot Flash Image System (FIS) tables"); 319