| // Copyright 2017 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| dfu = {}; |
| |
| 'use strict'; |
| |
| // DFU commands numbers in bRequest. |
| dfu.DFU_DETACH = 0; |
| dfu.DFU_DNLOAD = 1; |
| dfu.DFU_UPLOAD = 2; |
| dfu.DFU_GETSTATUS = 3; |
| dfu.DFU_CLRSTATUS = 4; |
| dfu.DFU_GETSTATE = 5; |
| dfu.DFU_ABORT = 6; |
| |
| // DFU Interface parameters. |
| dfu.INTERFACE_CLASS = 0xFE; /* Application Specific Class */ |
| dfu.INTERFACE_SUBCLASS = 0x01; /* Device Firmware Upgrade Code */ |
| |
| // iInterface string for the STM32F072 Flash DFU interface. |
| dfu.STM32F072_INTERFACE_NAME = "@Internal Flash /0x08000000/064*0002Kg"; |
| |
| dfu.Status = { |
| OK: 0x00, /* No error condition is present. */ |
| errTARGET: 0x01, /* File is not targeted for use by this device. */ |
| errFILE: 0x02, /* File is for this device but fails some vendor-specific verification test. */ |
| errWRITE: 0x03, /* Device is unable to write memory. */ |
| errERASE: 0x04, /* Memory erase function failed. */ |
| errCHECK_ERASED: 0x05, /* Memory erase check failed. */ |
| errPROG: 0x06, /* Program memory function failed. */ |
| errVERIFY: 0x07, /* Programmed memory failed verification. */ |
| errADDRESS: 0x08, /* Cannot program memory due to received address that is out of range. */ |
| errNOTDONE: 0x09, /* Received DFU_DNLOAD with wLength = 0, but device does not think it has all of the data yet. */ |
| errFIRMWARE: 0x0A, /* Device’s firmware is corrupt. It cannot return to run-time (non-DFU) operations. */ |
| errVENDOR: 0x0B, /* iString indicates a vendor-specific error. */ |
| errUSBR: 0x0C, /* Device detected unexpected USB reset signaling. */ |
| errPOR: 0x0D, /* Device detected unexpected power on reset. */ |
| errUNKNOWN: 0x0E, /* Something went wrong, but the device does not know what it was. */ |
| errSTALLEDPKT: 0x0F, |
| }; |
| |
| dfu.State = { |
| appIDLE: 0, /* Device is running its normal application. */ |
| appDETACH: 1, /* Device has received the DFU_DETACH request */ |
| dfuIDLE: 2, /* Device is operating in the DFU mode and is waiting for requests. */ |
| dfuDNLOAD_SYNC: 3, /* Device has received a block and is waiting for the host */ |
| dfuDNBUSY: 4, /* Device is programming a control-write block */ |
| dfuDNLOAD_IDLE: 5, /* Device is processing a download operation. */ |
| dfuMANIFEST_SYNC: 6, /* Device has received the final block of firmware or ... */ |
| dfuMANIFEST: 7, /* Device is in the Manifestation phase. */ |
| dfuMANIFEST_WAIT_RESET: 8, /* Device is waiting for a USB reset or a power on reset */ |
| dfuUPLOAD_IDLE: 9, /* The device is processing an upload operation. */ |
| dfuERROR: 10, /* An error has occurred. Awaiting the DFU_CLRSTATUS request. */ |
| }; |
| |
| // DfuSe (DFU with ST Microsystems extensions) protocol. |
| dfu.STM_CMD_GET_COMMANDS = 0x00; |
| dfu.STM_CMD_SET_ADDRESS = 0x21; |
| dfu.STM_CMD_ERASE = 0x41; |
| dfu.STM_CMD_READ_UNPROTECT = 0x92; |
| |
| dfu.STM_FLASH_ADDRESS = 0x08000000; |
| dfu.STM_MAX_SIZE = 2048; |
| dfu.STM_PAGE_SIZE = 2048; // MCU specific TODO: parse it from the interface string |
| |
| // USB identifiers. |
| dfu.STM_VENDOR_ID = 0x0483 ; |
| dfu.STM_PRODUCT_ID = 0xdf11; |
| |
| dfu.isDFU = function(device) { |
| return device.vendorId == dfu.STM_VENDOR_ID && |
| device.productId == dfu.STM_PRODUCT_ID; |
| } |
| |
| dfu.Dongle = function(device) { |
| this.device_ = device; |
| console.log(device); |
| }; |
| |
| |
| dfu.Dongle.prototype.send = function(req, val, data) { |
| var xfer = { |
| 'requestType' : 'class', |
| 'recipient' : 'interface', |
| 'request' : req, |
| 'value': val, |
| 'index': this.ifaceNum_, |
| }; |
| return this.device_.controlTransferOut(xfer, data ? data : new ArrayBuffer()); |
| }; |
| |
| dfu.Dongle.prototype.recv = function(req, val, len) { |
| return new Promise(function(resolve, reject) { |
| var xfer = { |
| 'requestType' : 'class', |
| 'recipient' : 'interface', |
| 'request' : req, |
| 'value': val, |
| 'index': this.ifaceNum_, |
| }; |
| this.device_.controlTransferIn(xfer, len).then(result => { |
| if (result.status == 'ok') { |
| resolve(new Uint8Array(result.data.buffer)); |
| } else { |
| console.log("Transfer failed " + result.status); |
| reject(result.status); |
| } |
| }); |
| }.bind(this)); |
| }; |
| |
| // --- Standard DFU requests --- |
| |
| // 10100001b DFU_GETSTATE Zero Interface 1 State |
| dfu.Dongle.prototype.getState = function() { |
| return new Promise(function(resolve, reject) { |
| this.recv(dfu.DFU_GETSTATE, 0, 1).then(data => { |
| resolve(data[0]); |
| }); |
| }.bind(this)); |
| }; |
| |
| // 10100001b DFU_GETSTATUS Zero Interface 6 Status |
| dfu.Dongle.prototype.getStatus = function() { |
| return this.recv(dfu.DFU_GETSTATUS, 0, 6); |
| }; |
| |
| // 00100001b DFU_CLRSTATUS Zero Interface Zero None |
| dfu.Dongle.prototype.clearStatus = function() { |
| return this.send(dfu.DFU_CLRSTATUS, 0, null); |
| }; |
| |
| //00100001b DFU_DNLOAD wBlockNum Interface Length Firmware |
| dfu.Dongle.prototype.download = function(blockNum, fw) { |
| return this.send(dfu.DFU_DNLOAD, blockNum, fw); |
| }; |
| |
| //10100001b DFU_UPLOAD Zero Interface Length Firmware |
| dfu.Dongle.prototype.upload = function(blockNum, len) { |
| return this.recv(dfu.DFU_UPLOAD, blockNum, len); |
| }; |
| |
| //00100001b DFU_ABORT Zero Interface Zero None |
| dfu.Dongle.prototype.abort = function() { |
| return this.send(dfu.DFU_ABORT, 0, null); |
| }; |
| |
| //00100001b DFU_DETACH wTimeout Interface Zero None |
| dfu.Dongle.prototype.clearStatus = function() { |
| return this.send(dfu.DFU_CLRSTATUS, 0, null); |
| }; |
| |
| dfu.Dongle.prototype.checkStatus = function(cond) { |
| return new Promise(function(resolve, reject) { |
| if (!cond) |
| cond = {'status':dfu.Status.OK}; |
| |
| this.getStatus().then(pkt => { |
| var bStatus = pkt[0]; |
| var bState = pkt[4]; |
| var result = true; |
| if (cond.state && bState != cond.state) |
| result = false; |
| if (cond.status && bStatus != cond.status) |
| result = false; |
| |
| if (result) { |
| resolve(); |
| } else { |
| reject("Invalid Status "+bState+"/"+bStatus+" expected:"+cond); |
| } |
| }).catch(error => { |
| reject("Cannot get status: "+error); |
| }); |
| }.bind(this)); |
| }; |
| // --- DFUse requests --- |
| dfu.Dongle.prototype.stmGetCommands = function() { |
| return this.recv(dfu.DFU_UPLOAD, 0, 4); |
| }; |
| |
| dfu.Dongle.prototype.stmErase = function(addr) { |
| var cmd = new Uint8Array(5); //2 for mass-erase |
| cmd[0] = dfu.STM_CMD_ERASE; |
| cmd[1] = (addr >> 0) & 0xFF; |
| cmd[2] = (addr >> 8) & 0xFF; |
| cmd[3] = (addr >> 16) & 0xFF; |
| cmd[4] = (addr >> 24) & 0xFF; |
| return this.send(dfu.DFU_DNLOAD, 0, cmd) |
| .then(() => this.checkStatus({'state':dfu.State.dfuDNBUSY})) |
| .then(() => this.checkStatus()) |
| .then(() => this.abort()) // Go back to dfuIDLE |
| .then(() => this.checkStatus()); |
| }; |
| |
| dfu.Dongle.prototype.stmSetAddress = function(addr) { |
| var cmd = new Uint8Array(5); |
| cmd[0] = dfu.STM_CMD_SET_ADDRESS; |
| cmd[1] = (addr >> 0) & 0xFF; |
| cmd[2] = (addr >> 8) & 0xFF; |
| cmd[3] = (addr >> 16) & 0xFF; |
| cmd[4] = (addr >> 24) & 0xFF; |
| return this.send(dfu.DFU_DNLOAD, 0, cmd) |
| .then(() => this.checkStatus({'state':dfu.State.dfuDNBUSY})) |
| .then(() => this.checkStatus()) |
| .then(() => this.abort()) // Go back to dfuIDLE |
| .then(() => this.checkStatus()); |
| }; |
| |
| dfu.Dongle.prototype.stmReadUnprotect = function() { |
| var cmd = new Uint8Array(1); |
| cmd[0] = dfu.STM_CMD_READ_UNPROTECT; |
| return this.send(dfu.DFU_DNLOAD, 0, cmd) |
| .then(() => this.checkStatus({'state':dfu.State.dfuDNBUSY})); |
| // Device reboots here |
| }; |
| |
| dfu.Dongle.prototype.stmLeaveDfu = function() { |
| return this.send(dfu.DFU_DNLOAD, 0, 0); |
| }; |
| |
| dfu.Dongle.prototype.stmEraseMemory = function(addr, size) { |
| return new Promise(function(resolve, reject) { |
| if (size % dfu.STM_PAGE_SIZE) |
| reject("Invalid erase size " + size); |
| |
| var remaining = size; |
| |
| let eraseOnePage = () => { |
| this.stmErase(addr) |
| .then(() => { |
| addr += dfu.STM_PAGE_SIZE; |
| remaining -= dfu.STM_PAGE_SIZE; |
| if (remaining == 0) { |
| resolve(); |
| } else { |
| eraseOnePage(); |
| } |
| }).catch(error => { |
| reject("Erase error: " + error); |
| }); |
| }; |
| |
| eraseOnePage(); |
| }.bind(this)); |
| }; |
| |
| dfu.Dongle.prototype.stmReadMemory = function(addr, size) { |
| return new Promise(function(resolve, reject) { |
| var remaining = size; |
| var offset = 0; |
| var mem = new Uint8Array(size); |
| |
| let readBlock = () => { |
| var len = Math.min(remaining, dfu.STM_MAX_SIZE); |
| var blockNum = offset / len + 2; |
| this.upload(blockNum, len) |
| .then(pkt => { |
| mem.set(pkt, offset); |
| offset += pkt.length; |
| remaining -= pkt.length; |
| if (remaining == 0) { |
| resolve(mem); |
| } else { |
| readBlock(); |
| } |
| }).catch(error => { |
| reject("Read error: " + error); |
| }); |
| }; |
| |
| this.stmSetAddress(addr) |
| .then(() => { |
| readBlock(); |
| }); |
| }.bind(this)); |
| }; |
| |
| dfu.Dongle.prototype.stmWriteMemory = function(addr, image, total_size) { |
| if (!total_size) { |
| var modulo = image.length % dfu.STM_PAGE_SIZE; |
| total_size = image.length; |
| if (modulo) |
| total_size += dfu.STM_PAGE_SIZE - modulo; |
| //TODO extend image ? |
| //var mem = new Uint8Array(size); |
| } |
| |
| return new Promise(function(resolve, reject) { |
| var remaining = size; |
| var offset = 0; |
| |
| let writeBlock = () => { |
| var len = Math.min(remaining, dfu.STM_MAX_SIZE); |
| var blockNum = offset / len + 2; |
| this.download(blockNum, len) |
| .then(() => this.checkStatus({'state':dfu.State.dfuDNBUSY})) |
| .then(() => this.checkStatus()) |
| .then(() => { |
| offset += len; |
| remaining -= len; |
| if (remaining == 0) { |
| resolve(); |
| } else { |
| writeBlock(); |
| } |
| }).catch(error => { |
| reject("Write error: " + error); |
| }); |
| }; |
| |
| //TODO erase |
| this.stmSetAddress(addr) |
| .then(() => { |
| writeBlock(); |
| }); |
| }.bind(this)); |
| }; |
| // ------------------------- |
| |
| dfu.Dongle.prototype.connect = function() { |
| return this.device_.open().then(() => { |
| var ifaces = this.device_.configuration.interfaces; |
| var dfuIface = ifaces[0]; |
| for (var iface in ifaces) { |
| for (var alt in ifaces[iface].alternates) { |
| var i = ifaces[iface].alternates[alt]; |
| if (i.interfaceClass == dfu.INTERFACE_CLASS && |
| i.interfaceSubclass == dfu.INTERFACE_SUBCLASS) { |
| console.log(i); |
| //console.log(i.interfaceName); |
| if (i.interfaceName == dfu.STM32F072_INTERFACE_NAME) { |
| dfuIface = ifaces[iface]; |
| break; |
| } |
| } |
| } |
| } |
| this.ifaceNum_ = dfuIface.interfaceNumber; |
| }) |
| .then(() => this.device_.claimInterface(this.ifaceNum_)); |
| }; |
| |
| dfu.Dongle.prototype.disconnect = function() { |
| return this.device_.close(); |
| }; |
| |
| dfu.Dongle.prototype.gotoIdle = function() { |
| return this.getState() |
| .then(st => { |
| if (st == dfu.State.dfuERROR) |
| return this.clearStatus() |
| .then(() => this.getStatus()); |
| }) |
| .then(() => this.abort()) |
| .then(() => this.checkStatus({'state':dfu.State.OK, 'status':dfu.Status.dfuIDLE})); |
| }; |
| |
| dfu.Dongle.prototype.backupFw = function() { |
| return this.gotoIdle() |
| .then(() => this.stmReadMemory(dfu.STM_FLASH_ADDRESS, 128*1024)); |
| }; |
| |
| dfu.Dongle.prototype.updateFw = function(img) { |
| return this.gotoIdle() |
| .then(() => this.stmEraseMemory(dfu.STM_FLASH_ADDRESS + 64*1024, 64*1024)) |
| .then(() => this.stmWriteMemory(dfu.STM_FLASH_ADDRESS + 64*1024, 64*1024)); |
| }; |