aboutsummaryrefslogtreecommitdiff
path: root/src/ihex.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ihex.c')
-rw-r--r--src/ihex.c241
1 files changed, 241 insertions, 0 deletions
diff --git a/src/ihex.c b/src/ihex.c
new file mode 100644
index 0000000..6a0f589
--- /dev/null
+++ b/src/ihex.c
@@ -0,0 +1,241 @@
+/*
+ ihex
+ Copyright (C) 2025 Thomas Albers Raviola <thomas@thomaslabs.org>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <ihex.h>
+#include <util.h>
+#include <array.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+
+#define MAX_IHEX_BYTE_COUNT 255
+
+enum record_type {
+ REC_DATA = 0x00,
+ REC_EOF = 0x01,
+ REC_EXT_SEG_ADDR = 0x02,
+ REC_START_SEG_ADDR = 0x03,
+ REC_EXT_LIN_ADDR = 0x04,
+ REC_START_LIN_ADDR = 0x05,
+ REC_UNKNOWN = 0xFF
+};
+
+struct record {
+ unsigned int length;
+ unsigned int offset;
+ unsigned int type;
+ unsigned int checksum;
+};
+
+static const char *current_file;
+
+static int
+skip_to_record(FILE *file)
+{
+ int c;
+ while ((c = fgetc(file)) != EOF) {
+ if (c == ':')
+ return 0;
+ }
+
+ return EOF;
+}
+
+#define HEX_UINT8 2
+#define HEX_UINT16 4
+
+static unsigned int
+read_hex(int digits, FILE *file)
+{
+ int c;
+ int i;
+ unsigned int ret = 0;
+
+ for (i = digits - 1; i >= 0; --i) {
+ c = fgetc(file);
+
+ if (c == EOF)
+ die(current_file, "Unexpected end of line\n");
+
+ if (!isxdigit(c)) {
+ die(current_file, "Invalid character at position %ld\n",
+ ftell(file));
+ }
+
+ if (c >= 'a')
+ c = c - 'a' + 10;
+ else if (c >= 'A')
+ c = c - 'A' + 10;
+ else
+ c = c - '0';
+
+ ret |= c << (4 * i);
+ }
+
+ return ret;
+}
+
+static unsigned int
+read_data_record(const struct record *record, struct array *data, FILE *file)
+{
+ char *p;
+ unsigned int i;
+ unsigned int b;
+ unsigned int checksum = 0;
+
+ for (i = 0; i < record->length; ++i) {
+ p = array_append(data, NULL);
+ b = read_hex(HEX_UINT8, file);
+ *p = b;
+ checksum += b;
+ }
+
+ return checksum;
+}
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define MIN(a, b) ((a) > (b) ? (b) : (a))
+
+static void
+rebase_blocks(size_t num_blocks, struct block *blocks, long base_address)
+{
+ size_t i;
+ long offset = LONG_MAX;
+
+ if (base_address < 0)
+ return;
+
+ for (i = 0; i < num_blocks; ++i)
+ offset = MIN(offset, blocks[i].address);
+
+ offset = base_address - offset;
+
+ for (i = 0; i < num_blocks; ++i)
+ blocks[i].address += offset;
+}
+
+int
+read_ihex_file(const char *filename, struct array *blocks, struct array *data,
+ long base_address)
+{
+ size_t num_blocks;
+ size_t block_offset = blocks->length;
+ struct block *new_blocks;
+
+ FILE *file = fopen(filename, "r");
+ current_file = filename;
+
+ while (!skip_to_record(file)) {
+ unsigned int checksum = 0;
+ struct record record;
+ struct block *block;
+
+ record.length = read_hex(HEX_UINT8, file);
+ record.offset = read_hex(HEX_UINT16, file);
+ record.type = read_hex(HEX_UINT8, file);
+
+ checksum += record.length;
+ checksum += ((record.offset >> 8) + record.offset) & 0xFF;
+ checksum += record.type;
+
+ if (record.type == REC_EOF)
+ break;
+
+ switch (record.type) {
+ case REC_DATA:
+ block = array_append(blocks, NULL);
+ block->address = record.offset;
+ block->length = record.length;
+ block->offset = data->length;
+ block->filename = filename;
+ checksum += read_data_record(&record, data, file);
+ break;
+ default:
+ die(current_file, "Invalid record type\n");
+ }
+
+ record.checksum = read_hex(HEX_UINT8, file);
+
+ if ((checksum + record.checksum) & 0xFF)
+ die(current_file, "Checksum mismatch\n", filename);
+ }
+
+ fclose(file);
+
+ num_blocks = blocks->length - block_offset;
+ new_blocks = &((struct block *)blocks->data)[block_offset];
+ rebase_blocks(num_blocks, new_blocks, base_address);
+ return 0;
+}
+
+static int
+blocks_collide(const struct block *a, const struct block *b)
+{
+ return (((a->address >= b->address)
+ && (a->address < b->address + b->length))
+ || ((b->address >= a->address)
+ && (b->address < a->address + a->length)));
+}
+
+static int
+block_compar(const void *a, const void *b) {
+ unsigned long x = ((const struct block *)a)->address;
+ unsigned long y = ((const struct block *)b)->address;
+
+ if (x < y)
+ return -1;
+ if (x > y)
+ return 1;
+ return 0;
+}
+
+int
+write_hex(struct array *_blocks, struct array *_data, FILE *file)
+{
+ unsigned int i;
+ unsigned long pos = 0;
+ struct block *b;
+ struct block *blocks;
+ size_t num_blocks = _blocks->length;
+ unsigned char *data;
+
+ array_sort(_blocks, block_compar);
+ blocks = (struct block *)_blocks->data;
+ data = (unsigned char *)_data->data;
+
+ for (i = 0; i < num_blocks; ++i) {
+ b = &blocks[i];
+
+ if (i < num_blocks - 1 && blocks_collide(&b[0], &b[1])) {
+ fprintf(stderr,
+ "Block collision between files %s and %s\n",
+ b[0].filename, b[1].filename);
+ return -1;
+ }
+
+ for (; pos < b->address; ++pos)
+ fputc(0xFF, file);
+
+ fwrite(&data[b->offset], 1, b->length, file);
+ pos += b->length;
+ }
+
+ return 0;
+}