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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! Stock SysTick tick source.
//!
//! SysTick itself is architectural (fixed address `0xE000E010` on every
//! Cortex-M) — but the *reload value* that makes it fire at the kernel's
//! configured tick rate depends on the board's clock frequency, which only
//! the board knows. Call [`init`] with a reload value computed by the
//! board (`sysclk_hz / tick_hz`) from `__rivet_board_tick_start`; this
//! module handles the mechanism (counting ticks — not microseconds, so a
//! u32 counter wraps in ~49 days instead of the ~71 minutes a microsecond
//! counter would give) and the timing-sensitive enable-after-PSP-is-valid
//! ordering.
use ;
/// Tick counter. Counts *ticks*, not microseconds: a u32 tick counter at a
/// typical 1 kHz wraps in ~49 days, versus ~71 minutes for a u32
/// microsecond counter. Conversion happens at the API boundary in
/// [`now_micros`]. The tick handler is the only writer.
static SYSTEM_TICKS: AtomicU32 = new;
/// Tick period in microseconds, set by [`init`] from the kernel's
/// configured `TICK_HZ` (not necessarily 1000 — configurable via
/// `RIVET_TICK_HZ`).
static TICK_PERIOD_US: AtomicU32 = new;
/// Configure SysTick's reload value, but deliberately do NOT enable it
/// here (ENABLE/TICKINT bits left clear). If SysTick (and therefore
/// PendSV) could fire this early, it could land while still on the plain
/// boot stack with PSP never set — PendSV's asm unconditionally does `mrs
/// r0, psp; stmia r0, {r4-r11}` assuming PSP is valid, so an uninitialized
/// PSP there faults immediately. [`enable`] is called only once PSP is
/// safely set up (from `__rivet_arch_start_first_task`).
///
/// `reload_ticks` is the board-computed `sysclk_hz / tick_hz`;
/// `tick_period_us` is `1_000_000 / tick_hz`, used to convert the tick
/// count to microseconds in [`now_micros`].
/// Enable SysTick (ENABLE + TICKINT). Call only once PSP has been set up
/// — see [`init`] for why.
/// Override the SysTick reload value (in system-clock ticks) after
/// [`init`]. Safe to call before `run()` (the countdown starts from the
/// new value when SysTick is enabled); also resets the current value so
/// the first underflow uses the new period.
/// Test hook: seed the tick counter so a test can start near the u32
/// boundary and observe a wrap crossing without running days of simulated
/// time. Harmless in production: it merely rewinds/advances the monotonic
/// tick count.
/// Call from the board's `SysTick` exception handler: advance system time,
/// wake expired `Sleep` futures, then request a reschedule opportunity via
/// PendSV (never switches stacks directly).
/// Current system time in microseconds: tick count x tick period,
/// converted at the API boundary.
/// Same as [`now_micros`] but with sub-tick resolution: reads SysTick's own
/// down-counter (`CVR`) for how far into the *current* tick period we are,
/// instead of only reporting whole-tick multiples. [`now_micros`] alone is
/// fine for scheduling deadlines (millisecond-granularity is the point —
/// the tick period *is* the scheduling quantum), but it makes every event
/// that happens between two ticks look like it happened at the exact same
/// instant, which is useless for anything that wants to see what actually
/// happened *within* a tick (e.g. a trace/debugger timeline).
///
/// Reads `SYSTEM_TICKS` before and after `CVR` and retries once if a tick
/// boundary landed between them (rare — only within a few cycles of a
/// rollover) rather than risk pairing a stale tick count with a
/// post-rollover `CVR`, which would show time briefly running backward.
/// Still anchored to the same monotonic tick counter as [`now_micros`], so
/// it carries the same ~49-day wraparound, not the ~4.5-minute wraparound
/// a raw 32-bit cycle counter would have at a typical MCU clock.