Skip to content

Commit 049bddc

Browse files
committed
block/partitions/amlogic: Add support
This adds support of Amlogic's proprietary partitions mainly seen on eMMC of devices using their SoCs to the block subsystem. The support is based on reverse-engineering so it could have major diffe- rences on technical details, the most notable one is that Amlogic added their own support to this format to the MMC driver instead of the block subsystem. There are probably multiple iterations of such implementaions by Amlogic but this support is based on the one with magic MPT and version 01.00.00 It is added as the last parsed partition type after all other existing ones like msdos, gpt, etc. So any existing msdos or gpt table on a block device will prevent the Amlogic one from being parsed. The support could be enabled with CONFIG_AMLOGIC_PARTITION=y Two kernel command line options could be used to determine the behaviours: - apt_checksum= : when set to 0, will turn off checksum; when set to other values, or left empty, will turn on checksum - apt_blkdevs= : a list of block devices seperated by ',', only devices listed here will be parsed. A special value all means all block devices should be parsed Signed-off-by: Guoxin Pu <pugokushin@gmail.com>
1 parent 489fa31 commit 049bddc

File tree

6 files changed

+330
-0
lines changed

6 files changed

+330
-0
lines changed
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
==========================================
2+
Amlogic proprietary eMMC partition parsing
3+
==========================================
4+
5+
Available kernel command line options:
6+
7+
apt_checksum (bool):
8+
decides whether the table should be checked with Amlogic's
9+
checksum algorithm to decide if it's a valid table
10+
11+
any value other than 0 enables the checksum (default), this
12+
should be OK for tables you didn't touch or have modified
13+
with ampart as it will update the checksum automatically
14+
15+
0 disables the checksum, which is only recommended if you
16+
modify the table by hand
17+
18+
apt_blkdevs (list of <blkdev-id>):
19+
a list of block devices that the table should be parsed on,
20+
seperated by ',', if kept empty no block device could be
21+
used
22+
23+
a special value 'all' means all block devices should be used
24+
25+
default value: (empty), no block devices could be parsed
26+
27+
suggested value: mmcblk2, so only eMMC will be parsed

block/partitions/Kconfig

+12
Original file line numberDiff line numberDiff line change
@@ -270,4 +270,16 @@ config CMDLINE_PARTITION
270270
Say Y here if you want to read the partition table from bootargs.
271271
The format for the command line is just like mtdparts.
272272

273+
config AMLOGIC_PARTITION
274+
bool "Amlogic proprietary partition support" if PARTITION_ADVANCED
275+
help
276+
Say Y here if you want to read Amlogic's proprietary partition
277+
table.
278+
Otherwise, say N.
279+
The support is implemented mainly with 7Ji's reverse-engineering
280+
of this partition format on several devices with results available
281+
in the ampart project.
282+
The technical details could have some differences from Amlogic's
283+
own impementation.
284+
273285
endmenu

block/partitions/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ obj-$(CONFIG_IBM_PARTITION) += ibm.o
2020
obj-$(CONFIG_EFI_PARTITION) += efi.o
2121
obj-$(CONFIG_KARMA_PARTITION) += karma.o
2222
obj-$(CONFIG_SYSV68_PARTITION) += sysv68.o
23+
obj-$(CONFIG_AMLOGIC_PARTITION) += amlogic.o

block/partitions/amlogic.c

+286
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Copyright (C) 2023 Guoxin "7Ji" Pu
4+
* Author: Pu Guoxin <pugokushin@gmail.com>
5+
*
6+
* Read Amlogic's proprietary eMMC partition table that is mainly
7+
* used on the eMMC on TV boxes running Android with their SoCs.
8+
*
9+
* The usage is not limited to eMMC but can be limited with kernel
10+
* commandline.
11+
*
12+
* For further information, see "Documentation/block/amlogic-partition.rst"
13+
*
14+
*/
15+
16+
#include "check.h"
17+
18+
#define APT_PART_NAME_MAXLEN 15
19+
#define APT_MAX_PARTS 32
20+
#define APT_MAGIC_STRING "MPT"
21+
#define APT_MAGIC_LE32 (__le32)0x0054504DU
22+
#define APT_VERSION_STRING "01.00.00"
23+
24+
struct amlogic_header {
25+
__le32 magic;
26+
char version[12];
27+
__le32 count;
28+
__le32 checksum;
29+
} __packed;
30+
31+
struct amlogic_partition {
32+
char name[16];
33+
__le64 size;
34+
__le64 offset;
35+
__le32 mask_flags;
36+
u32 padding;
37+
} __packed;
38+
39+
struct amlogic_table {
40+
struct amlogic_header header;
41+
struct amlogic_partition parts[APT_MAX_PARTS];
42+
} __packed;
43+
44+
45+
u32 amlogic_checksum(struct amlogic_table *apt) {
46+
u32 checksum = 0;
47+
u32 *p;
48+
for (u32 i = 0; i < le32_to_cpu(apt->header.count); ++i) {
49+
p = (u32 *)apt->parts;
50+
for (u32 j = sizeof *apt->parts / sizeof *p; j > 0; --j) {
51+
checksum += le32_to_cpu(*p++);
52+
}
53+
}
54+
return checksum;
55+
}
56+
57+
static bool apt_checksum = true;
58+
static int __init apt_checksum_setup(char *s) {
59+
if (s && s[0] == '0' && s[1] == '\0') {
60+
pr_warn("Amlogic partition: checksum is disabled\n");
61+
apt_checksum = false;
62+
}
63+
return 1;
64+
}
65+
__setup("apt_checksum=", apt_checksum_setup);
66+
67+
bool amlogic_is_partition_name_valid(char *name) {
68+
char safe_name[19];
69+
for (u8 i = 0; i < 16; ++i) {
70+
switch (name[i]) {
71+
case 'a'...'z':
72+
case '_':
73+
break;
74+
case '\0':
75+
if (i == 0) {
76+
pr_warn("Amlogic partition: partition name empty, which is illegal\n");
77+
return false;
78+
} else {
79+
return true;
80+
}
81+
default:
82+
pr_warn("Amlogic partition: illegal character in apt partition name: %c, allowed for now, MAYBE FORBIDDEN IN THE FUTURE\n", name[i]);
83+
break;
84+
}
85+
}
86+
strncpy(safe_name, name, 15);
87+
strncpy(safe_name + 15, "...", 3);
88+
pr_warn("Amlogic partition: partition name not ended properly: %s\n", safe_name);
89+
return false;
90+
}
91+
92+
bool amlogic_is_partition_valid(struct amlogic_partition *part) {
93+
u64 offset_raw;
94+
u64 offset_round;
95+
u64 size_raw;
96+
u64 size_round;
97+
if (!amlogic_is_partition_name_valid(part->name)) {
98+
return false;
99+
}
100+
/* They could be not */
101+
offset_raw = le64_to_cpu(part->offset);
102+
offset_round = offset_raw >> 9 << 9;
103+
if (offset_raw != offset_round) {
104+
pr_warn("Amlogic partition: partition's offset is not multiple of 512: %llx\n", offset_raw);
105+
return false;
106+
}
107+
size_raw = le64_to_cpu(part->size);
108+
size_round = size_raw >> 9 << 9;
109+
if (size_raw != size_round) {
110+
pr_warn("Amlogic partition: partition's size is not multiple of 512: %llx\n", size_raw);
111+
return false;
112+
}
113+
return true;
114+
}
115+
116+
bool amlogic_is_valid(struct amlogic_table *apt) {
117+
u32 checksum;
118+
if (!apt) {
119+
pr_warn("Amlogic partition: not allocated properly\n");
120+
return false;
121+
}
122+
if (apt->header.magic != APT_MAGIC_LE32) {
123+
pr_warn("Amlogic partition: header magic not right: %x != %x\n", apt->header.magic, APT_MAGIC_LE32);
124+
return false;
125+
}
126+
if (strncmp(apt->header.version, APT_VERSION_STRING, strlen(APT_VERSION_STRING))) {
127+
char safe_version[15] = "";
128+
strncpy(safe_version, apt->header.version, 11);
129+
if (safe_version[11]) {
130+
strncpy(safe_version + 11, "...", 3);
131+
}
132+
pr_warn("Amlogic partition: header version not right: %s != %s\n", safe_version, APT_VERSION_STRING);
133+
return false;
134+
}
135+
if (!apt->header.count) {
136+
pr_warn("Amlogic partition: header entries count is 0, skipped\n");
137+
return false;
138+
}
139+
if (le32_to_cpu(apt->header.count) > APT_MAX_PARTS) {
140+
pr_warn("Amlogic partition: entries overflow: %u > %u\n", le32_to_cpu(apt->header.count), APT_MAX_PARTS);
141+
return false;
142+
}
143+
checksum = amlogic_checksum(apt);
144+
if (apt_checksum && checksum != le32_to_cpu(apt->header.checksum)) {
145+
pr_warn("Amlogic partition: checksum mismatch: calculated %x != recorded %x. (This check can be turned off by setting apt_checksum=0)\n", checksum, le32_to_cpu(apt->header.checksum));
146+
return false;
147+
}
148+
for (u32 i = 0; i < le32_to_cpu(apt->header.count); i++) {
149+
if (!amlogic_is_partition_valid(apt->parts + i)) {
150+
pr_warn("Amlogic partition: partition entry %u invalid\n", i);
151+
return false;
152+
}
153+
}
154+
return true;
155+
}
156+
157+
static char *apt_blkdevs;
158+
159+
static int __init apt_blkdevs_setup(char *s) {
160+
apt_blkdevs = s;
161+
return 1;
162+
}
163+
__setup("apt_blkdevs=", apt_blkdevs_setup);
164+
165+
bool amlogic_should_parse_block(struct parsed_partitions *state) {
166+
char *blkdev;
167+
if (!apt_blkdevs) {
168+
pr_debug("Amlogic partition: apt_blkdevs is not set, no block device should be parsed\n");
169+
return false;
170+
}
171+
if (!apt_blkdevs[0]) {
172+
pr_debug("Amlogic partition: apt_blkdevs is empty, no block devices should be parsed\n");
173+
return false;
174+
}
175+
if (!strncmp(apt_blkdevs, "all", 3)) {
176+
pr_debug("Amlogic partition: apt_blkdevs set to all, all block devices should be parsed\n");
177+
return true;
178+
}
179+
/* apt_blkdevs set and not empty, only parse block if its in the list */
180+
blkdev = apt_blkdevs;
181+
for (char *c = apt_blkdevs;; ++c) {
182+
switch (*c) {
183+
case ',':
184+
case '\0':
185+
if (strncmp(blkdev, state->disk->disk_name, c - blkdev)) {
186+
if (*c == '\0') {
187+
return false;
188+
}
189+
} else {
190+
return true;
191+
}
192+
blkdev = c + 1;
193+
/* end of a blkdev definition */
194+
break;
195+
default:
196+
break;
197+
}
198+
199+
}
200+
return false;
201+
}
202+
203+
/**
204+
* amlogic_partition - scan for Amlogic proprietary partitions
205+
* @state: disk parsed partitions
206+
*
207+
* Returns:
208+
* -1 if unable to read the partition table
209+
* 0 if this isn't our partition table
210+
* 1 if successful
211+
*
212+
*/
213+
int amlogic_partition(struct parsed_partitions *state){
214+
sector_t disk_sectors;
215+
sector_t disk_size;
216+
struct amlogic_table *apt;
217+
u8 apt_cache[((sizeof *apt >> 9) + 1) << 9] = {0};
218+
219+
if (!amlogic_should_parse_block(state)) {
220+
return 0;
221+
}
222+
223+
disk_sectors = get_capacity(state->disk);
224+
if (disk_sectors < 0x12003) {
225+
return 0;
226+
}
227+
disk_size = disk_sectors << 9;
228+
229+
for (sector_t i = 0; i < 3; ++i) {
230+
Sector sect;
231+
u8 *data = read_part_sector(state, 0x12000 + i, &sect);
232+
if (!data) {
233+
return -1;
234+
}
235+
memcpy(apt_cache + 512 * i, data, 512);
236+
put_dev_sector(sect);
237+
}
238+
239+
apt = (struct amlogic_table *)apt_cache;
240+
if (!amlogic_is_valid(apt)) {
241+
return 0;
242+
}
243+
244+
pr_debug("Amlogic partition: partition table is valid, now parsing it\n");
245+
246+
for (u32 i = 0; i < le32_to_cpu(apt->header.count) && i < state->limit-1; i++) {
247+
struct amlogic_partition *part = apt->parts + i;
248+
u64 offset = le64_to_cpu(part->offset) >> 9;
249+
u64 size = le64_to_cpu(part->size) >> 9;
250+
u64 end;
251+
struct partition_meta_info *info;
252+
size_t name_min;
253+
char tmp[sizeof(info->volname) + 4];
254+
if (offset > disk_sectors) {
255+
pr_warn("Amlogic partition: partition %s's offset is larger than disk size (sectors 0x%llx > 0x%llx), shifting its offset to disk end\n", part->name, offset, disk_sectors);
256+
offset = disk_sectors;
257+
}
258+
259+
end = offset + size;
260+
261+
if (end > disk_sectors) {
262+
u64 diff = end - disk_sectors;
263+
pr_warn("Amlogic partition: partition %s's size is too large and it exceeds the disk size (sectors end 0x%llx > 0x%llx), shrinking its size by 0x%llx sectors\n", part->name, end, disk_sectors, diff);
264+
size -= diff;
265+
}
266+
267+
if (size == 0) {
268+
pr_warn("Amlogic partition: partition %s's size is 0, you will probably not be able to access it\n", part->name);
269+
}
270+
271+
put_partition(state, i + 1, offset, size);
272+
273+
info = &state->parts[i + 1].info;
274+
name_min = min_t(size_t, sizeof info->volname, sizeof part->name);
275+
strncpy(info->volname, part->name, name_min);
276+
info->volname[name_min] = '\0';
277+
278+
snprintf(tmp, sizeof(tmp), "(%s)", info->volname);
279+
strlcat(state->pp_buf, tmp, PAGE_SIZE);
280+
281+
state->parts[i + 1].has_info = true;
282+
}
283+
284+
strlcat(state->pp_buf, "\n", PAGE_SIZE);
285+
return 1;
286+
}

block/partitions/check.h

+1
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,4 @@ int sgi_partition(struct parsed_partitions *state);
6767
int sun_partition(struct parsed_partitions *state);
6868
int sysv68_partition(struct parsed_partitions *state);
6969
int ultrix_partition(struct parsed_partitions *state);
70+
int amlogic_partition(struct parsed_partitions *state);

block/partitions/core.c

+3
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ static int (*check_part[])(struct parsed_partitions *) = {
8181
#endif
8282
#ifdef CONFIG_SYSV68_PARTITION
8383
sysv68_partition,
84+
#endif
85+
#ifdef CONFIG_AMLOGIC_PARTITION
86+
amlogic_partition,
8487
#endif
8588
NULL
8689
};

0 commit comments

Comments
 (0)