docs: AGENTS.md + strategy doc — U-Boot é obrigatório, GDB stub mandatório em todos os testes

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
This commit is contained in:
Fábio Coutada 2026-07-17 14:10:09 +01:00
parent 533f794dae
commit b5b29759af
2 changed files with 102 additions and 38 deletions

View file

@ -34,6 +34,18 @@ Safety-critical type-1 hypervisor (ARMv7 primary, AArch64/RISC-V in progress) im
2. **A cadeia de boot do ESP32 é ROM Boot → MCUboot (1st stage) → U-Boot (2nd stage) → UniversalisOS µ-kernel.** NÃO removas o U-Boot desta cadeia. NÃO saltes etapas. NÃO mudes o flash layout. Se algo não funciona, investiga PORQUÊ dentro da cadeia existente — não a contornes.
**Compromisso firme com U-Boot:** Estamos a PORTAR o U-Boot para Xtensa ESP32 (não-mainline). Isto inclui:
- Fix da consola (UART DM_SERIAL, garbled output)
- Fix dos exception vectors (discarded no linker)
- Fix da cache/MMU (init não pode resetar as mappings do MCUboot)
- Driver SPI flash, partition table parser, env backend, WDT, GPIO, timer (CCOMPARE0)
- Suporte IROM (flash-XIP) com mapeamento DROM para l32r
- ESP32 partition table parser (formato 0x50AA)
- bootelf para carregar UOS images
- Device Tree (DTS para ESP32)
Este é um fork não-mainline — todo o trabalho fica em `portugalfuturista/universalis-uboot`. Ver `docs/esp32-mcuboot-uboot-strategy.md` para o plano detalhado.
3. **O ESP32 usa o ESP HAL da Espressif.** Não substitutes por raw registers sem autorização. O HAL está em `rtos_ref/hal_espressif/`.
4. **Antes de mudar qualquer ficheiro, pergunta: "Este ficheiro faz parte explícita da tarefa?"** Se não, não o touches. Drive-by refactors são proibidos.
@ -48,6 +60,14 @@ Safety-critical type-1 hypervisor (ARMv7 primary, AArch64/RISC-V in progress) im
9. **Se o utilizador disser PARA, PÁRA.** Não faças mais mudanças. Não "fixes" sem permissão. Espera instruções.
10. **GDB/GDB STUB É OBRIGATÓRIO EM TODOS OS TESTES.** Sempre que correres QEMU ou testes em hardware (especialmente ESP32), deves usar GDB com GDB stubs. Isto aplica-se a:
- **QEMU:** `qemu-system-* -s -S` (GDB stub na porta 1234). O GDB attach é feito com `target remote :1234`.
- **ESP32 hardware:** O GDB stub integrado (em `microkernel/ports/esp32/uos_gdbstub.c`) é ativado pelo `break 0,0` em `_start`. Comunicação via UART0 a 115200 usando o protocolo RSP (Remote Serial Protocol).
- **Bootloader bring-up:** Usar GDB para single-step, ler registos, e verificar estado em cada etapa da cadeia de boot.
- **Exceções e crashes:** Use GDB para inspecionar o estado no momento da falha — não confies apenas em output UART.
- A GDB stub deve estar sempre compilada (`-DUOS_GDBSTUB=1` já está no Makefile para TARGET=esp32).
- O ficheiro `microkernel/gdb-connect.sh` contém o comando GDB para attach.
## Critical Build Gotchas
- **Default build target is RISC-V**, not ARM. The Makefile defaults to `ARCH=riscv PLATFORM=polarfire`.
@ -131,6 +151,7 @@ qemu-system-riscv64 -machine microchip-icicle-kit -smp 5 -m 2G -nographic \
- **ARMv7 + qemu-arm-virt**: Boots, produces UART output, runs scheduler/IPC/HM demos end-to-end. This is the target you should test against.
- **AArch64 + qemu-aarch64-virt**: EL2 hypervisor track. Builds clean (0 linker warnings) and boots to a full banner at EL2 — but **only** when QEMU is run with `virtualization=on` (see gotcha above). MP0 bring-up reaches the scheduler, `eret` into the first task works, and **timer-driven preemption is live** (the former U3 open item is resolved): the HV tick uses the EL2 **physical** timer CNTHP on **PPI 26** (`timer.cpp` → `gicv3_irq_enable(QEMU_TIMER_EL2_PHYS_PPI)`), `el2_irq_handler` (el2_trap.cpp) acks INTID 26 / re-arms CNTHP_CVAL / EOIs / runs `sched_tick()`, and `el2_irq_entry` (exceptions.S) performs the deferred preemptive switch via `g_need_reschedule` + the cur/next trap-frame pointers. Verified by boot: A/B tasks interleave in runs of 13 ticks at ~50/50. (PPI 27 enabled on MP1 is the *guest's* virtual timer via the vGIC — intentional, not the HV tick.)
- **RISC-V + polarfire (Icicle Kit)**: Self-contained S-mode port under `kernel/src/arch/riscv/`. Builds clean (`make ARCH=riscv PLATFORM=polarfire`) and boots to banner + `Partition 0/1 says hello!` for `-smp 2..5` (QEMU rejects `-smp 1`: min 2 CPUs; `-bios none` is BSP-only regardless of `-smp`). Two boot modes: `-bios none` (M-mode trampoline, BSP only — verifiable today) and the HSS-payload firmware path (`UOS_BOOT=firmware` + `config/icicle_hss_payload.yaml`, releases all 5 harts + SBI timer/IPI/HSM — needs Microchip `hss-payload-generator`). Timer is gated on `__uos_has_sbi`: SBI `set_timer` under firmware, direct CLINT under `-bios none`. `-bios none` is BSP-only by construction (secondaries held in reset; `riscv_cpu_wake_secondary()` is a no-op when `__uos_has_sbi==0`); the HSS path is the intended route to real SMP + preemptive ticks.
- **ESP32 + U-Boot (em curso)**: Boot chain ROM → MCUboot @ 0x1000 → U-Boot @ slot0 → UOS. MCUboot funciona e carrega U-Boot. U-Boot inicia mas consola está garbled. Estamos a portar drivers ESP32 (cache, UART, SPI flash, partition table, timer, GPIO, WDT) e a fixar os exception vectors para o fork Xtensa. Ver `docs/esp32-boot-chain-design.md` e `docs/esp32-master-plan.md` para o plano completo de 5 fases.
When making changes, **build and boot ARMv7** to verify. Do not assume changes to shared headers compile on all three arches unless you test each.
@ -167,10 +188,44 @@ Do **not** mix Universalisos work with other PortugalFuturista projects (DocSpac
## Verification Workflow
### Always use GDB/GDB stub
Every test — QEMU or hardware — must be run with GDB attached:
**QEMU targets (ARMv7, AArch64, RISC-V):**
```bash
# Terminal 1: start QEMU with GDB stub
qemu-system-arm -M virt -cpu cortex-a15 -m 512M \
-nographic -s -S -kernel build/armv7/qemu-arm-virt/universalisos.elf
# Terminal 2: attach GDB
gdb-multiarch -ex "target remote :1234" \
-ex "hb _start" -ex "c" \
build/armv7/qemu-arm-virt/universalisos.elf
```
**ESP32 hardware (via UART GDB stub):**
```bash
# ESP32 boots, break 0,0 fires, GDB stub sends T05 on UART
# Attach with xtensa-esp32-elf-gdb:
xtensa-esp32-elf-gdb -ex "target remote /dev/ttyACM0" \
-ex "set serial baud 115200" \
microkernel/build/esp32/universalisos.elf
```
**ESP32 hardware (via JTAG, future):**
```bash
openocd -f interface/esp_usb_jtag.cfg -f target/esp32.cfg &
xtensa-esp32-elf-gdb -ex "target remote :3333" microkernel/build/esp32/universalisos.elf
```
### Build verification
There is no unit-test framework, linter, or typechecker. Verification is:
1. `make ARCH=armv7 PLATFORM=qemu-arm-virt` — must compile clean.
2. Boot in QEMU — must reach UART banner and complete demo sequence (scheduler slots, IPC, shared memory, health monitoring, preemption).
3. Check for unexpected hangs or missing demo sections in output.
2. Boot in QEMU with GDB attached — must reach UART banner and complete demo sequence (scheduler slots, IPC, shared memory, health monitoring, preemption).
3. Verify via GDB that all expected symbols are at correct addresses.
4. Check for unexpected hangs or missing demo sections in output.
If a change touches shared headers, verify it does not break `ARCH=aarch64` or `ARCH=riscv` builds as well.
If a change touches shared headers, verify it does not break `ARCH=aarch64` or `ARCH=riscv` or `TARGET=esp32` builds as well.

View file

@ -14,12 +14,14 @@
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 / ESP32-S2 / ESP32-S3 | Xtensa LX6/LX7 | **Experimental, not mainline** | 🔴 risky |
| 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.
@ -32,23 +34,26 @@ MCUboot, by contrast, **supports every Espressif target** (Xtensa + RISC-V) via
## 2. The three candidate chains
```
CHAIN A — Zephyr-proven, RECOMMENDED (all targets)
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 (RISC-V only: C3/C6/H2)
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.
Adds a full U-Boot build + a UOS load command. Heavy; only for dev/debug richness.
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 — our in-tree bootstub (already QEMU-proven, 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)
Simplest. No signature/OTA. Good for bring-up; lacks secure boot + DFU.
```
*** QEMU only. Never flash to hardware. ***
---
@ -148,31 +153,33 @@ RISC-V U-Boot port + a UOS load command.
---
## 7. Recommended strategy (phased)
## 7. Estratégia (mandatada por Maestro Fabio)
**Phase 0 — Pin the layout (do first, blocks real HW).**
**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. Keep Zephyr-compatible offsets so
we can cross-check against a known-good `west build --sysbuild` if needed.
target flash size; record `slot0` offset.
**Phase 1 — Lock Chain A for ALL targets (secure-boot baseline).**
- Build `mcuboot/boot/espressif` for the target (`-DMCUBOOT_TARGET=esp32[c3|s3|…]`).
- Package with `esptool elf2image``mcuboot.bin @ 0x1000`.
- Build UOS app; sign with `imgtool` + prepend `esp_image_load_header`
(`0xace637d3`); flash at slot0.
- Verify on QEMU (esp32/esp32s3) then real HW. Add secure-boot + flash-enc
Kconfig once baseline boots.
**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).
**Phase 2 — Keep Chain C (bootstub) as the fast QEMU/dev path.**
- It already works. Keep it for iteration; do not flash it to secure-boot HW.
**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.
**Phase 3 — Chain B (MCUboot → U-Boot → UOS) ONLY for RISC-V (C3/C6/H2).**
- Only if a U-Boot pre-OS shell is actually wanted (debug/ provisioning).
- Otherwise skip — U-Boot adds ~200 KB + a second image format for no HV gain.
**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.
**Phase 4 — SMP / APP-CPU bring-up.**
- Reuse `CONFIG_ESP_MULTI_PROCESSOR_BOOT` path (`start_cpu1_image`) — already
present in both mcuboot and our `uos_loader.h` weak hook.
**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.
---
@ -188,15 +195,17 @@ RISC-V U-Boot port + a UOS load command.
---
## 9. Open decision (blocks Phase 1/3)
## 9. Open decisions (resolvidas)
1. **Target SoC(s)** — Xtensa (ESP32/S3) and/or RISC-V (C3/C6/H2)? This decides
whether Chain B (U-Boot) is even on the table.
2. **Is U-Boot actually required**, or was "mcuboot+uboot" shorthand for
"a proper verified boot chain"? If the latter, Chain A alone satisfies it
and is the Zephyr-proven path.
3. **Secure boot / flash encryption** needed at first boot, or bring-up first
then enable?
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.
---