// SPDX-License-Identifier: GPL-2.0-or-later /* * mkdlinkfw * * Copyright (C) 2018 Paweł Dembicki * * This tool is based on mktplinkfw. * Copyright (C) 2009 Gabor Juhos * Copyright (C) 2008,2009 Wang Jian */ #include #include #include #include #include /* for unlink() */ #include #include /* for getopt() */ #include #include #include #include #include #include /*for crc32 */ #include "mkdlinkfw-lib.h" /* ARM update header 2.0 * used only in factory images to erase and flash selected area */ struct auh_header { uint8_t rom_id[12]; /* 12-bit rom-id unique per router type */ uint16_t derange; /* used for scramble header */ uint16_t image_checksum; /* jboot_checksum of flashed data */ uint32_t space1; /* zeros */ uint32_t space2; /* zeros */ uint16_t space3; /* zerosu */ uint8_t lpvs; /* must be 0x01 */ uint8_t mbz; /* bust be 0 */ uint32_t time_stamp; /* timestamp calculated in jboot way */ uint32_t erase_start; /* erase start address */ uint32_t erase_length; /* erase length address */ uint32_t data_offset; /* data start address */ uint32_t data_length; /* data length address */ uint32_t space4; /* zeros */ uint32_t space5; /* zeros */ uint32_t space6; /* zeros */ uint32_t space7; /* zeros */ uint16_t header_id; /* magic 0x4842 */ uint16_t header_version; /* 0x02 for 2.0 */ uint16_t space8; /* zeros */ uint8_t section_id; /* section id */ uint8_t image_info_type; /* (?) 0x04 in factory images */ uint32_t image_info_offset; /* (?) zeros in factory images */ uint16_t family_member; /* unique per router type */ uint16_t header_checksum; /* negated jboot_checksum of header data */ }; struct stag_header { /* used only of sch2 wrapped kernel data */ uint8_t cmark; /* in factory 0xFF ,in sysuograde must be the same as id */ uint8_t id; /* 0x04 */ uint16_t magic; /* magic 0x2B24 */ uint32_t time_stamp; /* timestamp calculated in jboot way */ uint32_t image_length; /* lentgh of kernel + sch2 header */ uint16_t image_checksum; /* negated jboot_checksum of sch2 + kernel */ uint16_t tag_checksum; /* negated jboot_checksum of stag header data */ }; struct sch2_header { /* used only in kernel partitions */ uint16_t magic; /* magic 0x2124 */ uint8_t cp_type; /* 0x00 for flat, 0x01 for jz, 0x02 for gzip, 0x03 for lzma */ uint8_t version; /* 0x02 for sch2 */ uint32_t ram_addr; /* ram entry address */ uint32_t image_len; /* kernel image length */ uint32_t image_crc32; /* kernel image crc */ uint32_t start_addr; /* ram start address */ uint32_t rootfs_addr; /* rootfs flash address */ uint32_t rootfs_len; /* rootfls length */ uint32_t rootfs_crc32; /* rootfs crc32 */ uint32_t header_crc32; /* sch2 header crc32, durring calculation this area is replaced by zero */ uint16_t header_length; /* sch2 header length: 0x28 */ uint16_t cmd_line_length; /* cmd line length, known zeros */ }; /* globals */ static struct file_info inspect_info; struct file_info kernel_info; struct file_info rootfs_info; struct file_info image_info; char *ofname; char *progname; uint32_t firmware_size; uint32_t image_offset; uint16_t family_member; char *rom_id[12] = { 0 }; char image_type; static void usage(int status) { fprintf(stderr, "Usage: %s [OPTIONS...]\n", progname); fprintf(stderr, "\n" "Options:\n" " -i inspect given firmware file \n" " -f set family member id (hexval prefixed with 0x)\n" " -F read image and convert it to FACTORY\n" " -k read kernel image from the file \n" " -r read rootfs image from the file \n" " -o write output to the file \n" " -s set firmware partition size\n" " -m set rom id to (12-bit string val: \"DLK*********\")\n" " -h show this screen\n"); exit(status); } void print_auh_header(struct auh_header *printed_header) { printf("\trom_id: %s\n" "\tderange: 0x%04X\n" "\timage_checksum: 0x%04X\n" "\tspace1: 0x%08X\n" "\tspace2: 0x%08X\n" "\tspace3: 0x%04X\n" "\tlpvs: 0x%02X\n" "\tmbz: 0x%02X\n" "\ttime_stamp: 0x%08X\n" "\terase_start: 0x%08X\n" "\terase_length: 0x%08X\n" "\tdata_offset: 0x%08X\n" "\tdata_length: 0x%08X\n" "\tspace4: 0x%08X\n" "\tspace5: 0x%08X\n" "\tspace6: 0x%08X\n" "\tspace7: 0x%08X\n" "\theader_id: 0x%04X\n" "\theader_version: 0x%02X\n" "\tspace8: 0x%04X\n" "\tsection_id: 0x%02X\n" "\timage_info_type: 0x%02X\n" "\timage_info_offset 0x%08X\n" "\tfamily_member: 0x%04X\n" "\theader_checksum: 0x%04X\n", printed_header->rom_id, printed_header->derange, printed_header->image_checksum, printed_header->space1, printed_header->space2, printed_header->space3, printed_header->lpvs, printed_header->mbz, printed_header->time_stamp, printed_header->erase_start, printed_header->erase_length, printed_header->data_offset, printed_header->data_length, printed_header->space4, printed_header->space5, printed_header->space6, printed_header->space7, printed_header->header_id, printed_header->header_version, printed_header->space8, printed_header->section_id, printed_header->image_info_type, printed_header->image_info_offset, printed_header->family_member, printed_header->header_checksum); } void print_stag_header(struct stag_header *printed_header) { printf("\tcmark: 0x%02X\n" "\tid: 0x%02X\n" "\tmagic: 0x%04X\n" "\ttime_stamp: 0x%08X\n" "\timage_length: 0x%04X\n" "\timage_checksum: 0x%04X\n" "\ttag_checksum: 0x%04X\n", printed_header->cmark, printed_header->id, printed_header->magic, printed_header->time_stamp, printed_header->image_length, printed_header->image_checksum, printed_header->tag_checksum); } void print_sch2_header(struct sch2_header *printed_header) { printf("\tmagic: 0x%04X\n" "\tcp_type: 0x%02X\n" "\tversion: 0x%02X\n" "\tram_addr: 0x%08X\n" "\timage_len: 0x%08X\n" "\timage_crc32: 0x%08X\n" "\tstart_addr: 0x%08X\n" "\trootfs_addr: 0x%08X\n" "\trootfs_len: 0x%08X\n" "\trootfs_crc32: 0x%08X\n" "\theader_crc32: 0x%08X\n" "\theader_length: 0x%04X\n" "\tcmd_line_length: 0x%04X\n", printed_header->magic, printed_header->cp_type, printed_header->version, printed_header->ram_addr, printed_header->image_len, printed_header->image_crc32, printed_header->start_addr, printed_header->rootfs_addr, printed_header->rootfs_len, printed_header->rootfs_crc32, printed_header->header_crc32, printed_header->header_length, printed_header->cmd_line_length); } static int find_auh_headers(char *buf) { char *tmp_buf = buf; struct auh_header *tmp_header[MAX_HEADER_COUNTER]; int header_counter = 0; int ret = EXIT_FAILURE; while (tmp_buf - buf <= inspect_info.file_size - AUH_SIZE) { if (!memcmp(tmp_buf, AUH_MAGIC, 3)) { if (((struct auh_header *)tmp_buf)->header_checksum == (uint16_t) ~jboot_checksum(0, (uint16_t *) tmp_buf, AUH_SIZE - 2)) { uint16_t checksum = 0; printf("Find proper AUH header at: 0x%lX!\n", tmp_buf - buf); tmp_header[header_counter] = (struct auh_header *)tmp_buf; checksum = jboot_checksum(0, (uint16_t *) ((char *) tmp_header [header_counter] + AUH_SIZE), tmp_header [header_counter]->data_length); if (tmp_header[header_counter]->image_checksum == checksum) printf("Image checksum ok.\n"); else ERR("Image checksum incorrect! Stored: 0x%X Calculated: 0x%X\n", tmp_header[header_counter]->image_checksum, checksum); header_counter++; if (header_counter > MAX_HEADER_COUNTER) break; } } tmp_buf++; } if (header_counter == 0) ERR("Can't find proper AUH header!\n"); else if (header_counter > MAX_HEADER_COUNTER) ERR("To many AUH headers!\n"); else { for (int i = 0; i < header_counter; i++) { printf("AUH %d:\n", i); print_auh_header(tmp_header[i]); } ret = EXIT_SUCCESS; } return ret; } static int check_stag_header(char *buf, struct stag_header *header) { int ret = EXIT_FAILURE; uint8_t cmark_tmp = header->cmark; header->cmark = header->id; if (header->tag_checksum == (uint16_t) ~jboot_checksum(0, (uint16_t *) header, STAG_SIZE - 2)) { uint16_t checksum = 0; printf("Find proper STAG header at: 0x%lX!\n", (char *)header - buf); checksum = jboot_checksum(0, (uint16_t *) ((char *)header + STAG_SIZE), header->image_length); if (header->image_checksum == checksum) { printf("Image checksum ok.\n"); header->cmark = cmark_tmp; print_stag_header(header); ret = EXIT_SUCCESS; } else ERR("Image checksum incorrect! Stored: 0x%X Calculated: 0x%X\n", header->image_checksum, checksum); } else ERR("STAG header checksum incorrect!"); header->cmark = cmark_tmp; return ret; } static int check_sch2_header(char *buf, struct sch2_header *header) { int ret = EXIT_FAILURE; uint32_t crc32_tmp = header->header_crc32; header->header_crc32 = 0; if (crc32_tmp == crc32(0, (uint8_t *) header, header->header_length)) { uint32_t crc32_val; printf("Find proper SCH2 header at: 0x%lX!\n", (char *)header - buf); crc32_val = crc32(0, (uint8_t *) header + header->header_length, header->image_len); if (header->image_crc32 == crc32_val) { printf("Kernel checksum ok.\n"); header->header_crc32 = crc32_tmp; print_sch2_header(header); ret = EXIT_SUCCESS; } else ERR("Kernel checksum incorrect! Stored: 0x%X Calculated: 0x%X\n", header->image_crc32, crc32_val); } else ERR("SCH2 header checksum incorrect!"); header->header_crc32 = crc32_tmp; return ret; } static int inspect_fw(void) { char *buf; struct stag_header *stag_header_kernel; struct sch2_header *sch2_header_kernel; int ret = EXIT_FAILURE; buf = malloc(inspect_info.file_size); if (!buf) { ERR("no memory for buffer!\n"); goto out; } ret = read_to_buf(&inspect_info, buf); if (ret) goto out_free_buf; ret = find_auh_headers(buf); if (ret) goto out_free_buf; stag_header_kernel = (struct stag_header *)(buf + AUH_SIZE); ret = check_stag_header(buf, stag_header_kernel); if (ret) goto out_free_buf; sch2_header_kernel = (struct sch2_header *)(buf + AUH_SIZE + STAG_SIZE); ret = check_sch2_header(buf, sch2_header_kernel); if (ret) goto out_free_buf; out_free_buf: free(buf); out: return ret; } static int check_options(void) { int ret; if (inspect_info.file_name) { ret = get_file_stat(&inspect_info); if (ret) return ret; return 0; } return 0; } int fill_sch2(struct sch2_header *header, char *kernel_ptr, char *rootfs_ptr) { header->magic = SCH2_MAGIC; header->cp_type = LZMA; header->version = SCH2_VER; header->ram_addr = RAM_LOAD_ADDR; header->image_len = kernel_info.file_size; header->image_crc32 = crc32(0, (uint8_t *) kernel_ptr, kernel_info.file_size); header->start_addr = RAM_ENTRY_ADDR; header->rootfs_addr = image_offset + STAG_SIZE + SCH2_SIZE + kernel_info.file_size; header->rootfs_len = rootfs_info.file_size; header->rootfs_crc32 = crc32(0, (uint8_t *) rootfs_ptr, rootfs_info.file_size); header->header_crc32 = 0; header->header_length = SCH2_SIZE; header->cmd_line_length = 0; header->header_crc32 = crc32(0, (uint8_t *) header, header->header_length); return EXIT_SUCCESS; } int fill_stag(struct stag_header *header, uint32_t length) { header->cmark = STAG_ID; header->id = STAG_ID; header->magic = STAG_MAGIC; header->time_stamp = jboot_timestamp(); header->image_length = length + SCH2_SIZE; header->image_checksum = jboot_checksum(0, (uint16_t *) ((char *)header + STAG_SIZE), header->image_length); header->tag_checksum = ~jboot_checksum(0, (uint16_t *) header, STAG_SIZE - 2); if (image_type == FACTORY) header->cmark = STAG_CMARK_FACTORY; return EXIT_SUCCESS; }; int fill_auh(struct auh_header *header, uint32_t length) { memcpy(header->rom_id, rom_id, 12); header->derange = 0; header->image_checksum = jboot_checksum(0, (uint16_t *) ((char *)header + AUH_SIZE), length); header->space1 = 0; header->space2 = 0; header->space3 = 0; header->lpvs = AUH_LVPS; header->mbz = 0; header->time_stamp = jboot_timestamp(); header->erase_start = image_offset; header->erase_length = firmware_size; header->data_offset = image_offset; header->data_length = length; header->space4 = 0; header->space5 = 0; header->space6 = 0; header->space7 = 0; header->header_id = AUH_HDR_ID; header->header_version = AUH_HDR_VER; header->space8 = 0; header->section_id = AUH_SEC_ID; header->image_info_type = AUH_INFO_TYPE; header->image_info_offset = 0; header->family_member = family_member; header->header_checksum = ~jboot_checksum(0, (uint16_t *) header, AUH_SIZE - 2); return EXIT_SUCCESS; } int build_fw(void) { char *buf; char *kernel_ptr; char *rootfs_ptr; int ret = EXIT_FAILURE; int writelen; struct stag_header *stag_header_kernel; struct sch2_header *sch2_header_kernel; if (!kernel_info.file_name | !rootfs_info.file_name) goto out; ret = get_file_stat(&kernel_info); if (ret) goto out; ret = get_file_stat(&rootfs_info); if (ret) goto out; buf = malloc(firmware_size); if (!buf) { ERR("no memory for buffer\n"); goto out; } if (rootfs_info.file_size + kernel_info.file_size + ALL_HEADERS_SIZE > firmware_size) { ERR("data is bigger than firmware_size!\n"); goto out; } memset(buf, 0xff, firmware_size); stag_header_kernel = (struct stag_header *)buf; sch2_header_kernel = (struct sch2_header *)((char *)stag_header_kernel + STAG_SIZE); kernel_ptr = (char *)sch2_header_kernel + SCH2_SIZE; ret = read_to_buf(&kernel_info, kernel_ptr); if (ret) goto out_free_buf; rootfs_ptr = kernel_ptr + kernel_info.file_size; ret = read_to_buf(&rootfs_info, rootfs_ptr); if (ret) goto out_free_buf; writelen = rootfs_ptr + rootfs_info.file_size - buf; fill_sch2(sch2_header_kernel, kernel_ptr, rootfs_ptr); fill_stag(stag_header_kernel, kernel_info.file_size); ret = write_fw(ofname, buf, writelen); if (ret) goto out_free_buf; ret = EXIT_SUCCESS; out_free_buf: free(buf); out: return ret; } int wrap_fw(void) { char *buf; char *image_ptr; int ret = EXIT_FAILURE; int writelen; struct auh_header *auh_header_kernel; if (!image_info.file_name) goto out; ret = get_file_stat(&image_info); if (ret) goto out; buf = malloc(firmware_size); if (!buf) { ERR("no memory for buffer\n"); goto out; } if (image_info.file_size + AUH_SIZE > firmware_size) { ERR("data is bigger than firmware_size!\n"); goto out; } if (!family_member) { ERR("No family_member!\n"); goto out; } if (!(rom_id[0])) { ERR("No rom_id!\n"); goto out; } memset(buf, 0xff, firmware_size); image_ptr = (char *)(buf + AUH_SIZE); ret = read_to_buf(&image_info, image_ptr); if (ret) goto out_free_buf; writelen = image_ptr + image_info.file_size - buf; auh_header_kernel = (struct auh_header *)buf; fill_auh(auh_header_kernel, writelen - AUH_SIZE); ret = write_fw(ofname, buf, writelen); if (ret) goto out_free_buf; ret = EXIT_SUCCESS; out_free_buf: free(buf); out: return ret; } int main(int argc, char *argv[]) { int ret = EXIT_FAILURE; progname = basename(argv[0]); image_type = SYSUPGRADE; family_member = 0; firmware_size = 0; image_offset = JBOOT_SIZE; while (1) { int c; c = getopt(argc, argv, "f:F:i:hk:m:o:O:r:s:"); if (c == -1) break; switch (c) { case 'f': sscanf(optarg, "0x%hx", &family_member); break; case 'F': image_info.file_name = optarg; image_type = FACTORY; break; case 'i': inspect_info.file_name = optarg; break; case 'k': kernel_info.file_name = optarg; break; case 'm': if (strlen(optarg) == 12) memcpy(rom_id, optarg, 12); break; case 'r': rootfs_info.file_name = optarg; break; case 'O': sscanf(optarg, "0x%x", &image_offset); break; case 'o': ofname = optarg; break; case 's': sscanf(optarg, "0x%x", &firmware_size); break; default: usage(EXIT_FAILURE); break; } } ret = check_options(); if (ret) goto out; if (!inspect_info.file_name) { if (image_type == FACTORY) ret = wrap_fw(); else ret = build_fw(); } else ret = inspect_fw(); out: return ret; }