aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/generic/files/drivers/mtd/parsers/routerbootpart.c
blob: f9bba0f3ba15ecc3c033a046728236be3202f970 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Parser for MikroTik RouterBoot partitions.
 *
 * Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation.
 *
 * This parser builds from the "fixed-partitions" one (see ofpart.c), but it can
 * handle dynamic partitions as found on routerboot devices.
 *
 * DTS nodes are defined as follows:
 * For fixed partitions:
 *	node-name@unit-address {
 *		reg = <prop-encoded-array>;
 *		label = <string>;
 *		read-only;
 *		lock;
 *	};
 *
 * reg property is mandatory; other properties are optional.
 * reg format is <address length>. length can be 0 if the next partition is
 * another fixed partition or a "well-known" partition as defined below: in that
 * case the partition will extend up to the next one.
 *
 * For dynamic partitions:
 *	node-name {
 *		size = <prop-encoded-array>;
 *		label = <string>;
 *		read-only;
 *		lock;
 *	};
 *
 * size property is normally mandatory. It can only be omitted (or set to 0) if:
 *	- the partition is a "well-known" one (as defined below), in which case
 *	  the partition size will be automatically adjusted; or
 *	- the next partition is a fixed one or a "well-known" one, in which case
 *	  the current partition will extend up to the next one.
 * Other properties are optional.
 * size format is <length>.
 * By default dynamic partitions are appended after the preceding one, except
 * for "well-known" ones which are automatically located on flash.
 *
 * Well-known partitions (matched via label or node-name):
 * - "hard_config"
 * - "soft_config"
 * - "dtb_config"
 *
 * Note: this parser will happily register 0-sized partitions if misused.
 *
 * This parser requires the DTS to list partitions in ascending order as
 * expected on the MTD device.
 *
 * Since only the "hard_config" and "soft_config" partitions are used in OpenWRT,
 * a minimal working DTS could define only these two partitions dynamically (in
 * the right order, usually hard_config then soft_config).
 *
 * Note: some mips RB devices encode the hard_config offset and length in two
 * consecutive u32 located at offset 0x14 (for ramips) or 0x24 (for ath79) on
 * the SPI NOR flash. Unfortunately this seems inconsistent across machines and
 * does not apply to e.g. ipq-based ones, so we ignore that information.
 *
 * Note: To find well-known partitions, this parser will go through the entire
 * top mtd partition parsed, _before_ the DTS nodes are processed. This works
 * well in the current state of affairs, and is a simpler implementation than
 * searching for known partitions in the "holes" left between fixed-partition,
 * _after_ processing DTS nodes.
 */

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/libfdt_env.h>
#include <linux/string.h>

#define RB_MAGIC_HARD	(('H') | ('a' << 8) | ('r' << 16) | ('d' << 24))
#define RB_MAGIC_SOFT	(('S') | ('o' << 8) | ('f' << 16) | ('t' << 24))
#define RB_BLOCK_SIZE	0x1000

struct routerboot_dynpart {
	const char * const name;
	const u32 magic;
	int (* const size_fixup)(struct mtd_info *, struct routerboot_dynpart *);
	size_t offset;
	size_t size;
	bool found;
};

static int routerboot_dtbsfixup(struct mtd_info *, struct routerboot_dynpart *);

static struct routerboot_dynpart rb_dynparts[] = {
	{
		.name = "hard_config",
		.magic = RB_MAGIC_HARD,	// stored in CPU-endianness on flash
		.size_fixup = NULL,
		.offset = 0x0,
		.size = RB_BLOCK_SIZE,
		.found = false,
	}, {
		.name = "soft_config",
		.magic = RB_MAGIC_SOFT,	// stored in CPU-endianness on flash
		.size_fixup = NULL,
		.offset = 0x0,
		.size = RB_BLOCK_SIZE,
		.found = false,
	}, {
		.name = "dtb_config",
		.magic = fdt32_to_cpu(OF_DT_HEADER),	// stored BE on flash
		.size_fixup = routerboot_dtbsfixup,
		.offset = 0x0,
		.size = 0x0,
		.found = false,
	}
};

static int routerboot_dtbsfixup(struct mtd_info *master, struct routerboot_dynpart *rbdpart)
{
	int err;
	size_t bytes_read, psize;
	struct {
		fdt32_t magic;
		fdt32_t totalsize;
		fdt32_t off_dt_struct;
		fdt32_t off_dt_strings;
		fdt32_t off_mem_rsvmap;
		fdt32_t version;
		fdt32_t last_comp_version;
		fdt32_t boot_cpuid_phys;
		fdt32_t size_dt_strings;
		fdt32_t size_dt_struct;
	} fdt_header;

	err = mtd_read(master, rbdpart->offset, sizeof(fdt_header),
		       &bytes_read, (u8 *)&fdt_header);
	if (err)
		return err;

	if (bytes_read != sizeof(fdt_header))
		return -EIO;

	psize = fdt32_to_cpu(fdt_header.totalsize);
	if (!psize)
		return -EINVAL;

	rbdpart->size = psize;
	return 0;
}

static void routerboot_find_dynparts(struct mtd_info *master)
{
	size_t bytes_read, offset;
	bool allfound;
	int err, i;
	u32 buf;

	/*
	 * Dynamic RouterBoot partitions offsets are aligned to RB_BLOCK_SIZE:
	 * read the whole partition at RB_BLOCK_SIZE intervals to find sigs.
	 * Skip partition content when possible.
	 */
	offset = 0;
	while (offset < master->size) {
		err = mtd_read(master, offset, sizeof(buf), &bytes_read, (u8 *)&buf);
		if (err) {
			pr_err("%s: mtd_read error while parsing (offset: 0x%X): %d\n",
			       master->name, offset, err);
			continue;
		}

		allfound = true;

		for (i = 0; i < ARRAY_SIZE(rb_dynparts); i++) {
			if (rb_dynparts[i].found)
				continue;

			allfound = false;

			if (rb_dynparts[i].magic == buf) {
				rb_dynparts[i].offset = offset;

				if (rb_dynparts[i].size_fixup) {
					err = rb_dynparts[i].size_fixup(master, &rb_dynparts[i]);
					if (err) {
						pr_err("%s: size fixup error while parsing \"%s\": %d\n",
						       master->name, rb_dynparts[i].name, err);
						continue;
					}
				}

				rb_dynparts[i].found = true;

				/*
				 * move offset to skip the whole partition on
				 * next iteration if size > RB_BLOCK_SIZE.
				 */
				if (rb_dynparts[i].size > RB_BLOCK_SIZE)
					offset += ALIGN_DOWN((rb_dynparts[i].size - RB_BLOCK_SIZE), RB_BLOCK_SIZE);

				break;
			}
		}

		offset += RB_BLOCK_SIZE;

		if (allfound)
			break;
	}
}

static int routerboot_partitions_parse(struct mtd_info *master,
				       const struct mtd_partition **pparts,
				       struct mtd_part_parser_data *data)
{
	struct device_node *rbpart_node, *pp;
	struct mtd_partition *parts;
	const char *partname;
	size_t master_ofs;
	int np;

	/* Pull of_node from the master device node */
	rbpart_node = mtd_get_of_node(master);
	if (!rbpart_node)
		return 0;

	/* First count the subnodes */
	np = 0;
	for_each_child_of_node(rbpart_node,  pp)
		np++;

	if (!np)
		return 0;

	parts = kcalloc(np, sizeof(*parts), GFP_KERNEL);
	if (!parts)
		return -ENOMEM;

	/* Preemptively look for known parts in flash */
	routerboot_find_dynparts(master);

	np = 0;
	master_ofs = 0;
	for_each_child_of_node(rbpart_node, pp) {
		const __be32 *reg, *sz;
		size_t offset, size;
		int i, len, a_cells, s_cells;

		partname = of_get_property(pp, "label", &len);
		/* Allow deprecated use of "name" instead of "label" */
		if (!partname)
			partname = of_get_property(pp, "name", &len);
		/* Fallback to node name per spec if all else fails: partname is always set */
		if (!partname)
			partname = pp->name;
		parts[np].name = partname;

		reg = of_get_property(pp, "reg", &len);
		if (reg) {
			/* Fixed partition */
			a_cells = of_n_addr_cells(pp);
			s_cells = of_n_size_cells(pp);

			if ((len / 4) != (a_cells + s_cells)) {
				pr_debug("%s: routerboot partition %pOF (%pOF) error parsing reg property.\n",
					 master->name, pp, rbpart_node);
				goto rbpart_fail;
			}

			offset = of_read_number(reg, a_cells);
			size = of_read_number(reg + a_cells, s_cells);
		} else {
			/* Dynamic partition */
			/* Default: part starts at current offset, 0 size */
			offset = master_ofs;
			size = 0;

			/* Check if well-known partition */
			for (i = 0; i < ARRAY_SIZE(rb_dynparts); i++) {
				if (!strcmp(partname, rb_dynparts[i].name) && rb_dynparts[i].found) {
					offset = rb_dynparts[i].offset;
					size = rb_dynparts[i].size;
					break;
				}
			}

			/* Standalone 'size' property? Override size */
			sz = of_get_property(pp, "size", &len);
			if (sz) {
				s_cells = of_n_size_cells(pp);
				if ((len / 4) != s_cells) {
					pr_debug("%s: routerboot partition %pOF (%pOF) error parsing size property.\n",
						 master->name, pp, rbpart_node);
					goto rbpart_fail;
				}

				size = of_read_number(sz, s_cells);
			}
		}

		if (np > 0) {
			/* Minor sanity check for overlaps */
			if (offset < (parts[np-1].offset + parts[np-1].size)) {
				pr_err("%s: routerboot partition %pOF (%pOF) \"%s\" overlaps with previous partition \"%s\".\n",
				       master->name, pp, rbpart_node,
				       partname, parts[np-1].name);
				goto rbpart_fail;
			}

			/* Fixup end of previous partition if necessary */
			if (!parts[np-1].size)
				parts[np-1].size = (offset - parts[np-1].offset);
		}

		if ((offset + size) > master->size) {
			pr_err("%s: routerboot partition %pOF (%pOF) \"%s\" extends past end of segment.\n",
			       master->name, pp, rbpart_node, partname);
			goto rbpart_fail;
		}

		parts[np].offset = offset;
		parts[np].size = size;
		parts[np].of_node = pp;

		if (of_get_property(pp, "read-only", &len))
			parts[np].mask_flags |= MTD_WRITEABLE;

		if (of_get_property(pp, "lock", &len))
			parts[np].mask_flags |= MTD_POWERUP_LOCK;

		/* Keep master offset aligned to RB_BLOCK_SIZE */
		master_ofs = ALIGN(offset + size, RB_BLOCK_SIZE);
		np++;
	}

	*pparts = parts;
	return np;

rbpart_fail:
	pr_err("%s: error parsing routerboot partition %pOF (%pOF)\n",
	       master->name, pp, rbpart_node);
	of_node_put(pp);
	kfree(parts);
	return -EINVAL;
}

static const struct of_device_id parse_routerbootpart_match_table[] = {
	{ .compatible = "mikrotik,routerboot-partitions" },
	{},
};
MODULE_DEVICE_TABLE(of, parse_routerbootpart_match_table);

static struct mtd_part_parser routerbootpart_parser = {
	.parse_fn = routerboot_partitions_parse,
	.name = "routerbootpart",
	.of_match_table = parse_routerbootpart_match_table,
};
module_mtd_part_parser(routerbootpart_parser);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MTD partitioning for RouterBoot");
MODULE_AUTHOR("Thibaut VARENE");