From 6d8612ebba96e2118ce9cf6e737d2a0cc494a662 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 25 Aug 2021 22:21:53 -0700 Subject: [PATCH 1/4] Add readPacket --- package-lock.json | 4 +- src/const.ts | 7 ++ src/esp_loader.ts | 193 ++++++++++++++++++++++++++++------------------ src/struct.ts | 108 ++++++++++++++++++++++++++ src/util.ts | 68 +--------------- 5 files changed, 240 insertions(+), 140 deletions(-) create mode 100644 src/struct.ts diff --git a/package-lock.json b/package-lock.json index 1042f758..6d3a6112 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "esp-web-flasher", "version": "3.1.3", "license": "MIT", "dependencies": { @@ -929,6 +928,9 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.56.3.tgz", "integrity": "sha512-Au92NuznFklgQCUcV96iXlxUbHuB1vQMaH76DHl5M11TotjOHwqk9CwcrT78+Tnv4FN9uTBxq6p4EJoYkpyekg==", "dev": true, + "dependencies": { + "fsevents": "~2.3.2" + }, "bin": { "rollup": "dist/bin/rollup" }, diff --git a/src/const.ts b/src/const.ts index 98b1c904..afaa4586 100644 --- a/src/const.ts +++ b/src/const.ts @@ -271,3 +271,10 @@ export const getUartDateRegAddress = (chipFamily: ChipFamily): number => { return -1; } }; + +export class SlipReadError extends Error { + constructor(message: string) { + super(message); + this.name = "SlipReadError"; + } +} diff --git a/src/esp_loader.ts b/src/esp_loader.ts index 2c9a1542..e95402a6 100644 --- a/src/esp_loader.ts +++ b/src/esp_loader.ts @@ -47,11 +47,13 @@ import { DETECTED_FLASH_SIZES, CHIP_DETECT_MAGIC_REG_ADDR, CHIP_DETECT_MAGIC_VALUES, + SlipReadError, } from "./const"; import { getStubCode } from "./stubs"; -import { pack, sleep, slipEncode, toHex, unpack } from "./util"; +import { hexFormatter, sleep, slipEncode, toHex } from "./util"; // @ts-ignore import { deflate } from "pako/dist/pako.esm.mjs"; +import { pack, unpack } from "./struct"; export class ESPLoader extends EventTarget { chipFamily!: ChipFamily; @@ -78,10 +80,6 @@ export class ESPLoader extends EventTarget { return this._parent ? this._parent._inputBuffer : this.__inputBuffer!; } - /** - * @name chipType - * ESP32 or ESP8266 based on which chip type we're talking to - */ async initialize() { await this.hardReset(true); @@ -220,17 +218,14 @@ export class ESPLoader extends EventTarget { return macAddr; } - /** - * @name readRegister - * Read a register within the ESP chip RAM, returns a 4-element list - */ async readRegister(reg: number) { if (this.debug) { - this.logger.debug("Reading Register", reg); + this.logger.debug("Reading from Register " + toHex(reg, 8)); } - let packet = pack("I", reg); - let register = (await this.checkCommand(ESP_READ_REG, packet))[0]; - return unpack("I", register!)[0]; + let packet = pack(" { timeout = Math.min(timeout, MAX_TIMEOUT); await this.sendCommand(opcode, buffer, checksum); let [value, data] = await this.getResponse(opcode, timeout); @@ -315,76 +310,128 @@ export class ESPLoader extends EventTarget { } /** - * @name getResponse - * Read response data and decodes the slip packet, then parses - * out the value/data and returns as a tuple of (value, data) where - * each is a list of bytes + * @name readPacket + * Generator to read SLIP packets from a serial port. + * Yields one full SLIP packet at a time, raises exception on timeout or invalid data. + * Designed to avoid too many calls to serial.read(1), which can bog + * down on slow systems. */ - async getResponse(opcode: number, timeout = DEFAULT_TIMEOUT) { - let reply: number[] = []; - let packetLength = 0; - let escapedByte = false; - let stamp = Date.now(); - while (Date.now() - stamp < timeout) { - if (this._inputBuffer.length > 0) { - let c = this._inputBuffer.shift()!; - if (c == 0xdb) { - escapedByte = true; - } else if (escapedByte) { - if (c == 0xdd) { - reply.push(0xdc); - } else if (c == 0xdc) { - reply.push(0xc0); + + async readPacket(timeout: number): Promise { + let partialPacket: number[] | null = null; + let inEscape = false; + let readBytes: number[] = []; + while (true) { + let stamp = Date.now(); + readBytes = []; + while (Date.now() - stamp < timeout) { + if (this._inputBuffer.length > 0) { + readBytes.push(this._inputBuffer.shift()!); + break; + } else { + await sleep(10); + } + } + if (readBytes.length == 0) { + let waitingFor = partialPacket === null ? "header" : "content"; + throw new SlipReadError("Timed out waiting for packet " + waitingFor); + } + if (this.debug) + this.logger.debug( + "Read " + readBytes.length + " bytes: " + hexFormatter(readBytes) + ); + for (let b of readBytes) { + if (partialPacket === null) { + // waiting for packet header + if (b == 0xc0) { + partialPacket = []; } else { - reply = reply.concat([0xdb, c]); + if (this.debug) { + this.logger.debug( + "Read invalid data: " + hexFormatter(readBytes) + ); + this.logger.debug( + "Remaining data in serial buffer: " + + hexFormatter(this._inputBuffer) + ); + } + throw new SlipReadError( + "Invalid head of packet (" + toHex(b) + ")" + ); } - escapedByte = false; + } else if (inEscape) { + // part-way through escape sequence + inEscape = false; + if (b == 0xdc) { + partialPacket.push(0xc0); + } else if (b == 0xdd) { + partialPacket.push(0xdb); + } else { + if (this.debug) { + this.logger.debug( + "Read invalid data: " + hexFormatter(readBytes) + ); + this.logger.debug( + "Remaining data in serial buffer: " + + hexFormatter(this._inputBuffer) + ); + } + throw new SlipReadError( + "Invalid SLIP escape (0xdb, " + toHex(b) + ")" + ); + } + } else if (b == 0xdb) { + // start of escape sequence + inEscape = true; + } else if (b == 0xc0) { + // end of packet + if (this.debug) + this.logger.debug( + "Received full packet: " + hexFormatter(partialPacket) + ); + return partialPacket; } else { - reply.push(c); + // normal byte in packet + partialPacket.push(b); } - } else { - await sleep(10); - } - if (reply.length > 0 && reply[0] != 0xc0) { - // packets must start with 0xC0 - reply.shift(); } - if (reply.length > 1 && reply[1] != 0x01) { - reply.shift(); + } + throw new SlipReadError("Invalid state"); + } + + /** + * @name getResponse + * Read response data and decodes the slip packet, then parses + * out the value/data and returns as a tuple of (value, data) where + * each is a list of bytes + */ + async getResponse( + opcode: number, + timeout = DEFAULT_TIMEOUT + ): Promise<[number, number[]]> { + let packet; + let resp, opRet, lenRet, val, data; + for (let i = 0; i < 100; i++) { + packet = await this.readPacket(timeout); + + if (packet.length < 8) { + continue; } - if (reply.length > 2 && reply[2] != opcode) { - reply.shift(); + + [resp, opRet, lenRet, val] = unpack(" 4) { - // get the length - packetLength = reply[3] + (reply[4] << 8); + data = packet.slice(8); + if (opcode == null || opRet == opcode) { + return [val, data]; } - if (reply.length == packetLength + 10) { - break; + if (data[0] != 0 && data[1] == ROM_INVALID_RECV_MSG) { + this._inputBuffer.length = 0; + throw new Error(`Invalid (unsupported) command ${toHex(opcode)}`); } } - - // Check to see if we have a complete packet. If not, we timed out. - if (reply.length != packetLength + 10) { - this.logger.log("Timed out after " + timeout + " milliseconds"); - return [null, null]; - } - if (this.debug) { - this.logger.debug( - "Reading " + - reply.length + - " byte" + - (reply.length == 1 ? "" : "s") + - ":", - reply - ); - } - let value = reply.slice(5, 9); - let data = reply.slice(9, -1); - if (this.debug) { - this.logger.debug("value:", value, "data:", data); - } - return [value, data]; + throw "Response doesn't match request"; } /** diff --git a/src/struct.ts b/src/struct.ts new file mode 100644 index 00000000..ce7691a7 --- /dev/null +++ b/src/struct.ts @@ -0,0 +1,108 @@ +const lut = { + b: { u: DataView.prototype.getInt8, p: DataView.prototype.setInt8, bytes: 1 }, + B: { + u: DataView.prototype.getUint8, + p: DataView.prototype.setUint8, + bytes: 1, + }, + h: { + u: DataView.prototype.getInt16, + p: DataView.prototype.setInt16, + bytes: 2, + }, + H: { + u: DataView.prototype.getUint16, + p: DataView.prototype.setUint16, + bytes: 2, + }, + i: { + u: DataView.prototype.getInt32, + p: DataView.prototype.setInt32, + bytes: 4, + }, + I: { + u: DataView.prototype.getUint32, + p: DataView.prototype.setUint32, + bytes: 4, + }, + q: { + // @ts-ignore + u: DataView.prototype.getInt64, + // @ts-ignore + p: DataView.prototype.setInt64, + bytes: 8, + }, + Q: { + // @ts-ignore + u: DataView.prototype.getUint64, + // @ts-ignore + p: DataView.prototype.setUint64, + bytes: 8, + }, +}; + +export const pack = (format: string, ...data: number[]) => { + let pointer = 0; + if (format.replace(/[<>]/, "").length != data.length) { + throw "Pack format to Argument count mismatch"; + } + let bytes: number[] = []; + let littleEndian = true; + for (let i = 0; i < format.length; i++) { + if (format[i] == "<") { + littleEndian = true; + } else if (format[i] == ">") { + littleEndian = false; + } else { + pushBytes(format[i], data[pointer]); + pointer++; + } + } + + function pushBytes(formatChar: string, value: number) { + if (!(formatChar in lut)) { + throw "Unhandled character '" + formatChar + "' in pack format"; + } + let dataSize = lut[formatChar].bytes; + let view = new DataView(new ArrayBuffer(dataSize)); + let dataViewFn = lut[formatChar].p.bind(view); + dataViewFn(0, value, littleEndian); + for (let i = 0; i < dataSize; i++) { + bytes.push(view.getUint8(i)); + } + } + + return bytes; +}; + +export const unpack = (format: string, bytes: number[]) => { + let pointer = 0; + let data: number[] = []; + let littleEndian = true; + + for (let c of format) { + if (c == "<") { + littleEndian = true; + } else if (c == ">") { + littleEndian = false; + } else { + pushData(c); + } + } + + function pushData(formatChar: string) { + if (!(formatChar in lut)) { + throw "Unhandled character '" + formatChar + "' in unpack format"; + } + let dataSize = lut[formatChar].bytes; + let view = new DataView(new ArrayBuffer(dataSize)); + for (let i = 0; i < dataSize; i++) { + view.setUint8(i, bytes[pointer + i] & 0xff); + } + let dataViewFn = lut[formatChar].u.bind(view); + data.push(dataViewFn(0, littleEndian)); + pointer += dataSize; + } + + return data; +}; diff --git a/src/util.ts b/src/util.ts index 19dd1ee2..fe5094db 100644 --- a/src/util.ts +++ b/src/util.ts @@ -32,72 +32,8 @@ export const toByteArray = (str: string): number[] => { return byteArray; }; -export const pack = (format: string, ...data: number[]) => { - // let format = args[0]; - let pointer = 0; - // let data = args.slice(1); - if (format.replace(/[<>]/, "").length != data.length) { - throw new Error("Pack format to Argument count mismatch"); - } - let bytes: number[] = []; - let littleEndian = true; - - const pushBytes = (value: number, byteCount: number) => { - for (let i = 0; i < byteCount; i++) { - if (littleEndian) { - bytes.push((value >> (i * 8)) & 0xff); - } else { - bytes.push((value >> ((byteCount - i) * 8)) & 0xff); - } - } - }; - - for (let i = 0; i < format.length; i++) { - if (format[i] == "<") { - littleEndian = true; - } else if (format[i] == ">") { - littleEndian = false; - } else if (format[i] == "B") { - pushBytes(data[pointer], 1); - pointer++; - } else if (format[i] == "H") { - pushBytes(data[pointer], 2); - pointer++; - } else if (format[i] == "I") { - pushBytes(data[pointer], 4); - pointer++; - } else { - throw new Error(`Unhandled character "${format[i]}" in pack format`); - } - } - - return bytes; -}; - -export const unpack = (format: string, bytes: number[]) => { - let pointer = 0; - let data = []; - for (let c of format) { - if (c == "B") { - data.push(bytes[pointer] & 0xff); - pointer += 1; - } else if (c == "H") { - data.push((bytes[pointer] & 0xff) | ((bytes[pointer + 1] & 0xff) << 8)); - pointer += 2; - } else if (c == "I") { - data.push( - (bytes[pointer] & 0xff) | - ((bytes[pointer + 1] & 0xff) << 8) | - ((bytes[pointer + 2] & 0xff) << 16) | - ((bytes[pointer + 3] & 0xff) << 24) - ); - pointer += 4; - } else { - throw new Error(`Unhandled character "${c}" in unpack format`); - } - } - return data; -}; +export const hexFormatter = (bytes: number[]) => + "[" + bytes.map((value) => toHex(value)).join(", ") + "]"; export const toHex = (value: number, size = 2) => { let hex = value.toString(16).toUpperCase(); From 69cce4b17e6fdeeff1f8291fd3866a7b75e48def Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 25 Aug 2021 22:42:04 -0700 Subject: [PATCH 2/4] Fixes --- src/esp_loader.ts | 87 +++++++++-------------------------------------- 1 file changed, 16 insertions(+), 71 deletions(-) diff --git a/src/esp_loader.ts b/src/esp_loader.ts index e95402a6..179b4c41 100644 --- a/src/esp_loader.ts +++ b/src/esp_loader.ts @@ -434,68 +434,6 @@ export class ESPLoader extends EventTarget { throw "Response doesn't match request"; } - /** - * @name read - * Read response data and decodes the slip packet. - * Keeps reading until we hit the timeout or get - * a packet closing byte - */ - async readBuffer(timeout = DEFAULT_TIMEOUT) { - let reply: number[] = []; - // let packetLength = 0; - let escapedByte = false; - let stamp = Date.now(); - while (Date.now() - stamp < timeout) { - if (this._inputBuffer.length > 0) { - let c = this._inputBuffer.shift()!; - if (c == 0xdb) { - escapedByte = true; - } else if (escapedByte) { - if (c == 0xdd) { - reply.push(0xdc); - } else if (c == 0xdc) { - reply.push(0xc0); - } else { - reply = reply.concat([0xdb, c]); - } - escapedByte = false; - } else { - reply.push(c); - } - } else { - await sleep(10); - } - if (reply.length > 0 && reply[0] != 0xc0) { - // packets must start with 0xC0 - reply.shift(); - } - if (reply.length > 1 && reply[reply.length - 1] == 0xc0) { - break; - } - } - - // Check to see if we have a complete packet. If not, we timed out. - if (reply.length < 2) { - this.logger.log("Timed out after " + timeout + " milliseconds"); - return null; - } - if (this.debug) { - this.logger.debug( - "Reading " + - reply.length + - " byte" + - (reply.length == 1 ? "" : "s") + - ":", - reply - ); - } - let data = reply.slice(1, -1); - if (this.debug) { - this.logger.debug("data:", data); - } - return data; - } - /** * @name checksum * Calculate checksum of a blob, as it is defined by the ROM @@ -561,6 +499,7 @@ export class ESPLoader extends EventTarget { */ async sync() { for (let i = 0; i < 5; i++) { + this._inputBuffer.length = 0; let response = await this._sync(); if (response) { await sleep(100); @@ -580,12 +519,13 @@ export class ESPLoader extends EventTarget { async _sync() { await this.sendCommand(ESP_SYNC, SYNC_PACKET); for (let i = 0; i < 8; i++) { - let [_reply, data] = await this.getResponse(ESP_SYNC, SYNC_TIMEOUT); - if (data === null) { - continue; - } - if (data.length > 1 && data[0] == 0 && data[1] == 0) { - return true; + try { + let [_reply, data] = await this.getResponse(ESP_SYNC, SYNC_TIMEOUT); + if (data.length > 1 && data[0] == 0 && data[1] == 0) { + return true; + } + } catch (err) { + // If read packet fails. } } return false; @@ -728,7 +668,10 @@ export class ESPLoader extends EventTarget { let eraseSize; let buffer; let flashWriteSize = this.getFlashWriteSize(); - if ([CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2].includes(this.chipFamily)) { + if ( + !this.IS_STUB && + [CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2].includes(this.chipFamily) + ) { await this.checkCommand(ESP_SPI_ATTACH, new Array(8).fill(0)); } if (this.chipFamily == CHIP_FAMILY_ESP32) { @@ -1191,8 +1134,10 @@ export class ESPLoader extends EventTarget { this.logger.log("Running stub..."); await this.memFinish(stub["entry"]); - const p = await this.readBuffer(100); - const pChar = String.fromCharCode(...p!); + let pChar: string; + + const p = await this.readPacket(500); + pChar = String.fromCharCode(...p); if (pChar != "OHAI") { throw new Error("Failed to start stub. Unexpected response: " + pChar); From 6b7009c7c816ec5260069ee0ebfd93f1e3852b06 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 25 Aug 2021 23:01:10 -0700 Subject: [PATCH 3/4] Take more commits --- src/esp_loader.ts | 38 +++++++++++--------------------------- src/util.ts | 3 ++- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/esp_loader.ts b/src/esp_loader.ts index 179b4c41..8a867191 100644 --- a/src/esp_loader.ts +++ b/src/esp_loader.ts @@ -109,7 +109,7 @@ export class ESPLoader extends EventTarget { if (this.chipFamily == CHIP_FAMILY_ESP8266) { baseAddr = 0x3ff00050; } else if (this.chipFamily == CHIP_FAMILY_ESP32) { - baseAddr = 0x6001a000; + baseAddr = 0x3ff5a000; } else if (this.chipFamily == CHIP_FAMILY_ESP32S2) { baseAddr = 0x6001a000; } @@ -279,6 +279,7 @@ export class ESPLoader extends EventTarget { throw new Error("Command failure error code " + toHex(status[1])); } } + return [value, data]; } @@ -288,21 +289,13 @@ export class ESPLoader extends EventTarget { * does not check response */ async sendCommand(opcode: number, buffer: number[], checksum = 0) { - //debugMsg("Running Send Command"); - this._inputBuffer.length = 0; // Reset input buffer - let packet = [0xc0, 0x00]; // direction - packet.push(opcode); - packet = packet.concat(pack("H", buffer.length)); - packet = packet.concat(slipEncode(pack("I", checksum))); - packet = packet.concat(slipEncode(buffer)); - packet.push(0xc0); + let packet = slipEncode([ + ...pack(" { const stub = await getStubCode(this.chipFamily); diff --git a/src/util.ts b/src/util.ts index fe5094db..06403005 100644 --- a/src/util.ts +++ b/src/util.ts @@ -4,7 +4,7 @@ * 0xdb is replaced with 0xdb 0xdd and 0xc0 is replaced with 0xdb 0xdc */ export const slipEncode = (buffer: number[]): number[] => { - let encoded: number[] = []; + let encoded = [0xc0]; for (let byte of buffer) { if (byte == 0xdb) { encoded = encoded.concat([0xdb, 0xdd]); @@ -14,6 +14,7 @@ export const slipEncode = (buffer: number[]): number[] => { encoded.push(byte); } } + encoded.push(0xc0); return encoded; }; From 91b49e0e1485305ff776655e00a3481caeb4f86d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 25 Aug 2021 23:05:49 -0700 Subject: [PATCH 4/4] Lint --- src/esp_loader.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/esp_loader.ts b/src/esp_loader.ts index 8a867191..3f20d387 100644 --- a/src/esp_loader.ts +++ b/src/esp_loader.ts @@ -402,20 +402,18 @@ export class ESPLoader extends EventTarget { opcode: number, timeout = DEFAULT_TIMEOUT ): Promise<[number, number[]]> { - let packet; - let resp, opRet, lenRet, val, data; for (let i = 0; i < 100; i++) { - packet = await this.readPacket(timeout); + const packet = await this.readPacket(timeout); if (packet.length < 8) { continue; } - [resp, opRet, lenRet, val] = unpack("