| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 2 | * $Id: cmdlinepart.c,v 1.19 2005/11/07 11:14:19 gleixner Exp $ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 3 | * | 
|  | 4 | * Read flash partition table from command line | 
|  | 5 | * | 
|  | 6 | * Copyright 2002 SYSGO Real-Time Solutions GmbH | 
|  | 7 | * | 
|  | 8 | * The format for the command line is as follows: | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 9 | * | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 10 | * mtdparts=<mtddef>[;<mtddef] | 
|  | 11 | * <mtddef>  := <mtd-id>:<partdef>[,<partdef>] | 
|  | 12 | * <partdef> := <size>[@offset][<name>][ro] | 
|  | 13 | * <mtd-id>  := unique name used in mapping driver/device (mtd->name) | 
|  | 14 | * <size>    := standard linux memsize OR "-" to denote all remaining space | 
|  | 15 | * <name>    := '(' NAME ')' | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 16 | * | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 17 | * Examples: | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 18 | * | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 19 | * 1 NOR Flash, with 1 single writable partition: | 
|  | 20 | * edb7312-nor:- | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 21 | * | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 22 | * 1 NOR Flash with 2 partitions, 1 NAND with one | 
|  | 23 | * edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home) | 
|  | 24 | */ | 
|  | 25 |  | 
|  | 26 | #include <linux/kernel.h> | 
|  | 27 | #include <linux/slab.h> | 
|  | 28 |  | 
|  | 29 | #include <linux/mtd/mtd.h> | 
|  | 30 | #include <linux/mtd/partitions.h> | 
|  | 31 | #include <linux/bootmem.h> | 
|  | 32 |  | 
|  | 33 | /* error message prefix */ | 
|  | 34 | #define ERRP "mtd: " | 
|  | 35 |  | 
|  | 36 | /* debug macro */ | 
|  | 37 | #if 0 | 
|  | 38 | #define dbg(x) do { printk("DEBUG-CMDLINE-PART: "); printk x; } while(0) | 
|  | 39 | #else | 
|  | 40 | #define dbg(x) | 
|  | 41 | #endif | 
|  | 42 |  | 
|  | 43 |  | 
|  | 44 | /* special size referring to all the remaining space in a partition */ | 
| Atsushi Nemoto | b175d03 | 2006-03-31 02:29:46 -0800 | [diff] [blame] | 45 | #define SIZE_REMAINING UINT_MAX | 
|  | 46 | #define OFFSET_CONTINUOUS UINT_MAX | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 47 |  | 
|  | 48 | struct cmdline_mtd_partition { | 
|  | 49 | struct cmdline_mtd_partition *next; | 
|  | 50 | char *mtd_id; | 
|  | 51 | int num_parts; | 
|  | 52 | struct mtd_partition *parts; | 
|  | 53 | }; | 
|  | 54 |  | 
|  | 55 | /* mtdpart_setup() parses into here */ | 
|  | 56 | static struct cmdline_mtd_partition *partitions; | 
|  | 57 |  | 
|  | 58 | /* the command line passed to mtdpart_setupd() */ | 
|  | 59 | static char *cmdline; | 
|  | 60 | static int cmdline_parsed = 0; | 
|  | 61 |  | 
|  | 62 | /* | 
|  | 63 | * Parse one partition definition for an MTD. Since there can be many | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 64 | * comma separated partition definitions, this function calls itself | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 65 | * recursively until no more partition definitions are found. Nice side | 
|  | 66 | * effect: the memory to keep the mtd_partition structs and the names | 
|  | 67 | * is allocated upon the last definition being found. At that point the | 
|  | 68 | * syntax has been verified ok. | 
|  | 69 | */ | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 70 | static struct mtd_partition * newpart(char *s, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 71 | char **retptr, | 
|  | 72 | int *num_parts, | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 73 | int this_part, | 
|  | 74 | unsigned char **extra_mem_ptr, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 75 | int extra_mem_size) | 
|  | 76 | { | 
|  | 77 | struct mtd_partition *parts; | 
|  | 78 | unsigned long size; | 
| Atsushi Nemoto | b175d03 | 2006-03-31 02:29:46 -0800 | [diff] [blame] | 79 | unsigned long offset = OFFSET_CONTINUOUS; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 80 | char *name; | 
|  | 81 | int name_len; | 
|  | 82 | unsigned char *extra_mem; | 
|  | 83 | char delim; | 
|  | 84 | unsigned int mask_flags; | 
|  | 85 |  | 
|  | 86 | /* fetch the partition size */ | 
|  | 87 | if (*s == '-') | 
|  | 88 | {	/* assign all remaining space to this partition */ | 
|  | 89 | size = SIZE_REMAINING; | 
|  | 90 | s++; | 
|  | 91 | } | 
|  | 92 | else | 
|  | 93 | { | 
|  | 94 | size = memparse(s, &s); | 
|  | 95 | if (size < PAGE_SIZE) | 
|  | 96 | { | 
|  | 97 | printk(KERN_ERR ERRP "partition size too small (%lx)\n", size); | 
|  | 98 | return NULL; | 
|  | 99 | } | 
|  | 100 | } | 
|  | 101 |  | 
|  | 102 | /* fetch partition name and flags */ | 
|  | 103 | mask_flags = 0; /* this is going to be a regular partition */ | 
|  | 104 | delim = 0; | 
|  | 105 | /* check for offset */ | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 106 | if (*s == '@') | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 107 | { | 
|  | 108 | s++; | 
|  | 109 | offset = memparse(s, &s); | 
|  | 110 | } | 
|  | 111 | /* now look for name */ | 
|  | 112 | if (*s == '(') | 
|  | 113 | { | 
|  | 114 | delim = ')'; | 
|  | 115 | } | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 116 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 117 | if (delim) | 
|  | 118 | { | 
|  | 119 | char *p; | 
|  | 120 |  | 
|  | 121 | name = ++s; | 
|  | 122 | if ((p = strchr(name, delim)) == 0) | 
|  | 123 | { | 
|  | 124 | printk(KERN_ERR ERRP "no closing %c found in partition name\n", delim); | 
|  | 125 | return NULL; | 
|  | 126 | } | 
|  | 127 | name_len = p - name; | 
|  | 128 | s = p + 1; | 
|  | 129 | } | 
|  | 130 | else | 
|  | 131 | { | 
|  | 132 | name = NULL; | 
|  | 133 | name_len = 13; /* Partition_000 */ | 
|  | 134 | } | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 135 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 136 | /* record name length for memory allocation later */ | 
|  | 137 | extra_mem_size += name_len + 1; | 
|  | 138 |  | 
|  | 139 | /* test for options */ | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 140 | if (strncmp(s, "ro", 2) == 0) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 141 | { | 
|  | 142 | mask_flags |= MTD_WRITEABLE; | 
|  | 143 | s += 2; | 
|  | 144 | } | 
|  | 145 |  | 
|  | 146 | /* test if more partitions are following */ | 
|  | 147 | if (*s == ',') | 
|  | 148 | { | 
|  | 149 | if (size == SIZE_REMAINING) | 
|  | 150 | { | 
|  | 151 | printk(KERN_ERR ERRP "no partitions allowed after a fill-up partition\n"); | 
|  | 152 | return NULL; | 
|  | 153 | } | 
|  | 154 | /* more partitions follow, parse them */ | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 155 | if ((parts = newpart(s + 1, &s, num_parts, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 156 | this_part + 1, &extra_mem, extra_mem_size)) == 0) | 
|  | 157 | return NULL; | 
|  | 158 | } | 
|  | 159 | else | 
|  | 160 | {	/* this is the last partition: allocate space for all */ | 
|  | 161 | int alloc_size; | 
|  | 162 |  | 
|  | 163 | *num_parts = this_part + 1; | 
|  | 164 | alloc_size = *num_parts * sizeof(struct mtd_partition) + | 
|  | 165 | extra_mem_size; | 
|  | 166 | parts = kmalloc(alloc_size, GFP_KERNEL); | 
|  | 167 | if (!parts) | 
|  | 168 | { | 
|  | 169 | printk(KERN_ERR ERRP "out of memory\n"); | 
|  | 170 | return NULL; | 
|  | 171 | } | 
|  | 172 | memset(parts, 0, alloc_size); | 
|  | 173 | extra_mem = (unsigned char *)(parts + *num_parts); | 
|  | 174 | } | 
|  | 175 | /* enter this partition (offset will be calculated later if it is zero at this point) */ | 
|  | 176 | parts[this_part].size = size; | 
|  | 177 | parts[this_part].offset = offset; | 
|  | 178 | parts[this_part].mask_flags = mask_flags; | 
|  | 179 | if (name) | 
|  | 180 | { | 
|  | 181 | strlcpy(extra_mem, name, name_len + 1); | 
|  | 182 | } | 
|  | 183 | else | 
|  | 184 | { | 
|  | 185 | sprintf(extra_mem, "Partition_%03d", this_part); | 
|  | 186 | } | 
|  | 187 | parts[this_part].name = extra_mem; | 
|  | 188 | extra_mem += name_len + 1; | 
|  | 189 |  | 
|  | 190 | dbg(("partition %d: name <%s>, offset %x, size %x, mask flags %x\n", | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 191 | this_part, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 192 | parts[this_part].name, | 
|  | 193 | parts[this_part].offset, | 
|  | 194 | parts[this_part].size, | 
|  | 195 | parts[this_part].mask_flags)); | 
|  | 196 |  | 
|  | 197 | /* return (updated) pointer to extra_mem memory */ | 
|  | 198 | if (extra_mem_ptr) | 
|  | 199 | *extra_mem_ptr = extra_mem; | 
|  | 200 |  | 
|  | 201 | /* return (updated) pointer command line string */ | 
|  | 202 | *retptr = s; | 
|  | 203 |  | 
|  | 204 | /* return partition table */ | 
|  | 205 | return parts; | 
|  | 206 | } | 
|  | 207 |  | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 208 | /* | 
|  | 209 | * Parse the command line. | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 210 | */ | 
|  | 211 | static int mtdpart_setup_real(char *s) | 
|  | 212 | { | 
|  | 213 | cmdline_parsed = 1; | 
|  | 214 |  | 
|  | 215 | for( ; s != NULL; ) | 
|  | 216 | { | 
|  | 217 | struct cmdline_mtd_partition *this_mtd; | 
|  | 218 | struct mtd_partition *parts; | 
|  | 219 | int mtd_id_len; | 
|  | 220 | int num_parts; | 
|  | 221 | char *p, *mtd_id; | 
|  | 222 |  | 
|  | 223 | mtd_id = s; | 
|  | 224 | /* fetch <mtd-id> */ | 
|  | 225 | if (!(p = strchr(s, ':'))) | 
|  | 226 | { | 
|  | 227 | printk(KERN_ERR ERRP "no mtd-id\n"); | 
|  | 228 | return 0; | 
|  | 229 | } | 
|  | 230 | mtd_id_len = p - mtd_id; | 
|  | 231 |  | 
|  | 232 | dbg(("parsing <%s>\n", p+1)); | 
|  | 233 |  | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 234 | /* | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 235 | * parse one mtd. have it reserve memory for the | 
|  | 236 | * struct cmdline_mtd_partition and the mtd-id string. | 
|  | 237 | */ | 
|  | 238 | parts = newpart(p + 1,		/* cmdline */ | 
|  | 239 | &s,		/* out: updated cmdline ptr */ | 
|  | 240 | &num_parts,	/* out: number of parts */ | 
|  | 241 | 0,		/* first partition */ | 
|  | 242 | (unsigned char**)&this_mtd, /* out: extra mem */ | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 243 | mtd_id_len + 1 + sizeof(*this_mtd) + | 
| Joern Engel | be76c5f | 2005-06-07 16:04:29 +0100 | [diff] [blame] | 244 | sizeof(void*)-1 /*alignment*/); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 245 | if(!parts) | 
|  | 246 | { | 
|  | 247 | /* | 
|  | 248 | * An error occurred. We're either: | 
|  | 249 | * a) out of memory, or | 
|  | 250 | * b) in the middle of the partition spec | 
|  | 251 | * Either way, this mtd is hosed and we're | 
|  | 252 | * unlikely to succeed in parsing any more | 
|  | 253 | */ | 
|  | 254 | return 0; | 
|  | 255 | } | 
|  | 256 |  | 
| Joern Engel | be76c5f | 2005-06-07 16:04:29 +0100 | [diff] [blame] | 257 | /* align this_mtd */ | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 258 | this_mtd = (struct cmdline_mtd_partition *) | 
| Joern Engel | be76c5f | 2005-06-07 16:04:29 +0100 | [diff] [blame] | 259 | ALIGN((unsigned long)this_mtd, sizeof(void*)); | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 260 | /* enter results */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 261 | this_mtd->parts = parts; | 
|  | 262 | this_mtd->num_parts = num_parts; | 
|  | 263 | this_mtd->mtd_id = (char*)(this_mtd + 1); | 
|  | 264 | strlcpy(this_mtd->mtd_id, mtd_id, mtd_id_len + 1); | 
|  | 265 |  | 
|  | 266 | /* link into chain */ | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 267 | this_mtd->next = partitions; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 268 | partitions = this_mtd; | 
|  | 269 |  | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 270 | dbg(("mtdid=<%s> num_parts=<%d>\n", | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 271 | this_mtd->mtd_id, this_mtd->num_parts)); | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 272 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 273 |  | 
|  | 274 | /* EOS - we're done */ | 
|  | 275 | if (*s == 0) | 
|  | 276 | break; | 
|  | 277 |  | 
|  | 278 | /* does another spec follow? */ | 
|  | 279 | if (*s != ';') | 
|  | 280 | { | 
|  | 281 | printk(KERN_ERR ERRP "bad character after partition (%c)\n", *s); | 
|  | 282 | return 0; | 
|  | 283 | } | 
|  | 284 | s++; | 
|  | 285 | } | 
|  | 286 | return 1; | 
|  | 287 | } | 
|  | 288 |  | 
|  | 289 | /* | 
|  | 290 | * Main function to be called from the MTD mapping driver/device to | 
|  | 291 | * obtain the partitioning information. At this point the command line | 
|  | 292 | * arguments will actually be parsed and turned to struct mtd_partition | 
|  | 293 | * information. It returns partitions for the requested mtd device, or | 
|  | 294 | * the first one in the chain if a NULL mtd_id is passed in. | 
|  | 295 | */ | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 296 | static int parse_cmdline_partitions(struct mtd_info *master, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 297 | struct mtd_partition **pparts, | 
|  | 298 | unsigned long origin) | 
|  | 299 | { | 
|  | 300 | unsigned long offset; | 
|  | 301 | int i; | 
|  | 302 | struct cmdline_mtd_partition *part; | 
|  | 303 | char *mtd_id = master->name; | 
|  | 304 |  | 
|  | 305 | if(!cmdline) | 
|  | 306 | return -EINVAL; | 
|  | 307 |  | 
|  | 308 | /* parse command line */ | 
|  | 309 | if (!cmdline_parsed) | 
|  | 310 | mtdpart_setup_real(cmdline); | 
|  | 311 |  | 
|  | 312 | for(part = partitions; part; part = part->next) | 
|  | 313 | { | 
|  | 314 | if ((!mtd_id) || (!strcmp(part->mtd_id, mtd_id))) | 
|  | 315 | { | 
|  | 316 | for(i = 0, offset = 0; i < part->num_parts; i++) | 
|  | 317 | { | 
| Atsushi Nemoto | b175d03 | 2006-03-31 02:29:46 -0800 | [diff] [blame] | 318 | if (part->parts[i].offset == OFFSET_CONTINUOUS) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 319 | part->parts[i].offset = offset; | 
|  | 320 | else | 
|  | 321 | offset = part->parts[i].offset; | 
|  | 322 | if (part->parts[i].size == SIZE_REMAINING) | 
|  | 323 | part->parts[i].size = master->size - offset; | 
|  | 324 | if (offset + part->parts[i].size > master->size) | 
|  | 325 | { | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 326 | printk(KERN_WARNING ERRP | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 327 | "%s: partitioning exceeds flash size, truncating\n", | 
|  | 328 | part->mtd_id); | 
|  | 329 | part->parts[i].size = master->size - offset; | 
|  | 330 | part->num_parts = i; | 
|  | 331 | } | 
|  | 332 | offset += part->parts[i].size; | 
|  | 333 | } | 
|  | 334 | *pparts = part->parts; | 
|  | 335 | return part->num_parts; | 
|  | 336 | } | 
|  | 337 | } | 
|  | 338 | return -EINVAL; | 
|  | 339 | } | 
|  | 340 |  | 
|  | 341 |  | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 342 | /* | 
|  | 343 | * This is the handler for our kernel parameter, called from | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 344 | * main.c::checksetup(). Note that we can not yet kmalloc() anything, | 
|  | 345 | * so we only save the commandline for later processing. | 
|  | 346 | * | 
|  | 347 | * This function needs to be visible for bootloaders. | 
|  | 348 | */ | 
|  | 349 | int mtdpart_setup(char *s) | 
|  | 350 | { | 
|  | 351 | cmdline = s; | 
|  | 352 | return 1; | 
|  | 353 | } | 
|  | 354 |  | 
|  | 355 | __setup("mtdparts=", mtdpart_setup); | 
|  | 356 |  | 
|  | 357 | static struct mtd_part_parser cmdline_parser = { | 
|  | 358 | .owner = THIS_MODULE, | 
|  | 359 | .parse_fn = parse_cmdline_partitions, | 
|  | 360 | .name = "cmdlinepart", | 
|  | 361 | }; | 
|  | 362 |  | 
|  | 363 | static int __init cmdline_parser_init(void) | 
|  | 364 | { | 
|  | 365 | return register_mtd_parser(&cmdline_parser); | 
|  | 366 | } | 
|  | 367 |  | 
|  | 368 | module_init(cmdline_parser_init); | 
|  | 369 |  | 
|  | 370 | MODULE_LICENSE("GPL"); | 
|  | 371 | MODULE_AUTHOR("Marius Groeger <mag@sysgo.de>"); | 
|  | 372 | MODULE_DESCRIPTION("Command line configuration of MTD partitions"); |