summaryrefslogtreecommitdiff
path: root/src/io/host.zig
blob: 7239f1afeff51bc830255880677f087cfdc72b7a (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
//! The host half of the scheduler's machine seam: the same eight declarations as `chip.zig`, for a
//! machine that is not the chip.
//!
//! This exists so the scheduler itself can be tested where it can be debugged. Everything above
//! `context.zig` and this file is portable - the run queue, the futex, cancellation, deadline
//! arithmetic - and all of it is the part where a mistake is a silent hang on the die.
//!
//! The clock here is **virtual**, not the wall clock, and that is the point. `ticks` returns a
//! counter that only advances when the scheduler idles, so a test that sleeps three tasks for
//! 5 ms, 1 ms and 3 ms finishes instantly and in a fixed order, with no tolerance windows and no
//! flakiness under load. It also means the scheduler's own deadlock watchdog is testable: the
//! no-deadline idle advances the clock too, so a watchdog measured in ticks still fires.

const std = @import("std");

/// The chip's rate, kept identically here so the host exercises the same tick/nanosecond arithmetic
/// (`* 125 / 2`) rather than a rounder number that would hide a division bug.
pub const ticks_hz: u64 = 16_000_000;

var virtual: u64 = 0;

pub fn init() void {
    virtual = 0;
}

pub fn ticks() u64 {
    return virtual;
}

/// No interrupts to mask on the host, and the scheduler is single-threaded, so this is a shape
/// rather than a mechanism. It still has to exist: it is what makes the chip's masked regions
/// reachable in a host test.
pub const Guard = struct {
    pub inline fn release(_: Guard) void {}
};

pub inline fn mask() Guard {
    return .{};
}

/// Advance the virtual clock. With a deadline, jump straight to it - nothing else can happen in
/// between on a host with no interrupts, so waiting is pure delay. Without one, advance by a
/// millisecond so that a caller spinning on "nothing runnable, nothing scheduled" reaches its
/// watchdog instead of looping forever.
pub fn idle(deadline: ?u64) void {
    if (deadline) |d| {
        if (d > virtual) virtual = d;
    } else {
        virtual += ticks_hz / 1000;
    }
}

/// Enough of `ets_printf`'s shape to typecheck the arguments, printed in Zig's own spelling. The
/// crash path is the only caller, and on the host the interesting question is whether it runs at
/// all, not how it looks.
pub inline fn print(comptime fmt: [*:0]const u8, args: anytype) void {
    std.debug.print("{s} <- {any}\n", .{ std.mem.span(fmt), args });
}

/// No hardware RNG. Returns zero rather than something plausible: `p4.zig` mixes this with the
/// clock and its own state, and a zero here makes it obvious in a test that the hardware
/// contribution is absent instead of quietly supplying the entropy the chip is being asked about.
pub inline fn entropyWord() u32 {
    return 0;
}

/// Deliberately constant: a host test that asserts something about the generator's output wants a
/// fixed seed, and the chip's own seed noise is `soc.cycles()`, which no host has.
pub inline fn noise() u64 {
    return 0x0420_0cafe_0420;
}

/// Test-only: move the virtual clock forward by hand.
pub fn advance(n: u64) void {
    virtual += n;
}