diff options
Diffstat (limited to 'src/oracle/clkrst_cases.zig')
| -rw-r--r-- | src/oracle/clkrst_cases.zig | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/src/oracle/clkrst_cases.zig b/src/oracle/clkrst_cases.zig new file mode 100644 index 0000000..f004dd4 --- /dev/null +++ b/src/oracle/clkrst_cases.zig @@ -0,0 +1,131 @@ +//! HP_SYS_CLKRST's side of the differential: the clock-gate and reset pairing table. +//! +//! This suite exists because of a bug that a hardware test failed to catch. `src/hal/clkrst.zig` +//! maps each peripheral to the register and bit that gate and reset it, and every row of that table +//! is a transcription from ESP-IDF's LL source - the field macros do not record which register they +//! live in, so there is nothing to derive it from. Two rows were wrong: timg0, timg1, systimer and +//! twai0 had their APB clock enables in `PERI_CLK_CTRL21` instead of `SOC_CLK_CTRL2`, so +//! `setClockEnabled` wrote a bit of an unrelated register. +//! +//! `examples/halcheck.zig` printed exactly the expected `twai0 boot=0 on=1 off=0` throughout, +//! because `isClockEnabled` read back the same wrong bit `setClockEnabled` had just written. A +//! self-consistent test proves the two halves of your own code agree; it does not prove either one +//! touches the hardware, and that one would have passed with the chip unplugged. +//! +//! ESP-IDF reaches these bits through its own generated struct definitions - a genuinely independent +//! path - so this comparison is the check a read-back cannot be. +//! +//! Not covered here: TWAI0. Its bus clock is the one that is gated off at power-on, which makes it +//! the interesting case, but ESP-IDF's TWAI bus-clock LL takes a controller handle this suite has no +//! business constructing. The four rows below share the two registers TWAI0's row uses, so a +//! transcription error in it would have to be independent of theirs to survive. + +const std = @import("std"); +const hal = @import("hal"); +const regs = @import("regs"); +const mmio = @import("mmio"); +const types = @import("differ_types.zig"); + +extern fn oracle_clkrst_timg_bus_clock(group: c_uint, enable: c_int) void; +extern fn oracle_clkrst_timg_reset(group: c_uint) void; +extern fn oracle_clkrst_systimer_bus_clock(enable: c_int) void; +extern fn oracle_clkrst_systimer_reset() void; +extern fn oracle_clkrst_uart_bus_clock(port: c_uint, enable: c_int) void; + +/// Known state: every peripheral this suite touches with its bus clock on, which is also the state +/// the chip powers up in ("All peripheral clocks are default enabled after chip is powered on", +/// esp_system/port/soc/esp32p4/clk.c:200). Nothing else in the block is touched - UART0's gates in +/// particular, because that is the console this result is printed over. +/// Restore through ESP-IDF's LL, never through the code under test. +/// +/// This suite exists to catch a `setClockEnabled` that writes the wrong register. Restoring with +/// `hal.clkrst.setClockEnabled` defeated exactly that: `differ.zig` runs restore, idf, snapshot, +/// restore, ours, snapshot, so with the HAL on both the restore and the "ours" side, a +/// `setClockEnabled` that did nothing at all would leave run B's snapshot equal to run A's and pass +/// all six clock cases. Which is how the original bug - four peripherals' gate bits in +/// PERI_CLK_CTRL21 instead of SOC_CLK_CTRL2 - could have survived this suite too. +fn restore() void { + oracle_clkrst_timg_bus_clock(1, 1); + oracle_clkrst_systimer_bus_clock(1); + oracle_clkrst_uart_bus_clock(1, 1); +} + +pub const suite: types.Suite = .{ + .descriptor = .{ + .name = "clkrst", + .base = @intCast(regs.HP_SYS_CLKRST_SOC_CLK_CTRL1_REG - 0x18), // block base + // 0x00 through HP_RST_EN2 at +0xC8: covers SOC_CLK_CTRL1/2 (+0x18, +0x1c), every + // PERI_CLK_CTRL register, and all three HP_RST_EN registers. Everything either + // implementation could plausibly hit is inside this window, which is the property that + // makes a difference detectable rather than merely absent. + .words = 52, + .restore = .{ .configure = restore }, + }, + .cases = &.{ + // Disable first in each pair: the restored state has them on, so "disable" is the operation + // with an observable effect and "enable" would otherwise be a no-op comparison. + .{ .name = "timg1_bus_clock", .arg = 0, .idf = idfTimgOff, .ours = ourTimgOff }, + .{ .name = "systimer_bus_clock", .arg = 0, .idf = idfSystimerOff, .ours = ourSystimerOff }, + .{ .name = "uart1_bus_clock", .arg = 0, .idf = idfUartOff, .ours = ourUartOff }, + .{ .name = "timg1_reset", .idf = idfTimgReset, .ours = ourTimgReset }, + .{ .name = "systimer_reset", .idf = idfSystimerReset, .ours = ourSystimerReset }, + // Last, so the block is left with everything on regardless of which side ran last. + .{ .name = "timg1_bus_clock", .arg = 1, .idf = idfTimgOn, .ours = ourTimgOn }, + .{ .name = "systimer_bus_clock", .arg = 1, .idf = idfSystimerOn, .ours = ourSystimerOn }, + .{ .name = "uart1_bus_clock", .arg = 1, .idf = idfUartOn, .ours = ourUartOn }, + }, +}; + +fn idfTimgOff() void { + oracle_clkrst_timg_bus_clock(1, 0); +} +fn ourTimgOff() void { + hal.clkrst.setClockEnabled(.timg1, false); +} +fn idfTimgOn() void { + oracle_clkrst_timg_bus_clock(1, 1); +} +fn ourTimgOn() void { + hal.clkrst.setClockEnabled(.timg1, true); +} +fn idfSystimerOff() void { + oracle_clkrst_systimer_bus_clock(0); +} +fn ourSystimerOff() void { + hal.clkrst.setClockEnabled(.systimer, false); +} +fn idfSystimerOn() void { + oracle_clkrst_systimer_bus_clock(1); +} +fn ourSystimerOn() void { + hal.clkrst.setClockEnabled(.systimer, true); +} +fn idfUartOff() void { + oracle_clkrst_uart_bus_clock(1, 0); +} +fn ourUartOff() void { + hal.clkrst.setClockEnabled(.uart1, false); +} +fn idfUartOn() void { + oracle_clkrst_uart_bus_clock(1, 1); +} +fn ourUartOn() void { + hal.clkrst.setClockEnabled(.uart1, true); +} + +/// The reset pairing, which is the other half of the table and the half that was right. IDF pulses +/// the bit and returns; so does ours, except for the timer groups, where it additionally clears the +/// flash-boot watchdog protection that the reset re-arms - so a difference in the WDT register is +/// expected and lives outside this window, while HP_RST_EN1 itself must match. +fn idfTimgReset() void { + oracle_clkrst_timg_reset(1); +} +fn ourTimgReset() void { + hal.clkrst.resetPeripheral(.timg1); +} +fn idfSystimerReset() void { + oracle_clkrst_systimer_reset(); +} +fn ourSystimerReset() void { + hal.clkrst.resetPeripheral(.systimer); +} |
