-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcpu.rs
More file actions
207 lines (196 loc) · 8.59 KB
/
Copy pathcpu.rs
File metadata and controls
207 lines (196 loc) · 8.59 KB
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
//! CPU control primitives and critical-section support.
//!
//! See [ADR-0008] for the trait-signature rationale.
//!
//! [ADR-0008]: https://github.com/HodeTech/Tyrne/blob/main/docs/decisions/0008-cpu-trait.md
/// Identifier for a CPU core.
///
/// On aarch64 this is derived from `MPIDR_EL1`; other architectures define
/// the mapping themselves. Core identifiers are stable for the lifetime of
/// a boot.
pub type CoreId = u32;
/// The state of the CPU's interrupt mask, as saved by
/// [`Cpu::disable_irqs`] and consumed by [`Cpu::restore_irq_state`].
///
/// A value returned by `disable_irqs` is otherwise opaque: pass it back
/// unmodified to `restore_irq_state` and do not inspect or recombine its
/// bits. The inner field is `pub` so that BSP implementations can construct
/// the value from raw architecture bits.
///
/// # Canonical zero value
///
/// `IrqState(0)` is the one value callers may legitimately synthesize: it
/// is defined as the **IRQs-enabled (unmasked)** state, and every [`Cpu`]
/// implementor must honour `restore_irq_state(IrqState(0))` as "enable
/// interrupts." This is the natural aarch64 encoding (a zero `DAIF` mask
/// leaves IRQs unmasked); the scheduler relies on it when dispatching a
/// freshly-created task that must begin with interrupts enabled. Test
/// doubles (`tyrne_test_hal::FakeCpu`) follow the same polarity, so a
/// shared fake cannot invert production interrupt semantics.
#[derive(Copy, Clone)]
pub struct IrqState(pub usize);
/// Privileged CPU state and control.
///
/// Covers the single-core primitives the kernel needs during boot, idle,
/// and critical sections: identifying the running core, masking and
/// restoring interrupts, halting until the next interrupt, and
/// synchronizing the instruction stream.
///
/// Multi-core start, context-switch primitives, and topology queries are
/// deliberately not on this trait; they will arrive through their own ADRs
/// alongside the work that needs them.
///
/// # Contract
///
/// - **Object-safe.** The kernel uses `&dyn Cpu`.
/// - **`Send + Sync`.** Compiler-enforced; the same `Cpu` instance may be
/// called from any online core.
/// - **No allocation.** Implementations must not touch the heap.
/// - **Primitive operations.** Each method corresponds to one
/// architecture-level operation; higher-level helpers (such as
/// [`IrqGuard`]) are layered on top, not into the trait.
pub trait Cpu: Send + Sync {
/// Return the identifier of the CPU core on which this call is
/// currently executing.
fn current_core_id(&self) -> CoreId;
/// Mask CPU-level interrupts and return the previous mask state.
///
/// Pair with [`Cpu::restore_irq_state`] to form a critical section.
/// Prefer the [`IrqGuard`] wrapper for RAII-style usage.
fn disable_irqs(&self) -> IrqState;
/// Restore the CPU interrupt mask to the given saved state.
///
/// `state` must be a value previously returned by
/// [`Cpu::disable_irqs`], with one canonical exception: callers may
/// synthesize `IrqState(0)`, which every implementation must treat as
/// "enable interrupts" (see [`IrqState`] § Canonical zero value).
/// Passing any other unsynthesized value is a violation of the calling
/// contract and the resulting behaviour is implementation-defined.
fn restore_irq_state(&self, state: IrqState);
/// Halt the CPU until the next interrupt wakes it.
///
/// On aarch64 this is `WFI`. On other architectures, the equivalent
/// low-power-halt instruction.
fn wait_for_interrupt(&self);
/// Synchronize the instruction stream.
///
/// Required after writing privileged system registers whose effects
/// may not yet be observed by the CPU's in-flight pipeline. On
/// aarch64 this is `ISB`. Data memory barriers are covered by
/// [`core::sync::atomic::fence`] and are not exposed on this trait.
fn instruction_barrier(&self);
}
/// RAII guard that masks interrupts for its lifetime and restores the
/// previous mask state on drop.
///
/// Construct with [`IrqGuard::new`] to enter a critical section. The
/// guard captures the interrupt state at entry, so nested guards compose
/// correctly — dropping an inner guard leaves the outer section's mask
/// in place rather than fully re-enabling interrupts.
///
/// Generic over the concrete CPU type `C` to avoid fat-pointer vtable
/// dispatch on critical-section paths. Dynamic dispatch (`&dyn Cpu`) is also
/// avoided because coercing a concrete type to a trait object at certain
/// inlining depths can produce vtable references that alias unrelated data
/// in `.rodata`; using a concrete type parameter eliminates the coercion site
/// entirely.
///
/// # Example
///
/// ```ignore
/// use tyrne_hal::IrqGuard;
///
/// let _g = IrqGuard::new(cpu);
/// // critical section: interrupts are masked here
/// // `_g` drops at end of scope; interrupts restored to previous state
/// ```
pub struct IrqGuard<'a, C: Cpu> {
cpu: &'a C,
prev: IrqState,
}
impl<'a, C: Cpu> IrqGuard<'a, C> {
/// Enter a critical section by masking interrupts on the current core.
///
/// The interrupt state at the moment of construction is remembered and
/// restored when the returned guard is dropped.
pub fn new(cpu: &'a C) -> Self {
let prev = cpu.disable_irqs();
Self { cpu, prev }
}
}
impl<C: Cpu> Drop for IrqGuard<'_, C> {
fn drop(&mut self) {
self.cpu.restore_irq_state(self.prev);
}
}
/// Read the current Exception Level on aarch64 bare-metal targets.
///
/// Returns the 2-bit `EL` field of the `CurrentEL` system register
/// (0 = EL0, 1 = EL1, 2 = EL2, 3 = EL3). This is the safe-Rust entry
/// point for any code that needs to assert which EL it is currently
/// running at — it lets callers avoid duplicating the inline-asm `MRS`
/// pattern from one site to another.
///
/// Free function rather than a [`Cpu`] trait method per ADR-0024
/// §Open questions: the early-boot path (between `_start` and
/// `kernel_entry`) needs to read the EL before any `Cpu` instance has
/// been constructed, so the helper must be callable without a `&self`
/// receiver. Test mocks (e.g. the `FakeCpu` instances in
/// [`tyrne_test_hal`] and the kernel's `sched` test module) therefore
/// do not need to declare an EL of their own.
///
/// # Availability
///
/// Defined only on `target_arch = "aarch64"` AND `target_os = "none"`
/// (i.e. the bare-metal kernel build). On hosted targets — including
/// `aarch64-apple-darwin` running unit tests, where `CurrentEL` reads
/// would trap with a `SIGILL` because user code is not in EL1 — the
/// function is intentionally absent. Host tests must mock the EL
/// rather than call this helper.
///
/// # Safety
///
/// `MRS x, CurrentEL` is a non-privileged read of a read-only system
/// register. It is callable at every EL ≥ 0, does not modify any
/// state, and `options(nostack, nomem)` is correct — there is no
/// stack-pointer touch and no memory access. The function presents
/// itself as `safe` because it upholds those invariants and returns a
/// plain `u8`.
///
/// **Why `unsafe` is required:** there is no Rust intrinsic, `core`
/// API, or stable safe primitive that exposes the `CurrentEL` system
/// register; inline assembly is the only available primitive for this
/// read on aarch64. Pulling a third-party crate (`aarch64-cpu` or
/// similar) for a single MRS is disproportionate per the project's
/// dependency policy. Audit: UNSAFE-2026-0018.
#[cfg(all(target_arch = "aarch64", target_os = "none"))]
#[must_use]
pub fn current_el() -> u8 {
let raw: u64;
// SAFETY: see the function-level safety paragraph above.
// (a) `unsafe` is required because aarch64 system-register reads
// have no safe-Rust primitive; inline asm is the only path.
// (b) Invariants: `MRS x, CurrentEL` is non-privileged, side-
// effect-free, and available at every EL ≥ 0;
// `options(nostack, nomem)` accurately describes the lack
// of stack and memory access.
// (c) Rejected alternatives: a `cortex-a` / `aarch64-cpu` crate
// would wrap the same MRS but add a dependency for one
// instruction (disproportionate per the dependency policy).
// Audit: UNSAFE-2026-0018.
unsafe {
core::arch::asm!(
"mrs {}, CurrentEL",
out(reg) raw,
options(nostack, nomem),
);
}
// CurrentEL bits [3:2] hold the EL field (0..=3); mask + shift
// and the result fits in u8 trivially.
#[allow(
clippy::cast_possible_truncation,
reason = "EL field is 2 bits — masked value is in 0..=3, fits in u8"
)]
let el = ((raw >> 2) & 0b11) as u8;
el
}