290 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*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_SOURCE21#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 28struct 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 value37 **/38static 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 46static 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 53static int process_ihex(uint8_t *data, ssize_t size);54static void file_record(struct ihex_binrec *record);55static int output_records(int outfd);56 57static int sort_records = 0;58static int wide_records = 0;59static int include_jump = 0;60 61static 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 72int 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 else101 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 else120 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 132static 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;144next_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 because221 * 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 execution239 * 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 252static struct ihex_binrec *records;253 254static 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 265static uint16_t ihex_binrec_size(struct ihex_binrec *p)266{267 return p->len + sizeof(p->addr) + sizeof(p->len);268}269 270static 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 represent285 the type field in the binary version */286 if (write(outfd, zeroes, 6) != 6)287 return 1;288 return 0;289}290