summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/pardes/app.zig34
-rw-r--r--tools/bench_main.zig28
2 files changed, 60 insertions, 2 deletions
diff --git a/src/pardes/app.zig b/src/pardes/app.zig
index 8b43791..17bef83 100644
--- a/src/pardes/app.zig
+++ b/src/pardes/app.zig
@@ -64,6 +64,10 @@ const uart = @import("uart.zig");
/// How the editor emits bytes. Called with finished runs of ANSI, many times per frame.
const WriteFn = *const fn (ctx: ?*anyopaque, ptr: [*]const u8, len: usize) callconv(.c) void;
+/// The board's pads, offered to the editor. Optional on the wire so a firmware with nothing to
+/// toggle passes null and the `Gpio` word reports that rather than the object guessing.
+const GpioFn = *const fn (ctx: ?*anyopaque, pin: u16, was: *u8, now: *u8) callconv(.c) bool;
+
/// This board's allocator, handed across as plain function pointers. `log2_align` is a log2 value,
/// which is exactly how `std.mem.Alignment` represents itself, so neither side needs a conversion
/// table.
@@ -81,7 +85,7 @@ const Allocator = extern struct {
/// The one number both sides must agree on. Linkers do not type-check C symbols, so a signature
/// that drifts on one side of this seam links cleanly and then corrupts the stack; checking this
/// before calling anything else turns that into a refusal to boot.
-const abi_version: u32 = 1;
+const abi_version: u32 = 2;
extern fn pardes_p4_abi_version() callconv(.c) u32;
/// Hand over the allocator and the output sink, and state the initial window size. Returns 0, or a
@@ -89,6 +93,7 @@ extern fn pardes_p4_abi_version() callconv(.c) u32;
extern fn pardes_p4_init(
alloc: *const Allocator,
write: WriteFn,
+ gpio: ?GpioFn,
ctx: ?*anyopaque,
cols: u16,
rows: u16,
@@ -124,6 +129,31 @@ fn writeOut(_: ?*anyopaque, ptr: [*]const u8, len: usize) callconv(.c) void {
uart.write(ptr[0..len]);
}
+/// Flip one pad and report the level before and after. The editor's `Gpio` word calls this; the
+/// editor has no register of its own for it, deliberately.
+///
+/// THIS IS WHY THE SEAM IS HERE. A toggle is not a write to GPIO_OUT: `configureOutput` points the
+/// pad's IO MUX at the GPIO function, routes the GPIO matrix's output to it, sets the drive strength
+/// and input buffer and clears the pulls, and only then enables the driver - four register files,
+/// indexed by a per-pin table. That code already exists in `hal/gpio.zig`, it is the same call
+/// `src/main.zig` blinks with, and its register numbers are checked against ESP-IDF's own headers by
+/// `zig build diff`. A second copy inside the editor object would be a second copy under no test.
+///
+/// `getDrivenLevel` rather than `getLevel`: the answer is the level this board is DRIVING, which is
+/// defined for every pin. The pad's own level is what the outside world says, and on an unconnected
+/// header pin that is noise. The input buffer is enabled anyway, so `Peek` of GPIO_IN_REG shows the
+/// pad for anyone who wants to compare the two.
+fn gpioToggle(_: ?*anyopaque, pin: u16, was: *u8, now: *u8) callconv(.c) bool {
+ if (pin > hal.gpio.max_pin) return false;
+ const p: u8 = @intCast(pin);
+ hal.gpio.configureOutput(p, .{ .readback = true });
+ const before = hal.gpio.getDrivenLevel(p);
+ if (before == 1) hal.gpio.setLow(p) else hal.gpio.setHigh(p);
+ was.* = before;
+ now.* = hal.gpio.getDrivenLevel(p);
+ return true;
+}
+
// ------------------------------------------------------------------------------------- the heap
/// The span the linker script hands over, from `l2high`'s ORIGIN and LENGTH.
@@ -221,7 +251,7 @@ export fn zig_main() noreturn {
// Ask for more than any grid this board will ever render, so the SHELL's own ceiling is what
// governs - it clamps to `-Dp4-cols`/`-Dp4-rows` and reports the result. Naming 80x24 here made
// the firmware a second opinion about the geometry, which is one opinion too many.
- const rc = pardes_p4_init(&editor_allocator, writeOut, null, 255, 255);
+ const rc = pardes_p4_init(&editor_allocator, writeOut, gpioToggle, null, 255, 255);
if (rc != 0) {
soc.rom.print("MARK PARDES_INIT_FAIL rc=%u\r\n", .{rc});
diff --git a/tools/bench_main.zig b/tools/bench_main.zig
index 18b204e..973c004 100644
--- a/tools/bench_main.zig
+++ b/tools/bench_main.zig
@@ -961,6 +961,34 @@ fn check(port: *serial.Port, o: Options, r: *Report) !void {
r.print(" and a later Peek still finds it {s}\n", .{if (still) "ok" else "FAILED"});
if (!still) failures += 1;
+ // 4. A PAD ACTUALLY FLIPS, AND THE FIRMWARE READS IT BACK RATHER THAN ASSUMING IT.
+ //
+ // `Gpio <pin>` crosses the whole seam this check exists for: the editor's word, the C ABI
+ // (`GpioFn`, which is why the ABI version is 2), the firmware's `hal.gpio` configure-and-drive,
+ // the pad, and the level read back out of the output register onto the message row. Nothing
+ // smaller exercises the ABI at all.
+ //
+ // The ORACLE IS THE ALTERNATION, not either answer alone. `0->1` on its own is what a firmware
+ // that printed a hardcoded string would also say; two runs reporting `0->1` then `1->0` can only
+ // come from a level that was stored somewhere and read again. Three runs, so the third confirms
+ // the second was not a coincidence of ordering.
+ //
+ // GPIO33 because `src/oracle/ledc_cases.zig` already documents it as a free pin on this board's
+ // JP1 header - pin 21. GPIO20 is the blink demo's pin and may have a wire on it.
+ var flips: [3][]const u8 = undefined;
+ var flip_text: [3][4096]u8 = undefined;
+ for (&flips, &flip_text) |*f, *dst| {
+ const raw = try runWord(port, &buf, "Gpio 33");
+ f.* = stripAnsi(dst, raw);
+ }
+ const first_low = std.mem.indexOf(u8, flips[0], "33: 0->1") != null;
+ const alternates = for (flips, 0..) |f, i| {
+ const want = if ((i % 2 == 0) == first_low) "33: 0->1" else "33: 1->0";
+ if (std.mem.indexOf(u8, f, want) == null) break false;
+ } else true;
+ r.print(" a pad flips and reads back {s}\n", .{if (alternates) "ok" else "FAILED"});
+ if (!alternates) failures += 1;
+
// A BURST IS NOT CHECKED HERE, deliberately. The bug it would cover - input lost while the
// transmitter was full - has a deterministic host test in `src/pardes/input_rescue.zig` that
// loses 67 bytes with the rescue removed and needs no board at all. Every hardware oracle for it