diff options
Diffstat (limited to 'src/ihex.c')
-rw-r--r-- | src/ihex.c | 241 |
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; +} |