@@ -3,6 +3,7 @@
* Parser for TRX format partitions
*
* Copyright (C) 2012 - 2017 Rafał Miłecki <rafal@milecki.pl>
+ * Copyright (C) 2022 Linus Walleij <linus.walleij@linaro.org>
*/
#include <linux/module.h>
@@ -10,7 +11,13 @@
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
-#define TRX_PARSER_MAX_PARTS 4
+/* Maximum number of TRX headers that can be found in a flash */
+#define TRX_PARSER_MAX_TRX 4
+/*
+ * Maximum number of partitions that can be found in total in a flash
+ * 3 offsets and free space = 4.
+ */
+#define TRX_PARSER_MAX_PARTS (TRX_PARSER_MAX_TRX * 4)
/* Magics */
#define TRX_MAGIC 0x30524448
@@ -25,26 +32,193 @@ struct trx_header {
uint32_t offset[3];
} __packed;
-static const char *parser_trx_data_part_name(struct mtd_info *master,
- size_t offset)
+static void parser_trx_dump_trx(struct trx_header *trx)
+{
+ pr_debug("TRX at %08x\n", (u32)trx);
+ pr_debug(" MAGIC %08x\n", trx->magic);
+ pr_debug(" LENGTH %08x\n", trx->length);
+ pr_debug(" CRC32 %08x\n", trx->crc32);
+ pr_debug(" FLAGS %04x\n", trx->flags);
+ pr_debug(" VERSION %04x\n", trx->version);
+ pr_debug(" OFFSET[0] %08x\n", trx->offset[0]);
+ pr_debug(" OFFSET[1] %08x\n", trx->offset[1]);
+ pr_debug(" OFFSET[2] %08x\n", trx->offset[2]);
+}
+
+static int parser_trx_data_part_name_and_extents(struct mtd_info *mtd,
+ uint64_t start, uint64_t end, int trx_index,
+ const char **out_name, uint64_t *out_size)
{
- uint32_t buf;
+ uint32_t blocksize = mtd->erasesize;
size_t bytes_read;
+ uint64_t offset;
+ uint64_t size;
+ uint32_t magic;
+ uint32_t tmp;
+ char *name;
int err;
- err = mtd_read(master, offset, sizeof(buf), &bytes_read,
- (uint8_t *)&buf);
+ err = mtd_read(mtd, start, sizeof(magic), &bytes_read, (uint8_t *)&magic);
if (err && !mtd_is_bitflip(err)) {
- pr_err("mtd_read error while parsing (offset: 0x%zX): %d\n",
- offset, err);
- goto out_default;
+ pr_err("mtd_read error while parsing (offset: 0x%08llx): %d\n",
+ offset, err);
+ magic = 0x0;
+ }
+
+ /* Here other data partion types can be added as needed */
+ switch (magic) {
+ case UBI_EC_MAGIC:
+ name = "ubi";
+ break;
+ default:
+ magic = 0x0;
+ name = "rootfs";
+ break;
+ }
+
+ /* First partion is just "name", second is "name2" etc */
+ if (trx_index > 0) {
+ name = kasprintf(GFP_KERNEL, "%s%d", name, trx_index + 1);
+ if (!name)
+ return -ENOMEM;
}
- if (buf == UBI_EC_MAGIC)
- return "ubi";
+ /*
+ * Attempt to determine extents, first assume it is
+ * the rest of the TRX partition, then scan to check.
+ */
+ size = end - start;
+ if (magic == 0x0)
+ goto out_no_scan;
+
+ for (offset = start; offset <= end - blocksize;
+ offset += blocksize) {
+ err = mtd_read(mtd, offset, sizeof(tmp), &bytes_read, (uint8_t *)&tmp);
+ if (err && !mtd_is_bitflip(err)) {
+ pr_err("mtd_read error while parsing (offset: 0x%08llx): %d\n",
+ offset, err);
+ break;
+ }
+ if (tmp != magic) {
+ size = offset - start;
+ break;
+ }
+ }
-out_default:
- return "rootfs";
+out_no_scan:
+ *out_name = name;
+ *out_size = size;
+
+ return 0;
+}
+
+static uint64_t parser_trx_get_offset(uint64_t start, struct trx_header *trx, int i)
+{
+ /* Include the TRX header into the first partition */
+ if ((i == 0) && (trx->offset[i] == sizeof(struct trx_header)))
+ return start;
+ return start + trx->offset[i];
+}
+
+static uint64_t parser_trx_get_size(uint64_t start, uint64_t end, struct trx_header *trx, int i)
+{
+ uint64_t this_offset;
+ uint64_t next_offset;
+ uint64_t retsz;
+
+ /*
+ * Get the size from the next offset if that is > 0, else we assume
+ * we are the last partition in the TRX partition block, and then we
+ * use the rest of the available size.
+ */
+ this_offset = parser_trx_get_offset(start, trx, i);
+ if ((i < 2) && (trx->offset[i + 1] > 0))
+ next_offset = parser_trx_get_offset(start, trx, i + 1);
+ else
+ next_offset = end;
+
+ retsz = next_offset - this_offset;
+ pr_debug("size of trx partion %d is %08llx\n", i, retsz);
+
+ return retsz;
+}
+
+static const char *parser_trx_get_partname(const char *basename, int trx_index)
+{
+ if (trx_index == 0)
+ return basename;
+ return kasprintf(GFP_KERNEL, "%s%d", basename, trx_index + 1);
+}
+
+/*
+ * This adds all the partitions found in a TRX partition block, which means
+ * up to three individual partitions. Returns number of added partitions.
+ */
+static int parser_trx_add_trx_partition(struct mtd_info *mtd, struct trx_header *trx,
+ int trx_index, uint64_t start, uint64_t end,
+ struct mtd_partition *parts,
+ int curr_part)
+{
+ struct mtd_partition *part;
+ bool has_loader = false;
+ uint64_t last_end;
+ int i = 0;
+ int ret;
+
+ parser_trx_dump_trx(trx);
+
+ /* We have an LZMA loader partition if there is an address in offset[2] */
+ if (trx->offset[2])
+ has_loader = true;
+
+ if (has_loader) {
+ part = &parts[curr_part + i];
+ part->name = parser_trx_get_partname("loader", trx_index);
+ if (!part->name)
+ return -ENOMEM;
+ part->offset = parser_trx_get_offset(start, trx, i);
+ part->size = parser_trx_get_size(start, end, trx, i);
+ i++;
+ }
+
+ if (trx->offset[i]) {
+ part = &parts[curr_part + i];
+ part->name = parser_trx_get_partname("linux", trx_index);
+ if (!part->name)
+ return -ENOMEM;
+ part->offset = parser_trx_get_offset(start, trx, i);
+ part->size = parser_trx_get_size(start, end, trx, i);
+ i++;
+ }
+
+ if (trx->offset[i]) {
+ part = &parts[curr_part + i];
+ part->offset = parser_trx_get_offset(start, trx, i);
+ part->size = parser_trx_get_size(start, end, trx, i);
+ ret = parser_trx_data_part_name_and_extents(mtd, part->offset, end, trx_index,
+ &part->name, &part->size);
+ if (ret)
+ return ret;
+ i++;
+ }
+
+ /*
+ * If there is free space after the last TRX add this as a separate
+ * partition. This happens when the data partition parser finds less than
+ * the space up until the end sector.
+ */
+ last_end = part->offset + part->size;
+ if (last_end < end) {
+ part = &parts[curr_part + i];
+ part->offset = last_end;
+ part->size = end - last_end;
+ part->name = "free";
+ i++;
+ }
+
+ pr_debug("Added %d partitions\n", i);
+
+ return i;
}
static int parser_trx_parse(struct mtd_info *mtd,
@@ -53,70 +227,71 @@ static int parser_trx_parse(struct mtd_info *mtd,
{
struct device_node *np = mtd_get_of_node(mtd);
struct mtd_partition *parts;
- struct mtd_partition *part;
- struct trx_header trx;
+ struct trx_header trxs[TRX_PARSER_MAX_TRX];
+ uint64_t trx_offsets[TRX_PARSER_MAX_TRX];
+ struct trx_header *trx;
size_t bytes_read;
- uint8_t curr_part = 0, i = 0;
+ int curr_part = 0;
+ int found_trx = 0;
+ int i;
+ uint64_t offset;
+ uint32_t blocksize = mtd->erasesize;
uint32_t trx_magic = TRX_MAGIC;
- int err;
+ int ret;
/* Get different magic from device tree if specified */
- err = of_property_read_u32(np, "brcm,trx-magic", &trx_magic);
- if (err != 0 && err != -EINVAL)
- pr_err("failed to parse \"brcm,trx-magic\" DT attribute, using default: %d\n", err);
+ ret = of_property_read_u32(np, "brcm,trx-magic", &trx_magic);
+ if (ret && ret != -EINVAL)
+ pr_err("failed to parse \"brcm,trx-magic\" DT attribute, using default: %d\n", ret);
parts = kcalloc(TRX_PARSER_MAX_PARTS, sizeof(struct mtd_partition),
GFP_KERNEL);
if (!parts)
return -ENOMEM;
- err = mtd_read(mtd, 0, sizeof(trx), &bytes_read, (uint8_t *)&trx);
- if (err) {
- pr_err("MTD reading error: %d\n", err);
- kfree(parts);
- return err;
- }
+ for (offset = 0; offset <= mtd->size - blocksize;
+ offset += blocksize) {
- if (trx.magic != trx_magic) {
- kfree(parts);
- return -ENOENT;
- }
+ trx = &trxs[found_trx];
+ ret = mtd_read(mtd, offset, sizeof(*trx), &bytes_read, (u_char *)trx);
+ if (ret) {
+ pr_err("MTD reading error: %d\n", ret);
+ goto out_free_parts;
+ }
- /* We have LZMA loader if there is address in offset[2] */
- if (trx.offset[2]) {
- part = &parts[curr_part++];
- part->name = "loader";
- part->offset = trx.offset[i];
- i++;
- }
+ if (trx->magic != trx_magic)
+ continue;
- if (trx.offset[i]) {
- part = &parts[curr_part++];
- part->name = "linux";
- part->offset = trx.offset[i];
- i++;
- }
+ trx_offsets[found_trx] = offset;
- if (trx.offset[i]) {
- part = &parts[curr_part++];
- part->name = parser_trx_data_part_name(mtd, trx.offset[i]);
- part->offset = trx.offset[i];
- i++;
+ found_trx++;
}
- /*
- * Assume that every partition ends at the beginning of the one it is
- * followed by.
- */
- for (i = 0; i < curr_part; i++) {
- u64 next_part_offset = (i < curr_part - 1) ?
- parts[i + 1].offset : mtd->size;
+ for (i = 0; i < found_trx; i++) {
+ uint64_t start;
+ uint64_t end;
- parts[i].size = next_part_offset - parts[i].offset;
+ trx = &trxs[i];
+ start = trx_offsets[i];
+ if (i < (found_trx - 1))
+ end = trx_offsets[i + 1];
+ else
+ end = mtd->size;
+
+ ret = parser_trx_add_trx_partition(mtd, trx, i, start, end,
+ parts, curr_part);
+ if (ret < 0)
+ goto out_free_parts;
+
+ curr_part += ret;
}
*pparts = parts;
- return i;
+ return curr_part;
+
+out_free_parts:
+ kfree(parts);
+ return ret;
};
static const struct of_device_id mtd_parser_trx_of_match_table[] = {
The TRX parser has never been fully implemented, the commit splitting it out of the bcm47xx parser says "There is still some place for improvement". Here are some improvements: 1. First partition includes TRX header offset: The parser can currently produce output like this: 0x00000000001c-0x000000280000 : "linux" mtd: partition "linux" doesn't start on an erase/write block boundary -- force read-only This is because the TRX header is not included into the partition, while it should be: the vendor code does this, and when replacing the kernel in flash we certainly want to write it along with a new TRX tag as well. Right now we cannot replace it from within Linux due to this limitation. 2. Scan for several TRX headers Currently only the first flash block is scanned for a TRX header, but several flashes have multiple TRX headers each with up to 3 partitions. 3. Determine extents of the data (rootfs) partition. While still a bit hacky (just scanning forward for UBI magic) we check where the rootfs volume actually ends, and do not assume it fills the rest of the flash memory. 4. Add free space as a separate partition. Before this I could not mount my UBI rootfs because the rootfs gets too big: all remaining space will put into the second detected partition so the UBI partition has the wrong size and will not attach. After this patch it mounts just fine. Vendor code partition detection (D-Link DWL-8610AP): Creating 5 MTD partitions on "brcmnand": 0x000000000000-0x000002800000 : "linux" 0x000000280000-0x000002800000 : "rootfs" 0x000002800000-0x000005000000 : "linux2" 0x000002a80000-0x000005000000 : "rootfs2" 0x000005000000-0x000008000000 : "jffs2" Before this patch: Creating 2 MTD partitions on "brcmnand.0": 0x00000000001c-0x000000280000 : "linux" mtd: partition "linux" doesn't start on an erase/write block boundary -- force read-only 0x000000280000-0x000008000000 : "ubi" After this patch: 5 trx partitions found on MTD device brcmnand.0 Creating 5 MTD partitions on "brcmnand.0": 0x000000000000-0x000000280000 : "linux" 0x000000280000-0x000002800000 : "ubi" 0x000002800000-0x000002a80000 : "linux2" 0x000002a80000-0x000005000000 : "ubi2" 0x000005000000-0x000008000000 : "free" Cc: Rafał Miłecki <zajec5@gmail.com> Cc: Hauke Mehrtens <hauke@hauke-m.de> Signed-off-by: Linus Walleij <linus.walleij@linaro.org> --- drivers/mtd/parsers/parser_trx.c | 289 +++++++++++++++++++++++++------ 1 file changed, 232 insertions(+), 57 deletions(-)