1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
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);
}
|