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

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:

  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=128K0x3FFB0000..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.