summaryrefslogtreecommitdiff
path: root/examples/iocheck.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-25 12:40:53 -0300
committerGabriel Schneider <[email protected]>2026-08-25 12:46:51 -0300
commitf5f8068fac59b4f16046c2022c2fc7c7e447ef4c (patch)
tree2731a3ed4e51cae09e184e25778eded5fc37d1f5 /examples/iocheck.zig
downloadesp32p4-f5f8068fac59b4f16046c2022c2fc7c7e447ef4c.tar.gz
esp32p4-f5f8068fac59b4f16046c2022c2fc7c7e447ef4c.zip
zig-p4: pure-Zig ESP32-P4 toolchain
build.zig generates the linker script and drives Zig's own LLD; tools/image.zig turns the ELF into a flashable image and tools/{rom,serial}.zig speak the mask ROM loader over the UART. No CMake, ninja, idf.py, esptool, or external linker. src/soc.zig is a comptime register model over ESP-IDF's own *_reg.h headers; src/hal/ adds peripheral sequences; src/io/ implements std.Io for the chip; src/oracle/ diffs this HAL against ESP-IDF's on the die.
Diffstat (limited to 'examples/iocheck.zig')
-rw-r--r--examples/iocheck.zig110
1 files changed, 110 insertions, 0 deletions
diff --git a/examples/iocheck.zig b/examples/iocheck.zig
new file mode 100644
index 0000000..959e7f4
--- /dev/null
+++ b/examples/iocheck.zig
@@ -0,0 +1,110 @@
+//! Does `std.Io.sleep` wake up on this chip?
+//!
+//! Written to answer one question during radio bring-up. ESP-Hosted's transport stops immediately
+//! after a successful SDIO card init, at a `_h_msleep(100)` - and a cooperative scheduler whose
+//! sleep never returns is indistinguishable, from the outside, from a driver that hung. The 21 host
+//! tests in src/io/p4.zig cover the scheduler against a virtual clock; what they cannot cover is
+//! whether a real deadline against the real systimer ever comes due.
+//!
+//! Four claims, each answered by the die:
+//!
+//! MARK IO_NOW the timebase advances at all, and at 16 MHz
+//! MARK IO_SLEEP a sleep from the main task returns, and after roughly the right time
+//! MARK IO_TASK a spawned task runs, sleeps, and finishes
+//! MARK IO_ORDER three tasks with different deadlines wake in deadline order
+//!
+//! Run: zig build -Dapp=examples/iocheck.zig run -Dseconds=15
+
+const std = @import("std");
+const soc = @import("soc");
+const hal = @import("hal");
+const p4 = @import("io");
+
+pub const std_options: std.Options = .{ .page_size_min = 4096 };
+
+pub const panic = std.debug.FullPanic(struct {
+ fn call(msg: []const u8, _: ?usize) noreturn {
+ soc.rom.print("MARK IO_PANIC %s\r\n", .{msg.ptr});
+ while (true) {}
+ }
+}.call);
+
+var pool: p4.Static(4, 4096) = .{};
+
+/// Wake order, appended by the three tasks in `IO_ORDER`.
+var order: [3]u8 = .{ 0, 0, 0 };
+var order_len: usize = 0;
+
+fn sleeper(ctx: struct { io: std.Io, ms: i64, tag: u8 }) void {
+ ctx.io.sleep(.fromMilliseconds(ctx.ms), .awake) catch {};
+ order[order_len] = ctx.tag;
+ order_len += 1;
+}
+
+export fn zig_main() noreturn {
+ // The bootloader leaves this armed and nothing here feeds it.
+ _ = hal.rwdt.disable();
+
+ const runtime = pool.init(.{});
+ const io = runtime.io();
+
+ // 1. Does the timebase move? Everything below is meaningless if not.
+ const t0 = hal.systimer.read(.unit0) orelse 0;
+ soc.rom.ets_delay_us(10_000);
+ const t1 = hal.systimer.read(.unit0) orelse 0;
+ soc.rom.print("MARK IO_NOW advanced=%u ticks expect~160000\r\n", .{@as(u32, @truncate(t1 - t0))});
+
+ // 2. The question that matters: does a sleep from the calling task return? Measured against the
+ // systimer rather than trusted, because a sleep that returns instantly would also look like
+ // success from a print alone.
+ const before = hal.systimer.read(.unit0) orelse 0;
+ io.sleep(.fromMilliseconds(100), .awake) catch |err| {
+ soc.rom.print("MARK IO_SLEEP failed=%s\r\n", .{@errorName(err).ptr});
+ park();
+ };
+ const after = hal.systimer.read(.unit0) orelse 0;
+ const slept_ms = (after - before) / 16_000;
+ soc.rom.print("MARK IO_SLEEP returned after %u ms, asked 100\r\n", .{@as(u32, @truncate(slept_ms))});
+
+ // 3. A spawned task that sleeps: this is the shape ESP-Hosted's threads have.
+ var fut = io.async(sleeper, .{.{ .io = io, .ms = 20, .tag = 'A' }});
+ fut.await(io);
+ soc.rom.print("MARK IO_TASK ran=%u\r\n", .{@as(u32, @intCast(order_len))});
+
+ // 4. Three deadlines, out of submission order, to prove the scheduler picks the earliest.
+ order_len = 0;
+ var f1 = io.async(sleeper, .{.{ .io = io, .ms = 60, .tag = '3' }});
+ var f2 = io.async(sleeper, .{.{ .io = io, .ms = 20, .tag = '1' }});
+ var f3 = io.async(sleeper, .{.{ .io = io, .ms = 40, .tag = '2' }});
+ f1.await(io);
+ f2.await(io);
+ f3.await(io);
+ soc.rom.print("MARK IO_ORDER %c%c%c expect 123\r\n", .{
+ @as(u32, order[0]), @as(u32, order[1]), @as(u32, order[2]),
+ });
+
+ soc.rom.print("MARK IO_DONE\r\n", .{});
+ park();
+}
+
+fn park() noreturn {
+ while (true) {}
+}
+
+export fn _start() linksection(".text.entry") callconv(.naked) noreturn {
+ asm volatile (
+ \\ li t0, 1 << 13
+ \\ csrs mstatus, t0
+ \\ la sp, __stack_top
+ \\ mv fp, sp
+ \\ la t0, __bss_start
+ \\ la t1, __bss_end
+ \\ bgeu t0, t1, 2f
+ \\1:
+ \\ sw zero, 0(t0)
+ \\ addi t0, t0, 4
+ \\ bltu t0, t1, 1b
+ \\2:
+ \\ j zig_main
+ );
+}