diff options
Diffstat (limited to 'src/oracle/intr_ref.c')
| -rw-r--r-- | src/oracle/intr_ref.c | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/src/oracle/intr_ref.c b/src/oracle/intr_ref.c new file mode 100644 index 0000000..ceeaa0d --- /dev/null +++ b/src/oracle/intr_ref.c @@ -0,0 +1,211 @@ +/* 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); +} |
