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