# Xtensa LX6 (ESP32) Debug Exception Architecture — GDB Stub Reference **Target:** ESP32 (Xtensa LX6, core `esp32_v3_49_prod`, XEA2, CALL0 ABI) **Purpose:** Definitive reference for building a bare-metal GDB stub **Sources:** `core-isa.h`, `corebits.h`, `specreg.h`, `xt_specreg.h`, `xtensa_vectors.S`, IDF `esp_gdbstub`, `xt_utils.h` --- ## 1. Debug Exception Mechanism ### How `break` triggers a debug exception The `break` instruction is a 3-byte opcode. When executed, it raises a **debug exception** at **interrupt level 6** (`XCHAL_DEBUGLEVEL = 6`). Debug exceptions do NOT go through the normal EXCCAUSE/vector mechanism for level 1–5; they vector to a **dedicated debug exception vector**. Key facts: - **`break s, t`** — the two immediate operands (s, t) are information-only (available to the debug handler via DEBUGCAUSE). `break 0, 0` is the standard software breakpoint. - **`break.n`** — a 16-bit (narrow) variant of break, flagged separately in DEBUGCAUSE. - Debug exceptions are taken at **INTLEVEL 6**, which is higher than all normal interrupts (levels 1–5) but below NMI (level 7). - The **EXCCAUSE** register is NOT set by the debug exception itself for `break` — DEBUGCAUSE records the reason. (However, the IDF panic handler manually writes EXCCAUSE to `PANIC_RSN_DEBUGEXCEPTION` for its own bookkeeping.) ### Registers used on debug exception entry | Register | SR# | Purpose | |----------|-----|---------| | **EPC6** | 182 | Exception PC — saved PC of the instruction that triggered (or the next instruction for ICOUNT step) | | **EPS6** | 198 | Exception PS — saved PS (processor state) register | | **EXCSAVE6** | 214 | Scratch save register for level 6 — conventionally used to save `a0` (return address) at vector entry | The hardware automatically saves: - `PC → EPC6` - `PS → EPS6` Then vectors to the debug exception vector address. ### What EXCSAVE6 is used for At the debug vector entry, `a0` is the only register you can use (no stack frame yet). The standard convention (from `xtensa_vectors.S`) is: ```asm _DebugExceptionVector: wsr a0, EXCSAVE6 /* save a0 — only safe scratch at level 6 */ j xt_debugexception /* jump to handler */ ``` `EXCSAVE6` is the handler's dedicated scratch slot to preserve the interrupted code's `a0` before it sets up a stack frame. ### EXCCAUSE for debug exceptions The debug exception does **not** use the standard EXCCAUSE values (0–63). Instead, the reason is in **DEBUGCAUSE** (SR 233). However, when the panic handler is invoked, it manually sets: ```c EXCCAUSE = PANIC_RSN_DEBUGEXCEPTION // (an IDF-specific value, not an ISA EXCCAUSE) ``` --- ## 2. Special Register Numbers (ESP32 / LX6) All register numbers confirmed from `specreg.h` / `xt_specreg.h`: ### Debug-specific registers | Register | SR Number | Symbol | R/W | Description | |----------|-----------|--------|-----|-------------| | **IBREAKENABLE** | **96** | `IBREAKENABLE` | RW | Bitmask: bit n enables IBREAKA_n | | **IBREAKA_0** | **128** | `IBREAKA_0` | RW | Instruction breakpoint address 0 | | **IBREAKA_1** | **129** | `IBREAKA_1` | RW | Instruction breakpoint address 1 | | **DBREAKA_0** | **144** | `DBREAKA_0` | RW | Data breakpoint (watchpoint) address 0 | | **DBREAKA_1** | **145** | `DBREAKA_1` | RW | Data breakpoint address 1 | | **DBREAKC_0** | **160** | `DBREAKC_0` | RW | Data breakpoint control 0 | | **DBREAKC_1** | **161** | `DBREAKC_1` | RW | Data breakpoint control 1 | | **DEBUGCAUSE** | **233** | `DEBUGCAUSE` | RO | Debug exception cause (set by hardware on debug exception) | | **ICOUNT** | **236** | `ICOUNT` | RW | Instruction count register (for single-stepping) | | **ICOUNTLEVEL** | **237** | `ICOUNTLEVEL` | RW | Interrupt level at which ICOUNT triggers a debug exception | ### Level-6 exception state registers | Register | SR Number | Symbol | Description | |----------|-----------|--------|-------------| | **EPC_6** | **182** | `EPC_6` | Exception PC for level 6 (debug) | | **EPS_6** | **198** | `EPS_6` | Exception PS for level 6 (debug) | | **EXCSAVE_6** | **214** | `EXCSAVE_6` | Scratch save for level 6 | | **DEPC** | **192** | `DEPC` | *(Alias for EPC at debug level in some docs; ESP32 uses EPC_6)* | ### Other relevant registers | Register | SR Number | Symbol | Description | |----------|-----------|--------|-------------| | **CONFIGID0** | **176** | `CONFIGID0` | RO — processor config ID (hi 32 bits) | | **CONFIGID1** | **208** | `CONFIGID1` | RO — processor config ID (lo 32 bits) | | **PS** | **230** | `PS` | Processor state register | | **VECBASE** | **231** | `VECBASE` | Vector base address register | | **EXCCAUSE** | **232** | `EXCCAUSE` | Exception cause register | | **EXCVADDR** | **238** | `EXCVADDR` | Exception virtual address | | **CCOUNT** | **234** | `CCOUNT` | Cycle count register | | **PRID** | **235** | `PRID` | Processor ID | ### EPC/EPS/EXCSAVE series base values ``` EPC series base: 176 → EPC_1 = 177, EPC_2 = 178, ..., EPC_6 = 182, EPC_7 = 183 EPS series base: 192 → EPS_2 = 194, EPS_3 = 195, ..., EPS_6 = 198, EPS_7 = 199 EXCSAVE series base: 208 → EXCSAVE_1 = 209, ..., EXCSAVE_6 = 214, EXCSAVE_7 = 215 ``` Note: There is no EPC_0 or EPS_0 or EPS_1 (level 0 and level 1 exceptions use different mechanisms — level 1 uses EPC_1/EPS_1... wait, actually: EPC_1=177, but EPS_1 does not exist in the table because level 1 exception PS is saved/restored differently in XEA2). ### Hardware config values ```c XCHAL_NUM_IBREAK = 2 /* 2 instruction breakpoints */ XCHAL_NUM_DBREAK = 2 /* 2 data watchpoints */ XCHAL_DEBUGLEVEL = 6 /* debug interrupt level */ XCHAL_NMILEVEL = 7 /* NMI level */ XCHAL_NUM_INTLEVELS = 6 /* levels 1-6 (not counting level 0) */ XCHAL_EXCM_LEVEL = 3 /* level masked by PS.EXCM */ XCHAL_HW_CONFIGID0 = 0xC2BCFFFE XCHAL_HW_CONFIGID1 = 0x1CC5FE96 ``` --- ## 3. DEBUGCAUSE Register (SR 233) Read-only. Set by hardware on entry to the debug exception vector. Bit layout from `corebits.h`: | Bit | Mask | Name | Meaning | |-----|------|------|---------| | 0 | 0x01 | **ICOUNT** | ICOUNT reached zero (single-step triggered) | | 1 | 0x02 | **IBREAK** | Instruction breakpoint match | | 2 | 0x04 | **DBREAK** | Data breakpoint (watchpoint) match | | 3 | 0x08 | **BREAK** | `break` instruction (3-byte) executed | | 4 | 0x10 | **BREAKN** | `break.n` instruction (2-byte narrow) executed | | 5 | 0x20 | **DEBUGINT** | External debug interrupt (OCD) | Reading: `rsr a0, DEBUGCAUSE` gives the OR of all active causes. ### Interpreting DEBUGCAUSE in a GDB stub ``` DEBUGCAUSE & 0x01 → ICOUNT → single-step completed DEBUGCAUSE & 0x02 → IBREAK → hardware breakpoint hit DEBUGCAUSE & 0x04 → DBREAK → watchpoint hit (load/store) DEBUGCAUSE & 0x08 → BREAK → software breakpoint (break 0,0) hit DEBUGCAUSE & 0x10 → BREAKN → narrow software breakpoint hit DEBUGCAUSE & 0x20 → DEBUGINT→ OCD external debug interrupt ``` For a software breakpoint placed by GDB (`break 0,0`), DEBUGCAUSE bit 3 (BREAK) is set. --- ## 4. Vector Table Layout ESP32 uses relocatable vectors (`XCHAL_HAVE_VECBASE = 1`). All vectors are offset from **VECBASE** (SR 231). ### Vector offsets in VECBASE | Offset | Vector | Symbol | |--------|--------|--------| | 0x000 | Window overflow 4 | `_WindowOverflow4` | | 0x040 | Window underflow 4 | `_WindowUnderflow4` | | 0x080 | Window overflow 8 | | | 0x0C0 | Window underflow 8 | | | 0x100 | Window overflow 12 | | | 0x140 | Window underflow 12 | | | 0x180 | **Level 2 interrupt** | `_Level2InterruptVector` | | 0x1C0 | **Level 3 interrupt** | `_Level3InterruptVector` | | 0x200 | **Level 4 interrupt** | `_Level4InterruptVector` | | 0x240 | **Level 5 interrupt** | `_Level5InterruptVector` | | **0x280** | **Level 6 interrupt (= DEBUG)** | `_DebugExceptionVector` | | 0x2C0 | Level 7 interrupt (= NMI) | `_NMIExceptionVector` | | 0x300 | **Kernel exception** | `_KernelExceptionVector` | | 0x340 | **User exception** | `_UserExceptionVector` | | 0x3C0 | **Double exception** | `_DoubleExceptionVector` | ### Debug exception vector ``` XCHAL_DEBUG_VECOFS = 0x00000280 XCHAL_DEBUG_VECTOR_VADDR = 0x40000280 (at reset VECBASE = 0x40000000) XCHAL_INTLEVEL6_VECOFS = 0x00000280 (same as DEBUG — level 6 IS debug) ``` **Critical:** On the ESP32, **interrupt level 6 IS the debug level**. There are no regular interrupts at level 6 (`XCHAL_INTLEVEL6_MASK = 0x00000000`). The debug exception vector at offset `0x280` from VECBASE is the entry point for ALL debug exceptions. ### Reset VECBASE value ``` XCHAL_VECBASE_RESET_VADDR = 0x40000000 (IRAM0 — instruction RAM) ``` ### Debug vector code (from `xtensa_vectors.S`) ```asm .section .DebugExceptionVector.text, "ax" .global _DebugExceptionVector .align 4 _DebugExceptionVector: wsr a0, EXCSAVE6 /* preserve a0 in EXCSAVE6 */ j xt_debugexception /* jump to handler (not call — a0 is saved) */ ``` The vector is minimal — it saves `a0` to `EXCSAVE6` and jumps to the full handler. The handler then: 1. Sets up a stack frame 2. Saves EPC6, EPS6 to the frame 3. Reads DEBUGCAUSE to determine why 4. Handles the debug event 5. Restores context and executes `rfi 6` --- ## 5. PS Register (SR 230) — Full Bit Layout From `corebits.h`: ``` 31 19 18 17 16 15 9 8 6 5 4 3 0 +-----------------------+-----+-------+--------+-------+-----+----+--------+ | reserved | WOE | CALLINC| reserved| OWB | UM |EXCM| INTLEVEL| +-----------------------+-----+-------+--------+-------+-----+----+--------+ ``` | Bits | Mask | Field | Description | |------|------|-------|-------------| | [3:0] | 0x0000000F | **INTLEVEL** | Current interrupt level (0–15). Interrupts at or below this level are masked. | | [4] | 0x00000010 | **EXCM** | Exception mode. When set, masks interrupts to `XCHAL_EXCM_LEVEL` (= 3 on ESP32). | | [5] | 0x00000020 | **UM** | User mode (1 = user/kernel mode distinction; 0 = privileged) | | [7:6] | 0x000000C0 | **RING** | Current protection ring (0–3). ESP32 has `XCHAL_MMU_RINGS = 1`, so always 0. | | [11:8] | 0x00000F00 | **OWB** | Old Window Base (saved WINDOWBASE on exception entry for windowed ABI) | | [17:16] | 0x00030000 | **CALLINC** | Call increment (windowed ABI — how many registers the current call frame uses: 4, 8, or 12) | | [18] | 0x00040000 | **WOE** | Window Overflow Enable (1 = window exceptions enabled; 0 = disabled) | ### CALL0 ABI considerations For the CALL0 ABI (no windowed registers): - `WOE = 0` — window overflow exceptions disabled - `CALLINC = 0` — no window call increment - `OWB` — unused/irrelevant ABI-specific PS values for setting up a handler: ```c // CALL0 ABI: #define PS_GDBSTUB_HANDLER PS_INTLEVEL(5) | PS_UM // = 0x25 // The IDF gdbstub uses: PS_INTLEVEL(5) | PS_UM | PS_WOE // but WOE is harmless if WOE=0 for CALL0 // For user exception exit (CALL0): #define PS_FOR_C_CALL PS_INTLEVEL(XCHAL_DEBUGLEVEL - 2) | PS_UM // INTLEVEL(4) | UM = 0x24 ``` ### INTLEVEL semantics - `INTLEVEL = 0`: All interrupts enabled - `INTLEVEL = N`: Masks interrupts at level ≤ N - `INTLEVEL = 6`: Debug level — masks everything except NMI and debug exceptions - `INTLEVEL = 7`: NMI level — masks everything When a debug exception is taken, PS is saved to EPS6 and the new PS.INTLEVEL is set to 6. ### Important PS note for rfi 6 Before executing `rfi 6`, EPS6 must contain the PS value you want to restore. The INTLEVEL in EPS6 determines the post-return interrupt mask. Typically, you restore the original (pre-exception) PS from your saved frame. --- ## 6. The `rfi 6` Instruction (Return from Interrupt) ### Syntax ``` rfi n ``` where `n` is the interrupt level (1–7). For debug exceptions: **`rfi 6`**. ### Semantics `rfi n` performs an atomic return-from-interrupt: 1. **Restores PS** from `EPS_n` (for n=6: from `EPS6`, SR 198) 2. **Sets PC** from `EPC_n` (for n=6: from `EPC6`, SR 182) 3. Begins execution at the restored PC 4. The new PS.INTLEVEL takes effect (unmasking interrupts as appropriate) ### What must be in EPC6/EPS6 before `rfi 6` | Register | Must contain | |----------|-------------| | **EPC6** (SR 182) | The **PC to resume at**. For a breakpoint: the address of the instruction *after* the `break` (or the same address if you want to re-execute). For a single-step: the PC of the next instruction to execute. | | **EPS6** (SR 198) | The **PS to restore** — the original pre-exception PS (interrupt level, UM, EXCM, etc. of the interrupted code). | ### Critical detail for software breakpoints When `break 0,0` fires, `EPC6` points to the **address of the break instruction itself** (or the next instruction depending on the implementation). The IDF gdbstub reads EPC6 into the frame and adjusts it: ```asm rsr a0, (EPC + XCHAL_DEBUGLEVEL) /* read EPC6 */ s32i a0, sp, XT_STK_PC /* save to frame */ ``` On return, the handler writes the (possibly modified) PC back to EPC6 before `rfi 6`: ```asm l32i a0, sp, XT_STK_PC wsr a0, (EPC + XCHAL_DEBUGLEVEL) /* restore EPC6 */ ... rfi 6 ``` **If you placed a software breakpoint (`break 0,0`) in flash/IRAM, you MUST:** 1. Restore the original instruction bytes before resuming 2. Set EPC6 to the address of that (now-restored) instruction 3. Execute `rfi 6` Otherwise the CPU will re-hit the breakpoint immediately. ### rsync requirement After `wsr` to PS, you should execute `rsync` before relying on the new PS value. After `wsr` to other registers, `isync` may be needed before the change takes effect on the instruction stream. --- ## 7. ICOUNT Single-Step Mechanism ### How it works **ICOUNT** (SR 236) is a signed 32-bit counter that increments on every instruction executed at an interrupt level **≥ ICOUNTLEVEL**. When ICOUNT transitions from negative to zero (i.e., reaches zero), a **debug exception is generated** if the current interrupt level ≥ ICOUNTLEVEL. ### The magic value: ICOUNT = 0xFFFFFFFE (= -2) ``` ICOUNT = 0xFFFFFFFE (= signed -2) ICOUNTLEVEL = N ``` **Sequence of events:** 1. Set `ICOUNTLEVEL = N` — only count instructions executed at INTLEVEL ≥ N 2. Set `ICOUNT = 0xFFFFFFFE` — start at -2 3. Execute `rfi 6` to return to the target code 4. **First instruction** executes: ICOUNT increments to `0xFFFFFFFF` (-1) 5. **Second instruction** executes: ICount increments to `0x00000000` (0) — **this triggers the debug exception** Wait — this would step TWO instructions. But the IDF code says "do one step." Let me clarify: **Actually, the correct interpretation:** The debug exception fires when ICOUNT *would increment to zero*, meaning the exception is taken *before* executing the instruction that would make it zero. So: - ICOUNT = -2, after `rfi 6`: - Instruction 1 executes → ICOUNT becomes -1 - Instruction 2 is about to execute → ICOUNT would become 0 → **debug exception fires INSTEAD of executing instruction 2** So **one instruction executes**, then the debug exception fires. EPC6 points to the instruction that was *about* to execute (the second one), i.e., the **next instruction after the step**. **Correction/Refinement (from IDF code):** The IDF `esp_gdbstub_do_step()` does: ```c void esp_gdbstub_do_step(esp_gdbstub_frame_t *frame) { uint32_t level = s_scratch.regfile.ps; level &= 0x7; // current INTLEVEL from saved PS level += 1; // set ICOUNTLEVEL one above current WSR(ICOUNTLEVEL, level); WSR(ICOUNT, -2); // 0xFFFFFFFE } ``` The `-2` value means: execute **one more instruction** after returning, then trap. ### ICOUNTLEVEL selection `ICOUNTLEVEL` must be set such that the instruction to be stepped runs at a level ≥ ICOUNTLEVEL. The IDF sets it to `(current PS.INTLEVEL + 1)`. **Why +1?** Because when the code resumes after `rfi 6`, it runs at the PS.INTLEVEL saved in EPS6. If ICOUNTLEVEL equals that INTLEVEL, the counter would NOT increment (instructions only count when INTLEVEL ≥ ICOUNTLEVEL... actually instructions count when the current level is **≤** ICOUNTLEVEL per the ISA — see clarification below). **ISA rule (from Xtensa ISA Reference):** ICOUNT increments on each instruction executed at an interrupt level **less than or equal to** ICOUNTLEVEL. So to count all normal code (INTLEVEL 0–5), set ICOUNTLEVEL ≥ the code's INTLEVEL. The IDF sets `ICOUNTLEVEL = current_intlevel + 1` to ensure: - The stepped instruction (at the code's original INTLEVEL) is counted - But interrupts at higher levels are not accidentally counted ### Clearing single-step ```c void esp_gdbstub_clear_step(void) { WSR(ICOUNT, 0); // Set to 0 — but since 0 is the trigger, this would immediately fire WSR(ICOUNTLEVEL, 0); // Set level to 0 — no instructions execute at level ≤ 0... // actually INTLEVEL 0 means count nothing useful } ``` Setting `ICOUNTLEVEL = 0` effectively disables stepping because no executed instruction has INTLEVEL ≤ 0 (except... level 0 code itself, but the check is "INTLEVEL ≤ ICOUNTLEVEL" — if both are 0, it would count). **The reliable way to disable:** Set `ICOUNTLEVEL = 0` AND `ICOUNT = 0`. When ICOUNTLEVEL is 0, only level-0 instructions increment ICOUNT. When ICOUNT is already 0, it stays at 0 and the debug exception condition (transition through zero) doesn't re-fire because there's no increment happening. Actually, the precise ISA semantics: a debug exception due to ICOUNT occurs when **ICOUNT transitions from non-zero to zero** during an increment. Setting ICOUNT=0 directly (via wsr) does **not** trigger the exception — only the hardware increment path does. ### Summary of single-step setup ``` To single-step exactly ONE instruction: 1. Read the target code's PS (from EPS6 / saved frame) 2. Extract INTLEVEL: level = ps & 0xF 3. Set ICOUNTLEVEL = level (count instructions at this level or below) 4. Set ICOUNT = 0xFFFFFFFE (-2) 5. Execute rfi 6 to resume 6. One instruction executes, ICOUNT → -1 → next increment would be 0 → debug exception 7. EPC6 now points to the instruction AFTER the one that executed ``` --- ## Quick Reference: Building the GDB Stub ### Software breakpoint (insert) Replace the target instruction with `break 0, 0` (3-byte encoding). Save the original bytes for restoration. ### Software breakpoint (handle) ```asm ; In debug exception handler: rsr a0, DEBUGCAUSE bbci a0, 3, not_a_break ; bit 3 = BREAK ; It's a software breakpoint ; EPC6 points at/near the break instruction ; Restore original instruction, report to GDB ``` ### Hardware breakpoint (IBREAK) ```c // Set breakpoint at addr using IBREAKA0: wsr(IBREAKA_0, addr); uint32_t en = rsr(IBREAKENABLE); en |= (1 << 0); // enable bit 0 wsr(IBREAKENABLE, en); ``` ### Watchpoint (DBREAK) ```c // Set watchpoint at addr, size bytes: uint32_t dbreakc = 0x3F; // mask: all 6 bits = full 32-bit dbreakc <<= (ffs(size) - 1); // shift for byte select dbreakc &= 0x3F; if (on_read) dbreakc |= (1 << 30); // DBREAKC_LOADBREAK if (on_write) dbreakc |= (1 << 31); // DBREAKC_STOREBREAK wsr(DBREAKA_0, addr); wsr(DBREAKC_0, dbreakc); ``` ### DBREAKC register bit layout | Bits | Field | Description | |------|-------|-------------| | [31] | STOREBREAK | Trigger on store | | [30] | LOADBREAK | Trigger on load | | [5:0] | MASK | Byte select mask (0x3F = all bytes; shift left by log2(size)) | ### Debug exception handler skeleton (CALL0 ABI) ```asm _DebugExceptionVector: wsr a0, EXCSAVE6 ; save a0 j _gdbstub_debug_handler ; jump to handler _gdbstub_debug_handler: ; Save context (a0 already in EXCSAVE6) mov a0, sp addi sp, sp, -FRAME_SIZE s32i a0, sp, OFF_SP rsr a0, EPS6 ; save PS s32i a0, sp, OFF_PS rsr a0, EPC6 ; save PC s32i a0, sp, OFF_PC rsr a0, EXCSAVE6 ; recover original a0 s32i a0, sp, OFF_A0 rsr a0, DEBUGCAUSE ; get debug cause s32i a0, sp, OFF_DEBUGCAUSE ; ... save remaining regs, call C handler ... ; Set up PS for C handler execution (allow level 5 interrupts, clear EXCM) movi a0, PS_INTLEVEL(5) | PS_UM ; = 0x25 for CALL0 wsr a0, PS rsync ; Call C handler with frame pointer mov a2, sp ; arg0 = frame pointer (CALL0 ABI) call0 gdbstub_debug_entry ; Restore and return l32i a0, sp, OFF_PC wsr a0, EPC6 ; restore PC (may be modified by GDB) l32i a0, sp, OFF_PS wsr a0, EPS6 ; restore PS ; ... restore remaining regs ... l32i a0, sp, OFF_SP addi sp, sp, FRAME_SIZE rfi 6 ; return from debug exception ``` --- ## Key Constants Summary | Constant | Value | Source | |----------|-------|--------| | `XCHAL_DEBUGLEVEL` | **6** | core-isa.h:405 | | `XCHAL_NMILEVEL` | **7** | core-isa.h:407 | | `XCHAL_NUM_IBREAK` | **2** | core-isa.h:613 | | `XCHAL_NUM_DBREAK` | **2** | core-isa.h:614 | | Debug vector offset | **0x280** | core-isa.h:588 | | `XCHAL_VECBASE_RESET_VADDR` | **0x40000000** | core-isa.h:549 | | `XCHAL_EXCM_LEVEL` | **3** | core-isa.h:351 | | CONFIGID0 | **0xC2BCFFFE** | core-isa.h:187 | | CONFIGID1 | **0x1CC5FE96** | core-isa.h:188 | ### Register quick-access (for `rsr`/`wsr`) ``` EPC_6 = 182 EPS_6 = 198 EXCSAVE_6 = 214 DEBUGCAUSE = 233 ICOUNT = 236 ICOUNTLEVEL = 237 IBREAKENABLE = 96 IBREAKA_0 = 128 IBREAKA_1 = 129 DBREAKA_0 = 144 DBREAKA_1 = 145 DBREAKC_0 = 160 DBREAKC_1 = 161 PS = 230 VECBASE = 231 EXCCAUSE = 232 CONFIGID0 = 176 CONFIGID1 = 208 ``` --- *Document generated from ESP-IDF v5.5.4 HAL source and Xtensa LX6 configuration headers.*