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