1 // SPDX-License-Identifier: GPL-2.0
3 * Parser for MikroTik RouterBoot partitions.
5 * Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License version 2 as published
9 * by the Free Software Foundation.
11 * This parser builds from the "fixed-partitions" one (see ofpart.c), but it can
12 * handle dynamic partitions as found on routerboot devices.
14 * DTS nodes are defined as follows:
15 * For fixed partitions:
16 * node-name@unit-address {
17 * reg = <prop-encoded-array>;
23 * reg property is mandatory; other properties are optional.
24 * reg format is <address length>. length can be 0 if the next partition is
25 * another fixed partition or a "well-known" partition as defined below: in that
26 * case the partition will extend up to the next one.
28 * For dynamic partitions:
30 * size = <prop-encoded-array>;
36 * size property is normally mandatory. It can only be omitted (or set to 0) if:
37 * - the partition is a "well-known" one (as defined below), in which case
38 * the partition size will be automatically adjusted; or
39 * - the next partition is a fixed one or a "well-known" one, in which case
40 * the current partition will extend up to the next one.
41 * Other properties are optional.
42 * size format is <length>.
43 * By default dynamic partitions are appended after the preceding one, except
44 * for "well-known" ones which are automatically located on flash.
46 * Well-known partitions (matched via label or node-name):
51 * Note: this parser will happily register 0-sized partitions if misused.
53 * This parser requires the DTS to list partitions in ascending order as
54 * expected on the MTD device.
56 * Since only the "hard_config" and "soft_config" partitions are used in OpenWRT,
57 * a minimal working DTS could define only these two partitions dynamically (in
58 * the right order, usually hard_config then soft_config).
60 * Note: some mips RB devices encode the hard_config offset and length in two
61 * consecutive u32 located at offset 0x14 (for ramips) or 0x24 (for ath79) on
62 * the SPI NOR flash. Unfortunately this seems inconsistent across machines and
63 * does not apply to e.g. ipq-based ones, so we ignore that information.
65 * Note: To find well-known partitions, this parser will go through the entire
66 * top mtd partition parsed, _before_ the DTS nodes are processed. This works
67 * well in the current state of affairs, and is a simpler implementation than
68 * searching for known partitions in the "holes" left between fixed-partition,
69 * _after_ processing DTS nodes.
72 #include <linux/module.h>
73 #include <linux/slab.h>
74 #include <linux/mtd/mtd.h>
75 #include <linux/mtd/partitions.h>
77 #include <linux/of_fdt.h>
78 #include <linux/libfdt_env.h>
79 #include <linux/string.h>
81 #define RB_MAGIC_HARD (('H') | ('a' << 8) | ('r' << 16) | ('d' << 24))
82 #define RB_MAGIC_SOFT (('S') | ('o' << 8) | ('f' << 16) | ('t' << 24))
83 #define RB_BLOCK_SIZE 0x1000
85 struct routerboot_dynpart
{
86 const char * const name
;
88 int (* const size_fixup
)(struct mtd_info
*, struct routerboot_dynpart
*);
94 static int routerboot_dtbsfixup(struct mtd_info
*, struct routerboot_dynpart
*);
96 static struct routerboot_dynpart rb_dynparts
[] = {
98 .name
= "hard_config",
99 .magic
= RB_MAGIC_HARD
, // stored in CPU-endianness on flash
102 .size
= RB_BLOCK_SIZE
,
105 .name
= "soft_config",
106 .magic
= RB_MAGIC_SOFT
, // stored in CPU-endianness on flash
109 .size
= RB_BLOCK_SIZE
,
112 .name
= "dtb_config",
113 .magic
= fdt32_to_cpu(OF_DT_HEADER
), // stored BE on flash
114 .size_fixup
= routerboot_dtbsfixup
,
121 static int routerboot_dtbsfixup(struct mtd_info
*master
, struct routerboot_dynpart
*rbdpart
)
124 size_t bytes_read
, psize
;
128 fdt32_t off_dt_struct
;
129 fdt32_t off_dt_strings
;
130 fdt32_t off_mem_rsvmap
;
132 fdt32_t last_comp_version
;
133 fdt32_t boot_cpuid_phys
;
134 fdt32_t size_dt_strings
;
135 fdt32_t size_dt_struct
;
138 err
= mtd_read(master
, rbdpart
->offset
, sizeof(fdt_header
),
139 &bytes_read
, (u8
*)&fdt_header
);
143 if (bytes_read
!= sizeof(fdt_header
))
146 psize
= fdt32_to_cpu(fdt_header
.totalsize
);
150 rbdpart
->size
= psize
;
154 static void routerboot_find_dynparts(struct mtd_info
*master
)
156 size_t bytes_read
, offset
;
162 * Dynamic RouterBoot partitions offsets are aligned to RB_BLOCK_SIZE:
163 * read the whole partition at RB_BLOCK_SIZE intervals to find sigs.
164 * Skip partition content when possible.
167 while (offset
< master
->size
) {
168 err
= mtd_read(master
, offset
, sizeof(buf
), &bytes_read
, (u8
*)&buf
);
170 pr_err("%s: mtd_read error while parsing (offset: 0x%X): %d\n",
171 master
->name
, offset
, err
);
177 for (i
= 0; i
< ARRAY_SIZE(rb_dynparts
); i
++) {
178 if (rb_dynparts
[i
].found
)
183 if (rb_dynparts
[i
].magic
== buf
) {
184 rb_dynparts
[i
].offset
= offset
;
186 if (rb_dynparts
[i
].size_fixup
) {
187 err
= rb_dynparts
[i
].size_fixup(master
, &rb_dynparts
[i
]);
189 pr_err("%s: size fixup error while parsing \"%s\": %d\n",
190 master
->name
, rb_dynparts
[i
].name
, err
);
195 rb_dynparts
[i
].found
= true;
198 * move offset to skip the whole partition on
199 * next iteration if size > RB_BLOCK_SIZE.
201 if (rb_dynparts
[i
].size
> RB_BLOCK_SIZE
)
202 offset
+= ALIGN_DOWN((rb_dynparts
[i
].size
- RB_BLOCK_SIZE
), RB_BLOCK_SIZE
);
208 offset
+= RB_BLOCK_SIZE
;
215 static int routerboot_partitions_parse(struct mtd_info
*master
,
216 const struct mtd_partition
**pparts
,
217 struct mtd_part_parser_data
*data
)
219 struct device_node
*rbpart_node
, *pp
;
220 struct mtd_partition
*parts
;
221 const char *partname
;
225 /* Pull of_node from the master device node */
226 rbpart_node
= mtd_get_of_node(master
);
230 /* First count the subnodes */
232 for_each_child_of_node(rbpart_node
, pp
)
238 parts
= kcalloc(np
, sizeof(*parts
), GFP_KERNEL
);
242 /* Preemptively look for known parts in flash */
243 routerboot_find_dynparts(master
);
247 for_each_child_of_node(rbpart_node
, pp
) {
248 const __be32
*reg
, *sz
;
250 int i
, len
, a_cells
, s_cells
;
252 partname
= of_get_property(pp
, "label", &len
);
253 /* Allow deprecated use of "name" instead of "label" */
255 partname
= of_get_property(pp
, "name", &len
);
256 /* Fallback to node name per spec if all else fails: partname is always set */
259 parts
[np
].name
= partname
;
261 reg
= of_get_property(pp
, "reg", &len
);
263 /* Fixed partition */
264 a_cells
= of_n_addr_cells(pp
);
265 s_cells
= of_n_size_cells(pp
);
267 if ((len
/ 4) != (a_cells
+ s_cells
)) {
268 pr_debug("%s: routerboot partition %pOF (%pOF) error parsing reg property.\n",
269 master
->name
, pp
, rbpart_node
);
273 offset
= of_read_number(reg
, a_cells
);
274 size
= of_read_number(reg
+ a_cells
, s_cells
);
276 /* Dynamic partition */
277 /* Default: part starts at current offset, 0 size */
281 /* Check if well-known partition */
282 for (i
= 0; i
< ARRAY_SIZE(rb_dynparts
); i
++) {
283 if (!strcmp(partname
, rb_dynparts
[i
].name
) && rb_dynparts
[i
].found
) {
284 offset
= rb_dynparts
[i
].offset
;
285 size
= rb_dynparts
[i
].size
;
290 /* Standalone 'size' property? Override size */
291 sz
= of_get_property(pp
, "size", &len
);
293 s_cells
= of_n_size_cells(pp
);
294 if ((len
/ 4) != s_cells
) {
295 pr_debug("%s: routerboot partition %pOF (%pOF) error parsing size property.\n",
296 master
->name
, pp
, rbpart_node
);
300 size
= of_read_number(sz
, s_cells
);
305 /* Minor sanity check for overlaps */
306 if (offset
< (parts
[np
-1].offset
+ parts
[np
-1].size
)) {
307 pr_err("%s: routerboot partition %pOF (%pOF) \"%s\" overlaps with previous partition \"%s\".\n",
308 master
->name
, pp
, rbpart_node
,
309 partname
, parts
[np
-1].name
);
313 /* Fixup end of previous partition if necessary */
314 if (!parts
[np
-1].size
)
315 parts
[np
-1].size
= (offset
- parts
[np
-1].offset
);
318 if ((offset
+ size
) > master
->size
) {
319 pr_err("%s: routerboot partition %pOF (%pOF) \"%s\" extends past end of segment.\n",
320 master
->name
, pp
, rbpart_node
, partname
);
324 parts
[np
].offset
= offset
;
325 parts
[np
].size
= size
;
326 parts
[np
].of_node
= pp
;
328 if (of_get_property(pp
, "read-only", &len
))
329 parts
[np
].mask_flags
|= MTD_WRITEABLE
;
331 if (of_get_property(pp
, "lock", &len
))
332 parts
[np
].mask_flags
|= MTD_POWERUP_LOCK
;
334 /* Keep master offset aligned to RB_BLOCK_SIZE */
335 master_ofs
= ALIGN(offset
+ size
, RB_BLOCK_SIZE
);
343 pr_err("%s: error parsing routerboot partition %pOF (%pOF)\n",
344 master
->name
, pp
, rbpart_node
);
350 static const struct of_device_id parse_routerbootpart_match_table
[] = {
351 { .compatible
= "mikrotik,routerboot-partitions" },
354 MODULE_DEVICE_TABLE(of
, parse_routerbootpart_match_table
);
356 static struct mtd_part_parser routerbootpart_parser
= {
357 .parse_fn
= routerboot_partitions_parse
,
358 .name
= "routerbootpart",
359 .of_match_table
= parse_routerbootpart_match_table
,
361 module_mtd_part_parser(routerbootpart_parser
);
363 MODULE_LICENSE("GPL v2");
364 MODULE_DESCRIPTION("MTD partitioning for RouterBoot");
365 MODULE_AUTHOR("Thibaut VARENE");