aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThomas Albers Raviola <thomas@thomaslabs.org>2025-03-13 15:58:09 +0100
committerThomas Albers Raviola <thomas@thomaslabs.org>2025-03-13 15:58:09 +0100
commit5ba1561999f8df7e0e622db8686ec1c4e0045d5f (patch)
tree1ed2fa10f7cdaa27454c1b3937e07ead78b6536e /src
Initial commitHEADmaster
Diffstat (limited to 'src')
-rw-r--r--src/array.c100
-rw-r--r--src/ihex.c241
-rw-r--r--src/main.c150
-rw-r--r--src/util.c64
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;
+}