summaryrefslogtreecommitdiff
path: root/src/oracle/gpio_cases.zig
blob: ea32eff971955fd4d1f1bb93155f1d398dae0876 (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
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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//! GPIO's side of the differential test: the same operations expressed as ESP-IDF's LL calls and as
//! this project's HAL calls.
//!
//! GPIO is restored by configuring rather than by resetting. It has no reset bit of its own in
//! HP_SYS_CLKRST, and the pads are the board's wiring - the console's own pins are in this block, so
//! a reset here would take the console with it. Configuring is sound for GPIO specifically because
//! every field in the block is plain read/write: there is nothing self-clearing to restore.

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_gpio_uses_rom_api() c_int;
extern fn oracle_gpio_set_level(pin: c_uint, level: c_uint) void;
extern fn oracle_gpio_output_enable(pin: c_uint) void;
extern fn oracle_gpio_output_disable(pin: c_uint) void;
extern fn oracle_gpio_input_enable(pin: c_uint) void;
extern fn oracle_gpio_input_disable(pin: c_uint) void;
extern fn oracle_gpio_func_sel(pin: c_uint, func: c_uint) void;
extern fn oracle_gpio_set_drive(pin: c_uint, strength: c_uint) void;
extern fn oracle_gpio_pullup_en(pin: c_uint) void;
extern fn oracle_gpio_pullup_dis(pin: c_uint) void;
extern fn oracle_gpio_pulldown_en(pin: c_uint) void;
extern fn oracle_gpio_pulldown_dis(pin: c_uint) void;
extern fn oracle_gpio_matrix_out(pin: c_uint, signal: c_uint) void;
extern fn oracle_gpio_od_enable(pin: c_uint) void;
extern fn oracle_gpio_od_disable(pin: c_uint) void;

/// Whether ESP-IDF's LL was compiled to call the mask ROM instead of writing registers. Must be 0,
/// or the differential is comparing this HAL against `rom_gpio_set_output_level` rather than against
/// IDF's register sequence. Governed by src/oracle/oracle_sdkconfig.h.
pub fn usesRomApi() bool {
    return oracle_gpio_uses_rom_api() != 0;
}

/// The pin under test. A module-level variable because Zig has no closures and the harness stores
/// plain `fn` pointers: a comptime-specialised pair per pin would compare code this project does not
/// ship instead of the code it does.
pub var pin: u8 = 20;

/// Pins worth testing. 20 is the board's LED pin and 33 is a free header pin above the 32-boundary
/// where this peripheral's bank arithmetic changes. GPIO54 is deliberately absent: it is this
/// board's ESP32-C6 reset line, held high by an external pull-up, and driving it resets the radio.
pub const pins = [_]u8{ 20, 33 };

/// Restore, built from register macros only.
///
/// Nothing here may call the code under test. `differ.zig` runs restore, idf, snapshot, restore,
/// ours, snapshot - so if restore is written with the HAL, run B starts from whatever IDF just wrote
/// and a HAL function that does nothing at all compares equal. This suite used to restore with
/// `hal.gpio.outputDisable` and `hal.gpio.setLow`, which made `output_disable` and `set_level(0)`
/// no-op-versus-no-op: they could not fail.
///
/// It must also be *total* over everything any case touches. Leaving `GPIO_PIN{n}_REG` alone made
/// both `open_drain` cases vacuous, because run B inherited run A's pad_driver bit.
fn restore() void {
    const b: u5 = @intCast(if (pin < 32) pin else pin - 32);
    const m = @as(u32, 1) << b;
    const enable_w1tc = if (pin < 32) regs.GPIO_ENABLE_W1TC_REG else regs.GPIO_ENABLE1_W1TC_REG;
    const out_w1tc = if (pin < 32) regs.GPIO_OUT_W1TC_REG else regs.GPIO_OUT1_W1TC_REG;
    mmio.Reg.atAddress(@intCast(enable_w1tc)).writeRaw(m);
    mmio.Reg.atAddress(@intCast(out_w1tc)).writeRaw(m);
    // The IO MUX pad word, the matrix output selector, and the GPIO block's own per-pin register.
    mmio.Reg.atAddress(@as(u32, @intCast(regs.PERIPHS_IO_MUX_U_PAD_GPIO0)) + 4 * @as(u32, pin)).writeRaw(0);
    mmio.Reg.atAddress(@as(u32, @intCast(regs.GPIO_FUNC0_OUT_SEL_CFG_REG)) + 4 * @as(u32, pin))
        .writeRaw(@intCast(regs.SIG_GPIO_OUT_IDX));
    mmio.Reg.atAddress(@as(u32, @intCast(regs.GPIO_PIN0_REG)) + 4 * @as(u32, pin)).writeRaw(0);
}

pub const suite: types.Suite = .{
    .descriptor = .{
        .name = "gpio",
        .base = @intCast(regs.GPIO_OUT_REG - 4), // GPIO_BT_SELECT_REG sits at +0x00
        // 0x640 bytes. The window has to reach 0x558 + 4*57, where the matrix's per-pad output
        // configuration lives: a first version stopped at 0x1C0 and was blind to a real bug in
        // exactly those words - it saw the redundant GPIO_ENABLE write but not the wrong OEN_SEL
        // that made it necessary.
        .words = 400,
        .volatile_words = &.{
            (0x03c - 0x000) / 4, // GPIO_IN - reflects the outside world, which moves
            (0x040 - 0x000) / 4, // GPIO_IN1
        },
        .restore = .{ .configure = restore },
    },
    .cases = &.{
        .{ .name = "set_level", .arg = 1, .idf = idfSetHigh, .ours = ourSetHigh },
        .{ .name = "set_level", .arg = 0, .idf = idfSetLow, .ours = ourSetLow },
        .{ .name = "output_enable", .idf = idfOutEnable, .ours = ourOutEnable },
        .{ .name = "output_disable", .idf = idfOutDisable, .ours = ourOutDisable },
        .{ .name = "input_enable", .idf = idfInEnable, .ours = ourInEnable },
        .{ .name = "input_disable", .idf = idfInDisable, .ours = ourInDisable },
        .{ .name = "func_sel_gpio", .arg = 1, .idf = idfFuncGpio, .ours = ourFuncGpio },
        .{ .name = "drive", .arg = 3, .idf = idfDriveStrong, .ours = ourDriveStrong },
        .{ .name = "drive", .arg = 0, .idf = idfDriveWeakest, .ours = ourDriveWeakest },
        .{ .name = "pull_up", .idf = idfPullUp, .ours = ourPullUp },
        .{ .name = "pull_down", .idf = idfPullDown, .ours = ourPullDown },
        .{ .name = "pull_none", .idf = idfPullNone, .ours = ourPullNone },
        .{ .name = "matrix_out", .arg = 43, .idf = idfMatrixOut, .ours = ourMatrixOut },
        // Open drain lives in the GPIO block's per-pin register, not the IO MUX pad register, and
        // had no accessor until the I2C port needed one - that bus is wired-AND, and a pin left
        // push-pull shorts it against another device's driver.
        .{ .name = "open_drain", .arg = 1, .idf = idfOdOn, .ours = ourOdOn },
        .{ .name = "open_drain", .arg = 0, .idf = idfOdOff, .ours = ourOdOff },
    },
};

/// The IO MUX, which the GPIO block's window does not reach.
///
/// Every pad-configuration function on this chip writes `IO_MUX.gpio[n]` at
/// PERIPHS_IO_MUX_U_PAD_GPIO0 = 0x500E1004 + 4*pin, and the GPIO block's compared window ends at
/// 0x500E063F - 0xC00 bytes short. So `input_enable`, `input_disable`, `func_sel`, both `drive`
/// cases and all three `pull` cases were comparing two identical snapshots of a register file none
/// of them touches: 8 operations across 2 pins, 16 of the suite's cases, structurally unable to
/// fail. They are the same cases; only the window is different.
pub const iomux_suite: types.Suite = .{
    .descriptor = .{
        .name = "iomux",
        .base = @intCast(regs.PERIPHS_IO_MUX_U_PAD_GPIO0),
        .words = 57, // one per pad, GPIO0..GPIO56
        .restore = .{ .configure = restoreIomux },
    },
    .cases = &.{
        .{ .name = "input_enable", .idf = idfInEnable, .ours = ourInEnable },
        .{ .name = "input_disable", .idf = idfInDisable, .ours = ourInDisable },
        .{ .name = "func_sel_gpio", .arg = 1, .idf = idfFuncGpio, .ours = ourFuncGpio },
        .{ .name = "drive", .arg = 3, .idf = idfDriveStrong, .ours = ourDriveStrong },
        .{ .name = "drive", .arg = 0, .idf = idfDriveWeakest, .ours = ourDriveWeakest },
        .{ .name = "pull_up", .idf = idfPullUp, .ours = ourPullUp },
        .{ .name = "pull_down", .idf = idfPullDown, .ours = ourPullDown },
        .{ .name = "pull_none", .idf = idfPullNone, .ours = ourPullNone },
    },
};

fn restoreIomux() void {
    mmio.Reg.atAddress(@as(u32, @intCast(regs.PERIPHS_IO_MUX_U_PAD_GPIO0)) + 4 * @as(u32, pin)).writeRaw(0);
}

fn idfSetHigh() void {
    oracle_gpio_set_level(pin, 1);
}
fn ourSetHigh() void {
    hal.gpio.setHigh(pin);
}
fn idfSetLow() void {
    oracle_gpio_set_level(pin, 0);
}
fn ourSetLow() void {
    hal.gpio.setLow(pin);
}
fn idfOutEnable() void {
    oracle_gpio_output_enable(pin);
}
fn ourOutEnable() void {
    hal.gpio.outputEnable(pin);
}
fn idfOutDisable() void {
    oracle_gpio_output_disable(pin);
}
fn ourOutDisable() void {
    hal.gpio.outputDisable(pin);
}
fn idfInEnable() void {
    oracle_gpio_input_enable(pin);
}
fn ourInEnable() void {
    hal.gpio.setInputEnable(pin, true);
}
fn idfInDisable() void {
    oracle_gpio_input_disable(pin);
}
fn ourInDisable() void {
    hal.gpio.setInputEnable(pin, false);
}
fn idfFuncGpio() void {
    oracle_gpio_func_sel(pin, 1);
}
fn ourFuncGpio() void {
    hal.gpio.setFunction(pin, .gpio);
}
fn idfDriveStrong() void {
    oracle_gpio_set_drive(pin, 3);
}
fn ourDriveStrong() void {
    hal.gpio.setDrive(pin, .strong);
}
fn idfDriveWeakest() void {
    oracle_gpio_set_drive(pin, 0);
}
fn ourDriveWeakest() void {
    hal.gpio.setDrive(pin, .weakest);
}
fn idfPullUp() void {
    oracle_gpio_pullup_en(pin);
    oracle_gpio_pulldown_dis(pin);
}
fn ourPullUp() void {
    hal.gpio.setPull(pin, .up);
}
fn idfPullDown() void {
    oracle_gpio_pulldown_en(pin);
    oracle_gpio_pullup_dis(pin);
}
fn ourPullDown() void {
    hal.gpio.setPull(pin, .down);
}
fn idfPullNone() void {
    oracle_gpio_pullup_dis(pin);
    oracle_gpio_pulldown_dis(pin);
}
fn ourPullNone() void {
    hal.gpio.setPull(pin, .none);
}
fn idfMatrixOut() void {
    oracle_gpio_matrix_out(pin, 43);
}
fn ourMatrixOut() void {
    hal.gpio.matrixOut(pin, 43);
}

fn idfOdOn() void {
    oracle_gpio_od_enable(pin);
}
fn ourOdOn() void {
    hal.gpio.setOpenDrain(pin, true);
}
fn idfOdOff() void {
    oracle_gpio_od_disable(pin);
}
fn ourOdOff() void {
    hal.gpio.setOpenDrain(pin, false);
}