/* ESP-IDF's own CLIC code, given external linkage so the differential harness can call it. * * The interrupt controller is the one peripheral where "wrap IDF's LL header" is not the whole * story, and the reason is worth recording rather than papering over. * * ESP-IDF splits CLIC access across four places: * 1. components/hal/include/hal/interrupt_clic_ll.h - the matrix route, SHV, and the two * getters. Included below and wrapped directly; this is the LL proper. * 2. components/riscv/include/esp_private/interrupt_clic.h - MTVT, the threshold, edge-ack and * the enabled-mask scan, all `FORCE_INLINE_ATTR`. Also included below and wrapped directly. * 3. the **mask ROM** - esprv_intc_int_enable / _set_priority / _set_type, aliased into * esprv_int_* by components/riscv/ld/rom.api.ld. There is no C source for these, so they * cannot be compiled into this image as a reference. Worse, one of them is *wrong* on this * die: components/esp_rom/patches/esp_rom_clic.c:12-22 exists because the ROM's * esprv_intc_int_set_type silently configures LEVEL when asked for EDGE, on exactly the * CONFIG_ESP32P4_SELECTS_REV_LESS_V3 silicon this board is. * 4. components/esp_tee/.../clic/esp_tee_rv_utils.h - a non-ROM implementation of enable, * disable, set_type and set_priority, written as byte stores. * * For (3) the reference below is the register expression from IDF's own replacement code, copied * statement for statement with the file and line it came from, and using IDF's macros so the * numbers are still IDF's. That is a transcription, and it is the weakest link in this file; it is * marked as such at each site. Everything else calls IDF's code directly. * * Note also what the ROM situation means for the differential's *value* here: for enable, priority * and trigger the comparison is against IDF's non-ROM path, which is the path IDF itself uses on * TEE builds and the path its ROM patch restores. It is not against the ROM function a stock * app_main would reach. */ /* IDF's clock and reset LL functions are shadowed by a wrapper macro referencing * `__DECLARE_RCC_ATOMIC_ENV`, an identifier IDF never defines anywhere, so that an unguarded call * fails to compile. Nothing in this translation unit gates a clock, but the header chain reaches * those declarations, so the name has to exist. Same reasoning as src/oracle/gpio_ref.c:18. */ static int __DECLARE_RCC_ATOMIC_ENV __attribute__((unused)); /* **First, and load-bearing.** soc/interrupt_reg.h tests CONFIG_ESP32P4_SELECTS_REV_LESS_V3 but * does not include sdkconfig.h itself - it relies on the caller having done so, which in IDF's own * build happens because CMake force-includes it. Include it *after* any header below and * INTTHRESH_STANDARD comes out 1, the rev-3 answer, and this reference would be built against the * mintthresh CSR that this silicon does not implement. That is not hypothetical: this file's first * version had the includes in the obvious order and the #error below fired. * * build.zig now also passes `-include oracle_sdkconfig.h` to every reference translation unit, so * this line is belt as well as braces. It stays because the ordering constraint is a property of * IDF's headers, not of our build flags, and the next person to reorder these should see why. */ #include "sdkconfig.h" #include "soc/soc.h" #include "soc/clic_reg.h" #include "soc/interrupt_reg.h" #include "hal/interrupt_clic_ll.h" #include "esp_private/interrupt_clic.h" /* Guard the whole point of this file: if the build ever stopped defining * CONFIG_ESP32P4_SELECTS_REV_LESS_V3 (src/oracle/oracle_sdkconfig.h:25), interrupt_reg.h:28-40 would * flip INTTHRESH_STANDARD to 1 and every threshold function below would silently switch from the * memory-mapped register to the mintthresh CSR - which this die does not implement. The reference * would then be comparing against a threshold mechanism that does not exist, and would agree with * nothing. Fail the compile instead. */ #if INTTHRESH_STANDARD #error "this die uses the memory-mapped CLIC threshold; INTTHRESH_STANDARD must be 0 here" #endif /* Report the numbers this reference was compiled with, so a run can never silently be against the * wrong variant of the controller. */ int oracle_intr_intthresh_standard(void) { return INTTHRESH_STANDARD; } int oracle_intr_mintstatus_csr(void) { return MINTSTATUS_CSR; } int oracle_intr_mtvt_csr(void) { return MTVT_CSR; } int oracle_intr_nlbits(void) { return NLBITS; } int oracle_intr_ext_offset(void) { return CLIC_EXT_INTR_NUM_OFFSET; } unsigned oracle_intr_thresh_reg_addr(void) { return (unsigned)CLIC_INT_THRESH_REG; } unsigned oracle_intr_ctrl_reg_addr(unsigned clic_id) { return (unsigned)CLIC_INT_CTRL_REG(clic_id); } /* ---------------------------------------------------------------- the interrupt matrix */ /* interrupt_clic_ll.h:35-48, with the `+ RV_EXTERNAL_INT_OFFSET` that riscv/interrupt_clic.c:26 * applies before calling it. Core 0 only: this image never releases core 1. */ void oracle_intr_route(unsigned intr_src, unsigned line) { interrupt_clic_ll_route(0, (int)intr_src, (int)line + RV_EXTERNAL_INT_OFFSET); } /* esp_system/port/cpu_start.c:185 - IDF's own way to detach a source, writing ETS_INVALID_INUM * (0 on this chip, soc/esp32p4/include/soc/soc.h:251) with no offset added. */ void oracle_intr_unroute(unsigned intr_src) { interrupt_clic_ll_route(0, (int)intr_src, ETS_INVALID_INUM); } /* ---------------------------------------------------------------- per-line control */ /* interrupt_clic_ll.h:99-102 via riscv/interrupt_clic.c:48-51. */ void oracle_intr_set_vectored(unsigned line, int vectored) { interrupt_clic_ll_set_vectored((int)line + RV_EXTERNAL_INT_OFFSET, vectored != 0); } /* interrupt_clic_ll.h:58-61 via riscv/interrupt_clic.c:30-33: 1 for edge, 0 for level. */ int oracle_intr_get_type(unsigned line) { return interrupt_clic_ll_get_type((int)line + RV_EXTERNAL_INT_OFFSET); } /* interrupt_clic_ll.h:71-75 via riscv/interrupt_clic.c:36-39. */ int oracle_intr_get_priority(unsigned line) { return interrupt_clic_ll_get_priority((int)line + RV_EXTERNAL_INT_OFFSET); } /* TRANSCRIBED, not called: the ROM owns esprv_intc_int_enable and there is no source for it. * The store is esp_tee/subproject/main/include/clic/esp_tee_rv_utils.h:74, verbatim - a byte write * of BYTE_CLIC_INT_IE to BYTE_CLIC_INT_IE_REG. Byte 1 of the control word holds nothing but IE, so * this and a 32-bit read-modify-write of CLIC_INT_IE leave the same word behind; that equivalence * is precisely what the differential is there to check rather than assert. */ void oracle_intr_enable(unsigned line) { const unsigned id = line + CLIC_EXT_INTR_NUM_OFFSET; *(uint8_t volatile *)(BYTE_CLIC_INT_IE_REG(id)) = BYTE_CLIC_INT_IE; } /* TRANSCRIBED: esp_tee_rv_utils.h:88. */ void oracle_intr_disable(unsigned line) { const unsigned id = line + CLIC_EXT_INTR_NUM_OFFSET; *(uint8_t volatile *)(BYTE_CLIC_INT_IE_REG(id)) = 0; } /* TRANSCRIBED: esp_rom/patches/esp_rom_clic.c:21, which is IDF's *replacement* for the ROM's * broken esprv_intc_int_set_type on pre-v3 P4 silicon. A 32-bit REG_SET_FIELD on CLIC_INT_ATTR_TRIG, * so unlike the TEE build's byte store it preserves SHV by read-modify-write rather than by the * byte's other bits happening to be reloaded - same result, different mechanism. `type` is the raw * two-bit encoding (0 level, 1 rising, 3 falling; clic_reg.h:84-88). */ void oracle_intr_set_type(unsigned line, unsigned type) { const unsigned id = line + CLIC_EXT_INTR_NUM_OFFSET; REG_SET_FIELD(CLIC_INT_CTRL_REG(id), CLIC_INT_ATTR_TRIG, type); } /* TRANSCRIBED: esp_tee_rv_utils.h:112. Note the encoding - priority left-aligned into the top * NLBITS of the byte with the low bits **zero**, which differs from the threshold's encoding * below. */ void oracle_intr_set_priority(unsigned line, unsigned priority) { const unsigned id = line + CLIC_EXT_INTR_NUM_OFFSET; *(uint8_t volatile *)(BYTE_CLIC_INT_CTL_REG(id)) = (uint8_t)(priority << BYTE_CLIC_INT_CTL_S); } /* esp_private/interrupt_clic.h, rv_utils_intr_edge_ack: writing 1 to IP is what *clears* an * edge-triggered pending. Called directly - this one is a real IDF inline. */ void oracle_intr_edge_ack(unsigned line) { rv_utils_intr_edge_ack(line); } /* esp_private/interrupt_clic.h, rv_utils_intr_get_enabled_mask. */ unsigned oracle_intr_enabled_mask(void) { return rv_utils_intr_get_enabled_mask(); } /* ---------------------------------------------------------------- the threshold */ /* esp_private/interrupt_clic.h:153-156 -> :129-146. Called directly, so the reference includes * IDF's own read-back-to-force-the-store and IDF's own NLBITS_TO_BYTE padding, and the harness * compares against those rather than against a re-derivation of them. */ void oracle_intr_set_threshold(unsigned level) { rv_utils_restore_intlevel(level); } /* esp_private/interrupt_clic.h:44-57. Returns an absolute level 0..7. */ unsigned oracle_intr_get_threshold(void) { return rv_utils_get_interrupt_threshold(); } /* ---------------------------------------------------------------- vector table */ /* esp_private/interrupt_clic.h:63-66. MTVT is CSR 0x307. Writing it has no effect on any register * the harness photographs, so this exists for the behavioural test rather than for the diff. */ void oracle_intr_set_mtvt(unsigned mtvt) { rv_utils_set_mtvt(mtvt); }