| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright © 2022 Rafał Miłecki <rafal@milecki.pl> |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/mtd/mtd.h> |
| #include <linux/mtd/partitions.h> |
| #include <linux/of.h> |
| #include <linux/slab.h> |
| |
| #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"); |