summaryrefslogtreecommitdiff
path: root/src/io/chip.zig
blob: c5e4127ba561713ee805721405f196bd0e467fb0 (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
//! The ESP32-P4 half of the scheduler's machine seam: time, critical sections, idling, entropy and
//! a way to print when nothing else works.
//!
//! This is the only file in `src/io/` that touches the chip, and it is deliberately thin - eight
//! functions - because everything above it is portable and gets tested on the host through
//! `host.zig`, which implements the same eight. Nothing here re-derives a register address or a
//! clock: the timebase is `hal.systimer`, the critical section is `hal.intr`'s (which is
//! `clkrst.Guard` under another name, so a critical section written against either module is the
//! same one), and the console is the mask-ROM `ets_printf` that `soc.rom` already declares.

const std = @import("std");
const hal = @import("hal");
const soc = @import("soc");
const regs = @import("regs");
const mmio = @import("mmio");

/// The one timebase on this board that does not move. SYSTIMER is XTAL/2.5 = 16 MHz, fixed
/// (`clk_tree_defs.h:196-198`, and `hal/systimer.zig:28-31`): it is not derived from the CPU clock,
/// which the bootloader left at a measured ~90 MHz and which nothing here reconfigures. Using the
/// cycle counter instead would make every timeout in the stack wrong by a factor of four the moment
/// somebody raises the PLL.
pub const ticks_hz: u64 = hal.systimer.hz;

/// Last value the counter gave us, so `ticks` can be monotonic even when the read fails.
var last_ticks: u64 = 0;

/// Bring the timebase up. Idempotent, and specifically does *not* reprogram the clock source or
/// divider - `hal.systimer.init` documents why: re-running that on a live counter makes the
/// timebase jump, which would corrupt every deadline already computed from it.
pub fn init() void {
    hal.systimer.init();
    last_ticks = hal.systimer.read(.unit0) orelse 0;
}

/// The 52-bit counter, through the update/valid handshake `hal.systimer.read` implements.
///
/// A gated-off systimer never sets VALUE_VALID, and `hal.systimer.read` reports that as `null`
/// rather than hanging. Returning the previous value there is the only safe answer: returning zero
/// would send time backwards, and every deadline in the scheduler is an unsigned comparison against
/// it, so one backwards step would turn every pending sleep into "already expired".
pub fn ticks() u64 {
    const t = hal.systimer.read(.unit0) orelse return last_ticks;
    last_ticks = t;
    return t;
}

/// A critical section against interrupt handlers. `hal.intr.Guard` nests correctly - `release` only
/// sets mstatus.MIE if MIE was set on entry - so the scheduler can take one inside a handler.
pub const Guard = hal.intr.Guard;

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

/// Wait for something to change. Called with interrupts in whatever state the caller had them,
/// which is normally enabled, and free to return at any time: every caller re-checks its condition.
///
/// This **spins** rather than issuing `wfi`, and that is a decision worth stating. `wfi` is what a
/// power-managed system would do, but it can only be woken by an interrupt, and on this board there
/// is no timer interrupt to wake it: `hal/systimer.zig` exposes the counters and none of the six
/// comparators, and nothing in `hal.intr` is wired to them. A `wfi` with a pending deadline and no
/// alarm configured is a hang, and a `wfi` with no deadline at all is a hang that the scheduler's
/// deadlock watchdog cannot even report, because the watchdog needs to keep running to fire. So
/// this spins on the counter, which costs power and finds every bug.
///
/// The follow-up is small and worth doing when power matters: a SYSTIMER comparator (`TARGET0`,
/// `SYSTIMER_TARGET0_INT`) routed through `hal.intr` would let this be `wfi` with an exact wake.
pub fn idle(deadline: ?u64) void {
    _ = deadline;
    // One counter read is ~20 cycles of handshake, which is a fine spin quantum and re-reads the
    // register the caller is about to compare against anyway.
    _ = ticks();
}

/// Print, when the machinery that would normally print has failed. `ets_printf` is a mask ROM
/// address (`esp32p4.rom.ld:24`, re-declared by this project's linker script), so it allocates
/// nothing, takes no lock, and works before or after any of this project's code is functional.
pub inline fn print(comptime fmt: [*:0]const u8, args: anytype) void {
    soc.rom.print(fmt, args);
}

/// The hardware random number register: `WDEV_RND_REG`, which on a pre-v3 P4 die is
/// `LP_SYSTEM_REG_RNG_DATA_REG` (`components/soc/esp32p4/register/hw_ver1/soc/wdev_reg.h:16`) at
/// `DR_REG_LP_SYS_BASE + 0x1a4` = 0x501101a4. `esp_random` reads exactly this register and nothing
/// else (`components/esp_hw_support/hw_random.c:78,86`).
///
/// How much entropy is behind it is a separate question, and the answer for this image is "not
/// established" - see `p4.zig`'s `random` for what is done about that. The register itself is real.
const rng_data = mmio.Reg.at(regs.LP_SYSTEM_REG_RNG_DATA_REG);

pub inline fn entropyWord() u32 {
    return rng_data.raw();
}

/// Anything else the machine can contribute to a seed. The cycle counter is not a second timebase -
/// it is the same instant measured with a different, unknown divisor - but its low bits carry the
/// jitter of however many bus stalls happened since reset, which is exactly what a seed wants.
pub inline fn noise() u64 {
    return soc.cycles();
}