ax_cpu/aarch64/uspace.rs
1//! Structures and functions for user space.
2
3use core::{
4 mem::{offset_of, size_of},
5 ops::{Deref, DerefMut},
6};
7
8use aarch64_cpu::registers::{ESR_EL1, FAR_EL1, Readable};
9use ax_memory_addr::VirtAddr;
10use tock_registers::LocalRegisterCopy;
11
12use super::trap::{TrapKind, is_valid_page_fault};
13pub use crate::uspace_common::{ExceptionKind, ExceptionSyndrome, ReturnReason};
14use crate::{TrapFrame, trap::PageFaultFlags};
15
16/// Context to enter user space.
17#[repr(C, align(16))]
18#[derive(Debug, Clone, Copy)]
19pub struct UserContext {
20 tf: TrapFrame,
21 /// Stack Pointer (SP_EL0).
22 pub sp: u64,
23 /// Software Thread ID Register (TPIDR_EL0).
24 pub tpidr: u64,
25}
26
27// SAFETY: `TrapFrame`, `sp`, and `tpidr` are contiguous integer storage and
28// their combined size is already a multiple of the declared 16-byte alignment.
29unsafe impl bytemuck::NoUninit for UserContext {}
30
31const _: () = {
32 assert!(size_of::<TrapFrame>() == 34 * size_of::<u64>());
33 assert!(offset_of!(UserContext, tf) == 0);
34 assert!(offset_of!(UserContext, sp) == size_of::<TrapFrame>());
35 assert!(offset_of!(UserContext, tpidr) == size_of::<TrapFrame>() + size_of::<u64>());
36 assert!(size_of::<UserContext>() == size_of::<TrapFrame>() + 2 * size_of::<u64>());
37};
38
39impl UserContext {
40 /// Creates a new user context with the given entry point, stack top, and argument.
41 pub fn new(entry: usize, ustack_top: VirtAddr, arg0: usize) -> Self {
42 use aarch64_cpu::registers::SPSR_EL1;
43 let mut regs = [0; 31];
44 regs[0] = arg0 as _;
45 Self {
46 tf: TrapFrame {
47 x: regs,
48 elr: entry as _,
49 spsr: (SPSR_EL1::M::EL0t
50 + SPSR_EL1::D::Masked
51 + SPSR_EL1::A::Masked
52 + SPSR_EL1::I::Unmasked
53 + SPSR_EL1::F::Masked)
54 .value,
55 sp: 0,
56 },
57 sp: ustack_top.as_usize() as _,
58 tpidr: 0,
59 }
60 }
61
62 /// Emulates an EL0 `MRS Xt, ID_AA64*_EL1` that trapped as an "unknown"
63 /// exception (EC=0), mirroring Linux's `emulate_mrs`. AArch64 CPU-feature
64 /// detection — e.g. the Go runtime's cpu probing — reads these ID feature
65 /// registers from EL0; reading an EL1 register from EL0 is UNDEFINED and
66 /// traps, which would otherwise be delivered as SIGILL and crash every such
67 /// program. Reads the real register (the kernel runs at EL1) into `Xt`,
68 /// advances the PC, and returns `true`; returns `false` if the faulting
69 /// instruction is not one of the emulated ID-register reads so the caller can
70 /// fall back to SIGILL.
71 ///
72 /// # Safety
73 /// Reads the 4-byte instruction at the trapped PC (`elr`) from the active
74 /// user address space. The caller invokes this only while that aspace is
75 /// installed and the PC was just executing, so the page is mapped.
76 pub unsafe fn emulate_mrs_id_reg(&mut self) -> bool {
77 let insn = unsafe { core::ptr::read(self.tf.elr as *const u32) };
78 // MRS (register, read direction): bits[31:20] == 0xD53 and L (bit 21) set.
79 if (insn & 0xFFF0_0000) != 0xD530_0000 || (insn & (1 << 21)) == 0 {
80 return false;
81 }
82 let op0 = (insn >> 19) & 0x3;
83 let op1 = (insn >> 16) & 0x7;
84 let crn = (insn >> 12) & 0xf;
85 let crm = (insn >> 8) & 0xf;
86 let op2 = (insn >> 5) & 0x7;
87 let rt = (insn & 0x1f) as usize;
88 // The AArch64 ID feature register space is op0=3, op1=0, CRn=0, CRm=4..7.
89 // We must NOT leak the raw EL1 register to EL0 (as Linux's `emulate_mrs`
90 // also avoids): the host/QEMU CPU may advertise SVE/SME/MTE/BTI/PAuth/RAS
91 // etc. that need kernel context-save/enable flows StarryOS does not have.
92 // A program reading those bits would then execute the corresponding
93 // instructions and crash or corrupt state. So we expose a sanitized
94 // user-safe view: keep only the feature bits whose instructions are plain
95 // and stateless (so they Just Work, including under TCG), and report
96 // everything else as not-implemented (RAZ).
97 macro_rules! rd {
98 ($reg:literal) => {{
99 let v: u64;
100 unsafe { core::arch::asm!(concat!("mrs {}, ", $reg), out(reg) v) };
101 v
102 }};
103 }
104 // Field masks (each ID field is 4 bits):
105 // PFR0 low 24 bits = EL0/EL1/EL2/EL3/FP/AdvSIMD — baseline, always safe.
106 // Bits >=24 (GIC/RAS/SVE/SEL2/MPAM/AMU) are hidden.
107 const PFR0_SAFE: u64 = 0x0000_0000_00FF_FFFF;
108 // ISAR1 PAuth fields APA[7:4] API[11:8] GPA[27:24] GPI[31:28] need kernel
109 // key management; clear them, keep DPB/JSCVT/FCMA/LRCPC/SB/BF16/I8MM/...
110 const ISAR1_PAUTH: u64 = 0x0000_0000_FF00_0FF0;
111 let val: u64 = match (op0, op1, crn, crm, op2) {
112 (3, 0, 0, 4, 0) => rd!("ID_AA64PFR0_EL1") & PFR0_SAFE,
113 (3, 0, 0, 6, 0) => rd!("ID_AA64ISAR0_EL1"),
114 (3, 0, 0, 6, 1) => rd!("ID_AA64ISAR1_EL1") & !ISAR1_PAUTH,
115 (3, 0, 0, 7, 0) => rd!("ID_AA64MMFR0_EL1"),
116 (3, 0, 0, 7, 1) => rd!("ID_AA64MMFR1_EL1"),
117 (3, 0, 0, 7, 2) => rd!("ID_AA64MMFR2_EL1"),
118 // Every other ID register in the architectural space — PFR1/PFR2,
119 // DFR0/1/2, ZFR0 (SVE), SMFR0 (SME), ISAR2/3 (PAuth/MOPS), MMFR3/4,
120 // reserved — describes state-bearing or kernel-only features StarryOS
121 // does not implement. Report not-implemented (RAZ) rather than SIGILL,
122 // so feature probing degrades to the baseline path instead of crashing.
123 (3, 0, 0, 4..=7, _) => 0,
124 _ => return false,
125 };
126 // Rt == 31 encodes XZR; the result is discarded.
127 if rt < 31 {
128 self.tf.x[rt] = val;
129 }
130 self.tf.elr += 4;
131 true
132 }
133
134 /// Normalizes a cloned user context so it can safely return to EL0.
135 pub fn prepare_clone_child_return_state(&mut self) {
136 use aarch64_cpu::registers::SPSR_EL1;
137
138 self.tf.spsr = (self.tf.spsr
139 & !(SPSR_EL1::M.mask
140 | SPSR_EL1::D.mask
141 | SPSR_EL1::A.mask
142 | SPSR_EL1::I.mask
143 | SPSR_EL1::F.mask))
144 | (SPSR_EL1::M::EL0t
145 + SPSR_EL1::D::Masked
146 + SPSR_EL1::A::Masked
147 + SPSR_EL1::I::Unmasked
148 + SPSR_EL1::F::Masked)
149 .value;
150 }
151
152 /// Clears any architecture single-step state after a debug exception.
153 ///
154 /// AArch64 user single-step is currently emulated by the Starry ptrace layer,
155 /// so there is no saved CPU flag to clear here.
156 pub const fn clear_single_step_after_debug(&mut self) -> bool {
157 false
158 }
159
160 /// Returns the syscall instruction length in bytes.
161 pub const fn syscall_insn_len(&self) -> usize {
162 4
163 }
164
165 /// Gets the stack pointer.
166 pub const fn sp(&self) -> usize {
167 self.sp as _
168 }
169
170 /// Sets the stack pointer.
171 pub const fn set_sp(&mut self, sp: usize) {
172 self.sp = sp as _;
173 }
174
175 /// Gets the TLS area.
176 pub const fn tls(&self) -> usize {
177 self.tpidr as _
178 }
179
180 /// Sets the TLS area.
181 pub const fn set_tls(&mut self, tls: usize) {
182 self.tpidr = tls as _;
183 }
184
185 /// Returns whether this register image can be restored as an interruptible
186 /// EL0 context.
187 pub fn has_interruptible_user_return_mode(&self) -> bool {
188 use aarch64_cpu::registers::SPSR_EL1;
189
190 let runtime_daif =
191 SPSR_EL1::D::Masked + SPSR_EL1::A::Masked + SPSR_EL1::I::Unmasked + SPSR_EL1::F::Masked;
192 self.tf.spsr & SPSR_EL1::M::EL0t.mask() == SPSR_EL1::M::EL0t.value
193 && self.tf.spsr & runtime_daif.mask() == runtime_daif.value
194 }
195
196 /// Enters user space without validating the runtime transition.
197 ///
198 /// It restores the user registers and jumps to the user entry point
199 /// (saved in `elr`).
200 ///
201 /// This function returns when an exception or syscall occurs.
202 ///
203 /// # Safety
204 ///
205 /// The caller must be the runtime's prepared user-entry boundary for the
206 /// current scheduler task. Its context-switch tail must be complete, no
207 /// IRQ/preemption guard or hard interrupt may be active, and local IRQs
208 /// must remain disabled after the final scheduler-work check. The active
209 /// logical address space, hardware root and CPU footprint must match this
210 /// task and keep every user address referenced by `self` valid. SPSR must
211 /// describe an interruptible EL0 return. No code may run between those
212 /// validations and this call.
213 ///
214 /// Safe code cannot invoke this raw boundary:
215 ///
216 /// ```compile_fail
217 /// fn bypass_runtime(context: &mut ax_cpu::uspace::UserContext) {
218 /// context.run_unchecked();
219 /// }
220 /// ```
221 pub unsafe fn run_unchecked(&mut self) -> ReturnReason {
222 unsafe extern "C" {
223 fn enter_user(uctx: &mut UserContext) -> TrapKind;
224 }
225
226 assert!(
227 !crate::asm::irqs_enabled(),
228 "raw user entry requires the prepared IRQ-off boundary"
229 );
230 assert!(
231 self.has_interruptible_user_return_mode(),
232 "raw user entry requires an interruptible EL0 register image"
233 );
234 let kind = unsafe { enter_user(self) };
235
236 let ret = match kind {
237 TrapKind::Irq => {
238 crate::trap::dispatch_irq(0, crate::trap::TrapOrigin::User);
239 ReturnReason::Interrupt
240 }
241 TrapKind::Fiq | TrapKind::SError => ReturnReason::Unknown,
242 TrapKind::Synchronous => {
243 let esr = ESR_EL1.extract();
244 let far = FAR_EL1.get() as usize;
245
246 let iss = esr.read(ESR_EL1::ISS);
247
248 match esr.read_as_enum(ESR_EL1::EC) {
249 Some(ESR_EL1::EC::Value::SVC64) => ReturnReason::Syscall,
250 Some(ESR_EL1::EC::Value::InstrAbortLowerEL) if is_valid_page_fault(iss) => {
251 ReturnReason::PageFault(
252 va!(far),
253 PageFaultFlags::EXECUTE | PageFaultFlags::USER,
254 )
255 }
256 Some(ESR_EL1::EC::Value::DataAbortLowerEL) if is_valid_page_fault(iss) => {
257 let wnr = (iss & (1 << 6)) != 0; // WnR: Write not Read
258 let cm = (iss & (1 << 8)) != 0; // CM: Cache maintenance
259 ReturnReason::PageFault(
260 va!(far),
261 if wnr & !cm {
262 PageFaultFlags::WRITE
263 } else {
264 PageFaultFlags::READ
265 } | PageFaultFlags::USER,
266 )
267 }
268 _ => ReturnReason::Exception(ExceptionInfo { esr, far }),
269 }
270 }
271 };
272
273 crate::asm::enable_irqs();
274 ret
275 }
276}
277
278const _: unsafe fn(&mut UserContext) -> ReturnReason = UserContext::run_unchecked;
279
280impl Deref for UserContext {
281 type Target = TrapFrame;
282
283 fn deref(&self) -> &Self::Target {
284 &self.tf
285 }
286}
287
288impl DerefMut for UserContext {
289 fn deref_mut(&mut self) -> &mut Self::Target {
290 &mut self.tf
291 }
292}
293
294/// Information about an exception that occurred in user space.
295#[derive(Debug, Clone, Copy)]
296pub struct ExceptionInfo {
297 /// Exception Syndrome Register
298 pub esr: LocalRegisterCopy<u64, ESR_EL1::Register>,
299 /// Fault Address Register
300 pub far: usize,
301}
302
303impl ExceptionInfo {
304 /// Returns the faulting virtual address when the CPU records one.
305 pub const fn fault_addr(&self) -> Option<usize> {
306 Some(self.far)
307 }
308
309 /// Returns architecture-neutral syndrome information for this exception.
310 pub fn syndrome(&self) -> ExceptionSyndrome {
311 ExceptionSyndrome {
312 raw: self.esr_value(),
313 class: self.ec_value(),
314 iss: self.iss_value(),
315 }
316 }
317
318 /// Returns the raw Exception Syndrome Register value.
319 pub fn esr_value(&self) -> u64 {
320 self.esr.get()
321 }
322
323 /// Returns the raw exception class bits.
324 pub fn ec_value(&self) -> u64 {
325 self.esr.read(ESR_EL1::EC)
326 }
327
328 /// Returns the instruction specific syndrome bits.
329 pub fn iss_value(&self) -> u64 {
330 self.esr.read(ESR_EL1::ISS)
331 }
332
333 /// Returns a generalized kind of this exception.
334 pub fn kind(&self) -> ExceptionKind {
335 match self.esr.read_as_enum(ESR_EL1::EC) {
336 Some(ESR_EL1::EC::Value::Brk64) | Some(ESR_EL1::EC::Value::Bkpt32) => {
337 ExceptionKind::Breakpoint
338 }
339 Some(ESR_EL1::EC::Value::IllegalExecutionState) | Some(ESR_EL1::EC::Value::Unknown) => {
340 ExceptionKind::IllegalInstruction
341 }
342 Some(ESR_EL1::EC::Value::PCAlignmentFault)
343 | Some(ESR_EL1::EC::Value::SPAlignmentFault) => ExceptionKind::Misaligned,
344 _ => ExceptionKind::Other,
345 }
346 }
347}