diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/array.c | 100 | ||||
-rw-r--r-- | src/ihex.c | 241 | ||||
-rw-r--r-- | src/main.c | 150 | ||||
-rw-r--r-- | src/util.c | 64 |
4 files changed, 555 insertions, 0 deletions
diff --git a/src/array.c b/src/array.c new file mode 100644 index 0000000..8c34469 --- /dev/null +++ b/src/array.c @@ -0,0 +1,100 @@ +/* + 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 <array.h> + +#include <stdlib.h> +#include <string.h> + +#if __STDC_VERSION__ < 202311L +#include <assert.h> +#define static_assert _Static_assert +#endif + +#define ARRAY_INITIAL_CAPACITY 8 + +void +init_array(struct array *arr, size_t elem_size) +{ + arr->length = 0; + arr->capacity = ARRAY_INITIAL_CAPACITY; + arr->elem_size = elem_size; + arr->data = malloc(arr->capacity * elem_size); +} + +void +free_array(struct array *arr) +{ + free(arr->data); + memset(arr, 0, sizeof(*arr)); +} + +// Return pointer to last allocated member +void * +array_append(struct array *arr, const void *data) +{ + void *addr = NULL; + + if (arr->length >= arr->capacity) { + arr->capacity = arr->capacity + arr->capacity / 2; + arr->data = realloc(arr->data, arr->elem_size * arr->capacity); + // Reallocation failed + if (!arr->data) + return NULL; + } + + addr = (void *)((char *)arr->data + arr->elem_size * arr->length); + arr->length++; + + if (data) + memcpy(addr, data, arr->elem_size); + + return addr; +} + +void +array_remove(struct array *arr, size_t n, void *dest) +{ + assert(n < arr->length); + + void *addr = array_elt(arr, n); + void *succ; + + if (dest) + memcpy(dest, addr, arr->elem_size); + + if (arr->length == n) { + arr->length--; + } else { + succ = array_elt(arr, n + 1); + memmove(addr, succ, (arr->length - n - 1) * arr->elem_size); + } +} + +void +array_sort(struct array *arr, int (*compar)(const void *, const void *)) +{ + qsort(arr->data, arr->length, arr->elem_size, compar); +} + +void * +array_search(struct array *arr, const void *key, + int (*compar)(const void *, const void *)) +{ + return bsearch(key, arr->data, arr->length, arr->elem_size, compar); +} 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; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..fc965aa --- /dev/null +++ b/src/main.c @@ -0,0 +1,150 @@ +/* + 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> + +#include <util.h> +#include <ihex.h> +#include <array.h> + +static void +usage(void) +{ + fputs("\ +Usage: ihex [OPTION]... [[BASE:]FILE]...\n", stdout); + fputs("\ +Convert ihex FILEs into binary data\n\n", stdout); + fputs("\ + -h, --help display this help and exit\n", stdout); + fputs("\ + -o, --output=NAME write output to NAME\n", stdout); + fputs("\n\ +The BASE argument is a base 16 encoded number indicating where to place the\n\ +file contents inside the resulting image\n\n", stdout); + fputs("\ +ihex home page: <https://thomaslabs.org/software/ihex>\n\ +\n\ +Copyright (C) 2025 Thomas Albers Raviola\n\ +This program comes with ABSOLUTELY NO WARRANTY.\n\ +This is free software: you free to change and redistribute it.\n", + stdout); +} + +static const char *opts = "ho:"; +static const struct option longopts[] = { + {.name = "help", .has_arg = 0, .flag = NULL, .val = 'h'}, + {.name = "output", .has_arg = 1, .flag = NULL, .val = 'o'}, + {0, 0, 0, 0} +}; + +static const char *output; + +void +decode_args(int argc, char *argv[]) +{ + int c; + while ((c = getopt_long(argc, argv, opts, longopts, NULL)) != -1) { + switch (c) { + case 0: + break; + case 'h': + usage(); + exit(EXIT_SUCCESS); + case 'o': + output = optarg; + break; + case '?': + usage(); + exit(EXIT_FAILURE); + default: + break; + } + } +} + +int +decode_filename(char *arg, long *base_address, const char **filename) +{ + char *junk; + char *sep = strchr(arg, ':'); + + if (sep) { + *sep++ = '\0'; + *filename = sep; + *base_address = strtol(arg, &junk, 16); + + if (*junk) { + fprintf(stderr, "Invalid base address for file %s\n", + *filename); + return -1; + } + } else { + *filename = arg; + *base_address = NO_BASE_ADDRESS; + } + + return 0; +} + +int +main(int argc, char *argv[]) +{ + int err = 0; + unsigned int i; + struct array blocks; + struct array data; + + decode_args(argc, argv); + + init_array(&blocks, sizeof(struct block)); + init_array(&data, sizeof(char)); + + for (i = optind; i < argc; ++i) { + long base_address; + const char *filename; + + err = decode_filename(argv[i], &base_address, &filename); + if (err < 0) + goto error; + + err = read_ihex_file(filename, &blocks, &data, base_address); + if (err < 0) + goto error; + } + + FILE *file = output ? fopen(output, "w") : stdout; + + if (!file) { + err = 1; + fprintf(stderr, "Could not open output file %s\n", output); + goto error; + } + + err = write_hex(&blocks, &data, file); + + if (output) + fclose(file); + +error: + free_array(&blocks); + free_array(&data); + return err; +} diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..44046f9 --- /dev/null +++ b/src/util.c @@ -0,0 +1,64 @@ +/* + 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <stdarg.h> +#include <errno.h> + +void +die(const char *current_file, const char *fmt, ...) +{ + va_list ap; + int saved_errno = errno; + + if (current_file) + fprintf(stderr, "File %s: ", current_file); + + va_start(ap, fmt); + if (fmt) + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (errno) + fprintf(stderr, " %s\n", strerror(saved_errno)); + + exit(EXIT_FAILURE); +} + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die(NULL, "Could not allocate memory!\n"); + + return p; +} + +void * +erealloc(void *ptr, size_t size) +{ + if (!(ptr = realloc(ptr, size))) + die(NULL, "Could not reallocate memory!\n"); + + return ptr; +} |