/**************************************************************************** * libs/libc/hex2bin/lib_hex2bin.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * References: * - http://en.wikipedia.org/wiki/Intel_HEX * - Hexadecimal Object File Format Specification, Revision A January 6, * 1988, Intel * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include "libc.h" #ifdef CONFIG_LIBC_HEX2BIN /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* ASCII record sizes */ #define BYTECOUNT_ASCSIZE 2 #define ADDRESS_ASCSIZE 4 #define RECTYPE_ASCSIZE 2 #define BYTECOUNT_LINENDX (0) #define ADDRESS_LINENDX (BYTECOUNT_LINENDX + BYTECOUNT_ASCSIZE) #define RECTYPE_LINENDX (ADDRESS_LINENDX + ADDRESS_ASCSIZE) #define DATA_LINENDX (RECTYPE_LINENDX + RECTYPE_ASCSIZE) #define HEADER_ASCSIZE DATA_LINENDX #define CHECKSUM_ASCSIZE 2 #define TRAILER_SIZE (CHECKSUM_ASCSIZE) #define MAXDATA_BINSIZE 255 #define RECORD_ASCSIZE(n) (HEADER_ASCSIZE + TRAILER_SIZE + 2*(n)) #define MAXRECORD_ASCSIZE RECORD_ASCSIZE(MAXDATA_BINSIZE) #define MINRECORD_ASCSIZE RECORD_ASCSIZE(0) #define LINE_ALLOC MAXRECORD_ASCSIZE /* Binary record sizes */ #define BYTECOUNT_BINSIZE 1 #define ADDRESS_BINSIZE 2 #define RECTYPE_BINSIZE 1 #define BYTECOUNT_BINNDX (0) #define ADDRESS_BINNDX (BYTECOUNT_BINNDX + BYTECOUNT_BINSIZE) #define RECTYPE_BINNDX (ADDRESS_BINNDX + ADDRESS_BINSIZE) #define DATA_BINNDX (RECTYPE_BINNDX + RECTYPE_BINSIZE) #define HEADER_BINSIZE DATA_BINNDX #define CHECKSUM_BINSIZE 1 #define TRAILER_BINSIZE CHECKSUM_BINSIZE #define RECORD_BINSIZE(n) (HEADER_BINSIZE + TRAILER_BINSIZE + (n)) #define MAXRECORD_BINSIZE RECORD_BINSIZE(MAXDATA_BINSIZE) #define MINRECORD_BKINSIZE RECORD_BINSIZE(0) #define BIN_ALLOC MAXRECORD_BINSIZE /* Record start code */ #define RECORD_STARTCODE ':' /* Record Types */ #define RECORD_DATA 0 /* Data */ #define RECORD_EOF 1 /* End of file */ #define RECORD_EXT_SEGADDR 2 /* Extended segment address record */ #define RECORD_START_SEGADDR 3 /* Start segment address record */ #define RECORD_EXT_LINADDR 4 /* Extended linear address record */ #define RECORD_START_LINADDR 5 /* Start linear address record */ /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: nibble2bin ****************************************************************************/ static int nibble2bin(uint8_t ascii) { if (ascii >= '0' && ascii <= '9') { return (ascii - 0x30); } else if (ascii >= 'a' && ascii <= 'f') { return (ascii - 'a' + 10); } else if (ascii >= 'A' && ascii <= 'F') { return (ascii - 'A' + 10); } return -EINVAL; } /**************************************************************************** * Name: byte2bin ****************************************************************************/ static int byte2bin(FAR const uint8_t *ascii) { int nibble; int byte; /* Get the MS nibble (big endian order) */ nibble = nibble2bin(*ascii++); if (nibble < 0) { return nibble; } byte = (nibble << 4); /* Get the MS nibble */ nibble = nibble2bin(*ascii); if (nibble < 0) { return nibble; } byte |= nibble; return byte; } /**************************************************************************** * Name: word2bin ****************************************************************************/ #if 0 /* Not used */ static int word2bin(FAR const char *ascii) { int byte; int word; /* Get the MS byte (big endian order) */ byte = word2bin(ascii); if (byte < 0) { return byte; } word = (byte << 8); /* Get the MS byte */ byte = word2bin(ascii + 2); if (byte < 0) { return byte; } word |= byte; return word; } #endif /**************************************************************************** * Name: data2bin ****************************************************************************/ int data2bin(FAR uint8_t *dest, FAR const uint8_t *src, int nsrcbytes) { int byte; /* An even number of source bytes is expected */ if ((nsrcbytes & 1) != 0) { return -EINVAL; } /* Convert src bytes in groups of 2, writing one byte to the output on each * pass through the loop. */ while (nsrcbytes > 0) { /* Get the MS nibble (big endian order) */ byte = byte2bin(src); if (byte < 0) { return byte; } src += 2; /* And write the byte to the destination */ *dest++ = byte; nsrcbytes -= 2; } return OK; } /**************************************************************************** * Name: readstream ****************************************************************************/ static int readstream(FAR struct lib_instream_s *instream, FAR uint8_t *line, unsigned int lineno) { int nbytes = 0; int ch; /* Skip until the beginning of line start code is encountered */ ch = lib_stream_getc(instream); while (ch != RECORD_STARTCODE && ch != EOF) { ch = lib_stream_getc(instream); } /* Skip over the startcode */ if (ch != EOF) { ch = lib_stream_getc(instream); } /* Then read, verify, and buffer until the end of line is encountered */ while (ch != EOF && nbytes < (MAXRECORD_ASCSIZE - 1)) { if (ch == '\n' || ch == '\r') { *line = '\0'; return nbytes; } /* Only hex data goes into the line buffer */ else if (isxdigit(ch)) { *line++ = ch; nbytes++; } else if (!isspace(ch)) /* Not expected */ { lerr("Line %u ERROR: Unexpected character %c[%02x] in stream\n", lineno, isprint(ch) ? ch : '.', ch); break; } /* Read the next character from the input stream */ ch = lib_stream_getc(instream); } /* Some error occurred: Unexpected EOF, line too long, or bad character in * stream */ lerr("Line %u ERROR: Failed to read line. %d characters read\n", lineno, nbytes); return EOF; } /**************************************************************************** * Name: hex2bin_swap16 and hex2bin_swap32 ****************************************************************************/ static inline void hex2bin_swap16(FAR uint8_t *data, int bytecount) { for (; bytecount > 0; bytecount -= 2) { uint8_t b0 = data[0]; uint8_t b1 = data[1]; *data++ = b1; *data++ = b0; } } static inline void hex2bin_swap32(FAR uint8_t *data, int bytecount) { for (; bytecount > 0; bytecount -= 4) { uint8_t b0 = data[0]; uint8_t b1 = data[1]; uint8_t b2 = data[2]; uint8_t b3 = data[3]; *data++ = b3; *data++ = b2; *data++ = b1; *data++ = b0; } } /**************************************************************************** * Name: writedata ****************************************************************************/ static inline void writedata(FAR struct lib_sostream_s *outstream, FAR uint8_t *data, int bytecount) { for (; bytecount > 0; bytecount--) { lib_stream_putc(outstream, *data++); } } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: hex2bin * * Description: * Read the Intel HEX ASCII data provided on the serial IN stream and write * the binary to the seek-able serial OUT stream. * * These streams may be files or, in another usage example, the IN stream * could be a serial port and the OUT stream could be a memory stream. * This would decode and write the serial input to memory. * * Input Parameters: * instream - The incoming stream from which Intel HEX data will be * received. * outstream - The outgoing stream in which binary data will be written. * baseaddr - The base address of the outgoing stream. Seeking in the * output stream will be relative to this address. * endpaddr - The end address (plus 1) of the outgoing stream. This * value is used only for range checking. endpaddr must * be larger than baseaddr. A zero value for endpaddr * disables range checking. * swap - Controls byte ordering. See enum hex2bin_swap_e for * description of the values. * * Returned Value: * Zero (OK) is returned on success; a negated errno value is returned on * failure. * ****************************************************************************/ int hex2bin(FAR struct lib_instream_s *instream, FAR struct lib_sostream_s *outstream, unsigned long baseaddr, unsigned long endpaddr, enum hex2bin_swap_e swap) { FAR uint8_t *alloc; FAR uint8_t *line; FAR uint8_t *bin; int nbytes; int bytecount; unsigned long address; unsigned long endaddr; unsigned long expected; unsigned long extension; uint16_t address16; uint8_t checksum; unsigned int lineno; int i; int ret = OK; /* Allocate buffer memory */ alloc = (FAR uint8_t *)lib_malloc(LINE_ALLOC + BIN_ALLOC); if (alloc == NULL) { lerr("ERROR: Failed to allocate memory\n"); return -ENOMEM; } line = alloc; bin = &alloc[LINE_ALLOC]; extension = 0; expected = 0; lineno = 0; /* Read and process the HEX input stream stream until the end of file * record is received (or until an error occurs) */ while ((nbytes = readstream(instream, line, lineno)) != EOF) { /* Increment the line number */ lineno++; /* Did we read enough data to do anything? */ if (nbytes < MINRECORD_ASCSIZE) { lerr("Line %u ERROR: Record too short: %d\n", lineno, nbytes); goto errout_with_einval; } /* We should always read an even number of bytes */ if ((nbytes & 1) != 0) { lerr("Line %u ERROR: Record length is odd: %d\n", lineno, nbytes); goto errout_with_einval; } /* Get the data byte count */ bytecount = byte2bin(&line[BYTECOUNT_LINENDX]); if (bytecount < 0) { lerr("Line %u ERROR: Failed to read bytecount: %d\n", lineno, bytecount); ret = bytecount; goto errout_with_buffers; } /* Verify that the bytecount matches the length of the record */ if (RECORD_ASCSIZE(bytecount) != nbytes) { lerr("Line %u ERROR: Expected %d bytes, read %d\n", lineno, RECORD_ASCSIZE(bytecount), nbytes); goto errout_with_einval; } /* Convert the entire line to binary. We need to do this for * checksum calculation which includes the entire line (minus * the start code and the checksum at the end of the line itself) */ ret = data2bin(bin, line, nbytes); if (ret < 0) { lerr("Line %u ERROR: Failed to convert line to binary: %d\n", lineno, ret); goto errout_with_buffers; } /* Calculate and verify the checksum over all of the data */ nbytes >>= 1; /* Number of bytes in bin[] */ checksum = 0; for (i = 0; i < nbytes; i++) { checksum += bin[i]; } if (checksum != 0) { lerr("Line %u ERROR: Bad checksum %02x\n", lineno, checksum); goto errout_with_einval; } /* Get the 16-bit (unextended) address from the record */ address16 = (uint16_t)bin[ADDRESS_BINNDX] << 8 | (uint16_t)bin[ADDRESS_BINNDX + 1]; /* Handle the record by its record type */ switch (bin[RECTYPE_BINNDX]) { case RECORD_DATA: /* Data */ { /* Swap data in place in the binary buffer as required */ switch (swap) { case HEX2BIN_NOSWAP: /* No swap, stream is the correct byte order */ break; case HEX2BIN_SWAP16: /* Swap bytes in 16-bit values */ { if ((bytecount & 1) != 0) { lerr("Line %d ERROR: Byte count %d is not a multiple " "of 2\n", lineno, bytecount); goto errout_with_einval; } /* Do the byte swap */ hex2bin_swap16(&bin[DATA_BINNDX], bytecount); } break; case HEX2BIN_SWAP32: /* Swap bytes in 32-bit values */ { if ((bytecount & 3) != 0) { lerr("Line %d ERROR: Byte count %d is not a multiple " "of 4\n", lineno, bytecount); goto errout_with_einval; } /* Do the byte swap */ hex2bin_swap32(&bin[DATA_BINNDX], bytecount); } break; default: { lerr("ERROR: Invalid swap argument: %d\n", swap); goto errout_with_einval; } } /* Get and verify the full 32-bit address */ address = extension + (unsigned long)address16; endaddr = address + bytecount; if (address < baseaddr || (endpaddr != 0 && endaddr >= endpaddr)) { lerr("Line %d ERROR: Extended address %08lx is out of " "range\n", lineno, (unsigned long)address); goto errout_with_einval; } /* Seek to the correct position in the OUT stream if we have * made an unexpected jump in the data address. */ if (address != expected) { off_t pos = lib_stream_seek(outstream, address - baseaddr, SEEK_SET); if (pos == (off_t)-1) { lerr("Line %u ERROR: Seek to address %08lu failed\n", lineno, (unsigned long)address); ret = -ESPIPE; goto errout_with_buffers; } } /* Transfer data to the OUT stream */ writedata(outstream, &bin[DATA_BINNDX], bytecount); /* This is the next data address that we expect to see */ expected = address + bytecount; } break; case RECORD_EOF: /* End of file */ /* End Of File record. Must occur exactly once per file in the * last line of the file. The byte count is 00 and the data field * is empty. Usually the address field is also 0000. */ if (bytecount == 0) { ret = OK; goto exit_with_buffers; } lerr("Line %u ERROR: Nonzero bytecount %d in EOF\n", lineno, bytecount); goto errout_with_einval; case RECORD_EXT_SEGADDR: /* Extended segment address record */ /* The address specified by the data field is multiplied by 16 * (shifted 4 bits left) and added to the subsequent data record * addresses. This allows addressing of up to a megabyte of * address space. The address field of this record has to be * 0000, the byte count is 02 (the segment is 16-bit). The * least significant hex digit of the segment address is always * 0. */ if (bytecount != 2 || address16 != 0 || bin[DATA_BINNDX + 1] != 0) { lerr("Line %u ERROR: Invalid segment address\n", lineno); lerr(" bytecount=%d address=%04x segment=%02x%02x\n", bytecount, address16, bin[DATA_BINNDX], bin[DATA_BINNDX + 1]); goto errout_with_einval; } extension = (unsigned long)bin[DATA_BINNDX] << 12; break; case RECORD_START_SEGADDR: /* Start segment address record */ /* For 80x86 processors, it specifies the initial content of * the CS:IP registers. The address field is 0000, the byte * count is 04, the first two bytes are the CS value, the * latter two are the IP value. */ break; case RECORD_EXT_LINADDR: /* Extended linear address record */ /* The address field is 0000, the byte count is 02. The two * data bytes (two hex digit pairs in big endian order) * represent the upper 16 bits of the 32 bit address for * all subsequent 00 type records until the next 04 type * record comes. If there is not a 04 type record, the * upper 16 bits default to 0000. To get the absolute * address for subsequent 00 type records, the address * specified by the data field of the most recent 04 record * is added to the 00 record addresses. */ if (bytecount != 2 || address16 != 0) { lerr("Line %u ERROR: Invalid linear address\n", lineno); lerr(" bytecount=%d address=%04x\n", bytecount, address16); goto errout_with_einval; } extension = (unsigned long)bin[DATA_BINNDX] << 24 | (unsigned long)bin[DATA_BINNDX + 1] << 16; break; case RECORD_START_LINADDR: /* Start linear address record */ /* The address field is 0000, the byte count is 04. The 4 * data bytes represent the 32-bit value loaded into the EIP * register of the 80386 and higher CPU. */ break; default: break; } } lerr("ERROR: No EOF record found\n"); errout_with_einval: ret = -EINVAL; errout_with_buffers: exit_with_buffers: lib_free(alloc); return ret; } #endif /* CONFIG_LIBC_HEX2BIN */