# UniversalisOS ESP32 — Master Implementation Plan **Status:** MCUboot + U-Boot chain working (2026-07-16) **Target:** ESP32-D0WDQ6-V3, Xtensa LX6, 4MB flash ## 1. Current State ### 1.1 Working - ROM → MCUboot @ 0x1000 (image validation, OTA slots) - MCUboot flash MMU: `cache_flash_mmu_set` → IROM @ 0x40800000 (rc=0) - MCUboot → U-Boot handoff: jumps to `_start`, prints `X\nW\nY\nE\n[UOS2\n` - U-Boot banner: "U-Boot 2025.04..." appears - DRAM loading via D-bus (0x3FFAF000+) - U-Boot IROM linker script at 0x40800000+ - Wrapper tool for ELF→MCUboot image format ### 1.2 Broken / Incomplete - **U-Boot console garbled** after banner — can't interact - **IROM data loads (l32r) fail** — D-bus has no path to I-bus addresses - **Exception vectors discarded** in U-Boot linker — faults go to ROM - **U-Boot init functions reset cache/MMU** — IROM mapping destroyed after board_early_init_f - **No DRAM loader for secondary images** (UOS apps) - **No partition table parser** in U-Boot ## 2. Architecture: Three-Layer Boot Chain ``` Flash Layout (4MB ESP32): ┌─────────────┬───────┬──────────────────────────────────┐ │ 0x0000 │ 4KB │ (reserved) │ │ 0x1000 │ 30KB │ MCUboot (1st stage bootloader) │ │ 0x8000 │ 4KB │ Partition Table (32B × 96) │ │ 0x9000 │ 128KB │ OTA Data (boot state) │ │ 0x20000 │ 1.3MB │ U-Boot (primary slot) │ │ 0x170000 │ 1.5MB │ UOS App (secondary slot / app) │ │ 0x300000 │ 1MB │ UOS Data / NVS / scratch │ └─────────────┴───────┴──────────────────────────────────┘ Boot Flow: ROM → MCUboot → U-Boot → UOS microkernel → FreeRTOS/Apps ``` ### 2.1 Layer 1: MCUboot (Bootloader) **Responsibilities:** - Image validation (SHA256 via imgtool TLV) - Flash MMU mapping for U-Boot IROM code - DRAM loading for U-Boot data - OTA slot management (primary/secondary swap) - Flash encryption / secure boot (future) **Current status:** ✅ Working. Needs: - [ ] Fix IROM bus mask in DPORT register (done, but U-Boot resets it) - [ ] Add DROM mapping alongside IROM (for l32r data loads) - [ ] Partition table reading (currently uses hardcoded slot offsets) ### 2.2 Layer 2: U-Boot (Hardware Init + OS Loader) **U-Boot's job on ESP32 (mirrors STM32/ARM U-Boot):** | Function | ARM U-Boot (STM32) | ESP32 U-Boot (target) | |----------|-------------------|----------------------| | Console | UART via DM_SERIAL | ✅ DM_SERIAL enabled, 🔴 garbled | | DRAM init | UCLASS_RAM driver | 🔴 Need SRAM sizing | | Clock init | rcc_clock_setup() | 🔴 Need PLL/APB config | | Flash driver | SPI NOR via DM_SPI_FLASH | 🔴 Need esp_flash driver | | Partition table | GPT/DOS parser | 🔴 Need ESP32 partition table parser | | Environment | ENV_IS_IN_SPI_FLASH | 🔴 Need ESP32 env backend | | Network | ETH/PHY driver | N/A (ESP32 has WiFi) | | Boot commands | bootm, bootelf, go | ✅ go works (jumps) | | Exception vectors | ARM vectors at 0x00 | 🔴 Discarded — need IROM vectors | | Cache control | icache_enable/dcache | 🔴 Need cache_hal wrappers | | Timer | ARM systick | 🔴 Need CCOUNT driver | | Watchdog | IWDG driver | 🔴 Need ESP32 WDT driver | | GPIO | DM_GPIO driver | 🔴 Need ESP32 GPIO driver | | Device Tree | FDT blob | 🔴 No DTS for ESP32 | ### 2.3 Layer 3: UOS Microkernel (RTOS) **UOS runs as a U-Boot application (loaded via bootelf/bootm):** UOS provides: - Preemptive scheduler (tick via CCOMPARE0) - IPC (message queues, semaphores) - Memory protection (MPU-based, Tier 1) - Health monitoring (heartbeat, watchdog) - Device framework (UART, SPI, I2C, GPIO) - Capability MDB (security model) **Current UOS ESP32 port has:** - UART0 init (polled TX) - Timer init (CCOMPARE0, tick via CCOUNT) - Interrupt controller (INTC_CPU_INT_ENA) - Startup sequence (esp32_app_entry) - Stack in DRAM, vectors in IRAM (old layout) - Exception handlers (user/kernel/double/NMI) ## 3. Implementation Plan — 5 Phases ### Phase 1: Stabilize U-Boot (Weeks 1-2) **Goal:** U-Boot console working, can type commands. | Task | Effort | Depends on | |------|--------|-----------| | 1.1 Implement `cache_ll_l1_enable_bus` from esp-hal-3rdparty HAL | 1d | — | | 1.2 Fix U-Boot init sequence: NO cache/MMU reset during boot | 1d | 1.1 | | 1.3 Fix IROM data loads: add DROM mapping for U-Boot IROM region | 1d | 1.1 | | 1.4 Fix U-Boot DM_SERIAL: proper UART0 config matching MCUboot | 1d | — | | 1.5 Implement exception vectors in IROM linker (xtensa_vectors.S) | 2d | 1.1 | | 1.6 Fix U-Boot env to RAM (ENV_IS_NOWHERE → ENV_IS_IN_SPI_FLASH) | 1d | 1.2 | | 1.7 Implement CCOUNT timer for U-Boot delay/get_timer | 1d | — | **Deliverable:** U-Boot prompt working, `help` shows commands. ### Phase 2: U-Boot Hardware Drivers (Weeks 3-4) **Goal:** U-Boot can access flash, load images, manage partitions. | Task | Effort | Depends on | |------|--------|-----------| | 2.1 Port ESP32 partition table parser from gen_esp32part.py to C | 2d | — | | 2.2 Implement `esp_flash_read` via SPI1 ROM functions | 1d | 1.1 | | 2.3 Implement DM_SPI_FLASH driver for ESP32 | 2d | 2.2 | | 2.4 Implement ENV in SPI flash backend (CONFIG_ENV_IS_IN_SPI_FLASH) | 2d | 2.2, 2.1 | | 2.5 Implement ESP32 WDT driver (disable/feed/config) | 1d | — | | 2.6 Implement basic GPIO driver | 1d | — | | 2.7 SDRAM sizing: detect SRAM0/SRAM1/SRAM2 at runtime | 1d | — | | 2.8 PLL/clock init: read from ROM defaults, expose APB freq | 1d | — | **Deliverable:** U-Boot can `fatload`/`ext4load` from flash, setenv/saveenv, reset. ### Phase 3: U-Boot OS Loader (Week 5) **Goal:** U-Boot can load and boot UOS microkernel images. | Task | Effort | Depends on | |------|--------|-----------| | 3.1 Implement `bootelf` for Xtensa ELF loading | 2d | 2.2 | | 3.2 Flash MMU mapping in U-Boot (add pages for UOS app) | 1d | 1.1 | | 3.3 DRAM loading for UOS .data/.bss | 1d | 2.2 | | 3.4 Auto-boot: CONFIG_BOOTCOMMAND → load UOS from partition | 1d | 3.1, 2.1 | | 3.5 Boot script support (boot.scr or bootcmd) | 1d | 2.4 | **Deliverable:** U-Boot boots UOS microkernel automatically. ### Phase 4: UOS Microkernel ESP32 Port (Weeks 6-8) **Goal:** UOS microkernel running on ESP32, loaded by U-Boot. | Task | Effort | Depends on | |------|--------|-----------| | 4.1 Update UOS linker to IROM (0x40800000+) + DRAM (0x3FFB0000+) | 1d | — | | 4.2 Port ESP32 exception vectors from esp-hal-3rdparty xtensa_vectors.S | 3d | — | | 4.3 Implement UOS port layer (uos_port.h API): | | | | 4.3.1 — tick_init (CCOMPARE0) | 1d | — | | 4.3.2 — context_switch (save/restore a0-a15, PS, EPC) | 2d | — | | 4.3.3 — task_stack_init | 1d | — | | 4.3.4 — critical_enter/exit (rsil) | 0.5d | — | | 4.3.5 — idle (waiti) | 0.5d | — | | 4.4 ESP32 UART driver (polled + interrupt) | 2d | 4.3 | | 4.5 ESP32 GPIO driver | 1d | 4.3 | | 4.6 ESP32 timer/WDT driver | 1d | 4.3 | | 4.7 Test: two tasks printing messages via UOS scheduler | 1d | 4.3 | | 4.8 Test: IPC between two tasks (message queue) | 1d | 4.7 | **Deliverable:** UOS scheduler running, tasks printing, IPC working. ### Phase 5: FreeRTOS/App Compatibility (Weeks 9-10) **Goal:** Run existing ESP-IDF/FreeRTOS apps on UOS. | Task | Effort | Depends on | |------|--------|-----------| | 5.1 FreeRTOS-to-UOS syscall shim (xTaskCreate→uos_task_create, etc.) | 3d | 4.3 | | 5.2 ESP-IDF peripheral driver shim (spi_flash, gpio, uart, i2c) | 3d | 4.4-4.6 | | 5.3 WiFi/BT stack integration (if desired) | 10d+ | Phase 4 | | 5.4 OTA update via MCUboot from UOS | 2d | 2.1 | | 5.5 Test: ESP-IDF blinky/hello_world on UOS | 1d | 5.1 | ## 4. Key Technical Decisions ### 4.1 IROM vs IRAM for Code ESP32 D-bus cannot write to I-bus addresses (0x40000000+). All executable code MUST be flash-MMU-mapped to IROM (0x40800000+), never memcpy'd to IRAM. Data (`.data`/`.bss`) lives in DRAM (0x3FFxxxxx) and is loaded via memcpy. ### 4.2 Flash MMU Architecture The ESP32 flash MMU maps 64KB-aligned flash pages to IROM/DROM virtual addresses: - **IROM0 entries (192-255):** I-bus for instruction fetch - **DROM0 entries (0-63):** D-bus for data loads For `l32r` (PC-relative data load) to work from IROM code, a DROM entry must map the same flash page to a DROM virtual address. The compiler generates `l32r` even with `-mtext-section-literals`. Solution: map each IROM page ALSO as a DROM page at a corresponding 0x3F4xxxxx address. The linker must ensure literals are referenced via DROM, not IROM. **Alternative:** Use only `movi.n` + `slli` sequences (no `l32r`) for early boot code. U-Boot startup does this. For general C code, use the DROM mapping approach. ### 4.3 Cache Bus Masks DPORT_PRO_CACHE_CTRL1_REG (0x3FF00044) bits: - Bit 0: DROM0 mask (1=masked) - Bit 1: DROM1 mask - Bit 2: IROM0 mask ← Must be CLEAR - Bit 3: IROM1 mask ← Must be CLEAR MCUboot clears these before jumping to U-Boot. U-Boot's init sequence must PRESERVE them. ### 4.4 Exception Vectors at VECBASE VECBASE = 0x40800000 (IROM0 base). Vector offsets: - 0x040: UserException - 0x100: KernelException - 0x180: Level2 interrupt - 0x1C0: Level3 interrupt - 0x200: Level4 interrupt - 0x240: Level5 interrupt - 0x280: Level6 interrupt - 0x2C0: NMI - 0x3C0: DoubleException Must use Espressif's `xtensa_vectors.S` (from esp-hal-3rdparty) or equivalent. Currently U-Boot discards these in the linker script. ### 4.5 ESP32 Partition Table Format ``` [esp_partition_info_t] × N (N ≤ 96) Each 32 bytes: uint16 magic = 0x50AA uint8 type (APP=0, DATA=1, BOOT=2) uint8 subtype (FACTORY=0, OTA_0=0x10, ...) uint32 offset (flash byte offset) uint32 size (flash byte size) char label[16] uint32 flags Terminator: magic = 0xFFFF ``` U-Boot needs a parser for this. The structure is simple enough to implement in ~200 lines of C. ## 5. Files to Create / Modify ### U-Boot (`universalis-uboot/`) ``` New files: arch/xtensa/cpu/cache.c — cache_hal wrapper arch/xtensa/cpu/ccompare_timer.c — CCOUNT-based timer arch/xtensa/lib/board.c — board_init_f/r board/espressif/esp32/partition.c — partition table parser drivers/mtd/spi/esp32_flash.c — SPI flash driver drivers/serial/esp32_uart.c — UART driver drivers/gpio/esp32_gpio.c — GPIO driver drivers/watchdog/esp32_wdt.c — WDT driver include/configs/esp32.h — update CFG_* macros arch/xtensa/dts/esp32.dts — Device Tree Modified: board/espressif/esp32/u-boot.lds — add vector sections (DROM mapped) arch/xtensa/include/asm/arch-esp32/core.h — add XCHAL_* from official core-isa.h configs/esp32_defconfig — enable DM_SPI_FLASH, ENV_IN_SPI_FLASH, etc. board/espressif/esp32/esp32.c — full board_init ``` ### UOS Microkernel (`universalisos/microkernel/`) ``` New files: ports/esp32/uos_port_asm.S — context switch in asm ports/esp32/uos_port_tick.c — CCOMPARE0 tick init ports/esp32/uos_port_uart.c — UART driver ports/esp32/uos_port_gpio.c — GPIO driver ports/esp32/uos_port_wdt.c — WDT driver ports/esp32/uos_vectors.S — exception handlers (from xtensa_vectors.S) Modified: ports/esp32/linker_flash.ld — IROM 0x40800000 + DRAM layout ports/esp32/startup.S — VECBASE, vectors from IROM ports/esp32/esp32_integration.c — full HW init ``` ## 6. Immediate Next Actions 1. **Fix U-Boot console** (Phase 1.4): Verify DM_SERIAL UART0 config. The garbled output after banner suggests baud rate mismatch or FIFO corruption between U-Boot init and console driver. 2. **Implement ESP32 cache_ll wrapper** (Phase 1.1-1.2): Copy `cache_ll_l1_enable_bus` from `esp-hal-3rdparty/components/hal/esp32/include/hal/cache_ll.h` into U-Boot. 3. **Preserve MMU state** (Phase 1.2): Audit U-Boot init sequence for any Cache_Read_Disable/Cache_Flush calls that would reset the MMU. Disable them for ESP32. 4. **Fix IROM data loads** (Phase 1.3): Map DROM entry alongside IROM entry in MCUboot.