Skip to content

Commit

Permalink
Implement dfu upload functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
keeramis committed Sep 18, 2023
1 parent 5a1dd1f commit faa5247
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/dfu-device.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ const DfuDevice = (base) => class extends base {
await this._dfu.setAltSetting(altSetting);
await this._dfu.doDownload({ startAddr, data, noErase, leave, progress });
}

async readOverDfu({ altSetting, startAddr, size, filename, progress }) {
await this._dfu.setAltSetting(altSetting);
await this._dfu.doUpload({ startAddr, maxSize: size, filename, progress });
return filename;
}
};

module.exports = {
Expand Down
101 changes: 101 additions & 0 deletions src/dfu.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/

const { DeviceError, UsbStallError } = require('./error');
const fs = require('fs-extra');

/**
* A generic DFU error.
Expand Down Expand Up @@ -366,6 +367,27 @@ class Dfu {
return this._dev.transferOut(setup, data);
}

async _sendUploadReqest(length, value) {
const setup = {
bmRequestType: DfuBmRequestType.DEVICE_TO_HOST,
bRequest: DfuRequestType.DFU_UPLOAD,
wIndex: this._interface,
wValue: value,
wLength: length
};
return this._dev.transferIn(setup);
}

async _sendAbortRequest() {
const setup = {
bmRequestType: DfuBmRequestType.DEVICE_TO_HOST,
bRequest: DfuRequestType.DFU_ABORT,
wIndex: this._interface,
wValue: 0
};
return this._dev.transferOut(setup, 1);
}

/**
* Retrieves the status from the DFU (Device Firmware Upgrade) device.
*
Expand Down Expand Up @@ -739,6 +761,85 @@ class Dfu {
}
return desc;
}

async doUpload({ startAddr, maxSize, filename, progress }) {
if (isNaN(startAddr)) {
startAddr = this._memoryInfo.segments[0].start;
this._log.warn('Using inferred start address 0x' + startAddr.toString(16));
} else if (this._getSegment(startAddr) === null) {
this._log.warn(`Start address 0x${startAddr.toString(16)} outside of memory map bounds`);
}

this._log.trace(`Reading up to 0x${maxSize.toString(16)} bytes starting at 0x${startAddr.toString(16)}`);
const state = await this._getStatus();
if (state.state !== DfuDeviceState.dfuIDLE) {
await this._clearStatus(); // suggested by dfu-util
await this.abortToIdle();
}
await this._dfuseCommand(DfuseCommand.DFUSE_COMMAND_SET_ADDRESS_POINTER, startAddr);
await this.abortToIdle();

// DfuSe encodes the read address based on the transfer size,
// the block number - 2, and the SET_ADDRESS pointer.
const data = await this._doUploadImpl(maxSize, 2, progress);
// current particle implementations only require binary encoding
await fs.writeFile(filename, data, 'binary');
}

async _doUploadImpl(maxSize = Infinity, firstBlock = 0, progress) {
let transaction = firstBlock;
const blocks = [];
let bytesRead = 0;

this._log.trace('Copying data from DFU device to browser');
if (progress) {
progress({ event: 'start-upload', bytes: maxSize });
}

let result;
let bytesToRead;
do {
bytesToRead = Math.min(this._transferSize, maxSize - bytesRead);
result = await this._sendUploadReqest(bytesToRead, transaction++);
this._log.traceg('Read ' + result.byteLength + ' bytes');
if (result.byteLength > 0) {
blocks.push(Buffer.from(result));
bytesRead += result.byteLength;
}
if (Number.isFinite(maxSize)) {
if (progress) {
progress({ event: 'uploaded', bytes: bytesRead });
}
} else {
if (progress) {
progress({ event: 'uploaded', bytes: bytesRead });
}
}
} while ((bytesRead < maxSize) && (result.byteLength === bytesToRead));

if (bytesRead === maxSize) {
await this.abortToIdle();
}

this._log.trace(`Read ${bytesRead} bytes`);
if (progress) {
progress({ event: 'complete-upload', bytes: bytesRead });
}

return Buffer.concat(blocks);
}

async abortToIdle() {
await this._sendAbortRequest();
let state = await this._getStatus();
if (state.state === DfuDeviceState.dfuERROR) {
await this._clearStatus();
state = await this._getStatus();
}
if (state.state !== DfuDeviceState.dfuIDLE) {
throw new Error('Failed to return to idle state after abort: state ' + state.state);
}
}
}

module.exports = {
Expand Down

0 comments on commit faa5247

Please sign in to comment.