| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2019 MediaTek Inc. |
| * Author: Guochun Mao <guochun.mao@mediatek.com> |
| * Xiaolei Li <xiaolei.li@mediatek.com> |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/string.h> |
| #include <linux/mtd/mtd.h> |
| #include <linux/mtd/partitions.h> |
| #include <asm/div64.h> |
| |
| #define MAX_PARTITION_COUNT 128 |
| #define MAX_PARTITION_NAME_LEN 64 |
| #define PT_SIG 0x50547633 //"PTv3" |
| #define MPT_SIG 0x4D505433 //"MPT3" |
| #define PT_SIG_SIZE 8 |
| |
| #define is_valid_mpt(buf) ((*(uint32_t *)(buf)) == MPT_SIG) |
| #define is_valid_pt(buf) ((*(uint32_t *)(buf)) == PT_SIG) |
| #define is_valid_pt_v1(buf) ((*(uint32_t *)(buf)) == PT_SIG_V1) |
| |
| typedef struct |
| { |
| unsigned char name[MAX_PARTITION_NAME_LEN]; /* partition name */ |
| unsigned long long size; /* partition size */ |
| unsigned long long part_id; /* partition region */ //will be used as download type on L branch. xiaolei |
| unsigned long long offset; /* partition start */ |
| unsigned long long mask_flags; /* partition flags */ |
| } pt_resident; |
| |
| static void pmt_add_part(struct mtd_partition *part, char *name, |
| u64 offset, uint32_t mask_flags, uint64_t size) |
| { |
| part->name = kstrdup_const(name, GFP_KERNEL); |
| part->offset = offset; |
| part->mask_flags = mask_flags; |
| part->size = size; |
| } |
| |
| static int pmt_parse(struct mtd_info *master, |
| const struct mtd_partition **pparts, |
| struct mtd_part_parser_data *data) |
| { |
| struct mtd_partition *parts; |
| int err, i; |
| u_char *buf; |
| u32 pmt_size = sizeof(pt_resident) * MAX_PARTITION_COUNT; |
| u8 pmt[PT_SIG_SIZE] = {0}; |
| pt_resident *pt; |
| size_t bytes_read = 0; |
| loff_t pmt_start_addr = (loff_t)master->size - 6 * (loff_t)master->erasesize; |
| u8 max_partition_count = 0; |
| |
| dev_dbg(&master->dev, "PMT: enter pmt parser...\n"); |
| |
| buf = kzalloc(pmt_size, GFP_KERNEL); |
| if (buf == NULL) |
| return -ENOMEM; |
| |
| pt = (pt_resident *)buf; |
| err = mtd_read(master, pmt_start_addr, sizeof(pmt), &bytes_read, pmt); |
| if (err < 0) |
| goto freebuf; |
| |
| /* look for the PMT tag */ |
| if (is_valid_mpt(pmt) == 0 && is_valid_pt(pmt) == 0) { |
| dev_err(&master->dev, "PMT: not find sig in 1st PMT block, try 2nd one\n"); |
| pmt_start_addr += master->erasesize; |
| err = mtd_read(master, pmt_start_addr, sizeof(pmt), &bytes_read, pmt); |
| if (err < 0) |
| goto freebuf; |
| if (is_valid_mpt(pmt) == 0 && is_valid_pt(pmt) == 0) { |
| dev_err(&master->dev, "PMT: not find sig in 2st PMT block, give up\n"); |
| goto freebuf; |
| } |
| } |
| |
| err = mtd_read(master, pmt_start_addr + PT_SIG_SIZE, pmt_size, &bytes_read, buf); |
| if (err < 0) |
| goto freebuf; |
| |
| for (i = 0 ; i < MAX_PARTITION_COUNT ; i++) { |
| if (strlen(pt[i].name) == 0) |
| break; |
| } |
| max_partition_count = i; |
| dev_dbg(&master->dev, "pmt there are <%d> parititons.\n", |
| max_partition_count); |
| |
| parts = kcalloc(max_partition_count, |
| sizeof(struct mtd_partition), GFP_KERNEL); |
| if (parts == NULL) |
| goto freebuf; |
| |
| for (i = 0 ; i < max_partition_count ; i++) { |
| pmt_add_part(&parts[i], pt[i].name, |
| pt[i].offset, 0, pt[i].size); |
| |
| } |
| |
| parsedone: |
| *pparts = parts; |
| kfree(buf); |
| return max_partition_count; |
| |
| freebuf: |
| kfree(buf); |
| return 0; |
| }; |
| |
| static struct mtd_part_parser pmt_parser = { |
| .owner = THIS_MODULE, |
| .parse_fn = pmt_parse, |
| .name = "pmtpart", |
| }; |
| |
| static int __init pmtpart_init(void) |
| { |
| return register_mtd_parser(&pmt_parser); |
| } |
| |
| static void __exit pmtpart_exit(void) |
| { |
| deregister_mtd_parser(&pmt_parser); |
| } |
| |
| module_init(pmtpart_init); |
| module_exit(pmtpart_exit); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("PMT partitioning for flash memories"); |