18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Parser/loader for IHEX formatted data. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright © 2008 David Woodhouse <dwmw2@infradead.org> 68c2ecf20Sopenharmony_ci * Copyright © 2005 Jan Harkes <jaharkes@cs.cmu.edu> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <stdint.h> 108c2ecf20Sopenharmony_ci#include <arpa/inet.h> 118c2ecf20Sopenharmony_ci#include <stdio.h> 128c2ecf20Sopenharmony_ci#include <errno.h> 138c2ecf20Sopenharmony_ci#include <sys/types.h> 148c2ecf20Sopenharmony_ci#include <sys/stat.h> 158c2ecf20Sopenharmony_ci#include <sys/mman.h> 168c2ecf20Sopenharmony_ci#include <fcntl.h> 178c2ecf20Sopenharmony_ci#include <string.h> 188c2ecf20Sopenharmony_ci#include <unistd.h> 198c2ecf20Sopenharmony_ci#include <stdlib.h> 208c2ecf20Sopenharmony_ci#define _GNU_SOURCE 218c2ecf20Sopenharmony_ci#include <getopt.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask)) 258c2ecf20Sopenharmony_ci#define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1) 268c2ecf20Sopenharmony_ci#define ALIGN(x, a) __ALIGN_KERNEL((x), (a)) 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_cistruct ihex_binrec { 298c2ecf20Sopenharmony_ci struct ihex_binrec *next; /* not part of the real data structure */ 308c2ecf20Sopenharmony_ci uint32_t addr; 318c2ecf20Sopenharmony_ci uint16_t len; 328c2ecf20Sopenharmony_ci uint8_t data[]; 338c2ecf20Sopenharmony_ci}; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci/** 368c2ecf20Sopenharmony_ci * nybble/hex are little helpers to parse hexadecimal numbers to a byte value 378c2ecf20Sopenharmony_ci **/ 388c2ecf20Sopenharmony_cistatic uint8_t nybble(const uint8_t n) 398c2ecf20Sopenharmony_ci{ 408c2ecf20Sopenharmony_ci if (n >= '0' && n <= '9') return n - '0'; 418c2ecf20Sopenharmony_ci else if (n >= 'A' && n <= 'F') return n - ('A' - 10); 428c2ecf20Sopenharmony_ci else if (n >= 'a' && n <= 'f') return n - ('a' - 10); 438c2ecf20Sopenharmony_ci return 0; 448c2ecf20Sopenharmony_ci} 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistatic uint8_t hex(const uint8_t *data, uint8_t *crc) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci uint8_t val = (nybble(data[0]) << 4) | nybble(data[1]); 498c2ecf20Sopenharmony_ci *crc += val; 508c2ecf20Sopenharmony_ci return val; 518c2ecf20Sopenharmony_ci} 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic int process_ihex(uint8_t *data, ssize_t size); 548c2ecf20Sopenharmony_cistatic void file_record(struct ihex_binrec *record); 558c2ecf20Sopenharmony_cistatic int output_records(int outfd); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic int sort_records = 0; 588c2ecf20Sopenharmony_cistatic int wide_records = 0; 598c2ecf20Sopenharmony_cistatic int include_jump = 0; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic int usage(void) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci fprintf(stderr, "ihex2fw: Convert ihex files into binary " 648c2ecf20Sopenharmony_ci "representation for use by Linux kernel\n"); 658c2ecf20Sopenharmony_ci fprintf(stderr, "usage: ihex2fw [<options>] <src.HEX> <dst.fw>\n"); 668c2ecf20Sopenharmony_ci fprintf(stderr, " -w: wide records (16-bit length)\n"); 678c2ecf20Sopenharmony_ci fprintf(stderr, " -s: sort records by address\n"); 688c2ecf20Sopenharmony_ci fprintf(stderr, " -j: include records for CS:IP/EIP address\n"); 698c2ecf20Sopenharmony_ci return 1; 708c2ecf20Sopenharmony_ci} 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ciint main(int argc, char **argv) 738c2ecf20Sopenharmony_ci{ 748c2ecf20Sopenharmony_ci int infd, outfd; 758c2ecf20Sopenharmony_ci struct stat st; 768c2ecf20Sopenharmony_ci uint8_t *data; 778c2ecf20Sopenharmony_ci int opt; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci while ((opt = getopt(argc, argv, "wsj")) != -1) { 808c2ecf20Sopenharmony_ci switch (opt) { 818c2ecf20Sopenharmony_ci case 'w': 828c2ecf20Sopenharmony_ci wide_records = 1; 838c2ecf20Sopenharmony_ci break; 848c2ecf20Sopenharmony_ci case 's': 858c2ecf20Sopenharmony_ci sort_records = 1; 868c2ecf20Sopenharmony_ci break; 878c2ecf20Sopenharmony_ci case 'j': 888c2ecf20Sopenharmony_ci include_jump = 1; 898c2ecf20Sopenharmony_ci break; 908c2ecf20Sopenharmony_ci default: 918c2ecf20Sopenharmony_ci return usage(); 928c2ecf20Sopenharmony_ci } 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci if (optind + 2 != argc) 968c2ecf20Sopenharmony_ci return usage(); 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci if (!strcmp(argv[optind], "-")) 998c2ecf20Sopenharmony_ci infd = 0; 1008c2ecf20Sopenharmony_ci else 1018c2ecf20Sopenharmony_ci infd = open(argv[optind], O_RDONLY); 1028c2ecf20Sopenharmony_ci if (infd == -1) { 1038c2ecf20Sopenharmony_ci fprintf(stderr, "Failed to open source file: %s", 1048c2ecf20Sopenharmony_ci strerror(errno)); 1058c2ecf20Sopenharmony_ci return usage(); 1068c2ecf20Sopenharmony_ci } 1078c2ecf20Sopenharmony_ci if (fstat(infd, &st)) { 1088c2ecf20Sopenharmony_ci perror("stat"); 1098c2ecf20Sopenharmony_ci return 1; 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, infd, 0); 1128c2ecf20Sopenharmony_ci if (data == MAP_FAILED) { 1138c2ecf20Sopenharmony_ci perror("mmap"); 1148c2ecf20Sopenharmony_ci return 1; 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci if (!strcmp(argv[optind+1], "-")) 1188c2ecf20Sopenharmony_ci outfd = 1; 1198c2ecf20Sopenharmony_ci else 1208c2ecf20Sopenharmony_ci outfd = open(argv[optind+1], O_TRUNC|O_CREAT|O_WRONLY, 0644); 1218c2ecf20Sopenharmony_ci if (outfd == -1) { 1228c2ecf20Sopenharmony_ci fprintf(stderr, "Failed to open destination file: %s", 1238c2ecf20Sopenharmony_ci strerror(errno)); 1248c2ecf20Sopenharmony_ci return usage(); 1258c2ecf20Sopenharmony_ci } 1268c2ecf20Sopenharmony_ci if (process_ihex(data, st.st_size)) 1278c2ecf20Sopenharmony_ci return 1; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci return output_records(outfd); 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic int process_ihex(uint8_t *data, ssize_t size) 1338c2ecf20Sopenharmony_ci{ 1348c2ecf20Sopenharmony_ci struct ihex_binrec *record; 1358c2ecf20Sopenharmony_ci size_t record_size; 1368c2ecf20Sopenharmony_ci uint32_t offset = 0; 1378c2ecf20Sopenharmony_ci uint32_t data32; 1388c2ecf20Sopenharmony_ci uint8_t type, crc = 0, crcbyte = 0; 1398c2ecf20Sopenharmony_ci int i, j; 1408c2ecf20Sopenharmony_ci int line = 1; 1418c2ecf20Sopenharmony_ci int len; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci i = 0; 1448c2ecf20Sopenharmony_cinext_record: 1458c2ecf20Sopenharmony_ci /* search for the start of record character */ 1468c2ecf20Sopenharmony_ci while (i < size) { 1478c2ecf20Sopenharmony_ci if (data[i] == '\n') line++; 1488c2ecf20Sopenharmony_ci if (data[i++] == ':') break; 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci /* Minimum record length would be about 10 characters */ 1528c2ecf20Sopenharmony_ci if (i + 10 > size) { 1538c2ecf20Sopenharmony_ci fprintf(stderr, "Can't find valid record at line %d\n", line); 1548c2ecf20Sopenharmony_ci return -EINVAL; 1558c2ecf20Sopenharmony_ci } 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci len = hex(data + i, &crc); i += 2; 1588c2ecf20Sopenharmony_ci if (wide_records) { 1598c2ecf20Sopenharmony_ci len <<= 8; 1608c2ecf20Sopenharmony_ci len += hex(data + i, &crc); i += 2; 1618c2ecf20Sopenharmony_ci } 1628c2ecf20Sopenharmony_ci record_size = ALIGN(sizeof(*record) + len, 4); 1638c2ecf20Sopenharmony_ci record = malloc(record_size); 1648c2ecf20Sopenharmony_ci if (!record) { 1658c2ecf20Sopenharmony_ci fprintf(stderr, "out of memory for records\n"); 1668c2ecf20Sopenharmony_ci return -ENOMEM; 1678c2ecf20Sopenharmony_ci } 1688c2ecf20Sopenharmony_ci memset(record, 0, record_size); 1698c2ecf20Sopenharmony_ci record->len = len; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci /* now check if we have enough data to read everything */ 1728c2ecf20Sopenharmony_ci if (i + 8 + (record->len * 2) > size) { 1738c2ecf20Sopenharmony_ci fprintf(stderr, "Not enough data to read complete record at line %d\n", 1748c2ecf20Sopenharmony_ci line); 1758c2ecf20Sopenharmony_ci return -EINVAL; 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci record->addr = hex(data + i, &crc) << 8; i += 2; 1798c2ecf20Sopenharmony_ci record->addr |= hex(data + i, &crc); i += 2; 1808c2ecf20Sopenharmony_ci type = hex(data + i, &crc); i += 2; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci for (j = 0; j < record->len; j++, i += 2) 1838c2ecf20Sopenharmony_ci record->data[j] = hex(data + i, &crc); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci /* check CRC */ 1868c2ecf20Sopenharmony_ci crcbyte = hex(data + i, &crc); i += 2; 1878c2ecf20Sopenharmony_ci if (crc != 0) { 1888c2ecf20Sopenharmony_ci fprintf(stderr, "CRC failure at line %d: got 0x%X, expected 0x%X\n", 1898c2ecf20Sopenharmony_ci line, crcbyte, (unsigned char)(crcbyte-crc)); 1908c2ecf20Sopenharmony_ci return -EINVAL; 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci /* Done reading the record */ 1948c2ecf20Sopenharmony_ci switch (type) { 1958c2ecf20Sopenharmony_ci case 0: 1968c2ecf20Sopenharmony_ci /* old style EOF record? */ 1978c2ecf20Sopenharmony_ci if (!record->len) 1988c2ecf20Sopenharmony_ci break; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci record->addr += offset; 2018c2ecf20Sopenharmony_ci file_record(record); 2028c2ecf20Sopenharmony_ci goto next_record; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci case 1: /* End-Of-File Record */ 2058c2ecf20Sopenharmony_ci if (record->addr || record->len) { 2068c2ecf20Sopenharmony_ci fprintf(stderr, "Bad EOF record (type 01) format at line %d", 2078c2ecf20Sopenharmony_ci line); 2088c2ecf20Sopenharmony_ci return -EINVAL; 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci break; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci case 2: /* Extended Segment Address Record (HEX86) */ 2138c2ecf20Sopenharmony_ci case 4: /* Extended Linear Address Record (HEX386) */ 2148c2ecf20Sopenharmony_ci if (record->addr || record->len != 2) { 2158c2ecf20Sopenharmony_ci fprintf(stderr, "Bad HEX86/HEX386 record (type %02X) at line %d\n", 2168c2ecf20Sopenharmony_ci type, line); 2178c2ecf20Sopenharmony_ci return -EINVAL; 2188c2ecf20Sopenharmony_ci } 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci /* We shouldn't really be using the offset for HEX86 because 2218c2ecf20Sopenharmony_ci * the wraparound case is specified quite differently. */ 2228c2ecf20Sopenharmony_ci offset = record->data[0] << 8 | record->data[1]; 2238c2ecf20Sopenharmony_ci offset <<= (type == 2 ? 4 : 16); 2248c2ecf20Sopenharmony_ci goto next_record; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci case 3: /* Start Segment Address Record */ 2278c2ecf20Sopenharmony_ci case 5: /* Start Linear Address Record */ 2288c2ecf20Sopenharmony_ci if (record->addr || record->len != 4) { 2298c2ecf20Sopenharmony_ci fprintf(stderr, "Bad Start Address record (type %02X) at line %d\n", 2308c2ecf20Sopenharmony_ci type, line); 2318c2ecf20Sopenharmony_ci return -EINVAL; 2328c2ecf20Sopenharmony_ci } 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci memcpy(&data32, &record->data[0], sizeof(data32)); 2358c2ecf20Sopenharmony_ci data32 = htonl(data32); 2368c2ecf20Sopenharmony_ci memcpy(&record->data[0], &data32, sizeof(data32)); 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci /* These records contain the CS/IP or EIP where execution 2398c2ecf20Sopenharmony_ci * starts. If requested output this as a record. */ 2408c2ecf20Sopenharmony_ci if (include_jump) 2418c2ecf20Sopenharmony_ci file_record(record); 2428c2ecf20Sopenharmony_ci goto next_record; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci default: 2458c2ecf20Sopenharmony_ci fprintf(stderr, "Unknown record (type %02X)\n", type); 2468c2ecf20Sopenharmony_ci return -EINVAL; 2478c2ecf20Sopenharmony_ci } 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci return 0; 2508c2ecf20Sopenharmony_ci} 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_cistatic struct ihex_binrec *records; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic void file_record(struct ihex_binrec *record) 2558c2ecf20Sopenharmony_ci{ 2568c2ecf20Sopenharmony_ci struct ihex_binrec **p = &records; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci while ((*p) && (!sort_records || (*p)->addr < record->addr)) 2598c2ecf20Sopenharmony_ci p = &((*p)->next); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci record->next = *p; 2628c2ecf20Sopenharmony_ci *p = record; 2638c2ecf20Sopenharmony_ci} 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_cistatic uint16_t ihex_binrec_size(struct ihex_binrec *p) 2668c2ecf20Sopenharmony_ci{ 2678c2ecf20Sopenharmony_ci return p->len + sizeof(p->addr) + sizeof(p->len); 2688c2ecf20Sopenharmony_ci} 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_cistatic int output_records(int outfd) 2718c2ecf20Sopenharmony_ci{ 2728c2ecf20Sopenharmony_ci unsigned char zeroes[6] = {0, 0, 0, 0, 0, 0}; 2738c2ecf20Sopenharmony_ci struct ihex_binrec *p = records; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci while (p) { 2768c2ecf20Sopenharmony_ci uint16_t writelen = ALIGN(ihex_binrec_size(p), 4); 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci p->addr = htonl(p->addr); 2798c2ecf20Sopenharmony_ci p->len = htons(p->len); 2808c2ecf20Sopenharmony_ci if (write(outfd, &p->addr, writelen) != writelen) 2818c2ecf20Sopenharmony_ci return 1; 2828c2ecf20Sopenharmony_ci p = p->next; 2838c2ecf20Sopenharmony_ci } 2848c2ecf20Sopenharmony_ci /* EOF record is zero length, since we don't bother to represent 2858c2ecf20Sopenharmony_ci the type field in the binary version */ 2868c2ecf20Sopenharmony_ci if (write(outfd, zeroes, 6) != 6) 2878c2ecf20Sopenharmony_ci return 1; 2888c2ecf20Sopenharmony_ci return 0; 2898c2ecf20Sopenharmony_ci} 290