summaryrefslogtreecommitdiff
path: root/src/oracle/intr_ref.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/oracle/intr_ref.c')
-rw-r--r--src/oracle/intr_ref.c211
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);
+}