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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
//! Structures and functions for user space.
use core::ops::{Deref, DerefMut};
use aarch64_cpu::registers::{ESR_EL1, FAR_EL1, Readable};
use ax_memory_addr::VirtAddr;
use tock_registers::LocalRegisterCopy;
use super::trap::{TrapKind, is_valid_page_fault};
pub use crate::uspace_common::{ExceptionKind, ReturnReason};
use crate::{TrapFrame, trap::PageFaultFlags};
/// Context to enter user space.
#[repr(C, align(16))]
#[derive(Debug, Clone, Copy)]
pub struct UserContext {
tf: TrapFrame,
/// Stack Pointer (SP_EL0).
pub sp: u64,
/// Software Thread ID Register (TPIDR_EL0).
pub tpidr: u64,
}
impl UserContext {
/// Creates a new user context with the given entry point, stack top, and argument.
pub fn new(entry: usize, ustack_top: VirtAddr, arg0: usize) -> Self {
use aarch64_cpu::registers::SPSR_EL1;
let mut regs = [0; 31];
regs[0] = arg0 as _;
Self {
tf: TrapFrame {
x: regs,
elr: entry as _,
spsr: (SPSR_EL1::M::EL0t
+ SPSR_EL1::D::Masked
+ SPSR_EL1::A::Masked
+ SPSR_EL1::I::Unmasked
+ SPSR_EL1::F::Masked)
.value,
sp: 0,
},
sp: ustack_top.as_usize() as _,
tpidr: 0,
}
}
/// Emulates an EL0 `MRS Xt, ID_AA64*_EL1` that trapped as an "unknown"
/// exception (EC=0), mirroring Linux's `emulate_mrs`. AArch64 CPU-feature
/// detection — e.g. the Go runtime's cpu probing — reads these ID feature
/// registers from EL0; reading an EL1 register from EL0 is UNDEFINED and
/// traps, which would otherwise be delivered as SIGILL and crash every such
/// program. Reads the real register (the kernel runs at EL1) into `Xt`,
/// advances the PC, and returns `true`; returns `false` if the faulting
/// instruction is not one of the emulated ID-register reads so the caller can
/// fall back to SIGILL.
///
/// # Safety
/// Reads the 4-byte instruction at the trapped PC (`elr`) from the active
/// user address space. The caller invokes this only while that aspace is
/// installed and the PC was just executing, so the page is mapped.
pub unsafe fn emulate_mrs_id_reg(&mut self) -> bool {
let insn = unsafe { core::ptr::read(self.tf.elr as *const u32) };
// MRS (register, read direction): bits[31:20] == 0xD53 and L (bit 21) set.
if (insn & 0xFFF0_0000) != 0xD530_0000 || (insn & (1 << 21)) == 0 {
return false;
}
let op0 = (insn >> 19) & 0x3;
let op1 = (insn >> 16) & 0x7;
let crn = (insn >> 12) & 0xf;
let crm = (insn >> 8) & 0xf;
let op2 = (insn >> 5) & 0x7;
let rt = (insn & 0x1f) as usize;
// The AArch64 ID feature register space is op0=3, op1=0, CRn=0, CRm=4..7.
// We must NOT leak the raw EL1 register to EL0 (as Linux's `emulate_mrs`
// also avoids): the host/QEMU CPU may advertise SVE/SME/MTE/BTI/PAuth/RAS
// etc. that need kernel context-save/enable flows StarryOS does not have.
// A program reading those bits would then execute the corresponding
// instructions and crash or corrupt state. So we expose a sanitized
// user-safe view: keep only the feature bits whose instructions are plain
// and stateless (so they Just Work, including under TCG), and report
// everything else as not-implemented (RAZ).
macro_rules! rd {
($reg:literal) => {{
let v: u64;
unsafe { core::arch::asm!(concat!("mrs {}, ", $reg), out(reg) v) };
v
}};
}
// Field masks (each ID field is 4 bits):
// PFR0 low 24 bits = EL0/EL1/EL2/EL3/FP/AdvSIMD — baseline, always safe.
// Bits >=24 (GIC/RAS/SVE/SEL2/MPAM/AMU) are hidden.
const PFR0_SAFE: u64 = 0x0000_0000_00FF_FFFF;
// ISAR1 PAuth fields APA[7:4] API[11:8] GPA[27:24] GPI[31:28] need kernel
// key management; clear them, keep DPB/JSCVT/FCMA/LRCPC/SB/BF16/I8MM/...
const ISAR1_PAUTH: u64 = 0x0000_0000_FF00_0FF0;
let val: u64 = match (op0, op1, crn, crm, op2) {
(3, 0, 0, 4, 0) => rd!("ID_AA64PFR0_EL1") & PFR0_SAFE,
(3, 0, 0, 6, 0) => rd!("ID_AA64ISAR0_EL1"),
(3, 0, 0, 6, 1) => rd!("ID_AA64ISAR1_EL1") & !ISAR1_PAUTH,
(3, 0, 0, 7, 0) => rd!("ID_AA64MMFR0_EL1"),
(3, 0, 0, 7, 1) => rd!("ID_AA64MMFR1_EL1"),
(3, 0, 0, 7, 2) => rd!("ID_AA64MMFR2_EL1"),
// Every other ID register in the architectural space — PFR1/PFR2,
// DFR0/1/2, ZFR0 (SVE), SMFR0 (SME), ISAR2/3 (PAuth/MOPS), MMFR3/4,
// reserved — describes state-bearing or kernel-only features StarryOS
// does not implement. Report not-implemented (RAZ) rather than SIGILL,
// so feature probing degrades to the baseline path instead of crashing.
(3, 0, 0, 4..=7, _) => 0,
_ => return false,
};
// Rt == 31 encodes XZR; the result is discarded.
if rt < 31 {
self.tf.x[rt] = val;
}
self.tf.elr += 4;
true
}
/// Normalizes a cloned user context so it can safely return to EL0.
pub fn prepare_clone_child_return_state(&mut self) {
use aarch64_cpu::registers::SPSR_EL1;
self.tf.spsr = (self.tf.spsr
& !(SPSR_EL1::M.mask
| SPSR_EL1::D.mask
| SPSR_EL1::A.mask
| SPSR_EL1::I.mask
| SPSR_EL1::F.mask))
| (SPSR_EL1::M::EL0t
+ SPSR_EL1::D::Masked
+ SPSR_EL1::A::Masked
+ SPSR_EL1::I::Unmasked
+ SPSR_EL1::F::Masked)
.value;
}
/// Gets the stack pointer.
pub const fn sp(&self) -> usize {
self.sp as _
}
/// Sets the stack pointer.
pub const fn set_sp(&mut self, sp: usize) {
self.sp = sp as _;
}
/// Gets the TLS area.
pub const fn tls(&self) -> usize {
self.tpidr as _
}
/// Sets the TLS area.
pub const fn set_tls(&mut self, tls: usize) {
self.tpidr = tls as _;
}
/// Enters user space.
///
/// It restores the user registers and jumps to the user entry point
/// (saved in `elr`).
///
/// This function returns when an exception or syscall occurs.
pub fn run(&mut self) -> ReturnReason {
unsafe extern "C" {
fn enter_user(uctx: &mut UserContext) -> TrapKind;
}
crate::asm::disable_irqs();
let kind = unsafe { enter_user(self) };
let ret = match kind {
TrapKind::Irq => {
crate::trap::irq_handler(0);
ReturnReason::Interrupt
}
TrapKind::Fiq | TrapKind::SError => ReturnReason::Unknown,
TrapKind::Synchronous => {
let esr = ESR_EL1.extract();
let far = FAR_EL1.get() as usize;
let iss = esr.read(ESR_EL1::ISS);
match esr.read_as_enum(ESR_EL1::EC) {
Some(ESR_EL1::EC::Value::SVC64) => ReturnReason::Syscall,
Some(ESR_EL1::EC::Value::InstrAbortLowerEL) if is_valid_page_fault(iss) => {
ReturnReason::PageFault(
va!(far),
PageFaultFlags::EXECUTE | PageFaultFlags::USER,
)
}
Some(ESR_EL1::EC::Value::DataAbortLowerEL) if is_valid_page_fault(iss) => {
let wnr = (iss & (1 << 6)) != 0; // WnR: Write not Read
let cm = (iss & (1 << 8)) != 0; // CM: Cache maintenance
ReturnReason::PageFault(
va!(far),
if wnr & !cm {
PageFaultFlags::WRITE
} else {
PageFaultFlags::READ
} | PageFaultFlags::USER,
)
}
_ => ReturnReason::Exception(ExceptionInfo { esr, far }),
}
}
};
crate::asm::enable_irqs();
ret
}
}
impl Deref for UserContext {
type Target = TrapFrame;
fn deref(&self) -> &Self::Target {
&self.tf
}
}
impl DerefMut for UserContext {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.tf
}
}
/// Information about an exception that occurred in user space.
#[derive(Debug, Clone, Copy)]
pub struct ExceptionInfo {
/// Exception Syndrome Register
pub esr: LocalRegisterCopy<u64, ESR_EL1::Register>,
/// Fault Address Register
pub far: usize,
}
impl ExceptionInfo {
/// Returns the raw Exception Syndrome Register value.
pub fn esr_value(&self) -> u64 {
self.esr.get()
}
/// Returns the raw exception class bits.
pub fn ec_value(&self) -> u64 {
self.esr.read(ESR_EL1::EC)
}
/// Returns the instruction specific syndrome bits.
pub fn iss_value(&self) -> u64 {
self.esr.read(ESR_EL1::ISS)
}
/// Returns a generalized kind of this exception.
pub fn kind(&self) -> ExceptionKind {
match self.esr.read_as_enum(ESR_EL1::EC) {
Some(ESR_EL1::EC::Value::Brk64) | Some(ESR_EL1::EC::Value::Bkpt32) => {
ExceptionKind::Breakpoint
}
Some(ESR_EL1::EC::Value::IllegalExecutionState) | Some(ESR_EL1::EC::Value::Unknown) => {
ExceptionKind::IllegalInstruction
}
Some(ESR_EL1::EC::Value::PCAlignmentFault)
| Some(ESR_EL1::EC::Value::SPAlignmentFault) => ExceptionKind::Misaligned,
_ => ExceptionKind::Other,
}
}
}