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.
38 KiB
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():
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:
- Verify VECBASE is correctly set before
break - Verify the vector at VECBASE+0x280 contains valid code
- Add a minimal raw UART write in the vector stub itself (before the jump)
- 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
-
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?
-
Which build target for hardware?
esp32(IRAM, simpler) oresp32hw(IROM flash-XIP, uses HAL)? -
Should I proceed with Phase 1 (proving the exception fires with debug UART writes)?
End of design document. Awaiting your review and authorization.