diff --git a/js/protocols/stm32usbdfu.js b/js/protocols/stm32usbdfu.js index 8347bcfb..a60b3e43 100644 --- a/js/protocols/stm32usbdfu.js +++ b/js/protocols/stm32usbdfu.js @@ -62,6 +62,7 @@ var STM32DFU_protocol = function () { dfuERROR: 10 // An error has occurred. Awaiting the DFU_CLRSTATUS request. }; + this.chipInfo = null; // information about chip's memory this.flash_layout = { 'start_address': 0, 'total_size': 0, 'sectors': []}; }; @@ -102,9 +103,9 @@ STM32DFU_protocol.prototype.connect = function (device, hex, options, callback) STM32DFU_protocol.prototype.checkChromeError = function() { if (chrome.runtime.lastError) { if(chrome.runtime.lastError.message) - console.log(chrome.runtime.lastError.message); + console.log('reporting chrome error: ' + chrome.runtime.lastError.message); else - console.log(chrome.runtime.lastError); + console.log('reporting chrome error: ' + chrome.runtime.lastError); return true; } @@ -153,9 +154,14 @@ STM32DFU_protocol.prototype.claimInterface = function (interfaceNumber) { var self = this; chrome.usb.claimInterface(this.handle, interfaceNumber, function claimed() { + if(self.checkChromeError()) { + console.log('Failed to claim USB device!'); + self.upload_procedure(99); + } + console.log('Claimed interface: ' + interfaceNumber); - self.upload_procedure(1); + self.upload_procedure(0); }); }; @@ -205,9 +211,50 @@ STM32DFU_protocol.prototype.getString = function (index, callback) { }); } -STM32DFU_protocol.prototype.getInterfaceDescriptor = function (_interface, callback) { +STM32DFU_protocol.prototype.getInterfaceDescriptors = function (interfaceNum, callback) { var self = this; + chrome.usb.getConfiguration( this.handle, function (config) { + if(self.checkChromeError()) { + console.log('USB getConfiguration failed!'); + callback([], -200); + return; + } + + var interfaceID = 0; + var descriptorStringArray = []; + var getDescriptorString = function () { + if(interfaceID < config.interfaces.length) { + self.getInterfaceDescriptor(interfaceID, function (descriptor, resultCode) { + if (resultCode) { + callback([], resultCode); + return; + } + interfaceID++; + self.getString(descriptor.iInterface, function (descriptorString, resultCode) { + if (resultCode) { + callback([], resultCode); + return; + } + if (descriptor.bInterfaceNumber == interfaceNum) { + descriptorStringArray.push(descriptorString); + } + getDescriptorString(); + }); + }); + } else { + //console.log(descriptorStringArray); + callback(descriptorStringArray, 0); + return; + } + } + getDescriptorString(); + }); +} + + +STM32DFU_protocol.prototype.getInterfaceDescriptor = function (_interface, callback) { + var self = this; chrome.usb.controlTransfer(this.handle, { 'direction': 'in', 'recipient': 'device', @@ -240,21 +287,16 @@ STM32DFU_protocol.prototype.getInterfaceDescriptor = function (_interface, callb }); } -STM32DFU_protocol.prototype.getFlashInfo = function (_interface, callback) { +STM32DFU_protocol.prototype.getChipInfo = function (_interface, callback) { var self = this; - self.getInterfaceDescriptor(0, function (descriptor, resultCode) { + self.getInterfaceDescriptors(0, function (descriptors, resultCode) { if (resultCode) { callback({}, resultCode); return; } - self.getString(descriptor.iInterface, function (str, resultCode) { - if (resultCode) { - callback({}, resultCode); - return; - } - + var parseDescriptor = function(str) { // F303: "@Internal Flash /0x08000000/128*0002Kg" // F40x: "@Internal Flash /0x08000000/04*016Kg,01*064Kg,07*128Kg" // F72x: "@Internal Flash /0x08000000/04*016Kg,01*64Kg,03*128Kg" @@ -273,21 +315,18 @@ STM32DFU_protocol.prototype.getFlashInfo = function (_interface, callback) { var total_size = 0; var tmp2 = tmp1[2].split(','); if (tmp2.length < 1) { - callback({}, -2); - return; + return null; } for (var i = 0; i < tmp2.length; i++) { // split into [num_pages, page_size] var tmp3 = tmp2[i].split('*'); if (tmp3.length != 2) { - callback({}, -3); - return; + return null; } var num_pages = parseInt(tmp3[0]); var page_size = parseInt(tmp3[1]); if (!page_size) { - callback({}, -4); - return; + return null; } var unit = tmp3[1].slice(-2, -1); switch (unit) { @@ -296,9 +335,11 @@ STM32DFU_protocol.prototype.getFlashInfo = function (_interface, callback) { case 'K': page_size *= 1024; break; +/* case ' ': + break; default: - callback({}, -4); - return; + return null; +*/ } sectors.push({ @@ -311,21 +352,33 @@ STM32DFU_protocol.prototype.getFlashInfo = function (_interface, callback) { total_size += num_pages * page_size; } - var flash = { + var memory = { 'type' : type, 'start_address': start_address, 'sectors' : sectors, 'total_size' : total_size } - - callback(flash, resultCode); - }); + return memory; + } + var chipInfo = descriptors.map(parseDescriptor).reduce(function(o, v, i) { + o[v.type.toLowerCase().replace(' ', '_')] = v; + return o; + }, {}); + callback(chipInfo, resultCode); }); } -STM32DFU_protocol.prototype.controlTransfer = function (direction, request, value, _interface, length, data, callback) { +STM32DFU_protocol.prototype.controlTransfer = function (direction, request, value, _interface, length, data, callback, _timeout) { var self = this; + // timeout support was added in chrome v43 + var timeout; + if (typeof _timeout === "undefined") { + timeout = 0; // default is 0 (according to chrome.usb API) + } else { + timeout = _timeout; + } + if (direction == 'in') { // data is ignored chrome.usb.controlTransfer(this.handle, { @@ -335,12 +388,13 @@ STM32DFU_protocol.prototype.controlTransfer = function (direction, request, valu 'request': request, 'value': value, 'index': _interface, - 'length': length + 'length': length, + 'timeout': timeout }, function (result) { if(self.checkChromeError()) { console.log('USB transfer failed!'); } - if (result.resultCode) console.log(result.resultCode); + if (result.resultCode) console.log('USB transfer result code: ' + result.resultCode); var buf = new Uint8Array(result.data); callback(buf, result.resultCode); @@ -362,12 +416,13 @@ STM32DFU_protocol.prototype.controlTransfer = function (direction, request, valu 'request': request, 'value': value, 'index': _interface, - 'data': arrayBuf + 'data': arrayBuf, + 'timeout': timeout }, function (result) { if(self.checkChromeError()) { console.log('USB transfer failed!'); } - if (result.resultCode) console.log(result.resultCode); + if (result.resultCode) console.log('USB transfer result code: ' + result.resultCode); callback(result); }); @@ -397,7 +452,7 @@ STM32DFU_protocol.prototype.clearStatus = function (callback) { check_status(); }; -STM32DFU_protocol.prototype.loadAddress = function (address, callback) { +STM32DFU_protocol.prototype.loadAddress = function (address, callback, abort) { var self = this; self.controlTransfer('out', self.request.DNLOAD, 0, 0, 0, [0x21, address, (address >> 8), (address >> 16), (address >> 24)], function () { @@ -411,7 +466,11 @@ STM32DFU_protocol.prototype.loadAddress = function (address, callback) { callback(data); } else { console.log('Failed to execute address load'); - self.upload_procedure(99); + if(typeof abort === "undefined" || abort) { + self.upload_procedure(99); + } else { + callback(data); + } } }); }, delay); @@ -443,65 +502,182 @@ STM32DFU_protocol.prototype.upload_procedure = function (step) { var self = this; switch (step) { - case 1: - self.getFlashInfo(0, function (flash, resultCode) { - if (resultCode != 0) { - console.log('Failed to detect chip flash info, resultCode: ' + resultCode); + case 0: + self.getChipInfo(0, function (chipInfo, resultCode) { + if (resultCode != 0 || typeof chipInfo === "undefined") { + console.log('Failed to detect chip info, resultCode: ' + resultCode); self.upload_procedure(99); } else { - self.flash_layout = flash; - self.available_flash_size = flash.total_size - (self.hex.start_linear_address - flash.start_address); + if (typeof chipInfo.internal_flash === "undefined") { + console.log('Failed to detect internal flash'); + self.upload_procedure(99); + } + + self.chipInfo = chipInfo; - GUI.log(chrome.i18n.getMessage('dfu_device_flash_info', (flash.total_size / 1024).toString())); + self.flash_layout = chipInfo.internal_flash; + self.available_flash_size = self.flash_layout.total_size - (self.hex.start_linear_address - self.flash_layout.start_address); + + GUI.log(chrome.i18n.getMessage('dfu_device_flash_info', (self.flash_layout.total_size / 1024).toString())); if (self.hex.bytes_total > self.available_flash_size) { - GUI.log(chrome.i18n.getMessage('dfu_error_image_size', - [(self.hex.bytes_total / 1024.0).toFixed(1), + GUI.log(chrome.i18n.getMessage('dfu_error_image_size', + [(self.hex.bytes_total / 1024.0).toFixed(1), (self.available_flash_size / 1024.0).toFixed(1)])); self.upload_procedure(99); } else { self.clearStatus(function () { - self.upload_procedure(2); + self.upload_procedure(1); }); } } }); break; - case 2: - // erase - if (self.options.erase_chip) { - // full chip erase - console.log('Executing global chip erase'); - $('span.progressLabel').text('Erasing ...'); + case 1: + if (typeof self.chipInfo.option_bytes === "undefined") { + console.log('Failed to detect option bytes'); + self.upload_procedure(99); + } + + var unprotect = function() { + console.log('Initiate read unprotect'); + GUI.log('Chip seems read protected. Initiating read unprotect'); + $('span.progressLabel').text('Board seems read protected. Unprotecting. Do not disconnect/unplug!'); + self.progress_bar_e.addClass('actionRequired'); - self.controlTransfer('out', self.request.DNLOAD, 0, 0, 0, [0x41], function () { + self.controlTransfer('out', self.request.DNLOAD, 0, 0, 0, [0x92], function () { // 0x92 initiates read unprotect self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function (data) { if (data[4] == self.state.dfuDNBUSY) { // completely normal var delay = data[1] | (data[2] << 8) | (data[3] << 16); + var total_delay = delay + 20000; // wait at least 20 seconds to make sure the user does not disconnect the board while erasing the memory + var timeSpentWaiting = 0; + var incr = 1000; // one sec incements + var waitForErase = setInterval(function () { + self.progress_bar_e.val( Math.min(timeSpentWaiting/total_delay,1) * 100); + if(timeSpentWaiting < total_delay) + { + timeSpentWaiting += incr; + return; + } + clearInterval(waitForErase); + self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function (data, error) { // should stall/disconnect + if(error) { // we encounter an error, but this is expected. should be a stall. + console.log('Unprotect memory command ran successfully. Unplug flight controller. Connect again in DFU mode and try flashing again.'); + GUI.log('Unprotect successful. ACTION REQUIRED: Unplug and re-connect flight controller in DFU mode to try flashing again!'); + $('span.progressLabel').text('ACTION REQUIRED: Unplug and re-connect flight controller in DFU mode to try flashing again!'); + self.progress_bar_e.val(0); + self.progress_bar_e.addClass('actionRequired'); + } else { // unprotecting the flight controller did not work. It did not reboot. + console.log('Failed to execute unprotect memory command'); + GUI.log('Failed to unprotect chip'); + $('span.progressLabel').text('Failed to unprotect board'); + self.progress_bar_e.addClass('invalid'); + console.log(data); + self.upload_procedure(99); + } + }, 2000); // this should stall/disconnect anyways. so we only wait 2 sec max. + }, incr); + } else { + console.log('Failed to initiate unprotect memory command'); + GUI.log('Failed to initiate unprotect routine'); + $('span.progressLabel').text('Failed to initate unprotect'); + self.progress_bar_e.addClass('invalid'); + self.upload_procedure(99); + } + }); + }); + } + - setTimeout(function () { - self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function (data) { + var tryReadOB = function() { + // the following should fail if read protection is active + self.controlTransfer('in', self.request.UPLOAD, 2, 0, self.chipInfo.option_bytes.total_size, 0, function (ob_data, errcode) { + if(errcode) { + console.log('USB transfer error while reading option bytes: ' + errcode1); + self.upload_procedure(99); + return; + } + self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function (data) { + if (data[4] == self.state.dfuUPLOAD_IDLE && ob_data.length == self.chipInfo.option_bytes.total_size) { + console.log('Option bytes read successfully'); + console.log('Chip does not appear read protected'); + GUI.log('Read protection not active'); + // it is pretty safe to continue to erase flash + self.clearStatus(function() { + self.upload_procedure(2); + }); + /* // this snippet is to protect the flash memory (only for the brave) + ob_data[1] = 0x0; + var writeOB = function() { + self.controlTransfer('out', self.request.DNLOAD, 2, 0, 0, ob_data, function () { + self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function (data) { + if (data[4] == self.state.dfuDNBUSY) { + var delay = data[1] | (data[2] << 8) | (data[3] << 16); + + setTimeout(function () { + self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function (data) { if (data[4] == self.state.dfuDNLOAD_IDLE) { - self.upload_procedure(4); + console.log('Failed to write ob'); + self.upload_procedure(99); } else { - console.log('Failed to execute global chip erase'); + console.log('Success writing ob'); self.upload_procedure(99); } - }); - }, delay); - } else { - console.log('Failed to initiate global chip erase'); - self.upload_procedure(99); - } - }); + }); + }, delay); + } else { + console.log('Failed to initiate write ob'); + self.upload_procedure(99); + } + }); + }); + } + self.clearStatus(function () { + self.loadAddress(self.chipInfo.option_bytes.start_address, function () { + self.clearStatus(writeOB); + }); + }); // */ + } else { + console.log('Option bytes could not be read. Quite possibly read protected.'); + self.clearStatus(unprotect); + } + }); }); + } + + var initReadOB = function (loadAddressResponse) { + // contrary to what is in the docs. Address load should in theory work even if read protection is active + // if address load fails with this specific error though, it is very likely bc of read protection + if(loadAddressResponse[4] == self.state.dfuERROR && loadAddressResponse[0] == self.status.errVENDOR) { + // read protected + GUI.log('Address load for option bytes sector failed. Very likely due to read protection.'); + self.clearStatus(unprotect); + return; + } else if(loadAddressResponse[4] == self.state.dfuDNLOAD_IDLE) { + console.log('Address load for option bytes sector succeeded.'); + self.clearStatus(tryReadOB); } else { - // local erase + GUI.log('Address load for option bytes sector failed with unknown error. Aborting.'); + self.upload_procedure(99); + } + } + self.clearStatus(function () { + // load address fails if read protection is active unlike as stated in the docs + self.loadAddress(self.chipInfo.option_bytes.start_address, initReadOB, false); + }); + break; + case 2: + // erase // find out which pages to erase var erase_pages = []; for (var i = 0; i < self.flash_layout.sectors.length; i++) { for (var j = 0; j < self.flash_layout.sectors[i].num_pages; j++) { + if (self.options.erase_chip) { + // full chip erase + erase_pages.push({'sector': i, 'page': j}); + } else { + // local erase var page_start = self.flash_layout.sectors[i].start_address + j * self.flash_layout.sectors[i].page_size; var page_end = page_start + self.flash_layout.sectors[i].page_size - 1; for (var k = 0; k < self.hex.data.length; k++) { @@ -518,20 +694,20 @@ STM32DFU_protocol.prototype.upload_procedure = function (step) { } } } + } } - $('span.progressLabel').text('Erasing ...'); - console.log('Executing local chip erase'); + console.log('Executing local chip erase'); var page = 0; var total_erased = 0; // bytes var erase_page = function() { - var page_addr = erase_pages[page].page * self.flash_layout.sectors[erase_pages[page].sector].page_size + + var page_addr = erase_pages[page].page * self.flash_layout.sectors[erase_pages[page].sector].page_size + self.flash_layout.sectors[erase_pages[page].sector].start_address; var cmd = [0x41, page_addr & 0xff, (page_addr >> 8) & 0xff, (page_addr >> 16) & 0xff, (page_addr >> 24) & 0xff]; total_erased += self.flash_layout.sectors[erase_pages[page].sector].page_size; - console.log('Erasing. sector ' + erase_pages[page].sector + + console.log('Erasing. sector ' + erase_pages[page].sector + ', page ' + erase_pages[page].page + ' @ 0x' + page_addr.toString(16)); self.controlTransfer('out', self.request.DNLOAD, 0, 0, 0, cmd, function () { @@ -569,7 +745,6 @@ STM32DFU_protocol.prototype.upload_procedure = function (step) { // start erase_page(); - } break; case 4: @@ -716,7 +891,6 @@ STM32DFU_protocol.prototype.upload_procedure = function (step) { if (verify) { console.log('Programming: SUCCESSFUL'); $('span.progressLabel').text('Programming: SUCCESSFUL'); - googleAnalytics.sendEvent('Flashing', 'Programming', 'success'); // update progress bar self.progress_bar_e.addClass('valid'); @@ -726,7 +900,6 @@ STM32DFU_protocol.prototype.upload_procedure = function (step) { } else { console.log('Programming: FAILED'); $('span.progressLabel').text('Programming: FAILED'); - googleAnalytics.sendEvent('Flashing', 'Programming', 'fail'); // update progress bar self.progress_bar_e.addClass('invalid');