/* sbc_harness/ihex.c - Intel Hex decoder * * Copyright (C) 2025 Luke T. Shumaker * SPDX-License-Identifier: AGPL-3.0-or-later */ /* https://archive.org/details/IntelHEXStandard */ #include /* for memchr() */ #include #include #define IMPLEMENTATION_FOR_IHEX_H YES #include "ihex.h" LO_IMPLEMENTATION_C(io_writer, 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); }