AGENTS.md: - Rule 2 strengthened: U-Boot é compromisso firme com lista de tasks - New Rule 10: GDB/GDB stub obrigatório em todos os testes (QEMU + HW) - Architecture Reality Check: added ESP32+U-Boot entry - Verification Workflow: completo com comandos GDB para cada target docs/esp32-mcuboot-uboot-strategy.md: - Chain B (U-Boot) agora é MANDATÓRIA para todos os targets - Chain A é stepping stone de desenvolvimento apenas - Chain C (bootstub) será eliminada - Open decisions marcadas como resolvidas - Tabela de viabilidade: Xtensa passou de 🔴 risky para 🟡 commitment
224 lines
11 KiB
Markdown
224 lines
11 KiB
Markdown
# Espressif Boot Strategy: MCUboot (+ U-Boot) → UniversalisOS
|
||
|
||
**Audience**: Maestro Fabio. **Status**: Strategy / research (no code changes yet).
|
||
**Inputs studied**:
|
||
- Zephyr `sysbuild` docs (main), `boards/espressif/*`, `boards/common/esp32.board.cmake`
|
||
- `rtos_ref/hal_espressif` (Espressif HAL module: `zephyr/`, `components/bootloader_support`, `components/bootloader`, `zephyr/port/boot/esp_image_loader.c`, `zephyr/scripts/partitions/esp_genpartition.py`)
|
||
- `mcuboot` repo (our fork `portugalfuturista/mcuboot`): `boot/espressif/main.c`, `port/esp_loader.c`, `port/esp32/bootloader.conf`
|
||
- In-tree: `microkernel/ports/esp32/{bootstub.c,bootstub_minimal.S,bootstub_entry.S,uos_loader.h}`, `microkernel/Makefile`, `docs/esp32-boot-chain-audit.md`, `XTENSA_AUDIT.md`
|
||
|
||
---
|
||
|
||
## 1. Reality check: U-Boot on Espressif 🟡
|
||
|
||
This is the single fact that shapes the whole design.
|
||
|
||
| SoC family | Arch | U-Boot support | Viability as a loader |
|
||
||---|---|---|---|
|
||
| ESP32 / ESP32-S2 / ESP32-S3 | Xtensa LX6/LX7 | **Experimental, not mainline** | 🟡 commitment: estamos a portar |
|
||
| ESP32-C3 / C6 / H2 | RISC-V | **Mainline** (since ~2023) | ✅ real |
|
||
| ESP32-P4 | RISC-V | Partial / under review | 🟡 |
|
||
| ESP32-C5 | RISC-V | not upstream | 🔴 |
|
||
|
||
**Decisão do Maestro Fabio:** U-Boot em Xtensa ESP32 é uma realidade — não saltamos, não simplificamos. Estamos a fazer um fork Xtensa do U-Boot. Isto significa portar drivers, fixar cache/MMU, excepções, e todo o ecossistema (partition table, spi flash, env, GPIO, timer, WDT). O trabalho vive em `portugalfuturista/universalis-uboot`.
|
||
|
||
MCUboot, by contrast, **supports every Espressif target** (Xtensa + RISC-V) via `boot/espressif`. So:
|
||
|
||
- **"MCUboot only" works on ALL targets** — it is the proven Zephyr path.
|
||
- **"MCUboot + U-Boot" is only sensible on RISC-V parts** (C3/C6/H2). On Xtensa we would be maintaining a non-mainline U-Boot fork for zero benefit (UOS is a bare-metal HV; it does not need a U-Boot pre-OS env).
|
||
|
||
**Recommendation**: treat U-Boot as an *optional* rich-loader stage **only for the RISC-V line**. Default secure-boot chain for every target = `ROM → MCUboot → UOS`.
|
||
|
||
---
|
||
|
||
## 2. The three candidate chains
|
||
|
||
```
|
||
CHAIN A — MCUboot → UOS (development/test stepping stone, Xtensa only)
|
||
ROM (mask, fixed)
|
||
→ MCUboot @ 0x1000 (2nd-stage, esptool elf2image 0xE9 format)
|
||
validates signature, swap/scratch, picks primary
|
||
→ UOS app @ slot0 (mcuboot-signed, esp_image_load_header 0xace637d3)
|
||
esp_loader.c copies IRAM/DRAM segs, jumps to entry
|
||
*** Use Chain A for GDB stub bring-up and µ-kernel development only ***
|
||
*** Chain B is the FINAL target for all Espressif targets ***
|
||
|
||
CHAIN B — MCUboot → U-Boot → UOS (MANDATORY, ALL ESPRESSIF TARGETS)
|
||
ROM → MCUboot @ 0x1000 → U-Boot @ slot0 → UOS (loaded by U-Boot)
|
||
U-Boot = the mcuboot "application image"; UOS = a payload U-Boot chainloads.
|
||
No shortcuts. No skipping U-Boot. Full boot chain verification required.
|
||
- Xtensa: fork não-mainline em porting (consola, cache, vectors, drivers)
|
||
- RISC-V: mainline, mais fácil — fazer depois do Xtensa
|
||
|
||
CHAIN C — in-tree bootstub (QEMU-only debugging, NOT for HW)
|
||
ROM → bootstub @ 0x1000 (own minimal 2nd-stage, watches-off + MMU map)
|
||
→ UOS app @ 0x10000 (plain 0xE9 image, mcuboot-style bounds-checked loader)
|
||
*** QEMU only. Never flash to hardware. ***
|
||
|
||
---
|
||
|
||
## 3. Flash memory map (Chain A, the canonical layout)
|
||
|
||
From `mcuboot/boot/espressif/port/esp32/bootloader.conf` **and** Zephyr's
|
||
`esp_genpartition.py` `scheme_default`:
|
||
|
||
| Offset | Size | Content | Source of truth |
|
||
|---|---|---|---|
|
||
| `0x0000` | `0x1000` | secure-boot digest area (if SB v1) | IDF |
|
||
| `0x1000` | ~`0x7000`-`0xF000` | **MCUboot** (2nd stage, `0xE9` img) | `CONFIG_ESP_BOOTLOADER_OFFSET=0x1000` |
|
||
| `0x8000` | `0x1000` | partition table (magic `0x50AA`) | hardware-fixed |
|
||
| `0x10000` | — | app factory / **slot0 (image-0)** | Zephyr default (`boot` partition scheme) |
|
||
| `0x20000` | `0x150000` | **slot0** primary (mcuboot-repo default) | `CONFIG_ESP_IMAGE0_PRIMARY_START_ADDRESS` |
|
||
| `0x170000` | `0x150000` | slot1 secondary | mcuboot-repo default |
|
||
| `0x3E0000` | `0x1F000` | scratch | mcuboot-repo default |
|
||
|
||
> ⚠️ **Offset discrepancy to pin down before flashing real HW**: Zephyr's
|
||
> partition scheme puts `slot0` at `0x10000`; the mcuboot-repo `bootloader.conf`
|
||
> puts it at `0x20000`. Decide ONE scheme and derive every offset from the
|
||
> *generated* `partitions.bin` + `build/mcuboot/zephyr/zephyr.bin` offsets.
|
||
> Do not hand-compute. The generator (`esp_genpartition.py`) is the single
|
||
> source of truth.
|
||
|
||
The MCUboot app image is **not** a raw `0xE9` image. MCUboot prepends
|
||
`esp_image_load_header_t` (magic `0xace637d3`, 96 B) describing IRAM/DRAM/
|
||
IROM/DROM/LP regions; `esp_loader.c::esp_app_image_load()` validates each
|
||
destination against `esp_ptr_in_iram/dram` and copies, then jumps to
|
||
`entry_addr`. Our `uos_loader.h` already mirrors this discipline.
|
||
|
||
---
|
||
|
||
## 4. How Zephyr wires it (the mechanics to copy)
|
||
|
||
`west build --sysbuild -b esp32s3_devkitc hello_world` with board `Kconfig.sysbuild`:
|
||
```
|
||
choice BOOTLOADER
|
||
default BOOTLOADER_MCUBOOT
|
||
endchoice
|
||
```
|
||
produces domains: `mcuboot/`, `<app>/`, `domains.yaml`. Sysbuild:
|
||
1. Builds `<app>` with `CONFIG_BOOTLOADER_MCUBOOT=y` → app becomes mcuboot-signed.
|
||
2. Builds `mcuboot` domain (the Espressif port) → `zephyr.bin` at `0x1000`.
|
||
3. Generates the partition table via `esp_genpartition.py` from the SoC dts.
|
||
4. `west flash` flashes bootloader + app in order (per `domains.yaml`).
|
||
|
||
The HAL's `zephyr/CMakeLists.txt` opts the app into the Espressif flash
|
||
section layout only when **not** `CONFIG_MCUBOOT` (i.e. MCUboot builds itself
|
||
with `CONFIG_MCUBOOT_ESPRESSIF` → early `return()`). `soc_init.c` prints
|
||
"MCUboot 2nd stage bootloader" vs "MCUboot Application image" accordingly.
|
||
|
||
**We do NOT need Zephyr to adopt this.** We can reproduce the identical chain
|
||
with our own build glue (see §6) because all the real logic lives in
|
||
`mcuboot/boot/espressif` + `esp_loader.c`, which we already fork.
|
||
|
||
---
|
||
|
||
## 5. What MCUboot-Espressif actually does (so we trust it)
|
||
|
||
`boot/espressif/main.c::main()`:
|
||
1. `bootloader_init()` — clocks, flash, cache, WDT (mirrors IDF 2nd stage).
|
||
2. Optional secure-boot key generation + eFuse burn (V2, `ABS_DONE_0`).
|
||
3. Optional flash-encryption key gen + in-place encrypt + eFuse burn.
|
||
4. `boot_go(&rsp)` — MCUboot core: validate primary/secondary, decide swap.
|
||
5. `do_boot()` → `start_cpu0_image()` (and `start_cpu1_image()` under
|
||
`CONFIG_ESP_MULTI_PROCESSOR_BOOT` for SMP) → jumps to app entry.
|
||
|
||
Build/packaging (`boot/espressif/CMakeLists.txt` ~L466/L492):
|
||
```
|
||
esptool.py --chip <target> elf2image --min-rev <rev> \
|
||
--flash-freq <f> <CONFIG_ESP_BOOTLOADER_OFFSET>
|
||
```
|
||
So MCUboot is compiled to an ELF, then `elf2image` turns it into the `0xE9`
|
||
ROM-loadable image at `0x1000`. **That is the trick that lets a fixed ROM
|
||
boot a full bootloader.** Our UOS app, when mcuboot is the loader, must be
|
||
signed with `imgtool` and carry the `esp_image_load_header`.
|
||
|
||
---
|
||
|
||
## 6. What we already have in-tree (reuse, don't rebuild)
|
||
|
||
`microkernel/ports/esp32/`:
|
||
- `bootstub.c` + `bootstub_minimal.S` + `bootstub_entry.S` — a from-scratch
|
||
2nd-stage: disables flashboot WDTs, maps flash page 1 via cache MMU, loads
|
||
the `0xE9` app at `0x10000`, jumps. **QEMU-proven** (per `XTENSA_AUDIT.md`
|
||
+ memory: "UOS Xtensa: QEMU✅").
|
||
- `uos_loader.h` / `bootstub.c` — mcuboot-style segment loader with exact
|
||
IRAM/DRAM bounds checks (`uos_ptr_in_iram/dram`, `uos_region_contains`).
|
||
- `Makefile` already builds `TARGET=esp32` (QEMU) and `TARGET=esp32hw`
|
||
(`esptool elf2image --flash-mode dio --flash-size 2MB`, flash @ `0x10000`).
|
||
|
||
So Chain C (bootstub) is **already working in emulation**. Chain A (MCUboot)
|
||
needs us to (a) build our `mcuboot` fork for the target and (b) sign+package
|
||
the UOS app with `imgtool` + the esp load header. Chain B (U-Boot) needs a
|
||
RISC-V U-Boot port + a UOS load command.
|
||
|
||
---
|
||
|
||
## 7. Estratégia (mandatada por Maestro Fabio)
|
||
|
||
**Fase 0 — Pin the layout (do first, blocks real HW).**
|
||
- Pick ONE partition scheme. Generate it with `esp_genpartition.py` for the
|
||
target flash size; record `slot0` offset.
|
||
|
||
**Fase 1 — Chain A funcional (MCUboot → UOS, stepping stone).**
|
||
- Build MCUboot/boot/espressif for the target.
|
||
- Build UOS app; sign with imgtool + prepend `esp_image_load_header`.
|
||
- Flash at slot0. Verify in QEMU + HW.
|
||
- **GDB stub mandatório em todos os testes** (break 0,0 → T05 via UART).
|
||
|
||
**Fase 2 — Chain B (MCUboot → U-Boot → UOS, OBRIGATÓRIO).**
|
||
- Portar U-Boot para Xtensa ESP32 no fork `universalis-uboot`.
|
||
- Fix consola garbled (Phase 1 do master plan: UART DM_SERIAL, cache/MMU, vectors).
|
||
- ESP32 drivers (SPI flash, partition table, env, GPIO, timer, WDT).
|
||
- bootelf / load command para carregar UOS image.
|
||
- Auto-boot via CONFIG_BOOTCOMMAND.
|
||
- Chain A continua a existir como GDB-stub development path.
|
||
|
||
**Fase 3 — Chain C (bootstub) eliminated.**
|
||
- Bootstub era para QEMU. Com Chain A funcional, não precisamos de bootstub.
|
||
- Remover `microkernel/ports/esp32/bootstub*` após Chain A+B estarem estáveis.
|
||
|
||
**Fase 4 — SMP / APP-CPU bring-up (MCUboot + U-Boot).**
|
||
- Reuse `CONFIG_ESP_MULTI_PROCESSOR_BOOT` path (`start_cpu1_image`) no MCUboot.
|
||
- U-Boot eventualmente carrega as duas CPUs.
|
||
|
||
---
|
||
|
||
## 8. Tooling required (present on this host ✅)
|
||
|
||
- `esptool` / `esptool.py` → `~/.local/bin` ✅
|
||
- IDF activate scripts (`activate_idf_release-v5.4/5.5/6.0`) ✅
|
||
- Xtensa toolchain `~/.espressif/tools/xtensa-esp-elf/...` (referenced by
|
||
`microkernel/Makefile`) ✅
|
||
- `mcuboot` fork present at `~/portugalfuturista/mcuboot` ✅ (has `boot/espressif`)
|
||
- `imgtool` (mcuboot signing) — needs `pip install imgtool` or use
|
||
`mcuboot/scripts/imgtool.py`.
|
||
|
||
---
|
||
|
||
## 9. Open decisions (resolvidas)
|
||
|
||
1. **Target SoC(s)** — Xtensa (ESP32/S3) é o alvo primário. RISC-V (C3/C6/H2)
|
||
depois do Xtensa. Chain B (U-Boot) para TODOS.
|
||
2. **U-Boot é obrigatório, não opcional.** "mcuboot+uboot" não era shorthand —
|
||
é a arquitetura de boot final. Chain A (MCUboot → UOS) é stepping stone
|
||
de desenvolvimento apenas.
|
||
3. **Secure boot / flash encryption** — primeiro bring-up das cadeias,
|
||
depois ativar segurança.
|
||
|
||
Ver docs/esp32-master-plan.md para o plano detalhado de 5 fases do U-Boot.
|
||
|
||
---
|
||
|
||
## 10. Risks / blockers 🔴🟡
|
||
|
||
- 🔴 **Xtensa U-Boot is not mainline** — if U-Boot is mandated on ESP32/S3 we
|
||
own a fork. Recommend declining on Xtensa.
|
||
- 🟡 **Offset mismatch** between Zephyr scheme (`slot0@0x10000`) and mcuboot-repo
|
||
conf (`slot0@0x20000`). Must be resolved to one scheme before HW flash.
|
||
- 🟡 **MCUboot image size** can exceed the `0x1000`–`0x8000` gap if the
|
||
partition table sits at `0x8000`; size the mcuboot build to fit (Zephyr
|
||
handles this; our standalone build must too).
|
||
- 🟡 **appcpu / SMP** needs the `esp_image_load_header` LP-core regions for
|
||
P4/C5; ensure `uos_loader.h` covers LP_IRAM/LP_DRAM if we target those.
|
||
- ✅ ROM is immutable and expects `0xE9` at `0x1000` — both MCUboot and our
|
||
bootstub already satisfy this.
|