@@ -65,15 +65,6 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
6565 // Sort the segments by address. This is what esptool does too.
6666 sort .SliceStable (segments , func (i , j int ) bool { return segments [i ].addr < segments [j ].addr })
6767
68- // Calculate checksum over the segment data. This is used in the image
69- // footer.
70- checksum := uint8 (0xef )
71- for _ , segment := range segments {
72- for _ , b := range segment .data {
73- checksum ^= b
74- }
75- }
76-
7768 // Write first to an in-memory buffer, primarily so that we can easily
7869 // calculate a hash over the entire image.
7970 // An added benefit is that we don't need to check for errors all the time.
@@ -88,6 +79,86 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
8879 chip = format [:len (format )- len ("-img" )]
8980 }
9081
82+ // For ESP32 (original): separate RAM segments (loadable by ROM bootloader)
83+ // from flash-mapped segments (DROM/IROM, require MMU setup by startup code).
84+ // The ROM bootloader on ESP32 does NOT handle flash-mapped segments —
85+ // it tries to memcpy to the virtual address, which crashes.
86+ var flashSegments []* espImageSegment
87+ if chip == "esp32" {
88+ var ramSegments []* espImageSegment
89+ for _ , seg := range segments {
90+ if (seg .addr >= 0x3F400000 && seg .addr < 0x3F800000 ) || // DROM
91+ (seg .addr >= 0x400D0000 && seg .addr < 0x40400000 ) { // IROM
92+ flashSegments = append (flashSegments , seg )
93+ } else {
94+ ramSegments = append (ramSegments , seg )
95+ }
96+ }
97+ segments = ramSegments
98+ }
99+
100+ // ESP32 flash XIP: compute where the DROM segment will be placed in flash
101+ // (page-aligned, right after the RAM segments) and patch the
102+ // _drom_flash_addr variable so the startup code can program the cache MMU.
103+ // This must happen before the checksum/hash are computed so the patched
104+ // value is covered by both.
105+ const esp32FlashBase = 0x1000 // esptool flashes the image at 0x1000
106+ // The ESP32 flash cache MMU supports configurable page sizes down to 256 B. 64 KiB is the reset/default size.
107+ // If the startup code ever changes the MMU page size, this constant must change too.
108+ const esp32PageSize = 0x10000 // 64KB MMU pages
109+ var esp32DromFlashAddr uint32
110+ if chip == "esp32" && len (flashSegments ) > 0 {
111+ // Compute the size of the RAM portion of the image (everything the ROM
112+ // bootloader loads, up to and including the appended SHA256 hash).
113+ ramImageSize := 0
114+ if makeImage {
115+ ramImageSize += 4096
116+ }
117+ ramImageSize += 24 // image header (8) + trailer fields (16)
118+ for _ , seg := range segments {
119+ ramImageSize += 8 + len (seg .data ) // segment header + data (4-aligned)
120+ }
121+ ramImageSize += 16 - ramImageSize % 16 // footer padding + checksum byte
122+ ramImageSize += 32 // appended SHA256 hash
123+
124+ // DROM flash address must be 64KB page-aligned.
125+ esp32DromFlashAddr = uint32 (esp32FlashBase + ramImageSize + esp32PageSize - 1 ) &^ (esp32PageSize - 1 )
126+
127+ // Patch _drom_flash_addr in whichever RAM segment contains it.
128+ syms , _ := inf .Symbols ()
129+ var dromSymAddr uint64
130+ for _ , s := range syms {
131+ if s .Name == "_drom_flash_addr" {
132+ dromSymAddr = s .Value
133+ break
134+ }
135+ }
136+ if dromSymAddr == 0 {
137+ return fmt .Errorf ("ESP32: _drom_flash_addr symbol not found" )
138+ }
139+ patched := false
140+ for _ , seg := range segments {
141+ if dromSymAddr >= uint64 (seg .addr ) && dromSymAddr + 4 <= uint64 (seg .addr )+ uint64 (len (seg .data )) {
142+ off := int (dromSymAddr - uint64 (seg .addr ))
143+ binary .LittleEndian .PutUint32 (seg .data [off :], esp32DromFlashAddr )
144+ patched = true
145+ break
146+ }
147+ }
148+ if ! patched {
149+ return fmt .Errorf ("ESP32: _drom_flash_addr (0x%x) not in any RAM segment" , dromSymAddr )
150+ }
151+ }
152+
153+ // Calculate checksum over the segment data. This is used in the image
154+ // footer.
155+ checksum := uint8 (0xef )
156+ for _ , segment := range segments {
157+ for _ , b := range segment .data {
158+ checksum ^= b
159+ }
160+ }
161+
91162 if makeImage {
92163 // The bootloader starts at 0x1000, or 4096.
93164 // TinyGo doesn't use a separate bootloader and runs the entire
@@ -191,6 +262,58 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
191262 outf .Write (hash [:])
192263 }
193264
265+ // For ESP32: append flash-mapped segments (DROM/IROM) at page-aligned flash
266+ // offsets after the RAM portion. The startup code maps them via the flash
267+ // cache MMU (DROM at esp32DromFlashAddr, patched into _drom_flash_addr).
268+ if len (flashSegments ) > 0 {
269+ const flashBase = esp32FlashBase
270+ const pageSize = esp32PageSize
271+ dromFlashAddr := esp32DromFlashAddr
272+
273+ // Separate DROM and IROM segments.
274+ var dromSegs , iromSegs []* espImageSegment
275+ for _ , seg := range flashSegments {
276+ if seg .addr >= 0x3F400000 && seg .addr < 0x3F800000 {
277+ dromSegs = append (dromSegs , seg )
278+ } else {
279+ iromSegs = append (iromSegs , seg )
280+ }
281+ }
282+
283+ // Write DROM segments at the computed page-aligned flash offset.
284+ dromSize := 0
285+ if len (dromSegs ) > 0 {
286+ targetImageOffset := int (dromFlashAddr - flashBase )
287+ if outf .Len () > targetImageOffset {
288+ return fmt .Errorf ("ESP32: RAM segments too large (%d bytes), overlap DROM at flash 0x%x" , outf .Len (), dromFlashAddr )
289+ }
290+ outf .Write (make ([]byte , targetImageOffset - outf .Len ()))
291+ for _ , seg := range dromSegs {
292+ outf .Write (seg .data )
293+ dromSize += len (seg .data )
294+ }
295+ }
296+
297+ // Write IROM segments immediately after DROM, at the next page boundary.
298+ // IROM flash addr = dromFlashAddr + ceil(dromSize/pageSize)*pageSize
299+ // (must match the computation in the startup assembly).
300+ if len (iromSegs ) > 0 {
301+ dromPages := (dromSize + pageSize - 1 ) / pageSize
302+ if dromPages == 0 {
303+ dromPages = 1
304+ }
305+ iromFlashAddr := dromFlashAddr + uint32 (dromPages )* pageSize
306+ targetImageOffset := int (iromFlashAddr - flashBase )
307+ if outf .Len () > targetImageOffset {
308+ return fmt .Errorf ("ESP32: DROM too large, overlaps IROM at flash 0x%x" , iromFlashAddr )
309+ }
310+ outf .Write (make ([]byte , targetImageOffset - outf .Len ()))
311+ for _ , seg := range iromSegs {
312+ outf .Write (seg .data )
313+ }
314+ }
315+ }
316+
194317 // QEMU (or more precisely, qemu-system-xtensa from Espressif) expects the
195318 // image to be a certain size.
196319 if makeImage {
0 commit comments