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 uint32_t offset = 0; 139 uint32_t data32; 140 uint8_t type, crc = 0, crcbyte = 0; 141 int i, j; 142 int line = 1; 143 int len; 144 145 i = 0; 146 next_record: 147 /* search for the start of record character */ 148 while (i < size) { 149 if (data[i] == '\n') line++; 150 if (data[i++] == ':') break; 151 } 152 153 /* Minimum record length would be about 10 characters */ 154 if (i + 10 > size) { 155 fprintf(stderr, "Can't find valid record at line %d\n", line); 156 return -EINVAL; 157 } 158 159 len = hex(data + i, &crc); i += 2; 160 if (wide_records) { 161 len <<= 8; 162 len += hex(data + i, &crc); i += 2; 163 } 164 record = malloc((sizeof (*record) + len + 3) & ~3); 165 if (!record) { 166 fprintf(stderr, "out of memory for records\n"); 167 return -ENOMEM; 168 } 169 memset(record, 0, (sizeof(*record) + len + 3) & ~3); 170 record->len = len; 171 172 /* now check if we have enough data to read everything */ 173 if (i + 8 + (record->len * 2) > size) { 174 fprintf(stderr, "Not enough data to read complete record at line %d\n", 175 line); 176 return -EINVAL; 177 } 178 179 record->addr = hex(data + i, &crc) << 8; i += 2; 180 record->addr |= hex(data + i, &crc); i += 2; 181 type = hex(data + i, &crc); i += 2; 182 183 for (j = 0; j < record->len; j++, i += 2) 184 record->data[j] = hex(data + i, &crc); 185 186 /* check CRC */ 187 crcbyte = hex(data + i, &crc); i += 2; 188 if (crc != 0) { 189 fprintf(stderr, "CRC failure at line %d: got 0x%X, expected 0x%X\n", 190 line, crcbyte, (unsigned char)(crcbyte-crc)); 191 return -EINVAL; 192 } 193 194 /* Done reading the record */ 195 switch (type) { 196 case 0: 197 /* old style EOF record? */ 198 if (!record->len) 199 break; 200 201 record->addr += offset; 202 file_record(record); 203 goto next_record; 204 205 case 1: /* End-Of-File Record */ 206 if (record->addr || record->len) { 207 fprintf(stderr, "Bad EOF record (type 01) format at line %d", 208 line); 209 return -EINVAL; 210 } 211 break; 212 213 case 2: /* Extended Segment Address Record (HEX86) */ 214 case 4: /* Extended Linear Address Record (HEX386) */ 215 if (record->addr || record->len != 2) { 216 fprintf(stderr, "Bad HEX86/HEX386 record (type %02X) at line %d\n", 217 type, line); 218 return -EINVAL; 219 } 220 221 /* We shouldn't really be using the offset for HEX86 because 222 * the wraparound case is specified quite differently. */ 223 offset = record->data[0] << 8 | record->data[1]; 224 offset <<= (type == 2 ? 4 : 16); 225 goto next_record; 226 227 case 3: /* Start Segment Address Record */ 228 case 5: /* Start Linear Address Record */ 229 if (record->addr || record->len != 4) { 230 fprintf(stderr, "Bad Start Address record (type %02X) at line %d\n", 231 type, line); 232 return -EINVAL; 233 } 234 235 memcpy(&data32, &record->data[0], sizeof(data32)); 236 data32 = htonl(data32); 237 memcpy(&record->data[0], &data32, sizeof(data32)); 238 239 /* These records contain the CS/IP or EIP where execution 240 * starts. If requested output this as a record. */ 241 if (include_jump) 242 file_record(record); 243 goto next_record; 244 245 default: 246 fprintf(stderr, "Unknown record (type %02X)\n", type); 247 return -EINVAL; 248 } 249 250 return 0; 251 } 252 253 static struct ihex_binrec *records; 254 255 static void file_record(struct ihex_binrec *record) 256 { 257 struct ihex_binrec **p = &records; 258 259 while ((*p) && (!sort_records || (*p)->addr < record->addr)) 260 p = &((*p)->next); 261 262 record->next = *p; 263 *p = record; 264 } 265 266 static uint16_t ihex_binrec_size(struct ihex_binrec *p) 267 { 268 return p->len + sizeof(p->addr) + sizeof(p->len); 269 } 270 271 static int output_records(int outfd) 272 { 273 unsigned char zeroes[6] = {0, 0, 0, 0, 0, 0}; 274 struct ihex_binrec *p = records; 275 276 while (p) { 277 uint16_t writelen = ALIGN(ihex_binrec_size(p), 4); 278 279 p->addr = htonl(p->addr); 280 p->len = htons(p->len); 281 if (write(outfd, &p->addr, writelen) != writelen) 282 return 1; 283 p = p->next; 284 } 285 /* EOF record is zero length, since we don't bother to represent 286 the type field in the binary version */ 287 if (write(outfd, zeroes, 6) != 6) 288 return 1; 289 return 0; 290 } 291