blob: 9297e44362f36093b67576b3a2c6ea15c9c64794 [file] [log] [blame]
// 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));
};