From patchwork Mon Apr 20 13:13:31 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Thibaut X-Patchwork-Id: 1273356 X-Patchwork-Delegate: koen.vandeputte@ncentric.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.openwrt.org (client-ip=2607:7c80:54:e::133; helo=bombadil.infradead.org; envelope-from=openwrt-devel-bounces+incoming=patchwork.ozlabs.org@lists.openwrt.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=quarantine dis=none) header.from=slashdirt.org Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=lists.infradead.org header.i=@lists.infradead.org header.a=rsa-sha256 header.s=bombadil.20170209 header.b=i9g8CFtE; dkim=fail reason="signature verification failed" (1024-bit key; secure) header.d=slashdirt.org header.i=@slashdirt.org header.a=rsa-sha256 header.s=mail header.b=cdCx2IrX; dkim-atps=neutral Received: from bombadil.infradead.org (bombadil.infradead.org [IPv6:2607:7c80:54:e::133]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 495Rx54bbNz9sSw for ; Mon, 20 Apr 2020 23:14:17 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:Cc:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:Subject:MIME-Version:References: In-Reply-To:Message-Id:Date:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=LFfX2Q+OUNdJ6Gk/ZzFIFsqjsxLd6WxtDiQYEmPMVz0=; b=i9g8CFtEIdqHpX 649eSfq7Cb7tvSVMlNIx8UMU5twZ15MEv30UqRWlasbVwkgOT2xHsziCSDWoOvf6/erFF0OHCIndb uIAjVpAvYrVeQbmrvIl5mcQDskumnEWk0S0LFkclxR+iHZdtbW1Q1hbQbvLSHKAYIMQV7zz3XbuTS KwR5fv25bCyXwm/GktW068DxA5ejvk/xueclf6896RymSDaUefeFaBu5XJ45K+0Iq3HtaBRcUmUNM QYPmrqGbEBrP/arJdt/eYi445FUiKH2ESD/4XBtB8qYJ6LZA5nJ1x5kSxkX8cC1j2o07utCXbasLy NMxQqYQPVIoInGOypH8w==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.92.3 #3 (Red Hat Linux)) id 1jQWFZ-0005MM-F2; Mon, 20 Apr 2020 13:14:13 +0000 Received: from vps.slashdirt.org ([144.91.108.218]) by bombadil.infradead.org with esmtps (Exim 4.92.3 #3 (Red Hat Linux)) id 1jQWFL-0004wP-Sz for openwrt-devel@lists.openwrt.org; Mon, 20 Apr 2020 13:14:02 +0000 Received: from supercopter (82-64-212-153.subs.proxad.net [82.64.212.153]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by vps.slashdirt.org (Postfix) with ESMTPS id 8039560335; Mon, 20 Apr 2020 15:13:51 +0200 (CEST) DMARC-Filter: OpenDMARC Filter v1.3.2 vps.slashdirt.org 8039560335 Authentication-Results: vps.slashdirt.org; dmarc=fail (p=quarantine dis=none) header.from=slashdirt.org DKIM-Filter: OpenDKIM Filter v2.11.0 vps.slashdirt.org 8039560335 DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=slashdirt.org; s=mail; t=1587388432; bh=JYlB+oWWHvUcu89CsPP8cfu5NhMJ+HOv0YRXYZtejls=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cdCx2IrXpDbLeTsdqwBpkPa+JUg6qU5MIhlH17Y38/ZrIFCCX+L1tpAi6KwIFHvO/ mu6fzTBlLacETWv10pqcR4x6ouMdYFVwnDRgFR9e4VjOKivbrf/W+ZXV8KQUh1ZAob lloPRbmeXo+oxWYW80KCsk9w2AFxA/QdIStW/n58= Received: by supercopter (sSMTP sendmail emulation); Mon, 20 Apr 2020 15:13:51 +0200 From: =?utf-8?q?Thibaut_VAR=C3=88NE?= To: openwrt-devel@lists.openwrt.org Date: Mon, 20 Apr 2020 15:13:31 +0200 Message-Id: <20200420131336.18252-2-hacks@slashdirt.org> X-Mailer: git-send-email 2.11.0 In-Reply-To: <20200420131336.18252-1-hacks@slashdirt.org> References: <20200420131336.18252-1-hacks@slashdirt.org> MIME-Version: 1.0 X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,NO_DNS_FOR_FROM,TXREP, UNPARSEABLE_RELAY shortcircuit=no autolearn=no autolearn_force=no version=3.4.2 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on vps.slashdirt.org X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20200420_061400_404387_849CF9BE X-CRM114-Status: GOOD ( 29.26 ) X-Spam-Score: 0.2 (/) X-Spam-Report: SpamAssassin version 3.4.4 on bombadil.infradead.org summary: Content analysis details: (0.2 points) pts rule name description ---- ---------------------- -------------------------------------------------- 0.4 NO_DNS_FOR_FROM RBL: Envelope sender has no MX or A DNS records [listed in slashdirt.org. IN A] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid Subject: [OpenWrt-Devel] [PATCH v2 1/6] generic: routerbootpart MTD parser for RouterBoot X-BeenThere: openwrt-devel@lists.openwrt.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: =?utf-8?q?Thibaut_VAR=C3=88NE?= , koen.vandeputte@ncentric.com Sender: "openwrt-devel" Errors-To: openwrt-devel-bounces+incoming=patchwork.ozlabs.org@lists.openwrt.org 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@unit-address { reg = ; label = ; read-only; lock; }; All properties but reg are optional. For dynamic partitions: node-name { size = ; label = ; 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 Tested-by: Roger Pueyo Centelles Tested-by: Tobias Schramm Tested-by: Christopher Hill Signed-off-by: Thibaut VARÈNE --- .../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 + * + * 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 = ; + * label = ; + * read-only; + * lock; + * }; + * + * reg property is mandatory; other properties are optional. + * reg format is
. 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 = ; + * label = ; + * 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 . + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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");