# Xtensa/ESP32 Integration Audit — UniversalisOS ## ESP-IDF Boot Sequence (Complete) ``` ┌─────────────────────────────────────────────────────────────┐ │ 1. ROM Bootloader (0x40000400) │ │ - Reads flash header │ │ - Loads 2nd stage bootloader to IRAM │ │ - Jumps to 2nd stage entry (0x40080644) │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 2. 2nd Stage Bootloader (0x40080400) │ │ - Configures cache, clocks, flash │ │ - Reads partition table │ │ - Finds "factory" app at offset 0x10000 │ │ - Loads app segments to IRAM/DRAM │ │ - Jumps to app entry (0x40080000) │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 3. start_cpu0() — Entry Point │ │ - do_core_init() → core init handlers │ │ - __libc_init_array() → C constructors │ │ - do_secondary_init() → secondary handlers │ │ - esp_startup_start_app() → FreeRTOS │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 4. esp_startup_start_app() │ │ - esp_int_wdt_init() → interrupt watchdog │ │ - esp_crosscore_int_init() → cross-core interrupts │ │ - xTaskCreatePinnedToCore(main_task) → create main task │ │ - vTaskStartScheduler() → start FreeRTOS │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 5. xPortStartScheduler() — FreeRTOS Port │ │ - portDISABLE_INTERRUPTS() │ │ - _xt_coproc_init() → co-processor setup │ │ - vPortSetupTimer() → configure CCOUNT or SYSTIMER │ │ - port_xSchedulerRunning[core] = 1 │ │ - xthal_window_spill() → clear window registers │ │ - _frxt_dispatch → first context switch │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 6. First Task Execution │ │ - main_task() runs │ │ - User's main() is called │ └─────────────────────────────────────────────────────────────┘ ``` ## Xtensa Context Frame (XtExcFrame) From `xtensa_context.h`: ```c STRUCT_FIELD(long, 4, XT_STK_EXIT, exit) /* exit point for dispatch */ STRUCT_FIELD(long, 4, XT_STK_PC, pc) /* return PC */ STRUCT_FIELD(long, 4, XT_STK_PS, ps) /* return PS */ STRUCT_FIELD(long, 4, XT_STK_A0, a0) STRUCT_FIELD(long, 4, XT_STK_A1, a1) /* stack pointer */ STRUCT_FIELD(long, 4, XT_STK_A2, a2) STRUCT_FIELD(long, 4, XT_STK_A3, a3) STRUCT_FIELD(long, 4, XT_STK_A4, a4) STRUCT_FIELD(long, 4, XT_STK_A5, a5) STRUCT_FIELD(long, 4, XT_STK_A6, a6) STRUCT_FIELD(long, 4, XT_STK_A7, a7) STRUCT_FIELD(long, 4, XT_STK_A8, a8) STRUCT_FIELD(long, 4, XT_STK_A9, a9) STRUCT_FIELD(long, 4, XT_STK_A10, a10) STRUCT_FIELD(long, 4, XT_STK_A11, a11) STRUCT_FIELD(long, 4, XT_STK_A12, a12) STRUCT_FIELD(long, 4, XT_STK_A13, a13) STRUCT_FIELD(long, 4, XT_STK_A14, a14) STRUCT_FIELD(long, 4, XT_STK_A15, a15) STRUCT_FIELD(long, 4, XT_STK_SAR, sar) STRUCT_FIELD(long, 4, XT_STK_EXCCAUSE, exccause) STRUCT_FIELD(long, 4, XT_STK_EXCVADDR, excvaddr) /* For CALL0 ABI: exit = _xt_user_exit, PS = PS_UM | PS_EXCM */ ``` ## Stack Frame Layout (for CALL0 ABI) ``` HIGH ADDRESS ┌─────────────────────────┐ │ Extra Storage (CPSA) │ ← XT_STK_EXTRA ├─────────────────────────┤ │ XtExcFrame (interrupt │ ← Task's SP points here │ frame on stack) │ │ [+0] exit = _xt_user_exit │ [+4] pc = task entry │ [+8] ps = PS_UM | PS_EXCM │ [+12] a0 = 0 │ [+16] a1 = SP + XT_STK_FRMSZ │ [+20] a2 = pvParameters │ ... │ [+76] a15 │ [+80] sar │ [+84] exccause │ [+88] excvaddr ├─────────────────────────┤ │ Base Save Area │ ├─────────────────────────┤ │ Task Stack │ └─────────────────────────┘ LOW ADDRESS ``` ## What We Got Wrong ### 1. Stack Frame Layout **ESP-IDF uses `XtExcFrame` structure with `exit` field pointing to `_xt_user_exit`** - `frame->exit = _xt_user_exit` (exception exit dispatcher) - `frame->pc = task entry point` - `frame->ps = PS_UM | PS_EXCM` (user mode, EXCM disabled) - `frame->a1 = SP + XT_STK_FRMSZ` (top of stack frame) - `frame->a2 = pvParameters` (task argument for CALL0) **Our code uses simple 16-word frame without `exit` field** ### 2. Interrupt Handler Flow **ESP-IDF flow:** ``` Hardware interrupt → _xt_int_enter → saves context → calls C handler → _xt_int_exit → checks for reschedule → restores context → rfe ``` **Our code:** ``` Hardware interrupt → saves context → calls handler → restores context → rfe ``` Missing: `_xt_context_save/restore`, interrupt nesting tracking, reschedule check ### 3. First Dispatch **ESP-IDF:** `xPortStartScheduler()` → `_frxt_dispatch` (assembly) - Sets `port_xSchedulerRunning[core] = 1` - Calls `_frxt_dispatch` which does the first context switch **Our code:** `uos_sched_start()` → `uos_port_dispatch_first()` (C function) - Simpler but missing: window spill, co-processor init, interrupt nesting ### 4. Timer Configuration **ESP-IDF uses two options:** 1. **CCOUNT (core timer)** — `CONFIG_FREERTOS_SYSTICK_USES_CCOUNT` - Uses internal timer 0 or 1 - `_frxt_tick_timer_init()` sets up CCOMPARE0 - Interrupt at level 1 2. **SYSTIMER** — `CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER` - Uses SYSTIMER peripheral - `SysTickIsrHandler()` handles the interrupt - Periodic mode with alarm **Our code:** Direct CCOUNT manipulation - Missing: interrupt allocation via `esp_intr_alloc()` - Missing: proper timer interrupt handler ## UniversalisOS Integration Strategy ### Replace Points | ESP-IDF Component | Our Equivalent | Status | |---|---|---| | `app_startup.c` | `esp32_integration.c` | ✅ Created | | `startup.c` | `startup.S` | ⚠️ Needs fix | | `port.c` | `uos_port_context.S` | ⚠️ Needs fix | | `portasm.S` | `esp32_vectors.S` | ⚠️ Needs fix | | `port_systick.c` | `esp32_timer.c` | ❌ Not created | ### What Needs to Change 1. **Stack frame must include `exit` field** — This is how FreeRTOS returns from exception 2. **Interrupt handlers need `_xt_context_save/restore`** — Save/restore all registers properly 3. **Timer must use `esp_intr_alloc()`** — Proper interrupt allocation 4. **First dispatch must use `_frxt_dispatch`** — Assembly context switch, not C function 5. **VECBASE must be set early** — Before any interrupts fire ### Key Functions to Implement ```c /* Our integration layer needs: */ void esp32_app_entry(void) { /* 1. Set VECBASE (must be first!) */ /* 2. Disable WDT */ /* 3. Configure UART for debug */ /* 4. Init interrupt controller */ /* 5. Init timer (CCOUNT or SYSTIMER) */ /* 6. Call uos_init() */ /* 7. Create tasks */ /* 8. Start scheduler */ } /* Context switch needs: */ void _frxt_dispatch(void); /* First dispatch — assembly */ void _frxt_int_enter(void); /* Interrupt entry — assembly */ void _frxt_int_exit(void); /* Interrupt exit — assembly */ /* Timer needs: */ void vPortSetupTimer(void); /* Configure CCOUNT/SYSTIMER */ void _frxt_tick_timer_init(void); /* CCOUNT timer init */ ``` ## Files to Study Further 1. `components/esp_system/startup.c` — Boot sequence ✅ Studied 2. `components/freertos/app_startup.c` — FreeRTOS startup ✅ Studied 3. `components/freertos/port_systick.c` — Timer config ✅ Studied 4. `components/freertos/FreeRTOS-Kernel-SMP/portable/xtensa/port.c` — Port layer ✅ Studied 5. `components/freertos/FreeRTOS-Kernel-SMP/portable/xtensa/portasm.S` — Context switch ✅ Studied 6. `components/xtensa/include/xtensa_context.h` — Context frame ✅ Studied 7. `components/xtensa/` — Xtensa HAL and runtime 8. `components/bootloader/` — 2nd stage bootloader ## Next Steps 1. Fix stack frame to include `exit` field 2. Implement proper interrupt handlers with `_xt_context_save/restore` 3. Implement `_frxt_dispatch` for first context switch 4. Implement `vPortSetupTimer` for proper timer configuration 5. Test on ESP32 QEMU with flash image approach