From 5bbb6922f4325769699edc9bcac2561c74df626c Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 24 Oct 2026 18:35:16 +0200 Subject: [PATCH] esp32c3: boot images with large flash segments The ROM rejects an image with an IROM or DROM segment of 1 MiB or more. Keep IROM and DROM out of the segments the ROM loads. They follow the RAM part of the image at the flash offsets the startup code already maps 1:1. machine.Flash now starts after DROM. --- builder/esp.go | 28 +++++++++++ builder/testdata/binary-size.txt | 4 +- targets/esp32c3.ld | 84 ++++++++++++++++---------------- 3 files changed, 71 insertions(+), 45 deletions(-) diff --git a/builder/esp.go b/builder/esp.go index fa96dd5b5e..7908c685b5 100644 --- a/builder/esp.go +++ b/builder/esp.go @@ -95,6 +95,18 @@ func makeESPFirmwareImage(infile, outfile, format string) error { } segments = ramSegments + case "esp32c3": + var ramSegments []*espImageSegment + for _, seg := range segments { + if (seg.addr >= 0x3C000000 && seg.addr < 0x3C800000) || + (seg.addr >= 0x42000000 && seg.addr < 0x42800000) { + flashSegments = append(flashSegments, seg) + } else { + ramSegments = append(ramSegments, seg) + } + } + segments = ramSegments + case "esp32s3": var ramSegments []*espImageSegment for _, seg := range segments { @@ -480,6 +492,22 @@ func makeESPFirmwareImage(infile, outfile, format string) error { outf.Write(esp32s3DromSegment.data) } + // ESP32-C3 IROM and DROM are mapped 1:1, so the offset in the 8 MB flash + // address space is the offset in the image. See targets/esp32c3.ld. + if chip == "esp32c3" { + sort.SliceStable(flashSegments, func(i, j int) bool { + return flashSegments[i].addr&0x7FFFFF < flashSegments[j].addr&0x7FFFFF + }) + for _, seg := range flashSegments { + offset := int(seg.addr & 0x7FFFFF) + if outf.Len() > offset { + return fmt.Errorf("ESP32-C3: segment at 0x%x overlaps the previous one", seg.addr) + } + outf.Write(make([]byte, offset-outf.Len())) + outf.Write(seg.data) + } + } + // QEMU (or more precisely, qemu-system-xtensa from Espressif) expects the // image to be a certain size. if makeImage { diff --git a/builder/testdata/binary-size.txt b/builder/testdata/binary-size.txt index 4c64a13be7..d71db72081 100644 --- a/builder/testdata/binary-size.txt +++ b/builder/testdata/binary-size.txt @@ -6,8 +6,8 @@ pico examples/serial z 7737 1071 112 5616 pico examples/serial 2 16346 862 108 5616 pico2 examples/serial z 6532 1172 112 5608 pico2 examples/serial 2 15957 979 108 5608 -esp32c3-generic examples/serial z 5864 768 0 8664 -esp32c3-generic examples/serial 2 9888 600 8 8672 +esp32c3-generic examples/serial z 5844 772 0 8664 +esp32c3-generic examples/serial 2 9956 604 8 8672 cortex-m-qemu ./testdata/size-slice z 3087 793 4 4168 cortex-m-qemu ./testdata/size-slice 2 7331 341 4 4168 cortex-m-qemu ./testdata/size-defer z 3782 818 4 4176 diff --git a/targets/esp32c3.ld b/targets/esp32c3.ld index e59fdce88b..440c76d691 100644 --- a/targets/esp32c3.ld +++ b/targets/esp32c3.ld @@ -15,16 +15,11 @@ * - Memory in DRAM and IRAM is loaded at reset by the ROM bootloader. * Luckily, this doesn't have significant alignment requirements. * - * This linker script has been written to carefully work around (or with) these - * limitations: - * - It adds dummy sections so that the bottom 16 bits of the virtual address - * and the physical address (in the generated firmware image) match. - * - It also offsets sections that share an address space using those same - * dummy sections. - * - It sorts the sections by load address, to avoid surprises as esptool.py - * also does it. - * This way, it's possible to create a very small firmware image that still - * conforms to the expectations of esptool.py and the ROM bootloader. + * This linker script works with these limitations: + * - The ROM bootloader rejects IROM or DROM segments of 1 MiB or more, so + * it only loads DRAM and IRAM. IROM and then DROM follow in flash. + * - IROM and DROM are mapped 1:1, so the bottom 16 bits of the virtual + * address and the flash offset match, and they don't overlap. */ MEMORY @@ -48,24 +43,6 @@ ENTRY(call_start_cpu0) SECTIONS { - /* Dummy section to make sure the .rodata section starts exactly behind the - * image header. - */ - .rodata_dummy (NOLOAD): ALIGN(4) - { - . += 0x18; /* image header at start of flash: esp_image_header_t */ - . += 0x8; /* DROM segment header (8 bytes) */ - } > DROM - - /* Constant global variables, stored in DROM. - */ - .rodata : ALIGN(4) - { - *(.rodata*) - *(.srodata*) - . = ALIGN (4); - } >DROM - /* Put the stack at the bottom of DRAM, so that the application will * crash on stack overflow instead of silently corrupting memory. * See: http://blog.japaric.io/stack-overflow-protection/ @@ -138,25 +115,30 @@ SECTIONS __init_start = .; *(.init) __init_end = .; + . = ALIGN (4); } >IRAM - /* Dummy section to put the IROM segment exactly behind the IRAM segment. - * This has to follow the app image format exactly. - */ + /* Size of the RAM part of the image. Header, segments, padding to 16 bytes + * and the SHA-256 digest, as written by builder/esp.go. */ + _ram_image_payload_size = + 0x18 + + SIZEOF(.data) + ((SIZEOF(.data) != 0) ? 0x8 : 0) + + SIZEOF(.iram) + 0x8; + + _ram_image_size = + _ram_image_payload_size + + 16 - (_ram_image_payload_size % 16) + + 0x20; + + /* The ROM only loads the RAM part of the image. IROM and DROM follow it + * in flash and esp32c3.S maps them 1:1, so each VMA offset is its flash + * offset. */ .text_dummy (NOLOAD): ALIGN(4) { - /* Note: DRAM and DROM are not always present so the header should only - * be inserted if it actually exists. - */ - . += 0x18; /* esp_image_header_t */ - . += SIZEOF(.rodata) + ((SIZEOF(.rodata) != 0) ? 0x8 : 0); /* DROM segment (optional) */ - . += SIZEOF(.data) + ((SIZEOF(.data) != 0) ? 0x8 : 0); /* DRAM segment (optional) */ - . += SIZEOF(.iram) + 0x8; /* IRAM segment */ - . += 0x8; /* IROM segment header */ + . += _ram_image_size; } > IROM - /* IROM segment. This contains all the actual code and is placed right after - * the DROM segment. + /* IROM segment. This contains all the actual code. */ .text : ALIGN(4) { @@ -164,11 +146,27 @@ SECTIONS *(.text.exception_vectors) . = ALIGN (4); *(.text .text.*) + . = ALIGN (4); _irom_end = .; } >IROM - /* The 64 bytes skip the image footer written by builder/esp.go. */ - PROVIDE(__flash_data_start = ALIGN(_irom_end - 0x42000000 + 0x3C000000 + 64, 4096)); + /* DROM and IROM share one address space, so DROM starts after IROM. */ + .rodata_dummy (NOLOAD): ALIGN(4) + { + . += _ram_image_size + SIZEOF(.text); + } > DROM + + /* Constant global variables, stored in DROM. + */ + .rodata : ALIGN(4) + { + *(.rodata*) + *(.srodata*) + . = ALIGN (4); + _drom_end = .; + } >DROM + + PROVIDE(__flash_data_start = ALIGN(_drom_end, 4096)); PROVIDE(__flash_data_end = 0x3C000000 + 4M); PROVIDE(__flash_data_phys = __flash_data_start - 0x3C000000);