diff --git a/src/bl_install.c b/src/bl_install.c index a54989a..a86b445 100644 --- a/src/bl_install.c +++ b/src/bl_install.c @@ -6,6 +6,7 @@ */ #include #include +#include #include #include @@ -14,28 +15,22 @@ #include "error.h" #include "funcs.h" -/** - * @brief Erase internal flash - * @retval error according to enum #ERR - */ -static int bootloader_erase(void); - /** * @brief Start the programming operation * @param offset Start of programmed region * @param length Length of programmed region * @retval error according to enum #ERR */ -static int bootloader_start(int offset, int length); +static int bootloader_erase_and_start(int offset, int length); /** * @brief Send firmware to bootloader - * @param fp Firmware file + * @param firmware Firmware image * @param length Length of programmed region * @param pCrc32 Locally-calculated CRC32 of the firmware * @retval error according to enum #ERR */ -static int bootloader_send(FILE *fp, int length, u32* pCrc32); +static int bootloader_send(u8 *firmware, int length, u32* pCrc32); /** * @brief Request CRC32 verification from the bootloader @@ -46,10 +41,6 @@ static int bootloader_send(FILE *fp, int length, u32* pCrc32); */ static int bootloader_checksum(int offset, int length, u32 *pCrc32); -/* this matches LEGO FW sizes */ -#define FLASH_START 0x00000000 -#define FLASH_SIZE (16 * 1000 * 1024) - /** * @brief Install new firmware binary to the internal flash. * @param fp Firmware file to download to the brick. @@ -59,98 +50,96 @@ static int bootloader_checksum(int offset, int length, u32 *pCrc32); */ int bootloader_install(FILE *fp) { - int err = ERR_UNK; - u32 local_crc32 = 0; - u32 remote_crc32 = 0; + u8 *firmware = calloc(FLASH_SIZE, 1); + if (!firmware) { + errmsg = "out of memory"; + return ERR_NOMEM; + } - puts("Erasing flash... (takes 1 minute 48 seconds)"); // 108 seconds - err = bootloader_erase(); - if (err != ERR_UNK) - return err; + int read_bytes = fread(firmware, 1, FLASH_SIZE, fp); // this may be less than FLASH_SIZE (e.g. Pybricks has a small firmware image) + if (ferror(fp)) { + errmsg = "Reading of firmware file failed"; + free(firmware); + return ERR_IO; + } - puts("Downloading..."); - err = bootloader_start(FLASH_START, FLASH_SIZE); // extremely short - if (err != ERR_UNK) - return err; - err = bootloader_send(fp, FLASH_SIZE, &local_crc32); // variable time - if (err != ERR_UNK) - return err; + int sector_count = (read_bytes + FLASH_SECTOR_SIZE - 1) / FLASH_SECTOR_SIZE; - puts("Calculating flash checksum... (takes 17 seconds)"); - err = bootloader_checksum(FLASH_START, FLASH_SIZE, &remote_crc32); // 16.7 seconds + printf("Programming %d EV3 flash sectors...\n", sector_count); - // handle usb 3.0 bug - if (err == ERR_USBLOOP) - { - puts("Checksum not checked, assuming update was OK. Rebooting."); - } - else if (err == ERR_UNK) // no error occurred - { - if (local_crc32 != remote_crc32) { - fprintf(stderr, "error: checksums do not match: remote %08X != local %08X\n", - remote_crc32, local_crc32); - return ERR_IO; - } else { - puts("Success! Local and remote checksums match. Rebooting."); + int err = ERR_UNK; + for (int sector = 0; sector < sector_count; sector++) { + u32 local_crc32 = 0; + u32 remote_crc32 = 0; + + printf("- Sector %3d (%2d %%)\n", sector, 100*sector/sector_count); + err = bootloader_erase_and_start(sector*FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE); + if (err != ERR_UNK) { + puts("Flash erase returned an error!"); + break; } - } - else // other error occurred - { - return err; - } - return bootloader_exit(); -} - -static int bootloader_erase(void) -{ - FW_ERASEFLASH *request = packet_alloc(FW_ERASEFLASH, 0); - int res = ev3_write(handle, (u8 *) request, request->packetLen + PREFIX_SIZE); - if (res < 0) - { - errmsg = "Unable to write FW_ERASEFLASH."; - return ERR_COMM; - } + err = bootloader_send(firmware + sector*FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE, &local_crc32); + if (err != ERR_UNK) { + puts("Programming returned an error!"); + break; + } - FW_ERASEFLASH_REPLY *reply = malloc(sizeof(FW_ERASEFLASH_REPLY)); - res = ev3_read_timeout(handle, (u8 *) reply, sizeof(FW_ERASEFLASH_REPLY), -1); - if (res <= 0) - { - errmsg = "Unable to read FW_ERASEFLASH"; - return ERR_COMM; + err = bootloader_checksum(sector*FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE, &remote_crc32); + if (err == ERR_USBLOOP && sector == 0) + { + puts("NOTE: CRC not checked, because the brick is likely plugged to a USB 3.0 port."); + } + else if (err == ERR_UNK) // no error occurred + { + if (local_crc32 != remote_crc32) { + printf("Checksum does not match: remote %08X != local %08X\n", remote_crc32, local_crc32); + err = ERR_COMM; + break; + } + } + else // other error occurred + { + puts("Checksum computation returned an error!"); + break; + } } - // note: accept looped-back packets (usb 3.0 bug; reply not required here) - if (reply->type != VM_OK && reply->type != VM_SYS_RQ) - { - errno = reply->ret; - fputs("Operation failed.\nlast_reply=", stderr); - print_bytes(reply, reply->packetLen + 2); - - errmsg = "`FW_ERASEFLASH` was denied."; - return ERR_VM; + free(firmware); + if (err == ERR_UNK) { + puts("Flashing finished, rebooting the brick."); + return bootloader_exit(); + } else { + puts("Some error occurred, leaving the brick in the bootloader mode."); + puts("To exit it manually, simply remove the EV3 battery and then put it back in."); + return err; } - return ERR_UNK; } -static int bootloader_start(int offset, int length) +static int bootloader_erase_and_start(int offset, int length) { - FW_START_DOWNLOAD *request = packet_alloc(FW_START_DOWNLOAD, 0); + FW_START_DOWNLOAD_WITH_ERASE *request = NULL; + FW_START_DOWNLOAD_WITH_ERASE_REPLY *reply = NULL; + int err; + + request = packet_alloc(FW_START_DOWNLOAD_WITH_ERASE, 0); request->flashStart = offset; request->flashLength = length; int res = ev3_write(handle, (u8 *) request, request->packetLen + PREFIX_SIZE); if (res < 0) { errmsg = "Unable to write FW_START_DOWNLOAD."; - return ERR_COMM; + err = ERR_COMM; + goto exit; } - FW_START_DOWNLOAD_REPLY *reply = malloc(sizeof(FW_START_DOWNLOAD_REPLY)); - res = ev3_read_timeout(handle, (u8 *) reply, sizeof(FW_START_DOWNLOAD_REPLY), -1); + reply = malloc(sizeof(FW_START_DOWNLOAD_WITH_ERASE_REPLY)); + res = ev3_read_timeout(handle, (u8 *) reply, sizeof(FW_START_DOWNLOAD_WITH_ERASE_REPLY), -1); if (res <= 0) { errmsg = "Unable to read FW_START_DOWNLOAD"; - return ERR_COMM; + err = ERR_COMM; + goto exit; } // note: accept looped-back packets (usb 3.0 bug; reply not required here) @@ -160,43 +149,35 @@ static int bootloader_start(int offset, int length) fputs("Operation failed.\nlast_reply=", stderr); print_bytes(reply, reply->packetLen + 2); - errmsg = "`FW_START_DOWNLOAD` was denied."; - return ERR_VM; + errmsg = "`FW_START_DOWNLOAD_WITH_ERASE_REPLY` was denied."; + err = ERR_VM; + goto exit; } - return ERR_UNK; + err = ERR_UNK; + +exit: + free(request); + free(reply); + return err; } -static int bootloader_send(FILE *fp, int length, u32* pCrc32) +static int bootloader_send(u8 *buffer, int length, u32* pCrc32) { - *pCrc32 = 0; - - int max_payload = 1024 - (sizeof(FW_DOWNLOAD_DATA) - PREFIX_SIZE + 2); + const int max_payload = 1024 - (sizeof(FW_DOWNLOAD_DATA) - PREFIX_SIZE + 2); FW_DOWNLOAD_DATA *request = packet_alloc(FW_DOWNLOAD_DATA, max_payload); FW_DOWNLOAD_DATA_REPLY *reply = malloc(sizeof(FW_DOWNLOAD_DATA_REPLY)); int total = length; int sent_so_far = 0; - int file_ended = 0; - int res = 0; - int last_percent = 0; + int err = ERR_UNK; u32 crc = 0; - printf("Progress: %3d %%\n", 0); - while (sent_so_far < total) { int remaining = total - sent_so_far; int this_block = remaining <= max_payload ? remaining : max_payload; + int res; - if (!file_ended) { - int real = fread(request->payload, 1, this_block, fp); - if (real < this_block) { - file_ended = 1; - memset(request->payload + real, 0, this_block - real); - } - } else { - memset(request->payload, 0, this_block); - } - + memcpy(request->payload, buffer + sent_so_far, this_block); crc = crc32(crc, request->payload, this_block); request->packetLen = sizeof(FW_DOWNLOAD_DATA) - PREFIX_SIZE + this_block; @@ -205,14 +186,16 @@ static int bootloader_send(FILE *fp, int length, u32* pCrc32) if (res < 0) { errmsg = "Unable to write FW_DOWNLOAD_DATA."; - return ERR_COMM; + err = ERR_COMM; + break; } res = ev3_read_timeout(handle, (u8 *) reply, sizeof(FW_DOWNLOAD_DATA_REPLY), -1); if (res <= 0) { errmsg = "Unable to read FW_DOWNLOAD_DATA"; - return ERR_COMM; + err = ERR_COMM; + break; } // note: accept looped-back packets (usb 3.0 bug; reply not required here) @@ -223,47 +206,125 @@ static int bootloader_send(FILE *fp, int length, u32* pCrc32) print_bytes(reply, reply->packetLen + 2); errmsg = "`FW_DOWNLOAD_DATA` was denied."; - return ERR_VM; + err = ERR_VM; + break; } sent_so_far += this_block; + } + + *pCrc32 = crc; + free(request); + free(reply); + return err; +} - int new_percent = sent_so_far * 100 / total; - if (new_percent >= last_percent + 5) { - last_percent += ((new_percent - last_percent) / 5) * 5; - printf("Progress: %3d %%\n", last_percent); +/** + * @brief Compare CRCs of the flash memory and the firmware image. + * @param fp Firmware file that is supposed to be flashed in the brick. + * @param starting_sector First 64 KiB sector of the flash memory to check (0-based) + * @param num_sectors Number of 64 KiB sectors to check + * @param verbose If true, CRC is checked for each sector individually. + * Otherwise, it is computed across all sectors at once. + * @retval error according to enum #ERR + */ +int bootloader_crc(FILE *fp, u32 starting_sector, u32 num_sectors, bool verbose) +{ + u8 *firmware = calloc(FLASH_SIZE, 1); + if (!firmware) { + errmsg = "out of memory"; + return ERR_NOMEM; + } + u32 read_bytes = fread(firmware, 1, FLASH_SIZE, fp); + if (ferror(fp)) { + errmsg = "Reading of firmware file failed"; + free(firmware); + return ERR_IO; + } + u32 wanted_bytes = (starting_sector + num_sectors) * FLASH_SECTOR_SIZE; + if (read_bytes < wanted_bytes) { + printf("WARNING: firmware file is truncated: %d bytes expected, %d bytes read\n", wanted_bytes, read_bytes); + } + + int err = ERR_UNK; + if (verbose) { + // Sector-by-sector CRC comparison. + // Compares the remote and local CRCs of each sector, showing the results of each. + printf("Sector CRC comparison:\n"); + bool all_ok = true; + for (u32 sector = starting_sector; sector < starting_sector + num_sectors; sector++) { + u32 local_sector_crc32 = crc32(0, firmware + sector * FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE); + u32 remote_sector_crc32 = 0; + int sector_err = bootloader_checksum(sector * FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE, &remote_sector_crc32); + + bool this_ok = remote_sector_crc32 == local_sector_crc32; + all_ok = all_ok && this_ok; + + if (sector_err == ERR_UNK) { + printf("Sector %3d: Remote CRC = %08X, local CRC = %08X%s\n", sector, remote_sector_crc32, + local_sector_crc32, this_ok ? "" : " ERR"); + } else { + printf("Error requesting sector CRC. Err = %i\n", sector_err); + err = sector_err; + } + } + + if (all_ok) { + puts("All sectors match."); + } else { + puts("Some sectors do not match."); + } + } else { + // Do a single comparison, calculating the CRC over multiple sectors. + u32 local_crc32 = crc32(0, firmware + starting_sector * FLASH_SECTOR_SIZE, num_sectors * FLASH_SECTOR_SIZE); + printf("Requesting remote CRC...\n"); + u32 remote_crc32 = 0; + err = bootloader_checksum(starting_sector * FLASH_SECTOR_SIZE, num_sectors * FLASH_SECTOR_SIZE, &remote_crc32); + if (err == ERR_UNK) { + printf("Remote CRC = %08X, local CRC = %08X%s\n", remote_crc32, local_crc32, + remote_crc32 == local_crc32 ? "" : " ERR"); + } else { + printf("Error requesting full CRC. Err = %i\n", err); } } - *pCrc32 = crc; - return ERR_UNK; + + free(firmware); + return err; } static int bootloader_checksum(int offset, int length, u32 *pCrc32) { + FW_GETCRC32 *request = NULL; + FW_GETCRC32_REPLY *reply = NULL; + int err; + *pCrc32 = 0; - FW_GETCRC32 *request = packet_alloc(FW_GETCRC32, 0); + request = packet_alloc(FW_GETCRC32, 0); request->flashStart = offset; request->flashLength = length; int res = ev3_write(handle, (u8 *) request, request->packetLen + PREFIX_SIZE); if (res < 0) { errmsg = "Unable to write FW_GETCRC32."; - return ERR_COMM; + err = ERR_COMM; + goto exit; } - FW_GETCRC32_REPLY *reply = malloc(sizeof(FW_GETCRC32_REPLY)); + reply = malloc(sizeof(FW_GETCRC32_REPLY)); res = ev3_read_timeout(handle, (u8 *) reply, sizeof(FW_GETCRC32_REPLY), -1); if (res <= 0) { errmsg = "Unable to read FW_GETCRC32"; - return ERR_COMM; + err = ERR_COMM; + goto exit; } // note: report loopback bug to outer code if (reply->type == VM_SYS_RQ) { - return ERR_USBLOOP; + err = ERR_USBLOOP; + goto exit; } if (reply->type != VM_OK) @@ -273,8 +334,14 @@ static int bootloader_checksum(int offset, int length, u32 *pCrc32) print_bytes(reply, reply->packetLen + 2); errmsg = "`FW_GETCRC32` was denied."; - return ERR_VM; + err = ERR_VM; + goto exit; } *pCrc32 = reply->crc32; - return ERR_UNK; + err = ERR_UNK; + +exit: + free(request); + free(reply); + return err; } diff --git a/src/funcs.h b/src/funcs.h index 6bad60d..c9e200d 100644 --- a/src/funcs.h +++ b/src/funcs.h @@ -17,6 +17,8 @@ #include #include +#include + /** * @name General Usage * @ingroup EV3 commands @@ -71,6 +73,18 @@ extern int bootloader_enter(void); //! install new firmware to the brick extern int bootloader_install(FILE *fp); +// First address of EV3 flash memory +#define FLASH_START 0x00000000 +// Size of EV3 flash memory in bytes +#define FLASH_SIZE (16 * 1000 * 1024) +// Size of EV3 flash memory erase block +#define FLASH_SECTOR_SIZE (64*1024) // N25Q128 datasheet says that it has 64-Kbyte sectors/eraseblocks +// Number of sectors in EV3 flash memory +#define FLASH_SECTOR_COUNT (FLASH_SIZE / FLASH_SECTOR_SIZE) + +//! compare device CRC to CRC from a file +extern int bootloader_crc(FILE *fp, u32 starting_sector, u32 num_sectors, bool verbose); + //! print brick hardware version extern int bootloader_info(void); diff --git a/src/main.c b/src/main.c index 2815fb0..34bbe7d 100644 --- a/src/main.c +++ b/src/main.c @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -154,6 +155,9 @@ const char *const usage_desc = "Info:\n" "flash install install the given firmware file to the EV3\n" "flash info show hardware and EEPROM versions\n" "flash exit exit from the firmware update mode without installing new firmware\n" + "flash crc compares CRCs of the given firmware file and the EV3 installed firmware\n" + "flash crc f n compares file/EV3 CRCs of a block of n 64k sectors, starting at sector #f (zero-based). Full size is 250 sectors.\n" + "flash crc f n v verbose (sector-by-sector) file/EV3 CRC comparison, starting at sector #f (zero-based) and doing n sectors (max 250).\n" "uf2 pack pack files into a Microsoft UF2 container\n" " note: common brickdir options are: \n" " 'Projects' (internal flash, default), 'SD Card', 'USB Stick',\n" @@ -656,6 +660,43 @@ int main(int argc, char *argv[]) ret = bootloader_install(fp); + } else if (strcmp(argv[0], "crc") == 0) { + // ev3duder --usb flash crc firmware.bin Full CRC comparison + // ev3duder --usb flash crc firmware.bin f n CRC comparison of n sectors, starting at sector f (zero-based) + // ev3duder --usb flash crc firmware.bin f n v Verbose comparison: compare CRC of every sector, showing the results + bool verbose = argc == 5 && strcmp(argv[4], "v") == 0; + if (argc == 2) { + fp = fopen(SANITIZE(argv[1]), "rb"); + if (!fp) { + printf("File <%s> doesn't exist.\n", argv[1]); + return ERR_IO; + } + printf("Comparing full device CRCs...\n"); + ret = bootloader_crc(fp, 0, FLASH_SECTOR_COUNT, false); + + } else if (argc == 4 || verbose) { + u32 first_sector = atol(argv[2]); + if (first_sector >= FLASH_SECTOR_COUNT) first_sector = FLASH_SECTOR_COUNT - 1; + u32 num_sectors = atol(argv[3]); + if (num_sectors == 0) num_sectors = 1; // Doesn't make sense to do zero. + if (first_sector + num_sectors > FLASH_SECTOR_COUNT) { + num_sectors = FLASH_SECTOR_COUNT - first_sector; + } + fp = fopen(SANITIZE(argv[1]), "rb"); + if (!fp) + { + printf("File <%s> doesn't exist.\n", argv[1]); + return ERR_IO; + } + printf("Comparing CRCs of sectors %d - %d (%d sectors)\n", first_sector, + first_sector + num_sectors - 1, num_sectors); + ret = bootloader_crc(fp, first_sector, num_sectors, verbose); + + } else { + ret = ERR_ARG; + printf("Expected either or \n"); + } + } else if (strcmp(argv[0], "info") == 0) { assert(argc == 1); ret = bootloader_info();