/* * kk_ihex_read.c: A simple library for reading the Intel HEX (IHEX) format. * * See the header `kk_ihex.h` for instructions. * * Copyright (c) 2013-2015 Kimmo Kulovesi, http://arkku.com/ * Provided with absolutely no warranty, use at your own risk only. * Use and distribute freely, mark modified copies as such. * * Modifications Copyright (c) 2015 National Instruments Corp. */ #include "kk_ihex_read.h" #include #include #define IHEX_START ':' #define AUTODETECT_ADDRESS (~0UL) #define ADDRESS_HIGH_MASK ((ihex_address_t) 0xFFFF0000U) enum ihex_read_state { READ_WAIT_FOR_START = 0, READ_COUNT_HIGH = 1, READ_COUNT_LOW, READ_ADDRESS_MSB_HIGH, READ_ADDRESS_MSB_LOW, READ_ADDRESS_LSB_HIGH, READ_ADDRESS_LSB_LOW, READ_RECORD_TYPE_HIGH, READ_RECORD_TYPE_LOW, READ_DATA_HIGH, READ_DATA_LOW }; #define IHEX_READ_RECORD_TYPE_MASK 0x07 #define IHEX_READ_STATE_MASK 0x78 #define IHEX_READ_STATE_OFFSET 3 void ihex_begin_read (struct ihex_state * const ihex) { ihex->address = 0; #ifndef IHEX_DISABLE_SEGMENTS ihex->segment = 0; #endif ihex->flags = 0; ihex->line_length = 0; ihex->length = 0; } void ihex_read_at_address (struct ihex_state * const ihex, ihex_address_t address) { ihex_begin_read(ihex); ihex->address = address; } #ifndef IHEX_DISABLE_SEGMENTS void ihex_read_at_segment (struct ihex_state * const ihex, ihex_segment_t segment) { ihex_begin_read(ihex); ihex->segment = segment; } #endif void ihex_end_read (struct ihex_state * const ihex, FILE* outfile) { uint_fast8_t type = ihex->flags & IHEX_READ_RECORD_TYPE_MASK; uint_fast8_t sum; if ((sum = ihex->length) == 0 && type == IHEX_DATA_RECORD) { return; } { // compute and validate checksum const uint8_t * const eptr = ihex->data + sum; const uint8_t *r = ihex->data; sum += type + (ihex->address & 0xFFU) + ((ihex->address >> 8) & 0xFFU); while (r != eptr) { sum += *r++; } sum = (~sum + 1U) ^ *eptr; // *eptr is the received checksum } if (ihex_data_read(ihex, type, sum, outfile)) { if (type == IHEX_EXTENDED_LINEAR_ADDRESS_RECORD) { ihex->address &= 0xFFFFU; ihex->address |= (((ihex_address_t) ihex->data[0]) << 24) | (((ihex_address_t) ihex->data[1]) << 16); #ifndef IHEX_DISABLE_SEGMENTS } else if (type == IHEX_EXTENDED_SEGMENT_ADDRESS_RECORD) { ihex->segment = (ihex_segment_t) ((ihex->data[0] << 8) | ihex->data[1]); #endif } } ihex->length = 0; ihex->flags = 0; } void ihex_read_byte (struct ihex_state * const ihex, const char byte, FILE* outfile) { uint_fast8_t b = (uint_fast8_t) byte; uint_fast8_t len = ihex->length; uint_fast8_t state = (ihex->flags & IHEX_READ_STATE_MASK); ihex->flags ^= state; // turn off the old state state >>= IHEX_READ_STATE_OFFSET; if (b >= '0' && b <= '9') { b -= '0'; } else if (b >= 'A' && b <= 'F') { b -= 'A' - 10; } else if (b >= 'a' && b <= 'f') { b -= 'a' - 10; } else if (b == IHEX_START) { // sync to a new record at any state state = READ_COUNT_HIGH; goto end_read; } else { // ignore unknown characters (e.g., extra whitespace) goto save_read_state; } if (!(++state & 1)) { // high nybble, store temporarily at end of data: b <<= 4; ihex->data[len] = b; } else { // low nybble, combine with stored high nybble: b = (ihex->data[len] |= b); switch (state >> 1) { default: // remain in initial state while waiting for : return; case (READ_COUNT_LOW >> 1): // data length ihex->line_length = b; #if IHEX_LINE_MAX_LENGTH < 255 if (b > IHEX_LINE_MAX_LENGTH) { ihex_end_read(ihex); return; } #endif break; case (READ_ADDRESS_MSB_LOW >> 1): // high byte of 16-bit address ihex->address &= ADDRESS_HIGH_MASK; // clear the 16-bit address ihex->address |= ((ihex_address_t) b) << 8U; break; case (READ_ADDRESS_LSB_LOW >> 1): // low byte of 16-bit address ihex->address |= (ihex_address_t) b; break; case (READ_RECORD_TYPE_LOW >> 1): // record type if (b & ~IHEX_READ_RECORD_TYPE_MASK) { // skip unknown record types silently return; } ihex->flags = (ihex->flags & ~IHEX_READ_RECORD_TYPE_MASK) | b; break; case (READ_DATA_LOW >> 1): if (len < ihex->line_length) { // data byte ihex->length = len + 1; state = READ_DATA_HIGH; goto save_read_state; } // end of line (last "data" byte is checksum) state = READ_WAIT_FOR_START; end_read: ihex_end_read(ihex, outfile); } } save_read_state: ihex->flags |= state << IHEX_READ_STATE_OFFSET; } void ihex_read_bytes (struct ihex_state * ihex, const char * data, ihex_count_t count, FILE* outfile) { while (count > 0) { ihex_read_byte(ihex, *data++, outfile); --count; } } ihex_bool_t ihex_data_read (struct ihex_state *ihex, ihex_record_type_t type, ihex_bool_t error, FILE* outfile) { unsigned long line_number = 1L; unsigned long file_position = 0L; unsigned long address_offset = 0L; bool debug_enabled = false; if (error) { (void) fprintf(stderr, "Checksum error on line %lu\n", line_number); return false; } if ((error = (ihex->length < ihex->line_length))) { (void) fprintf(stderr, "Line length error on line %lu\n", line_number); return false; } if (!outfile) { (void) fprintf(stderr, "Excess data after end of file record\n"); return false; } if (type == IHEX_DATA_RECORD) { unsigned long address = (unsigned long) IHEX_LINEAR_ADDRESS(ihex); if (address < address_offset) { if (address_offset == AUTODETECT_ADDRESS) { // autodetect initial address address_offset = address; if (debug_enabled) { (void) fprintf(stderr, "Address offset: 0x%lx\n", address_offset); } } else { (void) fprintf(stderr, "Address underflow on line %lu\n", line_number); return false; } } address -= address_offset; if (address != file_position) { if (debug_enabled) { (void) fprintf(stderr, "Seeking from 0x%lx to 0x%lx on line %lu\n", file_position, address, line_number); } if (outfile == stdout || fseek(outfile, (long) address, SEEK_SET)) { if (file_position < address) { // "seek" forward in stdout by writing NUL bytes do { (void) fputc('\0', outfile); } while (++file_position < address); } else { perror("fseek"); return false; } } file_position = address; } if (!fwrite(ihex->data, ihex->length, 1, outfile)) { perror("fwrite"); return false; } file_position += ihex->length; } else if (type == IHEX_END_OF_FILE_RECORD) { if (debug_enabled) { (void) fprintf(stderr, "%lu bytes written\n", file_position); } if (outfile != stdout) { (void) fclose(outfile); } outfile = NULL; } return true; }