diff --git a/source/board/microbitv2/microbitv2.c b/source/board/microbitv2/microbitv2.c index 8c61bc519..9ebfc3b19 100644 --- a/source/board/microbitv2/microbitv2.c +++ b/source/board/microbitv2/microbitv2.c @@ -446,13 +446,16 @@ static uint32_t read_file_data_txt(uint32_t sector_offset, uint8_t *data, uint32 return VFS_SECTOR_SIZE; } -uint8_t board_detect_incompatible_image(const uint8_t *data, uint32_t size) +uint8_t board_detect_incompatible_image(uint32_t addr, const uint8_t *data, uint32_t size) { - uint8_t result = 0; + // We can only check image compatibility on the first block of flash data + if (addr != 0) { + return 0; + } // Check difference in vectors (mem fault, bus fault, usage fault) // If these vectors are 0, we assume it's an M0 image (not compatible) - result = memcmp(data + M0_RESERVED_VECT_OFFSET, (uint8_t[M0_RESERVED_VECT_SIZE]){0}, M0_RESERVED_VECT_SIZE); + uint8_t result = memcmp(data + M0_RESERVED_VECT_OFFSET, (uint8_t[M0_RESERVED_VECT_SIZE]){0}, M0_RESERVED_VECT_SIZE); return result == 0; } diff --git a/source/daplink/drag-n-drop/file_stream.c b/source/daplink/drag-n-drop/file_stream.c index 284ccc9f8..062ce80cc 100644 --- a/source/daplink/drag-n-drop/file_stream.c +++ b/source/daplink/drag-n-drop/file_stream.c @@ -30,6 +30,16 @@ #include "compiler.h" #include "validation.h" +// Set to 1 to enable debugging +#define DEBUG_FILE_STREAM 0 + +#if DEBUG_FILE_STREAM +#include "daplink_debug.h" +#define stream_printf debug_msg +#else +#define stream_printf(...) +#endif + typedef enum { STREAM_STATE_CLOSED, STREAM_STATE_OPEN, @@ -75,9 +85,15 @@ static error_t open_hex(void *state); static error_t write_hex(void *state, const uint8_t *data, uint32_t size); static error_t close_hex(void *state); -stream_t stream[] = { +static bool detect_uhex_blocks(const uint8_t *data, uint32_t size); +static error_t open_uhex_blocks(void *state); +static error_t write_uhex_blocks(void *state, const uint8_t *data, uint32_t size); +static error_t close_uhex_blocks(void *state); + +static stream_t stream[] = { {detect_bin, open_bin, write_bin, close_bin}, // STREAM_TYPE_BIN {detect_hex, open_hex, write_hex, close_hex}, // STREAM_TYPE_HEX + {detect_uhex_blocks, open_uhex_blocks, write_uhex_blocks, close_uhex_blocks}, // STREAM_TYPE_UHEX_BLOCKS }; COMPILER_ASSERT(ARRAY_SIZE(stream) == STREAM_TYPE_COUNT); // STREAM_TYPE_NONE must not be included in count @@ -104,6 +120,7 @@ stream_type_t stream_start_identify(const uint8_t *data, uint32_t size) for (i = STREAM_TYPE_START; i < STREAM_TYPE_COUNT; i++) { if (stream[i].detect(data, size)) { + stream_printf("file_stream start_identify stream=%i\r\n", i); return i; } } @@ -118,12 +135,48 @@ stream_type_t stream_type_from_name(const vfs_filename_t filename) if (0 == strncmp("BIN", &filename[8], 3)) { return STREAM_TYPE_BIN; } else if (0 == strncmp("HEX", &filename[8], 3)) { - return STREAM_TYPE_HEX; + return STREAM_TYPE_HEX_OR_UHEX; } else { return STREAM_TYPE_NONE; } } +bool stream_compatible(stream_type_t type_left, stream_type_t type_right) +{ + if (type_left == type_right) { + return true; + } + + if ((type_left == STREAM_TYPE_HEX_OR_UHEX && + (type_right == STREAM_TYPE_HEX || type_right == STREAM_TYPE_UHEX_BLOCKS)) || + (type_right == STREAM_TYPE_HEX_OR_UHEX && + (type_left == STREAM_TYPE_HEX || type_left == STREAM_TYPE_UHEX_BLOCKS))) { + return true; + } + + return false; +} + +bool stream_self_contained_block(stream_type_t type, const uint8_t *data, uint32_t size) +{ + switch (type) { + case STREAM_TYPE_BIN: + return false; + + case STREAM_TYPE_HEX: + // A hex stream can also be a Universal Hex stream + return validate_uhex_block(data, size) ? true : false; + + case STREAM_TYPE_UHEX_BLOCKS: + // The Universal Hex stream can be ordered (sectors) or unordered (blocks) + return validate_uhex_block(data, size) ? true : false; + + default: + util_assert(0); + return false; + } +} + error_t stream_open(stream_type_t stream_type) { error_t status; @@ -147,6 +200,7 @@ error_t stream_open(stream_type_t stream_type) current_stream = &stream[stream_type]; // Initialize the specified stream status = current_stream->open(&shared_state); + stream_printf("file_stream stream_open(type=%d); open ret=%d\r\n", stream_type, status); if (ERROR_SUCCESS != status) { state = STREAM_STATE_ERROR; @@ -170,6 +224,7 @@ error_t stream_write(const uint8_t *data, uint32_t size) stream_thread_assert(); // Write to stream status = current_stream->write(&shared_state, data, size); + stream_printf("file_stream stream_write(size=%d); write ret=%d\r\n", size, status); if (ERROR_SUCCESS_DONE == status) { state = STREAM_STATE_END; @@ -198,6 +253,7 @@ error_t stream_close(void) stream_thread_assert(); // Close stream status = current_stream->close(&shared_state); + stream_printf("file_stream stream_close; close ret=%d\r\n", status); state = STREAM_STATE_CLOSED; return status; } @@ -289,6 +345,13 @@ static error_t close_bin(void *state) static bool detect_hex(const uint8_t *data, uint32_t size) { + // Both Universal Hex formats will pass the normal hex file validation, + // but a Universal Hex in block format needs to be processed with the + // STREAM_TYPE_UHEX_BLOCKS stream. + // A Universal Hex in segment format can be be processed as a normal hex. + if (1 == validate_uhex_block(data, size)) { + return false; + } return 1 == validate_hexfile(data); } @@ -315,6 +378,7 @@ static error_t write_hex(void *state, const uint8_t *data, uint32_t size) while (1) { // try to decode a block of hex data into bin data parse_status = parse_hex_blob(data, size, &block_amt_parsed, hex_state->bin_buffer, sizeof(hex_state->bin_buffer), &bin_start_address, &bin_buf_written); + stream_printf("file_stream write_hex; parse_hex_blob ret=%d, bin_buf_written=%d\r\n", parse_status, bin_buf_written); // the entire block of hex was decoded. This is a simple state if (HEX_PARSE_OK == parse_status) { @@ -364,3 +428,35 @@ static error_t close_hex(void *state) status = flash_decoder_close(); return status; } + +/* Universal Hex, block format, file processing */ +/* https://tech.microbit.org/software/spec-universal-hex/ */ +/* The Universal Hex segment format is processed by the Intel Hex parser. */ +/* This stream is for the Universal Hex block format only. */ + +static bool detect_uhex_blocks(const uint8_t *data, uint32_t size) +{ + return 1 == validate_uhex_block(data, size); +} + +static inline error_t open_uhex_blocks(void *state) +{ + return open_hex(state); +} + +static inline error_t write_uhex_blocks(void *state, const uint8_t *data, uint32_t size) +{ + error_t status = write_hex(state, data, size); + + // The block containing the EoF record could arrive at any point + if (ERROR_SUCCESS_DONE == status || ERROR_SUCCESS == status) { + status = ERROR_SUCCESS_DONE_OR_CONTINUE; + } + + return status; +} + +static inline error_t close_uhex_blocks(void *state) +{ + return close_hex(state); +} diff --git a/source/daplink/drag-n-drop/file_stream.h b/source/daplink/drag-n-drop/file_stream.h index 50bf38e08..605ca01b4 100644 --- a/source/daplink/drag-n-drop/file_stream.h +++ b/source/daplink/drag-n-drop/file_stream.h @@ -36,11 +36,15 @@ typedef enum { STREAM_TYPE_BIN = STREAM_TYPE_START, STREAM_TYPE_HEX, + STREAM_TYPE_UHEX_BLOCKS, // Add new stream types here STREAM_TYPE_COUNT, + // Needed as both formats share the same .hex extension + STREAM_TYPE_HEX_OR_UHEX, + STREAM_TYPE_NONE } stream_type_t; @@ -50,6 +54,12 @@ stream_type_t stream_start_identify(const uint8_t *data, uint32_t size); // Stateless function to identify a filestream by its name stream_type_t stream_type_from_name(const vfs_filename_t filename); +// Stateless function to identify if two streams are compatible +bool stream_compatible(stream_type_t type_left, stream_type_t type_right); + +// Stateless function to identify if a stream can take blocks out of order +bool stream_self_contained_block(stream_type_t type, const uint8_t *data, uint32_t size); + error_t stream_open(stream_type_t stream_type); error_t stream_write(const uint8_t *data, uint32_t size); diff --git a/source/daplink/drag-n-drop/flash_decoder.c b/source/daplink/drag-n-drop/flash_decoder.c index 46b4bc7ae..7551a303a 100644 --- a/source/daplink/drag-n-drop/flash_decoder.c +++ b/source/daplink/drag-n-drop/flash_decoder.c @@ -60,7 +60,7 @@ static bool flash_type_target_bin; static bool flash_decoder_is_at_end(uint32_t addr, const uint8_t *data, uint32_t size); -__WEAK uint8_t board_detect_incompatible_image(const uint8_t *data, uint32_t size) +__WEAK uint8_t board_detect_incompatible_image(uint32_t addr, const uint8_t *data, uint32_t size) { return 0; // Return 0 if image is compatible } @@ -190,14 +190,14 @@ error_t flash_decoder_get_flash(flash_decoder_type_t type, uint32_t addr, bool a return status; } -error_t flash_decoder_validate_target_image(flash_decoder_type_t type, const uint8_t *data, uint32_t size) +error_t flash_decoder_validate_target_image(flash_decoder_type_t type, uint32_t addr, const uint8_t *data, uint32_t size) { error_t status = ERROR_SUCCESS; if (daplink_is_interface()) { if (FLASH_DECODER_TYPE_TARGET == type) { if (g_board_info.target_cfg) { - if (board_detect_incompatible_image(data, size)){ + if (board_detect_incompatible_image(addr, data, size)){ status = ERROR_FD_INCOMPATIBLE_IMAGE; } else { status = ERROR_SUCCESS; @@ -295,7 +295,7 @@ error_t flash_decoder_write(uint32_t addr, const uint8_t *data, uint32_t size) // Validate incompatible target image file if (config_get_detect_incompatible_target()){ - status = flash_decoder_validate_target_image(flash_type, flash_buf, flash_buf_pos); + status = flash_decoder_validate_target_image(flash_type, addr, flash_buf, flash_buf_pos); if (ERROR_SUCCESS != status) { state = DECODER_STATE_ERROR; @@ -374,6 +374,9 @@ error_t flash_decoder_close(void) if ((DECODER_STATE_DONE != prev_state) && (flash_type != FLASH_DECODER_TYPE_TARGET) && (status == ERROR_SUCCESS)) { + flash_decoder_printf(" error: update incomplete\r\n"); + flash_decoder_printf(" prev_state=%d; flash_type=%d; status=%d\r\n", + prev_state, flash_type, status); status = ERROR_IAP_UPDT_INCOMPLETE; } diff --git a/source/daplink/drag-n-drop/flash_decoder.h b/source/daplink/drag-n-drop/flash_decoder.h index bf6b0bd8c..2895c93d0 100644 --- a/source/daplink/drag-n-drop/flash_decoder.h +++ b/source/daplink/drag-n-drop/flash_decoder.h @@ -45,7 +45,6 @@ typedef enum { flash_decoder_type_t flash_decoder_detect_type(const uint8_t *data, uint32_t size, uint32_t addr, bool addr_valid); error_t flash_decoder_get_flash(flash_decoder_type_t type, uint32_t addr, bool addr_valid, uint32_t *start_addr, const flash_intf_t **flash_intf); -error_t flash_decoder_validate_target_image(flash_decoder_type_t type, const uint8_t *data, uint32_t size); error_t flash_decoder_open(void); error_t flash_decoder_write(uint32_t addr, const uint8_t *data, uint32_t size); error_t flash_decoder_close(void); diff --git a/source/daplink/drag-n-drop/vfs_manager.c b/source/daplink/drag-n-drop/vfs_manager.c index 22f9dde33..87648e50c 100644 --- a/source/daplink/drag-n-drop/vfs_manager.c +++ b/source/daplink/drag-n-drop/vfs_manager.c @@ -85,6 +85,7 @@ typedef struct { bool stream_started; // Stream processing started. This only gets reset remount bool stream_finished; // Stream processing is done. This only gets reset remount bool stream_optional_finish; // True if the stream processing can be considered done + bool stream_ooo_blocks; // True if the stream data blocks can be processed out of order bool file_info_optional_finish; // True if the file transfer can be considered done bool transfer_timeout; // Set if the transfer was finished because of a timeout. This only gets reset remount stream_type_t stream; // Current stream or STREAM_TYPE_NONE is stream is closed. This only gets reset remount @@ -112,6 +113,7 @@ static const file_transfer_state_t default_transfer_state = { false, false, false, + false, STREAM_TYPE_NONE, }; @@ -439,13 +441,13 @@ static void file_change_handler(const vfs_filename_t filename, vfs_file_change_t static void file_data_handler(uint32_t sector, const uint8_t *buf, uint32_t num_of_sectors) { stream_type_t stream; - uint32_t size; + uint32_t size = VFS_SECTOR_SIZE * num_of_sectors; // this is the key for starting a file write - we dont care what file types are sent // just look for something unique (NVIC table, hex, srec, etc) until root dir is updated if (!file_transfer_state.stream_started) { // look for file types we can program - stream = stream_start_identify((uint8_t *)buf, VFS_SECTOR_SIZE * num_of_sectors); + stream = stream_start_identify((uint8_t *)buf, size); if (STREAM_TYPE_NONE != stream) { transfer_stream_open(stream, sector); @@ -453,13 +455,25 @@ static void file_data_handler(uint32_t sector, const uint8_t *buf, uint32_t num_ } if (file_transfer_state.stream_started) { - // Ignore sectors coming before this file - if (sector < file_transfer_state.start_sector) { - return; - } + bool self_contained_block = stream_self_contained_block(file_transfer_state.stream, buf, size); - // sectors must be in order - if (sector != file_transfer_state.file_next_sector) { + if (self_contained_block) { + vfs_mngr_printf("vfs_manager file_data_handler sector=%i\r\n", sector); + vfs_mngr_printf(" sector self-contained\r\n"); + file_transfer_state.stream_ooo_blocks = true; + } else if (file_transfer_state.stream_ooo_blocks) { + // Invalid block for a self-contained stream, can be ignored + vfs_mngr_printf("vfs_manager file_data_handler sector=%i\r\n", sector); + vfs_mngr_printf(" invalid data for self-contained stream\r\n"); + vfs_mngr_printf(" size=%d data=%x,%x,%x,%x,...,%x,%x,%x,%x\r\n", + size, buf[0], buf[1], buf[2], buf[3], buf[size - 4], + buf[size - 3], buf[size - 2], buf[size - 1]); + return; + } else if (sector < file_transfer_state.start_sector) { + // For ordered streams, ignore sectors coming before this file + return; + } else if (sector != file_transfer_state.file_next_sector) { + // For ordered streams, sector must be the next in the sequence vfs_mngr_printf("vfs_manager file_data_handler sector=%i\r\n", sector); if (sector < file_transfer_state.file_next_sector) { @@ -482,7 +496,6 @@ static void file_data_handler(uint32_t sector, const uint8_t *buf, uint32_t num_ } // This sector could be part of the file so record it - size = VFS_SECTOR_SIZE * num_of_sectors; file_transfer_state.size_transferred += size; file_transfer_state.file_next_sector = sector + num_of_sectors; @@ -584,15 +597,6 @@ static void transfer_update_file_info(vfs_file_t file, uint32_t start_sector, ui } } - // Initialize the stream if it has not been set - if (STREAM_TYPE_NONE == file_transfer_state.stream) { - file_transfer_state.stream = stream; - - if (stream != STREAM_TYPE_NONE) { - vfs_mngr_printf(" stream=%i\r\n", stream); - } - } - // Check - File size must either grow or be smaller than the size already transferred if ((size < file_transfer_state.file_size) && (size < file_transfer_state.size_transferred) && (size > 0)) { vfs_mngr_printf(" error: file size changed from %i to %i\r\n", file_transfer_state.file_size, size); @@ -608,7 +612,7 @@ static void transfer_update_file_info(vfs_file_t file, uint32_t start_sector, ui } // Check - stream must be the same - if ((stream != STREAM_TYPE_NONE) && (stream != file_transfer_state.stream)) { + if (stream != STREAM_TYPE_NONE && file_transfer_state.stream != STREAM_TYPE_NONE && !stream_compatible(file_transfer_state.stream, stream)) { vfs_mngr_printf(" error: changed types during transfer from %i to %i\r\n", file_transfer_state.stream, stream); transfer_update_state(ERROR_ERROR_DURING_TRANSFER); return; @@ -761,6 +765,12 @@ static void transfer_update_state(error_t status) (file_transfer_state.size_transferred >= file_transfer_state.file_size) && (file_transfer_state.file_size > 0) && (file_transfer_state.start_sector == file_transfer_state.file_start_sector); + if (file_transfer_state.stream_ooo_blocks && + file_transfer_state.file_size > 0 && + file_transfer_state.size_transferred >= file_transfer_state.file_size) { + vfs_mngr_printf(" OoO file_info_optional_finish=%d\r\n", file_transfer_state.file_info_optional_finish); + file_transfer_state.file_info_optional_finish = true; + } transfer_timeout = file_transfer_state.transfer_timeout; transfer_started = (VFS_FILE_INVALID != file_transfer_state.file_to_program) || (STREAM_TYPE_NONE != file_transfer_state.stream); diff --git a/source/daplink/validation.c b/source/daplink/validation.c index 0a3134f89..ba719b6d0 100644 --- a/source/daplink/validation.c +++ b/source/daplink/validation.c @@ -94,3 +94,12 @@ uint8_t validate_hexfile(const uint8_t *buf) return ((buf[0] == ':') && ((buf[8] == '0') || (buf[8] == '2') || (buf[8] == '3') || (buf[8] == '4') || (buf[8] == '5'))) ? 1 : 0; } } + +uint8_t validate_uhex_block(const uint8_t *buf, uint32_t size) { + if (size != 512) { + return 0; + } + return (memcmp(buf, (const void *)":02000004", 9) == 0) && + (memcmp(buf + 16, (const void *)":0400000A", 9) == 0) && + (buf[511] == '\n'); +} diff --git a/source/daplink/validation.h b/source/daplink/validation.h index 929d827fb..19e4ea60b 100644 --- a/source/daplink/validation.h +++ b/source/daplink/validation.h @@ -30,6 +30,7 @@ extern "C" { uint8_t validate_bin_nvic(const uint8_t *buf); uint8_t validate_hexfile(const uint8_t *buf); +uint8_t validate_uhex_block(const uint8_t *buf, uint32_t size); /*! * @brief Baseline implementation of NVIC validator.