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