// SPDX-License-Identifier: GPL-2.0-only /* * Copyright © 2022 Rafał Miłecki */ #include #include #include #include #include #include #define TPLINK_SAFELOADER_DATA_OFFSET 4 #define TPLINK_SAFELOADER_MAX_PARTS 32 struct safeloader_cmn_header { __be32 size; uint32_t unused; } __packed; static void *mtd_parser_tplink_safeloader_read_table(struct mtd_info *mtd) { struct safeloader_cmn_header hdr; struct device_node *np; size_t bytes_read; size_t size; u32 offset; char *buf; int err; np = mtd_get_of_node(mtd); if (mtd_is_partition(mtd)) of_node_get(np); else np = of_get_child_by_name(np, "partitions"); if (of_property_read_u32(np, "partitions-table-offset", &offset)) { pr_err("Failed to get partitions table offset\n"); goto err_put; } err = mtd_read(mtd, offset, sizeof(hdr), &bytes_read, (uint8_t *)&hdr); if (err && !mtd_is_bitflip(err)) { pr_err("Failed to read from %s at 0x%x\n", mtd->name, offset); goto err_put; } size = be32_to_cpu(hdr.size); buf = kmalloc(size + 1, GFP_KERNEL); if (!buf) goto err_put; err = mtd_read(mtd, offset + sizeof(hdr), size, &bytes_read, buf); if (err && !mtd_is_bitflip(err)) { pr_err("Failed to read from %s at 0x%zx\n", mtd->name, offset + sizeof(hdr)); goto err_kfree; } buf[size] = '\0'; of_node_put(np); return buf; err_kfree: kfree(buf); err_put: of_node_put(np); return NULL; } static int mtd_parser_tplink_safeloader_parse(struct mtd_info *mtd, const struct mtd_partition **pparts, struct mtd_part_parser_data *data) { struct mtd_partition *parts; char name[65]; size_t offset; size_t bytes; char *buf; int idx; int err; parts = kcalloc(TPLINK_SAFELOADER_MAX_PARTS, sizeof(*parts), GFP_KERNEL); if (!parts) { err = -ENOMEM; goto err_out; } buf = mtd_parser_tplink_safeloader_read_table(mtd); if (!buf) { err = -ENOENT; goto err_free_parts; } for (idx = 0, offset = TPLINK_SAFELOADER_DATA_OFFSET; idx < TPLINK_SAFELOADER_MAX_PARTS && sscanf(buf + offset, "partition %64s base 0x%llx size 0x%llx%zn\n", name, &parts[idx].offset, &parts[idx].size, &bytes) == 3; idx++, offset += bytes + 1) { parts[idx].name = kstrdup(name, GFP_KERNEL); if (!parts[idx].name) { err = -ENOMEM; goto err_free; } } if (idx == TPLINK_SAFELOADER_MAX_PARTS) pr_warn("Reached maximum number of partitions!\n"); kfree(buf); *pparts = parts; return idx; err_free: for (idx -= 1; idx >= 0; idx--) kfree(parts[idx].name); err_free_parts: kfree(parts); err_out: return err; }; static void mtd_parser_tplink_safeloader_cleanup(const struct mtd_partition *pparts, int nr_parts) { int i; for (i = 0; i < nr_parts; i++) kfree(pparts[i].name); kfree(pparts); } static const struct of_device_id mtd_parser_tplink_safeloader_of_match_table[] = { { .compatible = "tplink,safeloader-partitions" }, {}, }; MODULE_DEVICE_TABLE(of, mtd_parser_tplink_safeloader_of_match_table); static struct mtd_part_parser mtd_parser_tplink_safeloader = { .parse_fn = mtd_parser_tplink_safeloader_parse, .cleanup = mtd_parser_tplink_safeloader_cleanup, .name = "tplink-safeloader", .of_match_table = mtd_parser_tplink_safeloader_of_match_table, }; module_mtd_part_parser(mtd_parser_tplink_safeloader); MODULE_LICENSE("GPL");