//! 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); }