summaryrefslogtreecommitdiff
path: root/src/oracle/clkrst_cases.zig
blob: f004dd44c0e05ba7f6c334349d7fedc25e7eabca (plain) (blame)
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
//! 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);
}