# UniversalisOS ESP32 Boot Chain — Design & Analysis **Status:** FOR REVIEW — no implementation changes authorized yet **Date:** 2026-07-17 **Author:** Hermes Agent (design), Fabio Coutada (review) --- ## 1. Architecture Overview ``` ┌─────────────────────────────────────────────────────────────────────┐ │ ESP32-D0WDQ6-V3 │ │ (Xtensa LX6, dual-core, 240MHz) │ │ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │ ROM Boot │───▶│ MCUboot │───▶│ Universal │───▶│ Hello │ │ │ │ (1st) │ │ (2nd) │ │ -isOS │ │ World │ │ │ │ │ │ signed │ │ µ-kernel │ │ Task │ │ │ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │ │ 0x1000- 0x1000- IRAM/DRAM Application │ │ hardware verify+load loaded by code │ │ reset flash→RAM MCUboot │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 1.1 What Each Stage Does | Stage | What | Where | Responsibility | |-------|------|-------|----------------| | **ROM Boot** | ESP32 on-chip ROM | `0x40000000` | Read strapping pins → load MCUboot from flash `0x1000` | | **MCUboot** | Signed boot loader | Flash `0x1000` | Verify ECDSA-P256 signature, load image segments to IRAM/DRAM, jump to entry | | **UniversalisOS** | Our µ-kernel | IRAM `0x40080000` | Set up VECBASE, GDB break, init hardware, start scheduler | | **Hello World** | Application | Same IRAM | Simple task that prints via UART | ### 1.2 Flash Layout ``` 0x0000_0000 ┌─────────────────────┐ │ (reserved) │ 0x0000_1000 ├─────────────────────┤ │ MCUboot (30KB) │ ← 1st-stage bootloader 0x0000_9000 ├─────────────────────┤ │ (empty) │ 0x0002_0000 ├─────────────────────┤ │ UOS signed image │ ← MCUboot application slot 0 │ (96KB) │ │ [MCUboot hdr 0x20] │ │ [ESP load hdr 96B] │ │ [IRAM code 0x78F4] │ │ [DRAM data 4B] │ 0x0003_8000 ├─────────────────────┤ │ (empty) │ └─────────────────────┘ ``` --- ## 2. Detailed Boot Flow Sequence ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ T=0ms POWER ON / RESET │ │ ─────────────────────────────── │ │ ROM Boot reads strapping pins → SPI_FAST_FLASH_BOOT (boot:0x13) │ │ ROM loads MCUboot from flash 0x1000 → IRAM │ │ ROM prints: │ │ "ets Jul 29 2019 12:21:46" │ │ "rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)" │ │ "load:0x3fff5590,len:4604" (MCUboot DRAM segment) │ │ "load:0x40078000,len:6896" (MCUboot IRAM segment) │ │ "entry 0x40078ab8" (MCUboot entry point) │ │ ROM jumps to MCUboot entry. │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ T=~50ms MCUBOOT EXECUTION │ │ ─────────────────────────────── │ │ MCUboot main() at /portugalfuturista/mcuboot/boot/espressif/main.c │ │ │ │ 1. bootloader_init() — UART0@115200, clocks, WDT │ │ 2. boot_go() — find image in primary slot (0x20000) │ │ a. Read MCUboot image header (32 bytes at 0x20000) │ │ b. Verify ECDSA-P256 signature against root-ec-p256.pem │ │ c. Return boot_rsp with image offset + header size │ │ 3. do_boot(&rsp) → start_cpu0_image() │ │ a. esp_app_image_load() — read ESP load header (96 bytes) │ │ b. For IRAM images: memcpy flash→IRAM via bootloader_mmap() │ │ - flash 0x30020 (0x20000+0x20+0x60) → IRAM 0x40080000 │ │ - size: 0x78F4 bytes │ │ c. For DRAM: memcpy flash→DRAM │ │ - → DRAM 0x3FFB0000, size: 4 bytes │ │ d. Log: "[INF] start=0x4008040c" │ │ 4. ((void(*)(void))entry_addr)() — JUMP TO 0x4008040C │ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ │ │ This is a FUNCTION CALL, not a reset. │ │ CPU state at jump: │ │ PC = 0x4008040C (_start) │ │ SP = MCUboot's stack (somewhere in DRAM) │ │ PS = MCUboot's PS (INTLEVEL=0, EXCM=0) │ │ VECBASE = MCUboot's vector table (ROM or MCUboot IRAM) │ │ a0..a15 = MCUboot's return address + scratch │ │ UART0 = configured at 115200, 8N1 │ │ Cache = enabled, flash MMU configured │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ T=~200ms UNIVERSALISOS _start (OUR CODE) │ │ ─────────────────────────────── │ │ _start is at 0x4008040C (VECBASE+0x400 in current layout) │ │ BUT: MCUboot jumped here directly, VECBASE is NOT ours yet! │ │ │ │ Current startup.S sequence: │ │ 1. l32r a2, __vectors_base → a2 = 0x4008000C │ │ 2. wsr a2, VECBASE → VECBASE = 0x4008000C │ │ 3. isync → flush pipeline │ │ 4. l32r a1, 0x3FFD0000 → set stack pointer │ │ 5. rsil a2, 5 → disable IRQs (INTLEVEL=5) │ │ 6. wsr 0, ICOUNT → clear step counter │ │ 7. wsr 0, ICOUNTLEVEL → clear step level │ │ 8. break 0,0 → TRIGGER DEBUG EXCEPTION │ │ ^^^^^^^^^ │ │ EPC6 ← 0x40080423 (address of break instruction) │ │ EPS6 ← current PS │ │ CPU jumps to VECBASE+0x280 │ │ │ │ ⚠️ PROBLEM IDENTIFIED: │ │ VECBASE was set in step 2 to 0x4008000C, but then isync in step 3 │ │ takes effect. The break in step 8 SHOULD work because INTLEVEL=5 │ │ is below debug level 6. However, the debug vector at 0x4008028C │ │ must contain valid code — let me trace this. │ │ │ │ VECBASE+0x280 = 0x4008000C + 0x280 = 0x4008028C │ │ At 0x4008028C: wsr a0, EXCSAVE6; j _uos_gdbstub_debug_entry │ │ This SHOULD work. But see Section 3 for the actual problem. │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ DEBUG EXCEPTION HANDLER │ │ ─────────────────────────────── │ │ Vector at VECBASE+0x280: │ │ wsr a0, 214 (EXCSAVE6) → save interrupted a0 │ │ j _uos_gdbstub_debug_entry │ │ │ │ _uos_gdbstub_debug_entry (entry.S): │ │ 1. addi sp, sp, -112 → allocate frame │ │ 2. rsr a0, EXCSAVE6 → recover original a0 │ │ 3. s32i a0, sp, FRAME_A0 → save to frame │ │ 4. Save a1..a15 to frame │ │ 5. Save EPC6, EPS6, SAR, etc. to frame │ │ 6. Set PS = INTLEVEL(1) │ │ 7. call0 _uos_gdbstub_handler │ │ → Sends $T05 packet over UART0 │ │ → Enters GDB command loop │ │ → Returns on 'c' (continue) │ │ 8. Restore EPC6/EPS6 from frame │ │ 9. Restore a2..a15 │ │ 10. Restore a0 │ │ 11. addi sp, sp, +112 → deallocate frame │ │ 12. rsync → flush │ │ 13. rfi 6 → return from debug exception │ │ │ │ ⚠️ PROBLEM: EPC6 points AT the break instruction (0x40080423). │ │ After rfi 6, the CPU re-executes the break → infinite loop! │ │ The handler must advance PC by 3 (break = 3 bytes) before returning. │ │ This was partially addressed in the C handler, but may not work │ │ if the exception isn't even firing (see Section 3). │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ AFTER GDB CONTINUE — esp32_app_entry() │ │ ─────────────────────────────── │ │ 1. uos_gdbstub_init() │ │ 2. esp32_hw_init() │ │ 3. puts("=== UniversalisOS ESP32 ===") │ │ 4. main() → scheduler start → Hello World task │ └─────────────────────────────────────────────────────────────────────────┘ ``` --- ## 3. Problems Identified — Root Cause Analysis ### 3.1 Problem: `0x80` Garbage on UART / No GDB Response **Symptom:** After MCUboot logs `start=0x4008040c`, the UART shows `0x80` repeated patterns (framing errors) or complete silence. GDB cannot attach. **Root Cause Analysis:** There are **three candidate root causes**. All must be resolved: #### RC-1: VECBASE Alignment ``` Current layout: .vectors section starts at 0x40080000 (_vectors_start) __vectors_base label is at 0x4008000C (12 bytes into section) _start is at 0x4008040C VECBASE is set to 0x4008000C (value of __vectors_base) Debug vector at VECBASE+0x280 = 0x4008028C ✓ (code is there) BUT: Xtensa requires VECBASE to be aligned to a specific boundary. The Xtensa ISA says VECBASE must be aligned per XCHAL_VECBASE_RESET_VADDR. On ESP32, the reset VECBASE is 0x40000000 (ROM). Question: Does our 0x4008000C meet alignment requirements? Answer: VECBASE has NO alignment requirement per the ISA — any address works as long as the vectors are at the correct offsets from it. So this is NOT the problem. ``` #### RC-2: The `break 0,0` + `rfi 6` Re-execution Loop ``` break 0,0 at address 0x40080423 → EPC6 = 0x40080423 (points AT the break, not after it) → Debug exception fires → Handler sends T05 (if it gets that far) → GDB sends 'continue' → Handler returns via rfi 6 → CPU resumes at EPC6 = 0x40080423 → break 0,0 fires AGAIN → infinite loop FIX REQUIRED: Handler must increment frame->pc by 3 (size of break instruction) before returning, when DEBUGCAUSE bit 3 (BREAK) is set. ``` #### RC-3: MCUboot's State vs. Our Expectations ``` MCUboot calls our entry as: ((void(*)(void))entry_addr)() At this point: - PS.INTLEVEL is probably 0 (MCUboot enables interrupts) - PS.EXCM = 0 - VECBASE points to MCUboot's vectors (in IRAM at ~0x4007xxxx) - a0 = return address to MCUboot (but we never return) - The stack is MCUboot's stack - UART0 is configured and working - Flash cache is enabled Our _start immediately overwrites VECBASE. But: - The vector table is in our IRAM at 0x40080000 - Our code was memcpy'd there by MCUboot - So the vector table IS in RAM and IS accessible The break instruction should fire because: - XCHAL_HAVE_DEBUG = 1 (debug is always present on ESP32) - PS.INTLEVEL = 5 (set by rsil) < XCHAL_DEBUGLEVEL (6) - 'break' is not masked by INTLEVEL So the break SHOULD fire. The question is: does the handler work? ``` ### 3.2 The `0x80` Pattern Explained ``` 0x80 = 128 = 0b10000000 In UART 8N1 format: Start bit (0) + 8 data bits + stop bit (1) 0x80 = 0 00000001 1 (LSB first) = line goes low for 7 bit-times This pattern occurs when: 1. The TX FIFO is being written faster than the UART can transmit (120ns per byte at 115200 baud = ~87µs per byte) 2. The break exception is re-firing before the UART can send a complete byte 3. Each re-entry to the handler writes to the FIFO, but the previous write hasn't completed, corrupting the transmission This confirms the infinite break loop (RC-2). ``` --- ## 4. Memory Map (Complete) ### 4.1 ESP32-D0WDQ6-V3 Address Space ``` 0x0000_0000 ┌──────────────────────────────────┐ │ [unmapped / flash direct] │ 0x3F40_0000 ├──────────────────────────────────┤ │ DROM0 (flash data, cached) │ 4MB GDB-readable ✓ 0x3F80_0000 ├──────────────────────────────────┤ │ EXTRAM / PSRAM (cached) │ 4MB If populated 0x3FF8_0000 ├──────────────────────────────────┤ │ RTC FAST MEM (data alias) │ 8KB 0x3FF9_0000 ├──────────────────────────────────┤ │ Internal SRAM (byte-accessible) │ 0x3FFA_E000 ├──────────────────────────────────┤ │ DRAM (.data, .bss, stack) │ ~328KB GDB-readable ✓ │ ► UOS: 0x3FFB0000..0x3FFC0000 │ (our 64KB) 0x3FFF_E000 ├──────────────────────────────────┤ │ D/IRAM alias (byte-swapped) │ 128KB 0x4000_0000 ├──────────────────────────────────┤ │ IROM_MASK (ROM cache area) │ 448KB 0x4007_0000 ├──────────────────────────────────┤ │ CACHE_PRO SRAM │ 32KB 0x4007_8000 ├──────────────────────────────────┤ │ CACHE_APP SRAM │ 32KB 0x4008_0000 ├──────────────────────────────────┤ ◄── OUR CODE HERE │ IRAM0 (.text, .vectors) │ 128KB GDB-readable ✓ │ ► UOS: 0x40080000..0x400A0000 │ 0x400A_0000 ├──────────────────────────────────┤ │ IRAM1 (more SRAM) │ 128KB 0x400C_0000 ├──────────────────────────────────┤ │ RTC FAST MEM (instruction alias) │ 8KB 0x400D_0000 ├──────────────────────────────────┤ │ IROM0 (flash instruction, cached)│ ~3MB GDB-readable ✓ 0x4040_0000 ├──────────────────────────────────┤ │ [reserved IROM] │ 0x4FFF_FFFF └──────────────────────────────────┘ 0x3FF0_0000 ┌──────────────────────────────────┐ Peripherals (APB) │ DPORT (interrupts, cache ctrl) │ 0x3FF1_0000 ├──────────────────────────────────┤ MMU tables 0x3FF4_0000 ├──────────────────────────────────┤ UART0 ← GDB transport 0x3FF5_0000 ├──────────────────────────────────┤ GPIO, SPI, Timer, etc. 0x3FF6_F000 └──────────────────────────────────┘ 0x5000_0000 ┌──────────────────────────────────┐ RTC SLOW MEM │ (deep sleep persistence) │ 8KB GDB-readable ✓ 0x5000_2000 └──────────────────────────────────┘ 0x6000_0000 ┌──────────────────────────────────┐ AHB alias │ UART0 TX FIFO (AHB) │ Write-only 0x6000_FFFF └──────────────────────────────────┘ ``` ### 4.2 Xtensa Vector Table (VECBASE-relative) ``` Offset Vector Used by UOS? ────── ─────────────────── ──────────── 0x000 Window overflow 4 No (CALL0) 0x040 Window underflow 4 No 0x080 Window overflow 8 No 0x0C0 Window underflow 8 No 0x100 Window overflow 12 No 0x140 Window underflow 12 No 0x180 Level 2 interrupt No (reserved) 0x1C0 Level 3 interrupt No 0x200 Level 4 interrupt No 0x240 Level 5 interrupt No (reserved for WDT panic) 0x280 Level 6 / DEBUG YES ← GDB stub entry 0x2C0 NMI (level 7) No 0x300 Kernel exception No (unhandled = crash) 0x340 User exception No (unhandled = crash) 0x3C0 Double exception No (fatal) 0x400 Reset / _start YES ← Entry point ``` --- ## 5. GDB Stub Architecture ### 5.1 Component Diagram ``` ┌─────────────────────────────────────────────────────────────────┐ │ GDB Stub Subsystem │ │ │ │ ┌─────────────┐ ┌──────────────┐ ┌─────────────────────┐ │ │ │ startup.S │ │ entry.S │ │ uos_gdbstub.c │ │ │ │ │ │ │ │ │ │ │ │ • Set VECBASE│ │ • Debug exc │ │ • Packet I/O (RSP) │ │ │ │ • Set SP │ │ save/restore│ │ • Command dispatch │ │ │ │ • rsil 5 │ │ • Frame alloc│ │ • Reg file (g/G) │ │ │ │ • break 0,0 │ │ • Call C hndr│ │ • Memory (m/M/X) │ │ │ │ • call app │ │ • rfi 6 │ │ • Breakpoints (Z/z) │ │ │ └──────┬──────┘ └──────┬───────┘ │ • Step (ICOUNT) │ │ │ │ │ │ • Continue │ │ │ │ │ └─────────┬───────────┘ │ │ │ VECBASE+0x280│ │ │ │ │ ┌────────────┘ │ │ │ ▼ ▼ │ │ │ ┌──────────────────┐ ┌───────────┴───────────┐ │ │ │ Vector Table │ │ UART0 Transport │ │ │ │ (in IRAM) │ │ │ │ │ │ 0x280: wsr a0, │ │ • putc: wait TX FIFO │ │ │ │ EXCSAVE6 │ │ • getc: wait RX FIFO │ │ │ │ j handler │ │ • APB 0x3FF40000 │ │ │ │ │ │ • STATUS 0x3FF4001C │ │ │ │ 0x400: _start │ │ • 115200, 8N1 │ │ │ └──────────────────┘ └───────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ Xtensa Debug Registers │ │ │ │ │ │ │ │ EPC6 (SR 182) ← interrupted PC │ │ │ │ EPS6 (SR 198) ← interrupted PS │ │ │ │ EXCSAVE6 (SR 214) ← saved a0 │ │ │ │ DEBUGCAUSE (SR 233): │ │ │ │ bit 0: ICOUNT overflow │ │ │ │ bit 1: IBREAK match │ │ │ │ bit 2: DBREAK match │ │ │ │ bit 3: BREAK instruction ← our break 0,0 │ │ │ │ bit 4: BREAK.N instruction │ │ │ │ bit 5: DEBUGINT (external OCD) │ │ │ │ ICOUNT (SR 236) ← instruction counter for step │ │ │ │ ICOUNTLEVEL (SR 237) ← step trigger level │ │ │ │ IBREAKA0 (SR 128) ← hardware breakpoint addr 0 │ │ │ │ IBREAKA1 (SR 129) ← hardware breakpoint addr 1 │ │ │ │ IBREAKENABLE (SR 96) ← breakpoint enable bitmask │ │ │ └───────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 5.2 Debug Exception Entry Sequence ``` break 0,0 fires at PC=0x40080423 │ ▼ ┌─────────────────────────────────────────────────┐ │ HARDWARE (automatic, invisible to software): │ │ 1. PS ← EPS6 (INTLEVEL=6, EXCM=1) │ │ 2. PC of next instruction ← EPC6 (= break addr) │ │ 3. Jump to VECBASE + 0x280 │ └──────────────────────┬──────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────┐ │ VECTOR STUB (at VECBASE+0x280): │ │ │ │ wsr a0, EXCSAVE6 ; a0 is only free register │ │ j _uos_gdbstub_debug_entry │ └──────────────────────┬──────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────┐ │ _uos_gdbstub_debug_entry (entry.S): │ │ │ │ ; ---- SAVE CONTEXT ---- │ │ addi sp, sp, -112 ; allocate frame │ │ rsr a0, EXCSAVE6 ; get original a0 │ │ s32i a0, sp, FRAME_A0(12) ; save a0 │ │ addi a0, sp, 112 ; original sp │ │ s32i a0, sp, FRAME_A1(16) ; save sp │ │ s32i a2..a15, sp, ... ; save all regs │ │ rsr a0, EPC6 ; interrupted PC │ │ s32i a0, sp, FRAME_PC(4) │ │ rsr a0, EPS6 ; interrupted PS │ │ s32i a0, sp, FRAME_PS(8) │ │ rsr a0, SAR/EXCCAUSE/EXCVADDR/LBEG/LEND/LCOUNT│ │ s32i a0, sp, ... │ │ │ │ ; ---- CALL HANDLER ---- │ │ movi a0, 0x01 │ │ wsr a0, PS ; INTLEVEL=1 │ │ rsync │ │ mov a2, sp ; frame ptr as arg │ │ call0 _uos_gdbstub_handler │ │ │ │ ; ---- RESTORE CONTEXT ---- │ │ ; (reverse order: special regs, then GPRs) │ │ l32i a0, sp, FRAME_LCOUNT → wsr LCOUNT │ │ l32i a0, sp, FRAME_LEND → wsr LEND │ │ l32i a0, sp, FRAME_LBEG → wsr LBEG │ │ l32i a0, sp, FRAME_SAR → wsr SAR │ │ l32i a0, sp, FRAME_PS → wsr EPS6 │ │ l32i a0, sp, FRAME_PC → wsr EPC6 │ │ l32i a2..a15, sp, ... │ │ l32i a0, sp, FRAME_A0 ; restore a0 │ │ addi sp, sp, 112 ; dealloc frame │ │ rsync │ │ rfi 6 ; return from debug │ └─────────────────────────────────────────────────┘ ``` ### 5.3 Frame Layout (C struct ↔ Assembly offsets) ``` Offset Field C type Assembly macro ────── ─────── ──────── ────────────── +0 exit uint32_t FRAME_EXIT +4 pc uint32_t FRAME_PC ← from EPC6 +8 ps uint32_t FRAME_PS ← from EPS6 +12 a0 uint32_t FRAME_A0 ← from EXCSAVE6 +16 a1 (sp) uint32_t FRAME_A1 +20 a2 uint32_t FRAME_A2 +24 a3 uint32_t FRAME_A3 ... ... ... ... +72 a15 uint32_t FRAME_A15 +76 sar uint32_t FRAME_SAR +80 exccause uint32_t FRAME_EXCCAUSE +84 excvaddr uint32_t FRAME_EXCVADDR +88 lbeg uint32_t FRAME_LBEG +92 lend uint32_t FRAME_LEND +96 lcount uint32_t FRAME_LCOUNT ────── Total: 100 bytes data + 12 pad = 112 bytes allocated Frame MUST be 16-byte aligned (Xtensa ABI requirement). ``` ### 5.4 GDB RSP Protocol Flow ``` ESP32 (stub) Host (xtensa-esp32-elf-gdb) ───────────── ────────────────────────── ← attach ← ← $qSupported#xx ← $PacketSize=... → ← $Hg0#xx ← (set thread) $OK → ← $?#xx ← (stop reason) $T05 → (SIGTRAP) ← $g#xx ← (read all regs) $<74 hex words> → ← $m,#xx ← (read memory) $ → ← $Z0,,#xx ← (set breakpoint) $OK → ← $c#xx ← (continue) (execution resumes) ``` --- ## 6. Issues To Resolve (Before Implementation) ### 6.1 CRITICAL: Break Instruction Re-execution **Problem:** `break 0,0` at address X. EPC6 = X. After handler + `rfi 6`, CPU returns to X → re-executes break → infinite loop. **Required Fix:** In `_uos_gdbstub_handler()`: ```c uint32_t cause; rsr(cause, DEBUGCAUSE); if (cause & (1<<3)) { // BREAK bit frame->pc += 3; // skip 3-byte break instruction } ``` **Risk:** This was added to the C code but may not execute if the exception handler itself crashes before reaching the C handler. ### 6.2 CRITICAL: UART Transport Verification **Problem:** The debug 'H' character written from assembly entry.S was NOT observed on UART. This means either: - The debug exception is not firing at all - The exception fires but the handler crashes before reaching UART writes - The UART FIFO write doesn't work in the exception context **Required Investigation:** 1. Verify VECBASE is correctly set before `break` 2. Verify the vector at VECBASE+0x280 contains valid code 3. Add a minimal raw UART write in the vector stub itself (before the jump) 4. Check if MCUboot left the UART in a state where writes work ### 6.3 MEDIUM: Stack Pointer at Exception Entry **Problem:** MCUboot calls our entry as a function. The stack pointer is MCUboot's stack. Our `_start` overwrites SP to `0x3FFD0000`. But the debug exception uses the *current* SP for the frame. If SP is invalid when the break fires, the frame save will crash. **Current state:** SP is set to `0x3FFD0000` before the break. This is in DRAM range (`0x3FFB0000..0x3FFC0000` is our DRAM segment, but `0x3FFD0000` is ABOVE our segment!). Wait — the linker says `DRAM: 0x3FFB0000, LENGTH=128K` → `0x3FFB0000..0x3FFD0000`. So `0x3FFD0000` is the TOP of our DRAM segment. Stack grows downward, so SP=`0x3FFD0000` means the first push goes to `0x3FFCFFF0`. That's valid. ### 6.4 MEDIUM: Two Build Targets with Different Linker Scripts **Problem:** `TARGET=esp32` uses `linker.ld` (IRAM at `0x40080000`). `TARGET=esp32hw` uses `linker_flash.ld` (IROM at `0x40820000`, flash-XIP). The vector table and VECBASE differ between them. Only `TARGET=esp32` is being tested. ### 6.5 LOW: Watchdog Timer MCUboot may leave hardware watchdogs enabled. If the GDB session takes longer than the WDT timeout, the chip resets. The handler should disable WDTs. --- ## 7. Proposed Implementation Plan (For Your Authorization) ``` Phase 1: Prove the debug exception fires ───────────────────────────────────────── 1a. Add a raw UART write to the vector stub (before the jump to handler): VECBASE+0x280: wsr a0, EXCSAVE6 ; DEBUG: write 'D' to UART0 FIFO movi a1, 0x3FF40000 movi a2, 0x44 ; 'D' s32i a2, a1, 0 movi a1, 0 ; restore a1 (will be overwritten by handler anyway) j _uos_gdbstub_debug_entry If 'D' appears on UART → exception fires, vector is correct. If not → VECBASE or break is broken. 1b. If 'D' doesn't appear, add the raw write BEFORE the break: _start: ... set VECBASE, SP ... ; DEBUG: write 'S' to UART0 movi a2, 0x3FF40000 movi a3, 0x53 ; 'S' s32i a3, a2, 0 break 0,0 If 'S' appears but 'D' doesn't → VECBASE/vector issue. Phase 2: Fix break re-execution ─────────────────────────────── 2a. Read DEBUGCAUSE in the .S entry, save to frame. 2b. In C handler, if BREAK bit set, advance frame->pc by 3. Phase 3: Verify GDB attach ────────────────────────── 3a. Flash + reset + attach GDB. 3b. Verify $T05 response. 3c. Verify register read, memory read, continue. Phase 4: Watchdog disable ───────────────────────── 4a. Disable TIMG0 WDT and RTC WDT in _start or handler entry. ``` --- ## 8. File Inventory (Current State) ``` microkernel/ports/esp32/ ├── startup.S ← Vector table + _start + break ├── uos_gdbstub.h ← Frame/regfile structs, SR numbers ├── uos_gdbstub.c ← RSP protocol (g/G/m/M/Z/z/vCont/etc.) ├── uos_gdbstub_entry.S ← Debug exception save/restore ├── esp32_integration.c ← App entry (after GDB continue) ├── linker.ld ← IRAM layout (TARGET=esp32) ├── linker_flash.ld ← IROM flash-XIP layout (TARGET=esp32hw) ├── uos_hal.c ← UART/Timer/WDT raw register access └── sdkconfig.h ← ESP32 CONFIG_* macros ``` --- ## 9. Open Questions for You 1. **Where is U-Boot in this chain?** You mentioned MCUboot (1st stage) + U-Boot (2nd stage) + UniversalisOS. But the current flash layout only has MCUboot → UOS directly. Is U-Boot supposed to be between them? 2. **Which build target for hardware?** `esp32` (IRAM, simpler) or `esp32hw` (IROM flash-XIP, uses HAL)? 3. **Should I proceed with Phase 1** (proving the exception fires with debug UART writes)? --- *End of design document. Awaiting your review and authorization.*