diff options
Diffstat (limited to 'cmd/sbc_harness/ihex.c')
-rw-r--r-- | cmd/sbc_harness/ihex.c | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/cmd/sbc_harness/ihex.c b/cmd/sbc_harness/ihex.c new file mode 100644 index 0000000..5a7f6d5 --- /dev/null +++ b/cmd/sbc_harness/ihex.c @@ -0,0 +1,231 @@ +/* sbc_harness/ihex.c - Intel Hex decoder + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/* https://archive.org/details/IntelHEXStandard */ + +#include <string.h> /* for memchr() */ + +#include <libmisc/assert.h> +#include <libmisc/endian.h> + +#define IMPLEMENTATION_FOR_IHEX_H YES +#include "ihex.h" + +LO_IMPLEMENTATION_C(io_writer, struct ihex_decoder, ihex_decoder); +LO_IMPLEMENTATION_C(io_closer, struct ihex_decoder, ihex_decoder); + +enum ihex_record_type { + /* [U]SBA: [Upper] Segment Base Address : SBA = USBA<< 4 */ + /* [U]LBA: [Upper] Linear Base Address : LBA = ULBA<<16 */ + /* _EXT records define where DATA records are written to */ + /* _START records define where execution should start */ + IHEX_REC_DATA = 0x00, /* .dat is .len bytes of data, which go at either (USBA<<4)+(.off%64KiB) or ((ULBA<<16)+.off)%4GiB */ + IHEX_REC_EOF = 0x01, /* .len=0, .off=0 */ + IHEX_REC_ADDR_SEG_EXT = 0x02, /* .len=2, .off=0, .dat is u16be USBA */ + IHEX_REC_ADDR_SEG_START = 0x03, /* .len=4, .off=0, .dat is u16be CS register then u16be IP register */ + IHEX_REC_ADDR_LIN_EXT = 0x04, /* .len=2, .off=0, .dat is u16be ULBA */ + IHEX_REC_ADDR_LIN_START = 0x05, /* .len=4, .off=0, .dat is u32be EIP register */ +}; + +struct ihex_record { + uint8_t len; + uint16_t off; + enum ihex_record_type typ; + uint8_t *dat; +}; + +static error ihex_handle_record(struct ihex_decoder *self, struct ihex_record *rec) { + switch (rec->typ) { + case IHEX_REC_ADDR_SEG_EXT: + self->addr_mode = _IHEX_MODE_SEG; + self->addr_base = ((uint32_t)uint16be_decode(rec->dat)) << 4; + return ERROR_NULL; + case IHEX_REC_ADDR_LIN_EXT: + self->addr_mode = _IHEX_MODE_LIN; + self->addr_base = ((uint32_t)uint16be_decode(rec->dat)) << 16; + return ERROR_NULL;; + case IHEX_REC_DATA: + switch (self->addr_mode) { + case _IHEX_MODE_NONE: + return error_new(E_POSIX_EINVAL, "ihex: data record before base-address record"); + case _IHEX_MODE_SEG: + if (!self->handle_data) + return ERROR_NULL; + if (rec->len <= UINT16_MAX - rec->off) { + /* 1 write */ + return self->handle_data(self->handle_arg, self->addr_base + rec->off, rec->len, rec->dat); + } else { + /* wraps around; split into 2 writes */ + uint8_t first_len = (uint8_t) (UINT16_MAX - rec->off); + if (first_len) { + error err = self->handle_data(self->handle_arg, self->addr_base + rec->off, first_len, rec->dat); + if (!ERROR_IS_NULL(err)) + return err; + } + return self->handle_data(self->handle_arg, self->addr_base, rec->len - first_len, &rec->dat[first_len]); + } + case _IHEX_MODE_LIN: + if (!self->handle_data) + return ERROR_NULL; + uint32_t off = self->addr_base + rec->off; + if (rec->len <= UINT32_MAX - off) { + /* 1 write */ + return self->handle_data(self->handle_arg, off, rec->len, rec->dat); + } else { + /* wraps around; split into 2 writes */ + uint8_t first_len = (uint8_t) (UINT32_MAX - off); + if (first_len) { + error err = self->handle_data(self->handle_arg, off, first_len, rec->dat); + if (!ERROR_IS_NULL(err)) + return err; + } + return self->handle_data(self->handle_arg, 0, rec->len - first_len, &rec->dat[first_len]); + } + default: + assert_notreached("bad addr_mode"); + } + case IHEX_REC_EOF: + self->seen_eof = true; + if (!self->handle_eof) + return ERROR_NULL; + return self->handle_eof(self->handle_arg); + case IHEX_REC_ADDR_SEG_START: + if (!self->handle_set_exec_start_seg) + return ERROR_NULL; + uint16_t cs = uint16be_decode(&rec->dat[0]); + uint16_t ip = uint16be_decode(&rec->dat[2]); + return self->handle_set_exec_start_seg(self->handle_arg, cs, ip); + case IHEX_REC_ADDR_LIN_START: + if (!self->handle_set_exec_start_lin) + return ERROR_NULL; + uint32_t eip = uint32be_decode(rec->dat); + return self->handle_set_exec_start_lin(self->handle_arg, eip); + default: + assert_notreached("bad record type"); + } +} + +/** + * Hex-decode the byte 0xAB, and push it onto self->buf. If this + * completes the record in self->buf, then handle that record. + * + * @return the number of ASCII bytes consumed (0, 1, or 2) before + * encountering an error. + */ +static size_t_and_error ihex_decode_byte(struct ihex_decoder *self, char a, char b) { + uint8_t byte; + if ('0' <= a && a <= '9') + byte = (a - '0') << 4; + else if ('A' <= a && a <= 'F') + byte = (a - 'A' + 10) << 4; + else + return ERROR_AND(size_t, 0, error_new(E_POSIX_EILSEQ, "ihex: invalid hexadecimal: ", (qbyte, a))); + if ('0' <= b && b <= '9') + byte |= b - '0'; + else if ('A' <= b && b <= 'F') + byte |= b - 'A' + 10; + else + return ERROR_AND(size_t, 1, error_new(E_POSIX_EILSEQ, "ihex: invalid hexadecimal: ", (qbyte, b))); + self->buf[self->buf_len++] = byte; + if (self->buf_len == self->buf[0]+5) { + uint8_t sum = 0; + for (size_t i = 0; i < (size_t)self->buf[0]+5; i++) + sum += self->buf[i]; + if (sum != 0) { + self->sticky_err = error_new(E_POSIX_EPROTO, "ihex: checksum mismatch"); + return ERROR_AND(size_t, 2, error_dup(self->sticky_err)); + } + struct ihex_record rec = { + .len = self->buf[0], + .off = uint16be_decode(&self->buf[1]), + .typ = self->buf[3], + .dat = &self->buf[4], + }; + error err = ihex_handle_record(self, &rec); + if (!ERROR_IS_NULL(err)) { + self->sticky_err = err; + return ERROR_AND(size_t, 2, error_dup(err)); + } + self->in_record = false; + self->buf_len = 0; + } + return ERROR_AND(size_t, 2, ERROR_NULL); +} + +static size_t_and_error ihex_decoder_write(struct ihex_decoder *self, const char *dat, size_t len_in) { + assert(self); + if (!len_in) + return ERROR_AND(size_t, 0, ERROR_NULL); + assert(dat); + + if (!ERROR_IS_NULL(self->sticky_err)) + return ERROR_AND(size_t, 0, error_dup(self->sticky_err)); + + size_t len_consumed = 0; + + if (self->buf_char) { + assert(self->in_record); + size_t_and_error r = ihex_decode_byte(self, self->buf_char, dat[0]); + if (r.size_t) + len_consumed += r.size_t - 1; + self->buf_char = 0; + if (!ERROR_IS_NULL(r.err)) + return ERROR_AND(size_t, len_consumed, r.err); + } + + while (len_consumed < len_in) { + if (!self->in_record) { + const char *marker = memchr(&dat[len_consumed], ':', len_in-len_consumed); + if (!marker) { + len_consumed = len_in; + continue; + } + len_consumed += marker - &dat[len_consumed]; + + assert(dat[len_consumed] == ':'); + if (self->seen_eof) + return ERROR_AND(size_t, len_consumed, error_new(E_POSIX_EPROTO, "ihex: record after EOF record")); + len_consumed++; + self->in_record = true; + } + while (len_in - len_consumed >= 2 && self->in_record) { + size_t_and_error r = ihex_decode_byte(self, dat[len_consumed], dat[len_consumed+1]); + len_consumed += r.size_t; + if (!ERROR_IS_NULL(r.err)) + return ERROR_AND(size_t, len_consumed, r.err); + } + if (len_in - len_consumed && self->in_record) { + assert(len_in - len_consumed == 1); + if (!(('0' <= dat[len_consumed] && dat[len_consumed] <= '9') || + ('A' <= dat[len_consumed] && dat[len_consumed] <= 'F'))) + return ERROR_AND(size_t, len_consumed, error_new(E_POSIX_EILSEQ, "ihex: invalid hexadecimal: ", (qbyte, dat[len_consumed]))); + self->buf_char = dat[len_consumed++]; + } + } + + assert(len_consumed == len_in); + return ERROR_AND(size_t, len_in, ERROR_NULL); +} + +size_t_and_error ihex_decoder_writev(struct ihex_decoder *self, const struct iovec *iov, int iovcnt) { + assert(self); + assert(iov); + assert(iovcnt); + + size_t total = 0; + for (int i = 0; i < iovcnt; i++) { + size_t_and_error r = ihex_decoder_write(self, iov[i].iov_base, iov[i].iov_len); + total += r.size_t; + if (!ERROR_IS_NULL(r.err)) + return ERROR_AND(size_t, total, r.err); + } + return ERROR_AND(size_t, total, ERROR_NULL); +} + +error ihex_decoder_close(struct ihex_decoder *self) { + error_cleanup(&self->sticky_err); + return ERROR_NULL; +} |