universalisos/docs/esp32-boot-chain-design.md
Fábio Coutada 98ed638f3c WIP: emergency commit — ESP32 GDB stub, boot chain work, docs, AGENTS.md rules
All uncommitted work from ESP32 GDB stub development session.
Includes:
- GDB stub (uos_gdbstub.c/.h/_entry.S)
- Startup vector table rewrite
- ESP32 HAL integration files
- Boot chain design docs
- AGENTS.md absolute rules (commit before refactor, no unauthorized changes)
- All prior deepseek session work

This commit prevents further data loss. No claims of correctness.
2026-07-17 01:36:58 +01:00

647 lines
38 KiB
Markdown

# 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<addr>,<len>#xx ← (read memory)
$<hex bytes> →
← $Z0,<addr>,<kind>#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.*