162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * drivers/mtd/scpart.c: Sercomm Partition Parser 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2018 NOGUCHI Hiroshi 662306a36Sopenharmony_ci * Copyright (C) 2022 Mikhail Zhilkin 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/slab.h> 1162306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 1262306a36Sopenharmony_ci#include <linux/mtd/partitions.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#define MOD_NAME "scpart" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#ifdef pr_fmt 1862306a36Sopenharmony_ci#undef pr_fmt 1962306a36Sopenharmony_ci#endif 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#define pr_fmt(fmt) MOD_NAME ": " fmt 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define ID_ALREADY_FOUND 0xffffffffUL 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define MAP_OFFS_IN_BLK 0x800 2662306a36Sopenharmony_ci#define MAP_MIRROR_NUM 2 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic const char sc_part_magic[] = { 2962306a36Sopenharmony_ci 'S', 'C', 'F', 'L', 'M', 'A', 'P', 'O', 'K', '\0', 3062306a36Sopenharmony_ci}; 3162306a36Sopenharmony_ci#define PART_MAGIC_LEN sizeof(sc_part_magic) 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci/* assumes that all fields are set by CPU native endian */ 3462306a36Sopenharmony_cistruct sc_part_desc { 3562306a36Sopenharmony_ci uint32_t part_id; 3662306a36Sopenharmony_ci uint32_t part_offs; 3762306a36Sopenharmony_ci uint32_t part_bytes; 3862306a36Sopenharmony_ci}; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic uint32_t scpart_desc_is_valid(struct sc_part_desc *pdesc) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci return ((pdesc->part_id != 0xffffffffUL) && 4362306a36Sopenharmony_ci (pdesc->part_offs != 0xffffffffUL) && 4462306a36Sopenharmony_ci (pdesc->part_bytes != 0xffffffffUL)); 4562306a36Sopenharmony_ci} 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistatic int scpart_scan_partmap(struct mtd_info *master, loff_t partmap_offs, 4862306a36Sopenharmony_ci struct sc_part_desc **ppdesc) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci int cnt = 0; 5162306a36Sopenharmony_ci int res = 0; 5262306a36Sopenharmony_ci int res2; 5362306a36Sopenharmony_ci uint32_t offs; 5462306a36Sopenharmony_ci size_t retlen; 5562306a36Sopenharmony_ci struct sc_part_desc *pdesc = NULL; 5662306a36Sopenharmony_ci struct sc_part_desc *tmpdesc; 5762306a36Sopenharmony_ci uint8_t *buf; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci buf = kzalloc(master->erasesize, GFP_KERNEL); 6062306a36Sopenharmony_ci if (!buf) { 6162306a36Sopenharmony_ci res = -ENOMEM; 6262306a36Sopenharmony_ci goto out; 6362306a36Sopenharmony_ci } 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci res2 = mtd_read(master, partmap_offs, master->erasesize, &retlen, buf); 6662306a36Sopenharmony_ci if (res2 || retlen != master->erasesize) { 6762306a36Sopenharmony_ci res = -EIO; 6862306a36Sopenharmony_ci goto free; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci for (offs = MAP_OFFS_IN_BLK; 7262306a36Sopenharmony_ci offs < master->erasesize - sizeof(*tmpdesc); 7362306a36Sopenharmony_ci offs += sizeof(*tmpdesc)) { 7462306a36Sopenharmony_ci tmpdesc = (struct sc_part_desc *)&buf[offs]; 7562306a36Sopenharmony_ci if (!scpart_desc_is_valid(tmpdesc)) 7662306a36Sopenharmony_ci break; 7762306a36Sopenharmony_ci cnt++; 7862306a36Sopenharmony_ci } 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci if (cnt > 0) { 8162306a36Sopenharmony_ci int bytes = cnt * sizeof(*pdesc); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci pdesc = kcalloc(cnt, sizeof(*pdesc), GFP_KERNEL); 8462306a36Sopenharmony_ci if (!pdesc) { 8562306a36Sopenharmony_ci res = -ENOMEM; 8662306a36Sopenharmony_ci goto free; 8762306a36Sopenharmony_ci } 8862306a36Sopenharmony_ci memcpy(pdesc, &(buf[MAP_OFFS_IN_BLK]), bytes); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci *ppdesc = pdesc; 9162306a36Sopenharmony_ci res = cnt; 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_cifree: 9562306a36Sopenharmony_ci kfree(buf); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ciout: 9862306a36Sopenharmony_ci return res; 9962306a36Sopenharmony_ci} 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_cistatic int scpart_find_partmap(struct mtd_info *master, 10262306a36Sopenharmony_ci struct sc_part_desc **ppdesc) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci int magic_found = 0; 10562306a36Sopenharmony_ci int res = 0; 10662306a36Sopenharmony_ci int res2; 10762306a36Sopenharmony_ci loff_t offs = 0; 10862306a36Sopenharmony_ci size_t retlen; 10962306a36Sopenharmony_ci uint8_t rdbuf[PART_MAGIC_LEN]; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci while ((magic_found < MAP_MIRROR_NUM) && 11262306a36Sopenharmony_ci (offs < master->size) && 11362306a36Sopenharmony_ci !mtd_block_isbad(master, offs)) { 11462306a36Sopenharmony_ci res2 = mtd_read(master, offs, PART_MAGIC_LEN, &retlen, rdbuf); 11562306a36Sopenharmony_ci if (res2 || retlen != PART_MAGIC_LEN) { 11662306a36Sopenharmony_ci res = -EIO; 11762306a36Sopenharmony_ci goto out; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci if (!memcmp(rdbuf, sc_part_magic, PART_MAGIC_LEN)) { 12062306a36Sopenharmony_ci pr_debug("Signature found at 0x%llx\n", offs); 12162306a36Sopenharmony_ci magic_found++; 12262306a36Sopenharmony_ci res = scpart_scan_partmap(master, offs, ppdesc); 12362306a36Sopenharmony_ci if (res > 0) 12462306a36Sopenharmony_ci goto out; 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci offs += master->erasesize; 12762306a36Sopenharmony_ci } 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ciout: 13062306a36Sopenharmony_ci if (res > 0) 13162306a36Sopenharmony_ci pr_info("Valid 'SC PART MAP' (%d partitions) found at 0x%llx\n", res, offs); 13262306a36Sopenharmony_ci else 13362306a36Sopenharmony_ci pr_info("No valid 'SC PART MAP' was found\n"); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci return res; 13662306a36Sopenharmony_ci} 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic int scpart_parse(struct mtd_info *master, 13962306a36Sopenharmony_ci const struct mtd_partition **pparts, 14062306a36Sopenharmony_ci struct mtd_part_parser_data *data) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci const char *partname; 14362306a36Sopenharmony_ci int n; 14462306a36Sopenharmony_ci int nr_scparts; 14562306a36Sopenharmony_ci int nr_parts = 0; 14662306a36Sopenharmony_ci int res = 0; 14762306a36Sopenharmony_ci struct sc_part_desc *scpart_map = NULL; 14862306a36Sopenharmony_ci struct mtd_partition *parts = NULL; 14962306a36Sopenharmony_ci struct device_node *mtd_node; 15062306a36Sopenharmony_ci struct device_node *ofpart_node; 15162306a36Sopenharmony_ci struct device_node *pp; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci mtd_node = mtd_get_of_node(master); 15462306a36Sopenharmony_ci if (!mtd_node) { 15562306a36Sopenharmony_ci res = -ENOENT; 15662306a36Sopenharmony_ci goto out; 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci ofpart_node = of_get_child_by_name(mtd_node, "partitions"); 16062306a36Sopenharmony_ci if (!ofpart_node) { 16162306a36Sopenharmony_ci pr_info("%s: 'partitions' subnode not found on %pOF.\n", 16262306a36Sopenharmony_ci master->name, mtd_node); 16362306a36Sopenharmony_ci res = -ENOENT; 16462306a36Sopenharmony_ci goto out; 16562306a36Sopenharmony_ci } 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci nr_scparts = scpart_find_partmap(master, &scpart_map); 16862306a36Sopenharmony_ci if (nr_scparts <= 0) { 16962306a36Sopenharmony_ci pr_info("No any partitions was found in 'SC PART MAP'.\n"); 17062306a36Sopenharmony_ci res = -ENOENT; 17162306a36Sopenharmony_ci goto free; 17262306a36Sopenharmony_ci } 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci parts = kcalloc(of_get_child_count(ofpart_node), sizeof(*parts), 17562306a36Sopenharmony_ci GFP_KERNEL); 17662306a36Sopenharmony_ci if (!parts) { 17762306a36Sopenharmony_ci res = -ENOMEM; 17862306a36Sopenharmony_ci goto free; 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci for_each_child_of_node(ofpart_node, pp) { 18262306a36Sopenharmony_ci u32 scpart_id; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci if (of_property_read_u32(pp, "sercomm,scpart-id", &scpart_id)) 18562306a36Sopenharmony_ci continue; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci for (n = 0 ; n < nr_scparts ; n++) 18862306a36Sopenharmony_ci if ((scpart_map[n].part_id != ID_ALREADY_FOUND) && 18962306a36Sopenharmony_ci (scpart_id == scpart_map[n].part_id)) 19062306a36Sopenharmony_ci break; 19162306a36Sopenharmony_ci if (n >= nr_scparts) 19262306a36Sopenharmony_ci /* not match */ 19362306a36Sopenharmony_ci continue; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci /* add the partition found in OF into MTD partition array */ 19662306a36Sopenharmony_ci parts[nr_parts].offset = scpart_map[n].part_offs; 19762306a36Sopenharmony_ci parts[nr_parts].size = scpart_map[n].part_bytes; 19862306a36Sopenharmony_ci parts[nr_parts].of_node = pp; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci if (!of_property_read_string(pp, "label", &partname)) 20162306a36Sopenharmony_ci parts[nr_parts].name = partname; 20262306a36Sopenharmony_ci if (of_property_read_bool(pp, "read-only")) 20362306a36Sopenharmony_ci parts[nr_parts].mask_flags |= MTD_WRITEABLE; 20462306a36Sopenharmony_ci if (of_property_read_bool(pp, "lock")) 20562306a36Sopenharmony_ci parts[nr_parts].mask_flags |= MTD_POWERUP_LOCK; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci /* mark as 'done' */ 20862306a36Sopenharmony_ci scpart_map[n].part_id = ID_ALREADY_FOUND; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci nr_parts++; 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci if (nr_parts > 0) { 21462306a36Sopenharmony_ci *pparts = parts; 21562306a36Sopenharmony_ci res = nr_parts; 21662306a36Sopenharmony_ci } else 21762306a36Sopenharmony_ci pr_info("No partition in OF matches partition ID with 'SC PART MAP'.\n"); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci of_node_put(pp); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cifree: 22262306a36Sopenharmony_ci of_node_put(ofpart_node); 22362306a36Sopenharmony_ci kfree(scpart_map); 22462306a36Sopenharmony_ci if (res <= 0) 22562306a36Sopenharmony_ci kfree(parts); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ciout: 22862306a36Sopenharmony_ci return res; 22962306a36Sopenharmony_ci} 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_cistatic const struct of_device_id scpart_parser_of_match_table[] = { 23262306a36Sopenharmony_ci { .compatible = "sercomm,sc-partitions" }, 23362306a36Sopenharmony_ci {}, 23462306a36Sopenharmony_ci}; 23562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, scpart_parser_of_match_table); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cistatic struct mtd_part_parser scpart_parser = { 23862306a36Sopenharmony_ci .parse_fn = scpart_parse, 23962306a36Sopenharmony_ci .name = "scpart", 24062306a36Sopenharmony_ci .of_match_table = scpart_parser_of_match_table, 24162306a36Sopenharmony_ci}; 24262306a36Sopenharmony_cimodule_mtd_part_parser(scpart_parser); 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci/* mtd parsers will request the module by parser name */ 24562306a36Sopenharmony_ciMODULE_ALIAS("scpart"); 24662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 24762306a36Sopenharmony_ciMODULE_AUTHOR("NOGUCHI Hiroshi <drvlabo@gmail.com>"); 24862306a36Sopenharmony_ciMODULE_AUTHOR("Mikhail Zhilkin <csharper2005@gmail.com>"); 24962306a36Sopenharmony_ciMODULE_DESCRIPTION("Sercomm partition parser"); 250