[OpenWrt-Devel] [PATCH v2 1/6] generic: routerbootpart MTD parser for RouterBoot
Thibaut VARÈNE
hacks at slashdirt.org
Mon Apr 20 09:13:31 EDT 2020
This driver provides an OF MTD parser to properly assign the RouterBoot
partitions on the flash. This parser builds from the "fixed-partitions"
one (see ofpart.c), but it can handle dynamic partitions as found on
routerboot devices.
The parent node must contain the following:
compatible = "mikrotik,routerboot-partitions";
#address-cells = <1>;
#size-cells = <1>;
Children routerbootpart DTS nodes are defined as follows:
For fixed partitions
node-name at unit-address {
reg = <prop-encoded-array>;
label = <string>;
read-only;
lock;
};
All properties but reg are optional.
For dynamic partitions:
node-name {
size = <prop-encoded-array>;
label = <string>;
read-only;
lock;
};
size property is mandatory unless the next partition is a fixed one or
a "well-known" one (matched from the strings defined below) in which case
it can be omitted or set to 0; other properties are optional.
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"
This parser requires the DTS to list partitions in ascending order as
expected on the MTD device.
This parser has been successfully tested on BE (ath79) and LE (ipq40xx
and ramips) hardware.
Tested-by: Baptiste Jonglez <git at bitsofnetworks.org>
Tested-by: Roger Pueyo Centelles <roger.pueyo at guifi.net>
Tested-by: Tobias Schramm <t.schramm at manjaro.org>
Tested-by: Christopher Hill <ch6574 at gmail.com>
Signed-off-by: Thibaut VARÈNE <hacks at slashdirt.org>
---
.../files/drivers/mtd/parsers/routerbootpart.c | 357 +++++++++++++++++++++
1 file changed, 357 insertions(+)
create mode 100644 target/linux/generic/files/drivers/mtd/parsers/routerbootpart.c
diff --git a/target/linux/generic/files/drivers/mtd/parsers/routerbootpart.c b/target/linux/generic/files/drivers/mtd/parsers/routerbootpart.c
new file mode 100644
index 0000000000..8b0784388f
--- /dev/null
+++ b/target/linux/generic/files/drivers/mtd/parsers/routerbootpart.c
@@ -0,0 +1,357 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Parser for MikroTik RouterBoot partitions.
+ *
+ * Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel at 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 at 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 mandatory unless the next partition is a fixed one or
+ * a "well-known" one (matched from the strings defined below) in which case it
+ * can be omitted or set to 0; 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.
+ */
+
+
+#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.
+ */
+ 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 mtd_partition *parts;
+ struct device_node *rbpart_node, *pp;
+ 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");
+MODULE_DESCRIPTION("MTD partitioning for RouterBoot");
+MODULE_AUTHOR("Thibaut VARENE");
--
2.11.0
_______________________________________________
openwrt-devel mailing list
openwrt-devel at lists.openwrt.org
https://lists.openwrt.org/mailman/listinfo/openwrt-devel
More information about the openwrt-devel
mailing list