1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl>
7 * Standard Xiaomi firmware image consists of:
12 * Each blob section consists of:
16 * Signature consists of:
33 #if !defined(__BYTE_ORDER)
34 #error "Unknown byte order"
37 #if __BYTE_ORDER == __BIG_ENDIAN
38 #define cpu_to_le32(x) bswap_32(x)
39 #define le32_to_cpu(x) bswap_32(x)
40 #define cpu_to_be32(x) (x)
41 #define be32_to_cpu(x) (x)
42 #define cpu_to_le16(x) bswap_16(x)
43 #define le16_to_cpu(x) bswap_16(x)
44 #define cpu_to_be16(x) (x)
45 #define be16_to_cpu(x) (x)
46 #elif __BYTE_ORDER == __LITTLE_ENDIAN
47 #define cpu_to_le32(x) (x)
48 #define le32_to_cpu(x) (x)
49 #define cpu_to_be32(x) bswap_32(x)
50 #define be32_to_cpu(x) bswap_32(x)
51 #define cpu_to_le16(x) (x)
52 #define le16_to_cpu(x) (x)
53 #define cpu_to_be16(x) bswap_16(x)
54 #define be16_to_cpu(x) bswap_16(x)
56 #error "Unsupported endianness"
59 #define DEVICE_ID_MIWIFI_R1CM 0x0003
60 #define DEVICE_ID_MIWIFI_R2D 0x0004
61 #define DEVICE_ID_MIWIFI_R1CL 0x0005
62 #define DEVICE_ID_MIWIFI_R3 0x0007
63 #define DEVICE_ID_MIWIFI_R3D 0x0008
64 #define DEVICE_ID_MIWIFI_R3G 0x000d
65 #define DEVICE_ID_MIWIFI_R4CM 0x0012
66 #define DEVICE_ID_MIWIFI_R2100 0x0016
67 #define DEVICE_ID_MIWIFI_RA70 0x0025
69 #define BLOB_ALIGNMENT 0x4
71 #define BLOB_TYPE_UBOOT 0x0001
72 #define BLOB_TYPE_FW_UIMAGE 0x0004 /* Found in r1cl, r1cm */
73 #define BLOB_TYPE_FW_OS2 0x0006
74 #define BLOB_TYPE_FW_UIMAGE2 0x0007 /* Found in r4cm */
78 struct xiaomi_header
{
80 uint32_t signature_offset
;
84 uint32_t blob_offsets
[8];
87 struct xiaomi_blob_header
{
89 uint32_t flash_offset
;
96 struct xiaomi_signature_header
{
99 uint8_t content
[0x100];
104 struct xiaomifw_blob_info
{
105 struct xiaomi_blob_header header
;
110 struct xiaomifw_info
{
111 struct xiaomi_header header
;
113 struct xiaomifw_blob_info blobs
[8];
114 size_t signature_offset
;
118 static inline size_t xiaomifw_min(size_t x
, size_t y
) {
119 return x
< y
? x
: y
;
124 const char *device_name
;
127 static const struct device_map device_names
[] = {
128 { DEVICE_ID_MIWIFI_R1CM
, "r1cm" },
129 { DEVICE_ID_MIWIFI_R2D
, "r2d" },
130 { DEVICE_ID_MIWIFI_R1CL
, "r1cl" },
131 { DEVICE_ID_MIWIFI_R3
, "r3" },
132 { DEVICE_ID_MIWIFI_R3D
, "r3d" },
133 { DEVICE_ID_MIWIFI_R3G
, "r3g" },
134 { DEVICE_ID_MIWIFI_R4CM
, "r4cm" },
135 { DEVICE_ID_MIWIFI_R2100
, "r2100" },
136 { DEVICE_ID_MIWIFI_RA70
, "ra70" },
139 const char *xiaomifw_device_name(int device_id
) {
142 for (i
= 0; i
< sizeof(device_names
); i
++) {
143 if (device_names
[i
].device_id
== device_id
) {
144 return device_names
[i
].device_name
;
151 const int xiaomifw_device_id(const char *device_name
) {
154 for (i
= 0; i
< sizeof(device_names
); i
++) {
155 if (!strcmp(device_names
[i
].device_name
, device_name
)) {
156 return device_names
[i
].device_id
;
163 /**************************************************
165 **************************************************/
167 static const uint32_t crc32_tbl
[] = {
168 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
169 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
170 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
171 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
172 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
173 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
174 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
175 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
176 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
177 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
178 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
179 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
180 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
181 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
182 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
183 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
184 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
185 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
186 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
187 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
188 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
189 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
190 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
191 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
192 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
193 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
194 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
195 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
196 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
197 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
198 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
199 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
200 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
201 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
202 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
203 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
204 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
205 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
206 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
207 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
208 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
209 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
210 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
211 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
212 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
213 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
214 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
215 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
216 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
217 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
218 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
219 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
220 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
221 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
222 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
223 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
224 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
225 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
226 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
227 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
228 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
229 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
230 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
231 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
234 uint32_t xiaomifw_crc32(uint32_t crc
, const void *buf
, size_t len
) {
235 const uint8_t *in
= buf
;
238 crc
= crc32_tbl
[(crc
^ *in
) & 0xff] ^ (crc
>> 8);
246 /**************************************************
248 **************************************************/
250 static FILE *xiaomifw_open(const char *pathname
, const char *mode
) {
254 return fopen(pathname
, mode
);
256 if (isatty(fileno(stdin
))) {
257 fprintf(stderr
, "Reading from TTY stdin is unsupported\n");
261 if (fstat(fileno(stdin
), &st
)) {
262 fprintf(stderr
, "Failed to fstat stdin: %d\n", -errno
);
266 if (S_ISFIFO(st
.st_mode
)) {
267 fprintf(stderr
, "Reading from pipe stdin is unsupported\n");
274 static void xiaomifw_close(FILE *fp
) {
279 /**************************************************
280 * Existing firmware parser
281 **************************************************/
283 static int xiaomifw_parse(FILE *fp
, struct xiaomifw_info
*info
) {
284 struct xiaomi_header
*header
= &info
->header
;
292 memset(info
, 0, sizeof(*info
));
296 if (fstat(fileno(fp
), &st
)) {
298 fprintf(stderr
, "Failed to fstat: %d\n", err
);
301 info
->file_size
= st
.st_size
;
305 if (fread(header
, 1, sizeof(*header
), fp
) != sizeof(*header
)) {
306 fprintf(stderr
, "Failed to read Xiaomi header\n");
310 if (strncmp(header
->magic
, "HDR1", 4)) {
311 fprintf(stderr
, "Invalid Xiaomi header magic\n");
314 info
->signature_offset
= le32_to_cpu(header
->signature_offset
);
318 fseek(fp
, 12, SEEK_SET
);
320 info
->crc32
= 0xffffffff;
321 length
= info
->file_size
- 12;
322 while (length
&& (bytes
= fread(buf
, 1, xiaomifw_min(sizeof(buf
), length
), fp
)) > 0) {
323 info
->crc32
= xiaomifw_crc32(info
->crc32
, buf
, bytes
);
327 fprintf(stderr
, "Failed to read last %zd B of data\n", length
);
331 if (info
->crc32
!= le32_to_cpu(header
->crc32
)) {
332 fprintf(stderr
, "Invalid data crc32: 0x%08x instead of 0x%08x\n", info
->crc32
, le32_to_cpu(header
->crc32
));
338 for (i
= 0; i
< sizeof(info
->blobs
); i
++) {
339 size_t offset
= le32_to_cpu(info
->header
.blob_offsets
[i
]);
340 struct xiaomifw_blob_info
*file_info
= &info
->blobs
[i
];
346 fseek(fp
, offset
, SEEK_SET
);
348 if (fread(&file_info
->header
, 1, sizeof(file_info
->header
), fp
) != sizeof(file_info
->header
)) {
349 fprintf(stderr
, "Failed to read file Xiaomi header\n");
353 file_info
->offset
= offset
;
354 file_info
->size
= le32_to_cpu(file_info
->header
.size
);
356 offset
+= sizeof(file_info
->header
) + file_info
->size
;
357 offset
= (offset
+ 4) & ~(0x4 - 1);
363 /**************************************************
365 **************************************************/
367 static int xiaomifw_info(int argc
, char **argv
) {
368 struct xiaomifw_info info
;
369 const char *pathname
= NULL
;
376 while ((c
= getopt(argc
, argv
, "i:")) != -1) {
384 fp
= xiaomifw_open(pathname
, "r");
386 fprintf(stderr
, "Failed to open Xiaomi firmware image\n");
391 err
= xiaomifw_parse(fp
, &info
);
393 fprintf(stderr
, "Failed to parse Xiaomi firmware image\n");
397 device_id
= le16_to_cpu(info
.header
.device_id
);
399 printf("Device ID: 0x%04x (%s)\n", device_id
, xiaomifw_device_name(device_id
));
400 printf("CRC32: 0x%08x\n", info
.crc32
);
401 printf("Signature offset: 0x%08zx\n", info
.signature_offset
);
402 for (i
= 0; i
< sizeof(info
.blobs
) && info
.blobs
[i
].offset
; i
++) {
403 struct xiaomifw_blob_info
*file_info
= &info
.blobs
[i
];
405 printf("[Blob %d] offset:0x%08zx flash_offset:0x%08x size:0x%08zx type:0x%04x name:%s\n", i
, file_info
->offset
, file_info
->header
.flash_offset
, file_info
->size
, file_info
->header
.type
, file_info
->header
.name
);
414 /**************************************************
416 **************************************************/
418 static ssize_t
xiaomifw_create_append_zeros(FILE *fp
, size_t length
) {
421 buf
= malloc(length
);
424 memset(buf
, 0, length
);
426 if (fwrite(buf
, 1, length
, fp
) != length
) {
427 fprintf(stderr
, "Failed to write %zu B of zeros\n", length
);
437 static ssize_t
xiaomifw_create_append_file(FILE *fp
, char *blob
) {
438 struct xiaomi_blob_header header
= {
439 .magic
= le32_to_cpu(0x0000babe),
444 char *in_path
= NULL
;
456 /* sscanf and strtok can't handle optional fields (e.g. "::firmware.bin:/tmp/foo.bin") */
460 if ((tok
= strchr(resptr
, ':'))) {
470 header
.flash_offset
= cpu_to_le32(strtoul(p
, NULL
, 0));
477 strncpy(header
.name
, p
, sizeof(header
.name
));
486 fprintf(stderr
, "Failed to parse blob info\n");
490 in
= fopen(in_path
, "r");
492 fprintf(stderr
, "Failed to open %s\n", in_path
);
496 if (fstat(fileno(in
), &st
)) {
498 fprintf(stderr
, "Failed to fstat: %d\n", err
);
501 header
.size
= cpu_to_le32(st
.st_size
);
504 if (!strcmp(type
, "uimage")) {
505 header
.type
= cpu_to_le32(BLOB_TYPE_FW_UIMAGE
);
506 } else if (!strcmp(type
, "uimage2")) {
507 header
.type
= cpu_to_le32(BLOB_TYPE_FW_UIMAGE2
);
509 fprintf(stderr
, "Unsupported blob type: %s\n", type
);
514 bytes
= fwrite(&header
, 1, sizeof(header
), fp
);
515 if (bytes
!= sizeof(header
)) {
516 fprintf(stderr
, "Failed to write blob header\n");
521 while ((bytes
= fread(buf
, 1, sizeof(buf
), in
)) > 0) {
522 if (fwrite(buf
, 1, bytes
, fp
) != bytes
) {
523 fprintf(stderr
, "Failed to write %zu B of blob\n", bytes
);
531 if (length
& (BLOB_ALIGNMENT
- 1)) {
532 size_t padding
= BLOB_ALIGNMENT
- (length
% BLOB_ALIGNMENT
);
534 bytes
= xiaomifw_create_append_zeros(fp
, padding
);
535 if (bytes
!= padding
) {
536 fprintf(stderr
, "Failed to align blob\n");
545 static ssize_t
xiaomifw_create_write_signature(FILE *fp
) {
546 struct xiaomi_signature_header header
= {
550 bytes
= fwrite(&header
, 1, sizeof(header
), fp
);
551 if (bytes
!= sizeof(header
)) {
552 fprintf(stderr
, "Failed to write blob header\n");
559 static int xiaomifw_create(int argc
, char **argv
) {
560 struct xiaomi_header header
= {
561 .magic
= { 'H', 'D', 'R', '1' },
563 uint32_t crc32
= 0xffffffff;
575 fprintf(stderr
, "No Xiaomi firmware image pathname passed\n");
581 while ((c
= getopt(argc
, argv
, "m:b:")) != -1) {
584 device_id
= xiaomifw_device_id(optarg
);
587 fprintf(stderr
, "Failed to find device %s\n", optarg
);
590 header
.device_id
= device_id
;
599 fp
= fopen(argv
[2], "w+");
601 fprintf(stderr
, "Failed to open %s\n", argv
[2]);
606 offset
= sizeof(header
);
607 fseek(fp
, offset
, SEEK_SET
);
610 while ((c
= getopt(argc
, argv
, "m:b:")) != -1) {
615 if (blob_idx
>= sizeof(header
.blob_offsets
)) {
617 fprintf(stderr
, "Too many blobs specified\n");
620 bytes
= xiaomifw_create_append_file(fp
, optarg
);
623 fprintf(stderr
, "Failed to append blob: %d\n", err
);
626 header
.blob_offsets
[blob_idx
++] = cpu_to_le32(offset
);
634 bytes
= xiaomifw_create_write_signature(fp
);
637 fprintf(stderr
, "Failed to write signature: %d\n", err
);
640 header
.signature_offset
= cpu_to_le32(offset
);
643 crc32
= xiaomifw_crc32(crc32
, (uint8_t *)&header
+ 12, sizeof(header
) - 12);
644 fseek(fp
, sizeof(header
), SEEK_SET
);
645 length
= offset
- sizeof(header
);
646 while (length
&& (bytes
= fread(buf
, 1, xiaomifw_min(sizeof(buf
), length
), fp
)) > 0) {
647 crc32
= xiaomifw_crc32(crc32
, buf
, bytes
);
652 fprintf(stderr
, "Failed to calculate CRC32 over the last %zd B of data\n", length
);
656 header
.crc32
= cpu_to_le32(crc32
);
660 bytes
= fwrite(&header
, 1, sizeof(header
), fp
);
661 if (bytes
!= sizeof(header
)) {
662 fprintf(stderr
, "Failed to write header\n");
672 /**************************************************
674 **************************************************/
676 static int xiaomifw_extract(int argc
, char **argv
) {
677 struct xiaomifw_info info
;
678 const char *pathname
= NULL
;
679 const char *name
= NULL
;
689 while ((c
= getopt(argc
, argv
, "i:n:")) != -1) {
702 fprintf(stderr
, "No data to extract specified\n");
706 fp
= xiaomifw_open(pathname
, "r");
708 fprintf(stderr
, "Failed to open Xiaomi firmware image\n");
713 err
= xiaomifw_parse(fp
, &info
);
715 fprintf(stderr
, "Failed to parse Xiaomi firmware image\n");
719 for (i
= 0; i
< sizeof(info
.blobs
) && info
.blobs
[i
].offset
; i
++) {
720 struct xiaomifw_blob_info
*file_info
= &info
.blobs
[i
];
722 if (!strcmp(file_info
->header
.name
, name
)) {
723 offset
= file_info
->offset
;
724 size
= file_info
->size
;
728 if (!offset
|| !size
) {
730 fprintf(stderr
, "Failed to find requested data in input image\n");
734 fseek(fp
, offset
+ sizeof(struct xiaomi_blob_header
), SEEK_SET
);
735 while (size
&& (bytes
= fread(buf
, 1, xiaomifw_min(sizeof(buf
), size
), fp
)) > 0) {
736 fwrite(buf
, bytes
, 1, stdout
);
741 fprintf(stderr
, "Failed to read last %zd B of data\n", size
);
751 /**************************************************
753 **************************************************/
755 static void usage() {
758 printf("Info about a Xiaomi firmware image:\n");
759 printf("\txiaomifw info <options>\n");
760 printf("\t-i <file>\t\t\t\t\tinput Xiaomi firmware image\n");
762 printf("Creating a new Xiaomi firmware image:\n");
763 printf("\txiaomifw create <file> [options]\n");
764 printf("\t-m <model>\t\t\t\t\tmodel name (e.g. \"r4cm\")\n");
765 printf("\t-b <flash_offset>:<type>:<name>:<path>\t\tblob to include\n");
767 printf("Extracting from a Xiaomi firmware image:\n");
768 printf("\txiaomifw extract <options>\n");
769 printf("\t-i <file>\t\t\t\t\tinput Xiaomi firmware image\n");
770 printf("\t-n <type>\t\t\t\t\tname of blob to extract (e.g. \"firmware.bin\")\n");
772 printf("Examples:\n");
773 printf("\txiaomifw info -i miwifi_r4cm_firmware_c6fa8_3.0.23_INT.bin\n");
774 printf("\txiaomifw extract -i miwifi_r4cm_firmware_c6fa8_3.0.23_INT.bin -n firmware.bin\n");
775 printf("\txiaomifw create \\\n");
776 printf("\t\t-m r1cm \\\n");
777 printf("\t\t-b ::xiaoqiang_version:/tmp/xiaoqiang_version \\\n");
778 printf("\t\t-b 0x160000:uimage2:firmware.bin:/tmp/custom.bin\n");
781 int main(int argc
, char **argv
) {
784 if (!strcmp(argv
[1], "info"))
785 return xiaomifw_info(argc
, argv
);
786 else if (!strcmp(argv
[1], "create"))
787 return xiaomifw_create(argc
, argv
);
788 else if (!strcmp(argv
[1], "extract"))
789 return xiaomifw_extract(argc
, argv
);