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