From 7af9fb28a9d55f3472142bdbccdbee9820758adf Mon Sep 17 00:00:00 2001 From: Mark Haslinghuis Date: Thu, 3 Jun 2021 18:39:14 +0200 Subject: [PATCH] Verify board from firmware before flashing --- locales/en/messages.json | 18 ++++++ src/js/port_handler.js | 5 ++ src/js/protocols/stm32.js | 67 ++++++++++++-------- src/js/serial.js | 4 ++ src/js/tabs/firmware_flasher.js | 105 +++++++++++++++++++++++++++++++- src/tabs/firmware_flasher.html | 10 +++ 6 files changed, 181 insertions(+), 28 deletions(-) diff --git a/locales/en/messages.json b/locales/en/messages.json index f9113c2e..d20e6800 100644 --- a/locales/en/messages.json +++ b/locales/en/messages.json @@ -3146,6 +3146,24 @@ "firmwareFlasherFlashTrigger": { "message": "Detected: $1 - triggering flash on connect" }, + "firmwareFlasherVerifyBoard": { + "message": "

Firmware mismatch


The connected board is {{verified_board}} while you selected {{selected_board}}.

Do you want to continue flashing?", + "description": "Make a quick connection to read firmware target and never flash a wrong target again" + }, + "firmwareFlasherBoardVerificationSuccess": { + "message": "Configurator has successfully detected and verified the board: {{boardName}}", + "description": "Board verification has succeeded." + }, + "firmwareFlasherBoardVerificationFail": { + "message": "Configurator failed to verify the board, if this does not work please try switching tab slowly to retry, make a new usb connection or connect first if you might have forgotten to apply custom defaults", + "description": "Sometimes MSP values cannot be read from firmware correctly" + }, + "firmwareFlasherButtonAbort": { + "message": "Abort" + }, + "firmwareFlasherButtonContinue": { + "message": "Continue" + }, "unstableFirmwareAcknoledgementDialog": { "message": "You are about to flash a development build of the firmware. These builds are a work in progress, and any of the following can be the case:If you proceed with flashing this firmware, you are assuming full responsibility for the risk of any of the above happening. Furthermore you acknowledge that it is necessary to perform thorough bench tests with props off before any attempts to fly this firmware." }, diff --git a/src/js/port_handler.js b/src/js/port_handler.js index ee2fb171..3d5eadef 100644 --- a/src/js/port_handler.js +++ b/src/js/port_handler.js @@ -170,6 +170,11 @@ PortHandler.detectPort = function(currentPorts) { } }); + // Signal board verification + if (GUI.active_tab === 'firmware_flasher') { + TABS.firmware_flasher.boardNeedsVerification = true; + } + // auto-connect if enabled if (GUI.auto_connect && !GUI.connecting_to && !GUI.connected_to) { // start connect procedure. We need firmware flasher protection over here diff --git a/src/js/protocols/stm32.js b/src/js/protocols/stm32.js index 0c2f8621..06709f3a 100644 --- a/src/js/protocols/stm32.js +++ b/src/js/protocols/stm32.js @@ -92,7 +92,7 @@ STM32_protocol.prototype.connect = function (port, baud, hex, options, callback) var startFlashing = function() { // refresh device list PortHandler.check_usb_devices(function(dfu_available) { - if(dfu_available) { + if (dfu_available) { STM32DFU.connect(usbDevices, hex, options); } else { serial.connect(self.port, {bitrate: self.baud, parityBit: 'even', stopBits: 'one'}, function (openInfo) { @@ -107,6 +107,16 @@ STM32_protocol.prototype.connect = function (port, baud, hex, options, callback) }); }; + const onDisconnect = disconnectionResult => { + if (disconnectionResult) { + // delay to allow board to boot in bootloader mode + // required to detect if a DFU device appears + setTimeout(startFlashing, 1000); + } else { + GUI.connect_lock = false; + } + }; + var legacyRebootAndFlash = function() { serial.connect(self.port, {bitrate: self.options.reboot_baud}, function (openInfo) { if (!openInfo) { @@ -124,15 +134,7 @@ STM32_protocol.prototype.connect = function (port, baud, hex, options, callback) bufferView[0] = 0x52; serial.send(bufferOut, function () { - serial.disconnect(function (disconnectionResult) { - if (disconnectionResult) { - // delay to allow board to boot in bootloader mode - // required to detect if a DFU device appears - setTimeout(startFlashing, 1000); - } else { - GUI.connect_lock = false; - } - }); + serial.disconnect(disconnectionResult => onDisconnect(disconnectionResult)); }); }); }; @@ -151,7 +153,7 @@ STM32_protocol.prototype.connect = function (port, baud, hex, options, callback) } else { console.log('Looking for capabilities via MSP'); - MSP.send_message(MSPCodes.MSP_BOARD_INFO, false, false, function () { + MSP.send_message(MSPCodes.MSP_BOARD_INFO, false, false, () => { var rebootMode = 0; // FIRMWARE if (bit_check(FC.CONFIG.targetCapabilities, FC.TARGET_CAPABILITIES_FLAGS.HAS_FLASH_BOOTLOADER)) { // Board has flash bootloader @@ -164,25 +166,36 @@ STM32_protocol.prototype.connect = function (port, baud, hex, options, callback) rebootMode = 1; // MSP_REBOOT_BOOTLOADER_ROM; } - var buffer = []; - buffer.push8(rebootMode); - MSP.send_message(MSPCodes.MSP_SET_REBOOT, buffer, function() { + const selectedBoard = TABS.firmware_flasher.selectedBoard; + const connectedBoard = FC.CONFIG.boardName ? FC.CONFIG.boardName : 'UNKNOWN'; - // if firmware doesn't flush MSP/serial send buffers and gracefully shutdown VCP connections we won't get a reply, so don't wait for it. + function reboot() { + const buffer = []; + buffer.push8(rebootMode); + MSP.send_message(MSPCodes.MSP_SET_REBOOT, buffer, () => { - self.msp_connector.disconnect(function (disconnectionResult) { - if (disconnectionResult) { - // delay to allow board to boot in bootloader mode - // required to detect if a DFU device appears - setTimeout(startFlashing, 1000); - } else { - GUI.connect_lock = false; - } - }); + // if firmware doesn't flush MSP/serial send buffers and gracefully shutdown VCP connections we won't get a reply, so don't wait for it. + + self.msp_connector.disconnect(disconnectionResult => onDisconnect(disconnectionResult)); + + }, () => console.log('Reboot request received by device')); + + } + + function onAbort() { + GUI.connect_lock = false; + rebootMode = 0; + console.log('User cancelled because selected target does not match verified board'); + reboot(); + TABS.firmware_flasher.refresh(); + } + + if (selectedBoard !== connectedBoard) { + TABS.firmware_flasher.showDialogVerifyBoard(selectedBoard, connectedBoard, onAbort, reboot); + } else { + reboot(); + } - }, function () { - console.log('Reboot request recevied by device'); - }); }); } }; diff --git a/src/js/serial.js b/src/js/serial.js index b0f49988..5bce5a5f 100644 --- a/src/js/serial.js +++ b/src/js/serial.js @@ -18,6 +18,10 @@ const serial = { connect: function (path, options, callback) { const self = this; const testUrl = path.match(/^tcp:\/\/([A-Za-z0-9\.-]+)(?:\:(\d+))?$/); + if (self.connectionId || self.connected) { + console.warn('We already connected. Aborting', self.connectionId, self.connected); + return; + } if (testUrl) { self.connectTcp(testUrl[1], testUrl[2], options, callback); } else if (path === 'virtual') { diff --git a/src/js/tabs/firmware_flasher.js b/src/js/tabs/firmware_flasher.js index fab7cacc..635e78c3 100644 --- a/src/js/tabs/firmware_flasher.js +++ b/src/js/tabs/firmware_flasher.js @@ -7,6 +7,7 @@ const firmware_flasher = { gitHubApi: new GitHubApi(), localFirmwareLoaded: false, selectedBoard: undefined, + boardNeedsVerification: false, intel_hex: undefined, // standard intel hex in string format parsed_hex: undefined, // parsed raw hex in array format unifiedTarget: {}, // the Unified Target configuration to be spliced into the configuration @@ -316,6 +317,8 @@ firmware_flasher.initialize = function (callback) { $('select[name="board"]').val(boardReleases ? result.selected_board : 0).trigger('change'); } }); + + verifyBoard(); } const buildTypes = [ @@ -535,7 +538,7 @@ firmware_flasher.initialize = function (callback) { $("a.load_remote_file").addClass('disabled'); const target = $(this).val(); - if (!GUI.connect_lock) { + if (!GUI.connect_lock && target) { if (TABS.firmware_flasher.selectedBoard !== target) { // We're sure the board actually changed if (self.isConfigLocal) { @@ -766,6 +769,68 @@ firmware_flasher.initialize = function (callback) { } } + function verifyBoard() { + if (!$('option:selected', portPickerElement).data().isDFU) { + + function onFinishClose() { + MSP.clearListeners(); + GUI.connect_lock = false; + } + + function onClose() { + serial.disconnect(onFinishClose); + MSP.disconnect_cleanup(); + } + + function onFinish() { + const board = FC.CONFIG.boardName; + if (board) { + $('select[name="board"]').val(board).trigger('change'); + GUI.log(i18n.getMessage('firmwareFlasherBoardVerificationSuccess', {boardName: board})); + } else { + GUI.log(i18n.getMessage('firmwareFlasherBoardVerificationFail')); + } + onClose(); + } + + function getBoard() { + console.log(`Requesting board information`); + MSP.send_message(MSPCodes.MSP_API_VERSION, false, false, () => MSP.send_message(MSPCodes.MSP_BOARD_INFO, false, false, onFinish)); + } + + function onConnect(openInfo) { + if (openInfo) { + GUI.connect_lock = true; + serial.onReceive.addListener(data => MSP.read(data)); + const mspHelper = new MspHelper(); + MSP.listen(mspHelper.process_data.bind(mspHelper)); + getBoard(); + } else { + console.dir('Failed to open connection:', openInfo); + } + } + + // Can only verify if not in DFU mode. + if (String(portPickerElement.val()) !== '0') { + const port = String(portPickerElement.val()); + let baud = 115200; + if ($('input.flash_manual_baud').is(':checked')) { + baud = parseInt($('#flash_manual_baud_rate').val()); + } + console.log('Query board information to preselect right firmware'); + if (!(serial.connected || serial.connectionId)) { + serial.connect(port, {bitrate: baud}, onConnect); + } else { + console.warn('Attempting to connect while there still is a connection', serial.connected, serial.connectionId); + serial.disconnect(); + } + } else { + console.log('Please select valid serial port'); + GUI.log(i18n.getMessage('firmwareFlasherNoValidPort')); + } + } + } + ConfigStorage.get('erase_chip', function (result) { if (result.erase_chip) { $('input.erase_chip').prop('checked', true); @@ -1015,6 +1080,13 @@ firmware_flasher.initialize = function (callback) { if ($('option:selected', this).data().isDFU) { exitDfuElement.removeClass('disabled'); } else { + // Porthandler resets board on port detect + if (self.boardNeedsVerification) { + // reset to prevent multiple calls + self.boardNeedsVerification = false; + verifyBoard(); + } + $("a.load_remote_file").removeClass('disabled'); $("a.load_file").removeClass('disabled'); exitDfuElement.addClass('disabled'); @@ -1241,6 +1313,37 @@ firmware_flasher.enableFlashing = function (enabled) { } }; +firmware_flasher.refresh = function (callback) { + const self = this; + + GUI.tab_switch_cleanup(function() { + self.initialize(); + + if (callback) { + callback(); + } + }); +}; + +firmware_flasher.showDialogVerifyBoard = function (selected, verified, onAbort, onAccept) { + const dialogVerifyBoard = $('#dialog-verify-board')[0]; + + $('#dialog-verify-board-content').html(i18n.getMessage('firmwareFlasherVerifyBoard', {selected_board: selected, verified_board: verified})); + + if (!dialogVerifyBoard.hasAttribute('open')) { + dialogVerifyBoard.showModal(); + $('#dialog-verify-board-abort-confirmbtn').click(function() { + ConfigStorage.set({'selected_board': FC.CONFIG.boardName}); + dialogVerifyBoard.close(); + onAbort(); + }); + $('#dialog-verify-board-continue-confirmbtn').click(function() { + dialogVerifyBoard.close(); + onAccept(); + }); + } +}; + firmware_flasher.FLASH_MESSAGE_TYPES = { NEUTRAL : 'NEUTRAL', VALID : 'VALID', diff --git a/src/tabs/firmware_flasher.html b/src/tabs/firmware_flasher.html index 7d33051e..a659f38f 100644 --- a/src/tabs/firmware_flasher.html +++ b/src/tabs/firmware_flasher.html @@ -180,4 +180,14 @@ + + +
+
+
+ + +
+
+