162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright © 2022 Rafał Miłecki <rafal@milecki.pl> 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/kernel.h> 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 962306a36Sopenharmony_ci#include <linux/mtd/partitions.h> 1062306a36Sopenharmony_ci#include <linux/of.h> 1162306a36Sopenharmony_ci#include <linux/slab.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#define TPLINK_SAFELOADER_DATA_OFFSET 4 1462306a36Sopenharmony_ci#define TPLINK_SAFELOADER_MAX_PARTS 32 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistruct safeloader_cmn_header { 1762306a36Sopenharmony_ci __be32 size; 1862306a36Sopenharmony_ci uint32_t unused; 1962306a36Sopenharmony_ci} __packed; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic void *mtd_parser_tplink_safeloader_read_table(struct mtd_info *mtd) 2262306a36Sopenharmony_ci{ 2362306a36Sopenharmony_ci struct safeloader_cmn_header hdr; 2462306a36Sopenharmony_ci struct device_node *np; 2562306a36Sopenharmony_ci size_t bytes_read; 2662306a36Sopenharmony_ci size_t size; 2762306a36Sopenharmony_ci u32 offset; 2862306a36Sopenharmony_ci char *buf; 2962306a36Sopenharmony_ci int err; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci np = mtd_get_of_node(mtd); 3262306a36Sopenharmony_ci if (mtd_is_partition(mtd)) 3362306a36Sopenharmony_ci of_node_get(np); 3462306a36Sopenharmony_ci else 3562306a36Sopenharmony_ci np = of_get_child_by_name(np, "partitions"); 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci if (of_property_read_u32(np, "partitions-table-offset", &offset)) { 3862306a36Sopenharmony_ci pr_err("Failed to get partitions table offset\n"); 3962306a36Sopenharmony_ci goto err_put; 4062306a36Sopenharmony_ci } 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci err = mtd_read(mtd, offset, sizeof(hdr), &bytes_read, (uint8_t *)&hdr); 4362306a36Sopenharmony_ci if (err && !mtd_is_bitflip(err)) { 4462306a36Sopenharmony_ci pr_err("Failed to read from %s at 0x%x\n", mtd->name, offset); 4562306a36Sopenharmony_ci goto err_put; 4662306a36Sopenharmony_ci } 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci size = be32_to_cpu(hdr.size); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci buf = kmalloc(size + 1, GFP_KERNEL); 5162306a36Sopenharmony_ci if (!buf) 5262306a36Sopenharmony_ci goto err_put; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci err = mtd_read(mtd, offset + sizeof(hdr), size, &bytes_read, buf); 5562306a36Sopenharmony_ci if (err && !mtd_is_bitflip(err)) { 5662306a36Sopenharmony_ci pr_err("Failed to read from %s at 0x%zx\n", mtd->name, offset + sizeof(hdr)); 5762306a36Sopenharmony_ci goto err_kfree; 5862306a36Sopenharmony_ci } 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci buf[size] = '\0'; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci of_node_put(np); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci return buf; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cierr_kfree: 6762306a36Sopenharmony_ci kfree(buf); 6862306a36Sopenharmony_cierr_put: 6962306a36Sopenharmony_ci of_node_put(np); 7062306a36Sopenharmony_ci return NULL; 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic int mtd_parser_tplink_safeloader_parse(struct mtd_info *mtd, 7462306a36Sopenharmony_ci const struct mtd_partition **pparts, 7562306a36Sopenharmony_ci struct mtd_part_parser_data *data) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct mtd_partition *parts; 7862306a36Sopenharmony_ci char name[65]; 7962306a36Sopenharmony_ci size_t offset; 8062306a36Sopenharmony_ci size_t bytes; 8162306a36Sopenharmony_ci char *buf; 8262306a36Sopenharmony_ci int idx; 8362306a36Sopenharmony_ci int err; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci parts = kcalloc(TPLINK_SAFELOADER_MAX_PARTS, sizeof(*parts), GFP_KERNEL); 8662306a36Sopenharmony_ci if (!parts) { 8762306a36Sopenharmony_ci err = -ENOMEM; 8862306a36Sopenharmony_ci goto err_out; 8962306a36Sopenharmony_ci } 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci buf = mtd_parser_tplink_safeloader_read_table(mtd); 9262306a36Sopenharmony_ci if (!buf) { 9362306a36Sopenharmony_ci err = -ENOENT; 9462306a36Sopenharmony_ci goto err_free_parts; 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci for (idx = 0, offset = TPLINK_SAFELOADER_DATA_OFFSET; 9862306a36Sopenharmony_ci idx < TPLINK_SAFELOADER_MAX_PARTS && 9962306a36Sopenharmony_ci sscanf(buf + offset, "partition %64s base 0x%llx size 0x%llx%zn\n", 10062306a36Sopenharmony_ci name, &parts[idx].offset, &parts[idx].size, &bytes) == 3; 10162306a36Sopenharmony_ci idx++, offset += bytes + 1) { 10262306a36Sopenharmony_ci parts[idx].name = kstrdup(name, GFP_KERNEL); 10362306a36Sopenharmony_ci if (!parts[idx].name) { 10462306a36Sopenharmony_ci err = -ENOMEM; 10562306a36Sopenharmony_ci goto err_free; 10662306a36Sopenharmony_ci } 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci if (idx == TPLINK_SAFELOADER_MAX_PARTS) 11062306a36Sopenharmony_ci pr_warn("Reached maximum number of partitions!\n"); 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci kfree(buf); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci *pparts = parts; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci return idx; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cierr_free: 11962306a36Sopenharmony_ci for (idx -= 1; idx >= 0; idx--) 12062306a36Sopenharmony_ci kfree(parts[idx].name); 12162306a36Sopenharmony_cierr_free_parts: 12262306a36Sopenharmony_ci kfree(parts); 12362306a36Sopenharmony_cierr_out: 12462306a36Sopenharmony_ci return err; 12562306a36Sopenharmony_ci}; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cistatic void mtd_parser_tplink_safeloader_cleanup(const struct mtd_partition *pparts, 12862306a36Sopenharmony_ci int nr_parts) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci int i; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci for (i = 0; i < nr_parts; i++) 13362306a36Sopenharmony_ci kfree(pparts[i].name); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci kfree(pparts); 13662306a36Sopenharmony_ci} 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic const struct of_device_id mtd_parser_tplink_safeloader_of_match_table[] = { 13962306a36Sopenharmony_ci { .compatible = "tplink,safeloader-partitions" }, 14062306a36Sopenharmony_ci {}, 14162306a36Sopenharmony_ci}; 14262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, mtd_parser_tplink_safeloader_of_match_table); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic struct mtd_part_parser mtd_parser_tplink_safeloader = { 14562306a36Sopenharmony_ci .parse_fn = mtd_parser_tplink_safeloader_parse, 14662306a36Sopenharmony_ci .cleanup = mtd_parser_tplink_safeloader_cleanup, 14762306a36Sopenharmony_ci .name = "tplink-safeloader", 14862306a36Sopenharmony_ci .of_match_table = mtd_parser_tplink_safeloader_of_match_table, 14962306a36Sopenharmony_ci}; 15062306a36Sopenharmony_cimodule_mtd_part_parser(mtd_parser_tplink_safeloader); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 153