1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Parser/loader for IHEX formatted data. 4 * 5 * Copyright © 2008 David Woodhouse <dwmw2@infradead.org> 6 * Copyright © 2005 Jan Harkes <jaharkes@cs.cmu.edu> 7 */ 8 9 #include <stdint.h> 10 #include <arpa/inet.h> 11 #include <stdio.h> 12 #include <errno.h> 13 #include <sys/types.h> 14 #include <sys/stat.h> 15 #include <sys/mman.h> 16 #include <fcntl.h> 17 #include <string.h> 18 #include <unistd.h> 19 #include <stdlib.h> 20 #define _GNU_SOURCE 21 #include <getopt.h> 22 23 24 #define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask)) 25 #define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1) 26 #define ALIGN(x, a) __ALIGN_KERNEL((x), (a)) 27 28 struct ihex_binrec { 29 struct ihex_binrec *next; /* not part of the real data structure */ 30 uint32_t addr; 31 uint16_t len; 32 uint8_t data[]; 33 }; 34 35 /** 36 * nybble/hex are little helpers to parse hexadecimal numbers to a byte value 37 **/ 38 static uint8_t nybble(const uint8_t n) 39 { 40 if (n >= '0' && n <= '9') return n - '0'; 41 else if (n >= 'A' && n <= 'F') return n - ('A' - 10); 42 else if (n >= 'a' && n <= 'f') return n - ('a' - 10); 43 return 0; 44 } 45 46 static uint8_t hex(const uint8_t *data, uint8_t *crc) 47 { 48 uint8_t val = (nybble(data[0]) << 4) | nybble(data[1]); 49 *crc += val; 50 return val; 51 } 52 53 static int process_ihex(uint8_t *data, ssize_t size); 54 static void file_record(struct ihex_binrec *record); 55 static int output_records(int outfd); 56 57 static int sort_records = 0; 58 static int wide_records = 0; 59 static int include_jump = 0; 60 61 static int usage(void) 62 { 63 fprintf(stderr, "ihex2fw: Convert ihex files into binary " 64 "representation for use by Linux kernel\n"); 65 fprintf(stderr, "usage: ihex2fw [<options>] <src.HEX> <dst.fw>\n"); 66 fprintf(stderr, " -w: wide records (16-bit length)\n"); 67 fprintf(stderr, " -s: sort records by address\n"); 68 fprintf(stderr, " -j: include records for CS:IP/EIP address\n"); 69 return 1; 70 } 71 72 int main(int argc, char **argv) 73 { 74 int infd, outfd; 75 struct stat st; 76 uint8_t *data; 77 int opt; 78 79 while ((opt = getopt(argc, argv, "wsj")) != -1) { 80 switch (opt) { 81 case 'w': 82 wide_records = 1; 83 break; 84 case 's': 85 sort_records = 1; 86 break; 87 case 'j': 88 include_jump = 1; 89 break; 90 default: 91 return usage(); 92 } 93 } 94 95 if (optind + 2 != argc) 96 return usage(); 97 98 if (!strcmp(argv[optind], "-")) 99 infd = 0; 100 else 101 infd = open(argv[optind], O_RDONLY); 102 if (infd == -1) { 103 fprintf(stderr, "Failed to open source file: %s", 104 strerror(errno)); 105 return usage(); 106 } 107 if (fstat(infd, &st)) { 108 perror("stat"); 109 return 1; 110 } 111 data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, infd, 0); 112 if (data == MAP_FAILED) { 113 perror("mmap"); 114 return 1; 115 } 116 117 if (!strcmp(argv[optind+1], "-")) 118 outfd = 1; 119 else 120 outfd = open(argv[optind+1], O_TRUNC|O_CREAT|O_WRONLY, 0644); 121 if (outfd == -1) { 122 fprintf(stderr, "Failed to open destination file: %s", 123 strerror(errno)); 124 return usage(); 125 } 126 if (process_ihex(data, st.st_size)) 127 return 1; 128 129 return output_records(outfd); 130 } 131 132 static int process_ihex(uint8_t *data, ssize_t size) 133 { 134 struct ihex_binrec *record; 135 size_t record_size; 136 uint32_t offset = 0; 137 uint32_t data32; 138 uint8_t type, crc = 0, crcbyte = 0; 139 int i, j; 140 int line = 1; 141 int len; 142 143 i = 0; 144 next_record: 145 /* search for the start of record character */ 146 while (i < size) { 147 if (data[i] == '\n') line++; 148 if (data[i++] == ':') break; 149 } 150 151 /* Minimum record length would be about 10 characters */ 152 if (i + 10 > size) { 153 fprintf(stderr, "Can't find valid record at line %d\n", line); 154 return -EINVAL; 155 } 156 157 len = hex(data + i, &crc); i += 2; 158 if (wide_records) { 159 len <<= 8; 160 len += hex(data + i, &crc); i += 2; 161 } 162 record_size = ALIGN(sizeof(*record) + len, 4); 163 record = malloc(record_size); 164 if (!record) { 165 fprintf(stderr, "out of memory for records\n"); 166 return -ENOMEM; 167 } 168 memset(record, 0, record_size); 169 record->len = len; 170 171 /* now check if we have enough data to read everything */ 172 if (i + 8 + (record->len * 2) > size) { 173 fprintf(stderr, "Not enough data to read complete record at line %d\n", 174 line); 175 return -EINVAL; 176 } 177 178 record->addr = hex(data + i, &crc) << 8; i += 2; 179 record->addr |= hex(data + i, &crc); i += 2; 180 type = hex(data + i, &crc); i += 2; 181 182 for (j = 0; j < record->len; j++, i += 2) 183 record->data[j] = hex(data + i, &crc); 184 185 /* check CRC */ 186 crcbyte = hex(data + i, &crc); i += 2; 187 if (crc != 0) { 188 fprintf(stderr, "CRC failure at line %d: got 0x%X, expected 0x%X\n", 189 line, crcbyte, (unsigned char)(crcbyte-crc)); 190 return -EINVAL; 191 } 192 193 /* Done reading the record */ 194 switch (type) { 195 case 0: 196 /* old style EOF record? */ 197 if (!record->len) 198 break; 199 200 record->addr += offset; 201 file_record(record); 202 goto next_record; 203 204 case 1: /* End-Of-File Record */ 205 if (record->addr || record->len) { 206 fprintf(stderr, "Bad EOF record (type 01) format at line %d", 207 line); 208 return -EINVAL; 209 } 210 break; 211 212 case 2: /* Extended Segment Address Record (HEX86) */ 213 case 4: /* Extended Linear Address Record (HEX386) */ 214 if (record->addr || record->len != 2) { 215 fprintf(stderr, "Bad HEX86/HEX386 record (type %02X) at line %d\n", 216 type, line); 217 return -EINVAL; 218 } 219 220 /* We shouldn't really be using the offset for HEX86 because 221 * the wraparound case is specified quite differently. */ 222 offset = record->data[0] << 8 | record->data[1]; 223 offset <<= (type == 2 ? 4 : 16); 224 goto next_record; 225 226 case 3: /* Start Segment Address Record */ 227 case 5: /* Start Linear Address Record */ 228 if (record->addr || record->len != 4) { 229 fprintf(stderr, "Bad Start Address record (type %02X) at line %d\n", 230 type, line); 231 return -EINVAL; 232 } 233 234 memcpy(&data32, &record->data[0], sizeof(data32)); 235 data32 = htonl(data32); 236 memcpy(&record->data[0], &data32, sizeof(data32)); 237 238 /* These records contain the CS/IP or EIP where execution 239 * starts. If requested output this as a record. */ 240 if (include_jump) 241 file_record(record); 242 goto next_record; 243 244 default: 245 fprintf(stderr, "Unknown record (type %02X)\n", type); 246 return -EINVAL; 247 } 248 249 return 0; 250 } 251 252 static struct ihex_binrec *records; 253 254 static void file_record(struct ihex_binrec *record) 255 { 256 struct ihex_binrec **p = &records; 257 258 while ((*p) && (!sort_records || (*p)->addr < record->addr)) 259 p = &((*p)->next); 260 261 record->next = *p; 262 *p = record; 263 } 264 265 static uint16_t ihex_binrec_size(struct ihex_binrec *p) 266 { 267 return p->len + sizeof(p->addr) + sizeof(p->len); 268 } 269 270 static int output_records(int outfd) 271 { 272 unsigned char zeroes[6] = {0, 0, 0, 0, 0, 0}; 273 struct ihex_binrec *p = records; 274 275 while (p) { 276 uint16_t writelen = ALIGN(ihex_binrec_size(p), 4); 277 278 p->addr = htonl(p->addr); 279 p->len = htons(p->len); 280 if (write(outfd, &p->addr, writelen) != writelen) 281 return 1; 282 p = p->next; 283 } 284 /* EOF record is zero length, since we don't bother to represent 285 the type field in the binary version */ 286 if (write(outfd, zeroes, 6) != 6) 287 return 1; 288 return 0; 289 } 290