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.
647 lines
38 KiB
Markdown
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.*
|