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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! Xtensa core-internal timer + software interrupt (plan.md Phase 21).
//!
//! Unlike RISC-V's CLINT (a board-attached MMIO peripheral, so its base
//! address is a Group C board fact), Xtensa's `CCOUNT`/`CCOMPARE0` and the
//! software-interrupt-set/clear mechanism are genuine CPU special
//! registers — no MMIO, no base address, nothing board-specific about the
//! *mechanism*. Only the CPU clock rate (needed to convert a tick period
//! in Hz into a `CCOMPARE0` delta in cycles) is a board fact, supplied via
//! [`configure`] from `rivet-bsp-esp32s3`'s `__rivet_board_init`, mirroring
//! `rivet-arch-riscv::clint`'s `configure(base, mtime_hz)` shape minus the
//! base address.
//!
//! The tick (`Timer1`, interrupt number 15, `CCOMPARE1`) and the
//! reschedule self-IPI (`Software1`, interrupt number 29) are configured,
//! on the `xtensa-esp32s3-none-elf` target, at CPU interrupt priority
//! level **3** (confirmed against `xtensa-lx-rt`'s own
//! `config/esp32s3.rs`: `XCHAL_INT15_LEVEL = 3`, `XCHAL_INT29_LEVEL = 3`)
//! — deliberately *not* `Timer0`/`Software0` (level 1, `INT6`/`INT7`):
//! `esp-hal` (linked in by `rivet-bsp-esp32s3` for its clock-tree and
//! watchdog-disable bring-up — see that crate's `Cargo.toml`) claims the
//! level-1 slot itself (`__level_1_interrupt`, defined in
//! `esp-hal::interrupt::xtensa`) for its own peripheral-interrupt
//! dispatch. Two crates cannot both provide the same
//! `xtensa-lx-rt`-mandated symbol — found as a real `multiple definition`
//! link error, not assumed — so Rivet's own scheduler interrupts use
//! level 3 instead, which nothing else in this dependency graph claims.
//! `rivet-arch-xtensa::__level_3_interrupt` is the shared dispatcher for
//! both, distinguishing them by reading which bit(s) are pending.
use ;
/// `PS.WOE` (Window Overflow Enable) — must be set for any windowed-ABI
/// code (which is everything on this target) to run at all; see
/// `lib.rs`'s module docs for where this is used to fabricate a fresh
/// task's initial `PS`.
pub const PS_WOE: u32 = 0x0004_0000;
/// Interrupt bit 15 (`Timer1`, `CCOMPARE1`), level 3.
pub const TIMER1_MASK: u32 = 1 << 15;
/// Interrupt bit 29 (`Software1`), level 3.
pub const SOFTWARE1_MASK: u32 = 1 << 29;
static CPU_HZ: AtomicU32 = new;
static TICK_PERIOD: AtomicU32 = new;
/// Previous `CCOMPARE0` value armed by the tick handler — re-arming from
/// this (not from a fresh `CCOUNT` read) is what keeps tick cadence from
/// drifting by interrupt-entry latency, the same coalescing technique
/// `rivet-arch-riscv::clint` uses for `mtimecmp`.
static CCOMPARE_PREV: AtomicU32 = new;
/// Tell this module the CPU's real clock rate. Must be called before
/// [`tick_start`].
/// Arm the periodic tick at `tick_hz` and enable both `Timer1` and
/// `Software1` at the interrupt controller. Does **not** touch the global
/// interrupt-enable state — matching every other arch's `tick_start`,
/// that first happens when the dispatched task's context is actually
/// resumed (`PS.INTLEVEL` restored to 0 as part of its `Context`).
/// Re-arm `CCOMPARE1` for the next tick. Called from the level-3 handler
/// when `Timer1` is pending.
///
/// # Safety
/// Must only be called from within the level-3 interrupt handler.
pub unsafe