-
Notifications
You must be signed in to change notification settings - Fork 178
add detect chip with get security info #197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
88af7d0
879f011
aeb1f3d
1e0f8f7
bdee27d
907dfc7
8498192
436e80c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,15 +11,20 @@ import { FlashOptions } from "./types/flashOptions.js"; | |
| import { After, Before } from "./types/resetModes.js"; | ||
| import { FlashFreqValues, FlashModeValues, FlashSizeValues } from "./types/arguments.js"; | ||
| import { loadFirmwareImage } from "./image/index.js"; | ||
| import { ROM_LIST } from "./targets/index.js"; | ||
| import { parseSecurityFlags, SecurityInfo } from "./types/securityInfo.js"; | ||
|
|
||
| /** | ||
| * Flash read callback function type | ||
| * @param {Uint8Array} packet - Packet data | ||
| * @param {number} progress - Progress number | ||
| * @param {number} totalSize - Total size number | ||
| * Callback function type for handling packets received during flash memory read operations. | ||
| * @callback FlashReadCallback | ||
| * @param {Uint8Array} packet - The data packet received from the flash memory. | ||
| * @param {number} progress - The current progress of the read operation in bytes. | ||
| * @param {number} totalSize - The total size of the data to be read in bytes. | ||
| */ | ||
| export type FlashReadCallback = ((packet: Uint8Array, progress: number, totalSize: number) => void) | null; | ||
|
|
||
| export { SecurityInfo, SECURITY_INFO_FLAG_MAP, ParsedSecurityFlags } from "./types/securityInfo.js"; | ||
|
|
||
| /** | ||
| * Return the chip ROM based on the given magic number | ||
| * @param {number} magic - magic hex number to select ROM. | ||
|
|
@@ -106,6 +111,8 @@ export class ESPLoader { | |
| ESP_FLASH_DEFL_END = 0x12; | ||
| ESP_SPI_FLASH_MD5 = 0x13; | ||
|
|
||
| ESP_GET_SECURITY_INFO = 0x14; | ||
|
|
||
| // Only Stub supported commands | ||
| ESP_ERASE_FLASH = 0xd0; | ||
| ESP_ERASE_REGION = 0xd1; | ||
|
|
@@ -164,6 +171,8 @@ export class ESPLoader { | |
| chip!: ROM; | ||
| IS_STUB: boolean; | ||
| FLASH_WRITE_SIZE: number; | ||
| secureDownloadMode = false; | ||
| private securityInfoCache: SecurityInfo | null = null; | ||
|
|
||
| public transport: Transport; | ||
| private baudrate: number; | ||
|
|
@@ -637,30 +646,160 @@ export class ESPLoader { | |
| this.info("\n\r", false); | ||
|
|
||
| if (detecting) { | ||
| const chipMagicValue = (await this.readReg(this.CHIP_DETECT_MAGIC_REG_ADDR)) >>> 0; | ||
| this.debug("Chip Magic " + chipMagicValue.toString(16)); | ||
| const chip = await magic2Chip(chipMagicValue); | ||
| this.info("Detecting chip type... "); | ||
| await this.identifyChip(mode, attempts); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Identify the connected chip using GET_SECURITY_INFO chip-id, then magic-register fallback. | ||
| * @param {Before} mode Reset mode used if a reconnect is required | ||
| * @param {number} attempts Connection attempts used if a reconnect is required | ||
| */ | ||
| private async identifyChip(mode: Before, attempts: number) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Few thoughts on identifyChip method breakdown for better modularity, readability and reusability
Overall, I think typed errors (5) would be the most important change to consider in this MR because it affects correctness. The remaining points are mostly structural improvements and can reasonably be deferred if we want to keep this MR focused on parity. |
||
| let chip: ROM | null = null; | ||
| const errMsg = "Failed to autodetect chip type."; | ||
|
|
||
| try { | ||
| const chipId = await this.getChipId(); | ||
| for (const cls of ROM_LIST) { | ||
| // ESP8266/ESP32: command unsupported; ESP32-S2: no chip-id in the payload | ||
| if (cls.USES_MAGIC_VALUE) { | ||
| continue; | ||
| } | ||
| if (chipId === cls.IMAGE_CHIP_ID) { | ||
| chip = cls; | ||
| const securityInfo = await this.getSecurityInfo(); | ||
| this.secureDownloadMode = securityInfo.parsedFlags.SECURE_DOWNLOAD_ENABLE; | ||
| break; | ||
| } | ||
| } | ||
| if (chip === null) { | ||
| throw new ESPError( | ||
| `Unexpected CHIP magic value 0x${chipMagicValue.toString(16)}. Failed to autodetect chip type.`, | ||
| ); | ||
| } else { | ||
| this.chip = chip; | ||
| throw new ESPError(`Unexpected chip ID value ${chipId}. Failed to autodetect chip type.`); | ||
| } | ||
| } catch (error) { | ||
| if (error instanceof ESPError && error.message.startsWith("Unexpected chip ID value")) { | ||
| throw error; | ||
| } | ||
| this.debug("GET_SECURITY_INFO not supported, falling back to magic value"); | ||
| } | ||
|
|
||
| if (chip === null) { | ||
| try { | ||
| chip = await this.chipFromMagicValue(); | ||
| } catch (error) { | ||
| if (error instanceof ESPError && error.message === "unsupported command error") { | ||
| // ESP32-S2 supports GET_SECURITY_INFO but not magic-register reads in SDM | ||
| const { ESP32S2ROM } = await import("./targets/esp32s2.js"); | ||
| chip = new ESP32S2ROM(); | ||
| const securityInfo = await this.getSecurityInfo(); | ||
| this.secureDownloadMode = securityInfo.parsedFlags.SECURE_DOWNLOAD_ENABLE; | ||
| } else if (error instanceof ESPError && error.message.startsWith("Unexpected CHIP magic value")) { | ||
| throw error; | ||
| } else { | ||
| this.info(" Autodetection failed, trying again..."); | ||
| await this.transport.disconnect(); | ||
| await this.connect(mode, attempts, false); | ||
| this.info("Detecting chip type... "); | ||
| chip = await this.chipFromMagicValue(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (chip === null) { | ||
| throw new ESPError(errMsg); | ||
| } | ||
| this.chip = chip; | ||
| if (chip.SPI_ADDR_REG_MSB !== undefined) { | ||
| this.SPI_ADDR_REG_MSB = chip.SPI_ADDR_REG_MSB; | ||
| } | ||
| } | ||
|
|
||
| private async chipFromMagicValue(): Promise<ROM> { | ||
| const chipMagicValue = (await this.readReg(this.CHIP_DETECT_MAGIC_REG_ADDR)) >>> 0; | ||
| this.debug("Chip Magic " + chipMagicValue.toString(16)); | ||
| const chip = await magic2Chip(chipMagicValue); | ||
| if (chip === null) { | ||
| throw new ESPError( | ||
| `Unexpected CHIP magic value 0x${chipMagicValue.toString(16)}. Failed to autodetect chip type.`, | ||
| ); | ||
| } | ||
| return chip; | ||
| } | ||
|
|
||
| /** | ||
| * Read GET_SECURITY_INFO (0x14): flags, flash crypt count, key purposes, chip id, API version. | ||
| * Tries the 20-byte layout first (ESP32-S3 and later), then 12 bytes (ESP32-S2). | ||
| * @param {boolean} cache Return a previously parsed result when available | ||
| * @returns {Promise<SecurityInfo>} Parsed security information | ||
| */ | ||
| async getSecurityInfo(cache = true): Promise<SecurityInfo> { | ||
| if (cache && this.securityInfoCache !== null) { | ||
| return this.securityInfoCache; | ||
| } | ||
|
|
||
| let res: Uint8Array; | ||
| let esp32s2 = false; | ||
| try { | ||
| res = (await this.checkCommand( | ||
| "get security info", | ||
| this.ESP_GET_SECURITY_INFO, | ||
| new Uint8Array(0), | ||
| 0, | ||
| 20, | ||
| )) as Uint8Array; | ||
| } catch { | ||
| res = (await this.checkCommand( | ||
| "get security info", | ||
| this.ESP_GET_SECURITY_INFO, | ||
| new Uint8Array(0), | ||
| 0, | ||
| 12, | ||
| )) as Uint8Array; | ||
| esp32s2 = true; | ||
| } | ||
|
|
||
| const flags = this._byteArrayToInt(res[0], res[1], res[2], res[3]) >>> 0; | ||
| const securityInfo: SecurityInfo = { | ||
| flags, | ||
| flashCryptCnt: res[4], | ||
| keyPurposes: Array.from(res.slice(5, 12)), | ||
| chipId: esp32s2 ? null : this._byteArrayToInt(res[12], res[13], res[14], res[15]) >>> 0, | ||
| apiVersion: esp32s2 ? null : this._byteArrayToInt(res[16], res[17], res[18], res[19]) >>> 0, | ||
| parsedFlags: parseSecurityFlags(flags), | ||
| }; | ||
|
|
||
| this.securityInfoCache = securityInfo; | ||
| return securityInfo; | ||
| } | ||
|
|
||
| /** | ||
| * Get the CHIP ID from ESP_GET_SECURITY_INFO. | ||
| * @returns {number} Chip ID number | ||
| */ | ||
| async getChipId(): Promise<number> { | ||
| const chipId = (await this.getSecurityInfo()).chipId; | ||
| if (chipId === null) { | ||
| throw new ESPError( | ||
| "Security info command does not contain chip ID. " + | ||
| "This is expected for ESP32-S2 which doesn't support chip ID in security info.", | ||
| ); | ||
| } | ||
| this.debug("get_chip_id " + chipId.toString(16)); | ||
| return chipId; | ||
| } | ||
|
|
||
| /** | ||
| * Connect and detect the existing chip. | ||
| * @param {string} mode Reset mode to use for connection. | ||
| * @param {number} attempts - Number of connection attempts | ||
| */ | ||
| async detectChip(mode: Before = "default_reset") { | ||
| await this.connect(mode); | ||
| this.info("Detecting chip type... ", false); | ||
| async detectChip(mode: Before = "default_reset", attempts = 7) { | ||
| await this.connect(mode, attempts, true); | ||
| if (this.chip != null) { | ||
| this.info(this.chip.CHIP_NAME); | ||
| } else { | ||
| this.info("unknown!"); | ||
| this.info("unknown chip! detectchip has failed."); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1221,11 +1360,14 @@ export class ESPLoader { | |
| } | ||
|
|
||
| /** | ||
| * Read flash memory from the chip. | ||
| * @param {number} addr Address number | ||
| * @param {number} size Package size | ||
| * @param {FlashReadCallback} onPacketReceived Callback function to call when packet is received | ||
| * @returns {Uint8Array} Flash read data | ||
| * Read data from flash memory of the chip. | ||
| * This function reads a specified amount of data from the flash memory starting at a given address. | ||
| * It sends a read command to the chip and processes the response packets until the requested size is read. | ||
| * @param {number} addr - The starting address in flash memory to read from. | ||
| * @param {number} size - The number of bytes to read from flash memory. | ||
| * @param {FlashReadCallback} onPacketReceived - Optional callback function to handle each received packet. | ||
| * @returns {Promise<Uint8Array>} A promise that resolves to the data read from flash memory as a Uint8Array. | ||
| * @throws {ESPError} If the read operation fails or an unexpected response is received. | ||
| */ | ||
| async readFlash(addr: number, size: number, onPacketReceived: FlashReadCallback = null) { | ||
| let pkt = this._appendArray(this._intToByteArray(addr), this._intToByteArray(size)); | ||
|
|
@@ -1312,6 +1454,10 @@ export class ESPLoader { | |
| * Change the chip baudrate. | ||
| */ | ||
| async changeBaud() { | ||
| if (this.secureDownloadMode) { | ||
| this.info("Baud rate change is not supported in secure download mode. Keeping 115200 baud."); | ||
| return; | ||
| } | ||
| this.info("Changing baudrate to " + this.baudrate); | ||
| const secondArg = this.IS_STUB ? this.romBaudrate : 0; | ||
| const pkt = this._appendArray(this._intToByteArray(this.baudrate), this._intToByteArray(secondArg)); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This re-export seems to be redundant, should be removed from here and incorporated in the index barrel file