Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 5 additions & 39 deletions pkg/espflasher/chip.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ type chipDef struct {
SPIMISOOffs uint32
SPIW0Offs uint32

// SecureDownloadMode is true if esptool detects the ROM is in Secure Download Mode
SecureDownloadMode bool

// SPIAddrRegMSB: if true, SPI peripheral sends from MSB of 32-bit register.
SPIAddrRegMSB bool

Expand Down Expand Up @@ -129,45 +132,8 @@ var chipDefs = map[ChipType]*chipDef{
ChipESP32H2: defESP32H2,
}

// detectChip reads the chip magic register or chip ID to identify the
// connected ESP device.
func detectChip(c *conn) (*chipDef, error) {
// First try reading the magic register (works for all chips)
magic, err := c.readReg(chipDetectMagicRegAddr)
if err != nil {
return nil, fmt.Errorf("read chip detect register: %w", err)
}

// Check magic value against known chips
for _, def := range chipDefs {
if def.UsesMagicValue && def.MagicValue == magic {
return def, nil
}
}

// For newer chips (ESP32-S3, ESP32-C3, etc.), the magic register value
// may not match. These chips use chip ID from the security info instead.
// However, reading chip ID requires the GET_SECURITY_INFO command which
// may not work before we know the chip type.
//
// As a fallback, we can also detect by the UART date register value,
// or by trying known magic values for newer chips.
// Newer chips have different magic values at this register.

// Try matching by non-magic-value chips
for _, def := range chipDefs {
if !def.UsesMagicValue {
// For these chips, try to read a chip-specific register to verify
// We can't easily do chip_id check without knowing the chip first,
// so try to match by reading registers at known addresses.
continue
}
}

return nil, &ChipDetectError{MagicValue: magic}
}

// detectChipByMagic returns the chip definition matching the given magic value.
// detectChipByMagic returns the chip definition matching the given magic value,
// or nil if no match is found.
func detectChipByMagic(magic uint32) *chipDef {
for _, def := range chipDefs {
if def.UsesMagicValue && def.MagicValue == magic {
Expand Down
4 changes: 2 additions & 2 deletions pkg/espflasher/chip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ func TestChipDefsCompleteness(t *testing.T) {
if def.Name == "" {
t.Errorf("chipDefs[%s].Name is empty", ct)
}
if def.FlashSizes == nil || len(def.FlashSizes) == 0 {
if len(def.FlashSizes) == 0 {
t.Errorf("chipDefs[%s].FlashSizes is empty", ct)
}
if def.FlashFrequency == nil || len(def.FlashFrequency) == 0 {
if len(def.FlashFrequency) == 0 {
t.Errorf("chipDefs[%s].FlashFrequency is empty", ct)
}
}
Expand Down
113 changes: 62 additions & 51 deletions pkg/espflasher/flasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,38 @@ func DefaultOptions() *FlasherOptions {
}
}

// connection defines the low-level protocol operations for communicating
// with an ESP bootloader over a serial connection.
type connection interface {
sync() (uint32, error)
readReg(addr uint32) (uint32, error)
writeReg(addr, value, mask, delayUS uint32) error
securityInfo() ([]byte, error)
flashBegin(size, offset uint32, encrypted bool) error
flashData(block []byte, seq uint32) error
flashEnd(reboot bool) error
flashDeflBegin(uncompSize, compSize, offset uint32, encrypted bool) error
flashDeflData(block []byte, seq uint32) error
flashDeflEnd(reboot bool) error
flashMD5(addr, size uint32) ([]byte, error)
flashWriteSize() uint32
spiAttach(value uint32) error
spiSetParams(totalSize, blockSize, sectorSize, pageSize uint32) error
changeBaud(newBaud, oldBaud uint32) error
eraseFlash() error
eraseRegion(offset, size uint32) error
flushInput()
isStub() bool
setSupportsEncryptedFlash(v bool)
}

// Flasher manages the connection to an ESP device and provides
// high-level flash operations.
type Flasher struct {
port serial.Port
conn *conn
conn connection
chip *chipDef
opts *FlasherOptions
isStub bool
portStr string
}

Expand Down Expand Up @@ -175,8 +199,6 @@ func (f *Flasher) connect() error {
attempts = 7
}

var lastErr error

for attempt := 0; attempt < attempts; attempt++ {
// Reset the chip into bootloader mode
switch f.opts.ResetMode {
Expand All @@ -194,12 +216,11 @@ func (f *Flasher) connect() error {
// Try to sync with the bootloader
time.Sleep(100 * time.Millisecond) // Give bootloader time to start
f.conn.flushInput()
for syncAttempt := 0; syncAttempt < 5; syncAttempt++ {
for range 5 {
_, err := f.conn.sync()
if err == nil {
goto synced
}
lastErr = err
time.Sleep(50 * time.Millisecond)
}
}
Expand All @@ -224,50 +245,46 @@ synced:
f.chip = def
}

_ = lastErr // suppress unused warning

f.logf("Detected chip: %s", f.chip.Name)

// Propagate chip capabilities to the connection layer.
f.conn.supportsEncryptedFlash = f.chip.SupportsEncryptedFlash
f.conn.setSupportsEncryptedFlash(f.chip.SupportsEncryptedFlash)

return nil
}

// detectChip identifies the connected ESP chip.
func (f *Flasher) detectChip() (*chipDef, error) {
// Try reading the magic register first
magic, err := f.conn.readReg(chipDetectMagicRegAddr)
si, err := f.readSecurityInfo()
if err != nil {
return nil, fmt.Errorf("detect chip: %w", err)
f.logf("unable to read security info: %s", err)
}

// Check magic value for older chips (ESP8266, ESP32, ESP32-S2)
if def := detectChipByMagic(magic); def != nil {
return def, nil
}
for _, def := range chipDefs {
if def.UsesMagicValue {
continue
}

// For newer chips, try to match the magic value against known values
// These chips have different magic values at the detect register
// Source: esptool chip detection logic
type magicEntry struct {
magic uint32
def *chipDef
if si != nil && si.ChipID != nil && *si.ChipID == uint32(def.ImageChipID) {
def.SecureDownloadMode = si.ParsedFlags.SecureDownloadEnable
return def, nil
}
}

// Known magic values for newer chips (from esptool source)
newChipMagics := []magicEntry{
{0x6F51306F, defESP32C2}, // ESP32-C2
{0x1B31506F, defESP32C3}, // ESP32-C3
{0x0DA1806F, defESP32C6}, // ESP32-C6
{0xD7B73E80, defESP32H2}, // ESP32-H2
{0x09, defESP32S3}, // ESP32-S3 returns chip_id
// Otherwise, try to read the chip magic value to verify the chip type (ESP8266, ESP32, ESP32-S2)
magic, err := f.conn.readReg(chipDetectMagicRegAddr)
if err != nil {
// Only ESP32-S2 does not support chip id detection
// and supports secure download mode
f.logf("unable to read chip magic value. Defaulting to ESP32-S2: %s", err)

chipDefs[ChipESP32S2].SecureDownloadMode = si.ParsedFlags.SecureDownloadEnable
return chipDefs[ChipESP32S2], nil
}

for _, entry := range newChipMagics {
if magic == entry.magic {
return entry.def, nil
}
// Check magic value for older chips (ESP8266, ESP32, ESP32-S2)
if def := detectChipByMagic(magic); def != nil {
return def, nil
}

return nil, &ChipDetectError{MagicValue: magic}
Expand Down Expand Up @@ -332,7 +349,7 @@ func (f *Flasher) FlashImage(data []byte, offset uint32, progress ProgressFunc)
}

// Optionally switch to higher baud rate (not supported by ESP8266 ROM)
canChangeBaud := f.chip == nil || f.chip.ROMHasChangeBaud || f.conn.isStub
canChangeBaud := f.chip == nil || f.chip.ROMHasChangeBaud || f.conn.isStub()
if canChangeBaud && f.opts.FlashBaudRate > 0 && f.opts.FlashBaudRate != f.opts.BaudRate {
if err := f.changeBaud(f.opts.FlashBaudRate); err != nil {
f.logf("Warning: could not change baud rate to %d: %v", f.opts.FlashBaudRate, err)
Expand All @@ -341,7 +358,7 @@ func (f *Flasher) FlashImage(data []byte, offset uint32, progress ProgressFunc)
}

// Use compressed flash only if supported (ESP8266 ROM doesn't support it)
canCompress := f.chip == nil || f.chip.ROMHasCompressedFlash || f.conn.isStub
canCompress := f.chip == nil || f.chip.ROMHasCompressedFlash || f.conn.isStub()
if f.opts.Compress && canCompress {
return f.flashCompressed(data, offset, progress)
}
Expand Down Expand Up @@ -384,15 +401,15 @@ func (f *Flasher) FlashImages(images []ImagePart, progress ProgressFunc) error {
}

// Optionally switch to higher baud rate (not supported by ESP8266 ROM)
canChangeBaud := f.chip == nil || f.chip.ROMHasChangeBaud || f.conn.isStub
canChangeBaud := f.chip == nil || f.chip.ROMHasChangeBaud || f.conn.isStub()
if canChangeBaud && f.opts.FlashBaudRate > 0 && f.opts.FlashBaudRate != f.opts.BaudRate {
if err := f.changeBaud(f.opts.FlashBaudRate); err != nil {
f.logf("Warning: could not change baud rate to %d: %v", f.opts.FlashBaudRate, err)
}
}

// Determine if compressed flash is available
canCompress := f.chip == nil || f.chip.ROMHasCompressedFlash || f.conn.isStub
canCompress := f.chip == nil || f.chip.ROMHasCompressedFlash || f.conn.isStub()

totalSize := 0
for _, img := range images {
Expand Down Expand Up @@ -481,25 +498,19 @@ func (f *Flasher) flashCompressed(data []byte, offset uint32, progress ProgressF
seq := uint32(0)

for sent < compSize {
blockLen := compSize - sent
if blockLen > writeSize {
blockLen = writeSize
}
blockLen := min(compSize-sent, writeSize)

block := compressed[sent : sent+blockLen]
if err := f.conn.flashDeflData(block, seq); err != nil {
return fmt.Errorf("flash block %d: %w", seq, err)
return fmt.Errorf("flash block %d of %d: %w", seq, numBlocks, err)
}

sent += blockLen
seq++

if progress != nil {
// Map compressed bytes sent to approximate uncompressed progress
approxUncomp := int(float64(sent) / float64(compSize) * float64(uncompSize))
if approxUncomp > int(uncompSize) {
approxUncomp = int(uncompSize)
}
approxUncomp := min(int(float64(sent)/float64(compSize)*float64(uncompSize)), int(uncompSize))
progress(approxUncomp, int(uncompSize))
}
}
Expand All @@ -512,7 +523,7 @@ func (f *Flasher) flashCompressed(data []byte, offset uint32, progress ProgressF
f.logf("Flash complete. Verifying...")

// Verify via MD5 if stub is running
if f.conn.isStub {
if f.conn.isStub() {
if err := f.verifyMD5(data, offset); err != nil {
return err
}
Expand Down Expand Up @@ -555,7 +566,7 @@ func (f *Flasher) flashUncompressed(data []byte, offset uint32, progress Progres
}

if err := f.conn.flashData(block, seq); err != nil {
return fmt.Errorf("flash block %d: %w", seq, err)
return fmt.Errorf("flash block %d of %d: %w", seq, numBlocks, err)
}

sent += blockLen
Expand All @@ -574,7 +585,7 @@ func (f *Flasher) flashUncompressed(data []byte, offset uint32, progress Progres
f.logf("Flash complete. Verifying...")

// Verify via MD5 if stub is running
if f.conn.isStub {
if f.conn.isStub() {
if err := f.verifyMD5(data, offset); err != nil {
return err
}
Expand Down Expand Up @@ -607,7 +618,7 @@ func (f *Flasher) verifyMD5(data []byte, offset uint32) error {
// This operation can take a significant amount of time (30-120 seconds).
// Requires the stub loader to be running.
func (f *Flasher) EraseFlash() error {
if !f.conn.isStub {
if !f.conn.isStub() {
return &UnsupportedCommandError{Command: "erase flash (requires stub)"}
}

Expand Down Expand Up @@ -880,7 +891,7 @@ func flashSizeFromJEDEC(capByte uint8) string {
// chip capacity. Returns a size string (e.g. "1MB", "4MB") that matches
// the chip's FlashSizes map, or empty string if detection fails.
func (f *Flasher) detectFlashSize() string {
if f.chip == nil || f.conn == nil || f.conn.port == nil {
if f.chip == nil || f.conn == nil {
return ""
}

Expand Down
Loading
Loading