/* * s19reader.c * * Created on: 17.12.2012 * Author: mfro * The ACP Firebee project * * This file is part of BaS_gcc. * * BaS_gcc 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. * * BaS_gcc 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 BaS_gcc. If not, see . * * Copyright 2012 M. Froeschle */ #include #include "bas_printf.h" #include "bas_string.h" #include "sd_card.h" #include "diskio.h" #include "ff.h" #include "s19reader.h" #include "cache.h" /* * Yes, I know. The following doesn't really look like code should look like... * * I did try to map structures over the S-records with (packed) which didn't work reliably due to gcc _not_ packing them appropiate * and finally ended up with this. Not nice, put paid (and working). * */ #define SREC_TYPE(a) (a)[0] /* type of record */ #define SREC_COUNT(a) (a)[1] /* length of valid bytes to follow */ #define SREC_ADDR16(a) (256 * (a)[2] + (a)[3]) /* 2 byte address field */ #define SREC_ADDR24(a) (0x10000 * (a)[2] + 0x100 * \ (a)[3] + (a)[4]) /* 3 byte address field */ #define SREC_ADDR32(a) (0x1000000 * a[2] + 0x10000 * \ (a)[3] + 0x100 * (a)[4] + (a)[5]) /* 4 byte address field */ #define SREC_DATA16(a) ((uint8_t *)&((a)[4])) /* address of first byte of data in a record */ #define SREC_DATA24(a) ((uint8_t *)&((a)[5])) /* address of first data byte in 24 bit record */ #define SREC_DATA32(a) ((uint8_t *)&((a)[6])) /* adress of first byte of a record with 32 bit address field */ #define SREC_DATA16_SIZE(a) (SREC_COUNT((a)) - 3) /* length of the data[] array without the checksum field */ #define SREC_DATA24_SIZE(a) (SREC_COUNT((a)) - 4) /* length of the data[] array without the checksum field */ #define SREC_DATA32_SIZE(a) (SREC_COUNT((a)) - 5) /* length of the data[] array without the checksum field */ #define SREC_CHECKSUM(a) (a)[SREC_COUNT(a) + 2 - 1] /* record's checksum (two's complement of the sum of all bytes) */ /* * convert a single hex character into byte */ static uint8_t nibble_to_byte(uint8_t nibble) { if ((nibble >= '0') && (nibble <= '9')) return nibble - '0'; else if ((nibble >= 'A' && nibble <= 'F')) return 10 + nibble - 'A'; else if ((nibble >= 'a' && nibble <= 'f')) return 10 + nibble - 'a'; return 0; } /* * convert two hex characters into byte */ static uint8_t hex_to_byte(uint8_t hex[2]) { return 16 * (nibble_to_byte(hex[0])) + (nibble_to_byte(hex[1])); } #ifdef _NOT_USED_ /* * convert four hex characters into a 16 bit word */ static uint16_t hex_to_word(uint8_t hex[4]) { return 256 * hex_to_byte(&hex[0]) + hex_to_byte(&hex[2]); } /* * convert eight hex characters into a 32 bit word */ static uint32_t hex_to_long(uint8_t hex[8]) { return 65536 * hex_to_word(&hex[0]) + hex_to_word(&hex[4]); } #endif /* _NOT_USED_ */ /* * compute the record checksum * * it consists of the one's complement of the byte sum of the data from the count field until the end */ static uint8_t checksum(uint8_t arr[]) { int i; uint8_t checksum = SREC_COUNT(arr); for (i = 0; i < SREC_COUNT(arr) - 1; i++) { checksum += arr[i + 2]; } return ~checksum; } #ifdef _NOT_USED_ void print_record(uint8_t *arr) { switch (SREC_TYPE(arr)) { case 0: { xprintf("type 0x%x ", SREC_TYPE(arr)); xprintf("count 0x%x ", SREC_COUNT(arr)); xprintf("addr 0x%x ", SREC_ADDR16(arr)); xprintf("module %11.11s ", SREC_DATA16(arr)); xprintf("chk 0x%x 0x%x\r\n", SREC_CHECKSUM(arr), checksum(arr)); } break; case 3: case 7: { xprintf("type 0x%x ", SREC_TYPE(arr)); xprintf("count 0x%x ", SREC_COUNT(arr)); xprintf("addr 0x%x ", SREC_ADDR32(arr)); xprintf("data %02x,%02x,%02x,%02x,... ", SREC_DATA32(arr)[0], SREC_DATA32(arr)[1], SREC_DATA32(arr)[3], SREC_DATA32(arr)[4]); xprintf("chk 0x%x 0x%x\r\n", SREC_CHECKSUM(arr), checksum(arr)); } break; default: xprintf("unsupported report type %d in print_record\r\n", arr[0]); break; } } #endif /* _NOT_USED_ */ /* * convert an S-record line into its corresponding byte vector (ASCII->binary) */ static void line_to_vector(uint8_t *buff, uint8_t *vector) { int i; int length; uint8_t *vp = vector; length = hex_to_byte(buff + 2); buff++; *vp++ = nibble_to_byte(*buff); /* record type. Only one single nibble */ buff++; for (i = 0; i <= length; i++) { *vp++ = hex_to_byte(buff); buff += 2; } } /* * read and parse a Motorola S-record file and copy contents to dst. The theory of operation is to read and parse the S-record file * and to use the supplied callback routine to copy the buffer to the destination once the S-record line is converted. * The memcpy callback can be anything (as long as it conforms parameter-wise) - a basically empty function to just let * read_srecords validate the file, a standard memcpy() to copy file contents to destination RAM or a more sophisticated * routine that does write/erase flash * * FIXME: Currently only records that the gcc toolchain emits are supported. * * Parameters: * IN * filename - the filename that contains the S-records * callback - the memcpy() routine discussed above * OUT * start_address - the execution address of the code as read from the file. Can be used to jump into and execute it * actual_length - the overall length of the binary code read from the file * returns * OK or an err_t error code if anything failed */ err_t read_srecords(char *filename, void **start_address, uint32_t *actual_length, memcpy_callback_t callback) { FRESULT fres; FIL file; err_t ret = OK; if ((fres = f_open(&file, filename, FA_READ) == FR_OK)) { uint8_t line[80]; int lineno = 0; int data_records = 0; bool found_block_header = false; bool found_block_end = false; bool found_block_data = false; while (ret == OK && (uint8_t *) f_gets((char *) line, sizeof(line), &file) != NULL) { lineno++; uint8_t vector[80]; line_to_vector(line, vector); /* vector now contains the decoded contents of line, from line[1] on */ if (line[0] == 'S') { if (SREC_CHECKSUM(vector) != checksum(vector)) { xprintf("invalid checksum 0x%x (should be 0x%x) in line %d\r\n", SREC_CHECKSUM(vector), checksum(vector), lineno); ret = FAIL; } switch (vector[0]) { case 0: /* block header */ found_block_header = true; if (found_block_data || found_block_end) { xprintf("S7 or S3 record found before S0: S-records corrupt?\r\n"); ret = FAIL; } break; case 2: /* three byte address field data record */ if (!found_block_header || found_block_end) { xprintf("S3 record found before S0 or after S7: S-records corrupt?\r\n"); ret = FAIL; } ret = callback((uint8_t *) SREC_ADDR24(vector), SREC_DATA24(vector), SREC_DATA24_SIZE(vector)); data_records++; break; case 3: /* four byte address field data record */ if (!found_block_header || found_block_end) { xprintf("S3 record found before S0 or after S7: S-records corrupt?\r\n"); ret = FAIL; } ret = callback((uint8_t *) SREC_ADDR32(vector), SREC_DATA32(vector), SREC_DATA32_SIZE(vector)); data_records++; break; case 7: /* four byte address field end record */ if (!found_block_header || found_block_end) { xprintf("S7 record found before S0 or after S7: S-records corrupt?\r\n"); } else { // xprintf("S7 record (end) found after %d valid data blocks\r\n", data_records); *start_address = (void *) SREC_ADDR32(vector); } break; case 8: /* three byte address field end record */ if (!found_block_header || found_block_end) { xprintf("S8 record found before S0 or after S8: S-records corrupt?\r\n"); } else { // xprintf("S7 record (end) found after %d valid data blocks\r\n", data_records); *start_address = (void *) SREC_ADDR24(vector); } break; default: xprintf("unsupported record type (%d) found in line %d\r\n", vector[0], lineno); xprintf("offending line: \r\n"); xprintf("%s\r\n", line); ret = FAIL; break; } } else { xprintf("illegal character ('%c') found on line %d: S-records corrupt?\r\n", line[0], lineno); ret = FAIL; break; } } f_close(&file); } else { xprintf("could not open file %s\r\n", filename); ret = FILE_OPEN; } return ret; } /* * this callback just does nothing besides returning OK. Meant to do a dry run over the file to check its integrity */ static err_t simulate() { err_t ret = OK; return ret; } #ifdef _NOT_USED_ static err_t flash(uint8_t *dst, uint8_t *src, uint32_t length) { err_t ret = OK; /* TODO: do the actual flash */ amd_flash_program(dst, src, length, false, NULL, xputchar); return ret; } #endif /* _NOT_USED_ */ /* * this callback verifies the data against the S-record file contents after a write to destination */ static err_t verify(uint8_t *dst, uint8_t *src, size_t length) { uint8_t *end = src + length; do { if (*src++ != *dst++) return FAIL; } while (src < end); return OK; } /* * needed to avoid missing type cast warning below */ static inline err_t srec_memcpy(uint8_t *dst, uint8_t *src, size_t n) { err_t e = OK; memcpy((void *) dst, (void *) src, n); return e; } void srec_execute(char *flasher_filename) { DRESULT res; FRESULT fres; FATFS fs; FIL file; err_t err; void *start_address; uint32_t length; disk_initialize(0); res = disk_status(0); if (res == RES_OK) { fres = f_mount(0, &fs); if (fres == FR_OK) { if ((fres = f_open(&file, flasher_filename, FA_READ) != FR_OK)) { xprintf("flasher file %s not present on disk\r\n", flasher_filename); } else { f_close(&file); /* first pass: parse and check for inconsistencies */ xprintf("check file integrity: "); err = read_srecords(flasher_filename, &start_address, &length, simulate); if (err == OK) { /* next pass: copy data to destination */ xprintf("OK.\r\ncopy/flash data: "); err = read_srecords(flasher_filename, &start_address, &length, srec_memcpy); if (err == OK) { /* next pass: verify data */ xprintf("OK.\r\nverify data: "); err = read_srecords(flasher_filename, &start_address, &length, verify); if (err == OK) { xprintf("OK.\r\n"); typedef void void_func(void); void_func *func; xprintf("target successfully written and verified. Start address: %p\r\n", start_address); func = start_address; flush_and_invalidate_caches(); (*func)(); } else { xprintf("failed\r\n"); } } else { xprintf("failed\r\n"); } } else { xprintf("failed\r\n"); } } } else { // xprintf("could not mount FAT FS\r\n"); } f_mount(0, NULL); } else { // xprintf("could not initialize SD card\r\n"); } }