1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Parse RedBoot-style Flash Image System (FIS) tables and 4 * produce a Linux partition array to match. 5 * 6 * Copyright © 2001 Red Hat UK Limited 7 * Copyright © 2001-2010 David Woodhouse <dwmw2@infradead.org> 8 */ 9 10 #include <linux/kernel.h> 11 #include <linux/slab.h> 12 #include <linux/init.h> 13 #include <linux/vmalloc.h> 14 #include <linux/of.h> 15 #include <linux/mtd/mtd.h> 16 #include <linux/mtd/partitions.h> 17 #include <linux/module.h> 18 19 struct fis_image_desc { 20 unsigned char name[16]; // Null terminated name 21 uint32_t flash_base; // Address within FLASH of image 22 uint32_t mem_base; // Address in memory where it executes 23 uint32_t size; // Length of image 24 uint32_t entry_point; // Execution entry point 25 uint32_t data_length; // Length of actual data 26 unsigned char _pad[256-(16+7*sizeof(uint32_t))]; 27 uint32_t desc_cksum; // Checksum over image descriptor 28 uint32_t file_cksum; // Checksum over image data 29 }; 30 31 struct fis_list { 32 struct fis_image_desc *img; 33 struct fis_list *next; 34 }; 35 36 static int directory = CONFIG_MTD_REDBOOT_DIRECTORY_BLOCK; 37 module_param(directory, int, 0); 38 39 static inline int redboot_checksum(struct fis_image_desc *img) 40 { 41 /* RedBoot doesn't actually write the desc_cksum field yet AFAICT */ 42 return 1; 43 } 44 45 static void parse_redboot_of(struct mtd_info *master) 46 { 47 struct device_node *np; 48 u32 dirblock; 49 int ret; 50 51 np = mtd_get_of_node(master); 52 if (!np) 53 return; 54 55 ret = of_property_read_u32(np, "fis-index-block", &dirblock); 56 if (ret) 57 return; 58 59 /* 60 * Assign the block found in the device tree to the local 61 * directory block pointer. 62 */ 63 directory = dirblock; 64 } 65 66 static int parse_redboot_partitions(struct mtd_info *master, 67 const struct mtd_partition **pparts, 68 struct mtd_part_parser_data *data) 69 { 70 int nrparts = 0; 71 struct fis_image_desc *buf; 72 struct mtd_partition *parts; 73 struct fis_list *fl = NULL, *tmp_fl; 74 int ret, i; 75 size_t retlen; 76 char *names; 77 char *nullname; 78 int namelen = 0; 79 int nulllen = 0; 80 int numslots; 81 unsigned long offset; 82 #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED 83 static char nullstring[] = "unallocated"; 84 #endif 85 86 parse_redboot_of(master); 87 88 if ( directory < 0 ) { 89 offset = master->size + directory * master->erasesize; 90 while (mtd_block_isbad(master, offset)) { 91 if (!offset) { 92 nogood: 93 printk(KERN_NOTICE "Failed to find a non-bad block to check for RedBoot partition table\n"); 94 return -EIO; 95 } 96 offset -= master->erasesize; 97 } 98 } else { 99 offset = directory * master->erasesize; 100 while (mtd_block_isbad(master, offset)) { 101 offset += master->erasesize; 102 if (offset == master->size) 103 goto nogood; 104 } 105 } 106 buf = vmalloc(master->erasesize); 107 108 if (!buf) 109 return -ENOMEM; 110 111 printk(KERN_NOTICE "Searching for RedBoot partition table in %s at offset 0x%lx\n", 112 master->name, offset); 113 114 ret = mtd_read(master, offset, master->erasesize, &retlen, 115 (void *)buf); 116 117 if (ret) 118 goto out; 119 120 if (retlen != master->erasesize) { 121 ret = -EIO; 122 goto out; 123 } 124 125 numslots = (master->erasesize / sizeof(struct fis_image_desc)); 126 for (i = 0; i < numslots; i++) { 127 if (!memcmp(buf[i].name, "FIS directory", 14)) { 128 /* This is apparently the FIS directory entry for the 129 * FIS directory itself. The FIS directory size is 130 * one erase block; if the buf[i].size field is 131 * swab32(erasesize) then we know we are looking at 132 * a byte swapped FIS directory - swap all the entries! 133 * (NOTE: this is 'size' not 'data_length'; size is 134 * the full size of the entry.) 135 */ 136 137 /* RedBoot can combine the FIS directory and 138 config partitions into a single eraseblock; 139 we assume wrong-endian if either the swapped 140 'size' matches the eraseblock size precisely, 141 or if the swapped size actually fits in an 142 eraseblock while the unswapped size doesn't. */ 143 if (swab32(buf[i].size) == master->erasesize || 144 (buf[i].size > master->erasesize 145 && swab32(buf[i].size) < master->erasesize)) { 146 int j; 147 /* Update numslots based on actual FIS directory size */ 148 numslots = swab32(buf[i].size) / sizeof (struct fis_image_desc); 149 for (j = 0; j < numslots; ++j) { 150 151 /* A single 0xff denotes a deleted entry. 152 * Two of them in a row is the end of the table. 153 */ 154 if (buf[j].name[0] == 0xff) { 155 if (buf[j].name[1] == 0xff) { 156 break; 157 } else { 158 continue; 159 } 160 } 161 162 /* The unsigned long fields were written with the 163 * wrong byte sex, name and pad have no byte sex. 164 */ 165 swab32s(&buf[j].flash_base); 166 swab32s(&buf[j].mem_base); 167 swab32s(&buf[j].size); 168 swab32s(&buf[j].entry_point); 169 swab32s(&buf[j].data_length); 170 swab32s(&buf[j].desc_cksum); 171 swab32s(&buf[j].file_cksum); 172 } 173 } else if (buf[i].size < master->erasesize) { 174 /* Update numslots based on actual FIS directory size */ 175 numslots = buf[i].size / sizeof(struct fis_image_desc); 176 } 177 break; 178 } 179 } 180 if (i == numslots) { 181 /* Didn't find it */ 182 printk(KERN_NOTICE "No RedBoot partition table detected in %s\n", 183 master->name); 184 ret = 0; 185 goto out; 186 } 187 188 for (i = 0; i < numslots; i++) { 189 struct fis_list *new_fl, **prev; 190 191 if (buf[i].name[0] == 0xff) { 192 if (buf[i].name[1] == 0xff) { 193 break; 194 } else { 195 continue; 196 } 197 } 198 if (!redboot_checksum(&buf[i])) 199 break; 200 201 new_fl = kmalloc(sizeof(struct fis_list), GFP_KERNEL); 202 namelen += strlen(buf[i].name)+1; 203 if (!new_fl) { 204 ret = -ENOMEM; 205 goto out; 206 } 207 new_fl->img = &buf[i]; 208 if (data && data->origin) 209 buf[i].flash_base -= data->origin; 210 else 211 buf[i].flash_base &= master->size-1; 212 213 /* I'm sure the JFFS2 code has done me permanent damage. 214 * I now think the following is _normal_ 215 */ 216 prev = &fl; 217 while(*prev && (*prev)->img->flash_base < new_fl->img->flash_base) 218 prev = &(*prev)->next; 219 new_fl->next = *prev; 220 *prev = new_fl; 221 222 nrparts++; 223 } 224 #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED 225 if (fl->img->flash_base) { 226 nrparts++; 227 nulllen = sizeof(nullstring); 228 } 229 230 for (tmp_fl = fl; tmp_fl->next; tmp_fl = tmp_fl->next) { 231 if (tmp_fl->img->flash_base + tmp_fl->img->size + master->erasesize <= tmp_fl->next->img->flash_base) { 232 nrparts++; 233 nulllen = sizeof(nullstring); 234 } 235 } 236 #endif 237 parts = kzalloc(sizeof(*parts)*nrparts + nulllen + namelen, GFP_KERNEL); 238 239 if (!parts) { 240 ret = -ENOMEM; 241 goto out; 242 } 243 244 nullname = (char *)&parts[nrparts]; 245 #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED 246 if (nulllen > 0) { 247 strcpy(nullname, nullstring); 248 } 249 #endif 250 names = nullname + nulllen; 251 252 i=0; 253 254 #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED 255 if (fl->img->flash_base) { 256 parts[0].name = nullname; 257 parts[0].size = fl->img->flash_base; 258 parts[0].offset = 0; 259 i++; 260 } 261 #endif 262 for ( ; i<nrparts; i++) { 263 parts[i].size = fl->img->size; 264 parts[i].offset = fl->img->flash_base; 265 parts[i].name = names; 266 267 strcpy(names, fl->img->name); 268 #ifdef CONFIG_MTD_REDBOOT_PARTS_READONLY 269 if (!memcmp(names, "RedBoot", 8) || 270 !memcmp(names, "RedBoot config", 15) || 271 !memcmp(names, "FIS directory", 14)) { 272 parts[i].mask_flags = MTD_WRITEABLE; 273 } 274 #endif 275 names += strlen(names)+1; 276 277 #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED 278 if(fl->next && fl->img->flash_base + fl->img->size + master->erasesize <= fl->next->img->flash_base) { 279 i++; 280 parts[i].offset = parts[i-1].size + parts[i-1].offset; 281 parts[i].size = fl->next->img->flash_base - parts[i].offset; 282 parts[i].name = nullname; 283 } 284 #endif 285 tmp_fl = fl; 286 fl = fl->next; 287 kfree(tmp_fl); 288 } 289 ret = nrparts; 290 *pparts = parts; 291 out: 292 while (fl) { 293 struct fis_list *old = fl; 294 fl = fl->next; 295 kfree(old); 296 } 297 vfree(buf); 298 return ret; 299 } 300 301 static const struct of_device_id mtd_parser_redboot_of_match_table[] = { 302 { .compatible = "redboot-fis" }, 303 {}, 304 }; 305 MODULE_DEVICE_TABLE(of, mtd_parser_redboot_of_match_table); 306 307 static struct mtd_part_parser redboot_parser = { 308 .parse_fn = parse_redboot_partitions, 309 .name = "RedBoot", 310 .of_match_table = mtd_parser_redboot_of_match_table, 311 }; 312 module_mtd_part_parser(redboot_parser); 313 314 /* mtd parsers will request the module by parser name */ 315 MODULE_ALIAS("RedBoot"); 316 MODULE_LICENSE("GPL"); 317 MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 318 MODULE_DESCRIPTION("Parsing code for RedBoot Flash Image System (FIS) tables"); 319