kernel/arch/
user_context.rs

1//! Runtime gating for user-mode FPU / Vector context handling.
2//!
3//! This module complements Cargo feature gates (`user-fpu`, `user-vector`) with
4//! a DTB(FDT)-driven runtime switch. When the relevant Cargo feature is enabled,
5//! the kernel will decide at boot whether user-mode access should actually be
6//! allowed based on information found in the device tree.
7//!
8//! ## DTB overrides
9//!
10//! If present under `/chosen`, the following properties override auto-detection:
11//!
12//! - `scarlet,user-fpu` (boolean or u32/u64)
13//! - `scarlet,user-vector` (boolean or u32/u64)
14//!
15//! When absent, the kernel attempts architecture-specific detection when
16//! available (currently implemented for RISC-V via `riscv,isa`).
17
18use core::sync::atomic::{AtomicBool, AtomicU8, Ordering};
19
20use crate::device::fdt::FdtManager;
21
22const FLAG_USER_FPU: u8 = 1 << 0;
23const FLAG_USER_VECTOR: u8 = 1 << 1;
24
25static INITIALIZED: AtomicBool = AtomicBool::new(false);
26static FLAGS: AtomicU8 = AtomicU8::new(0);
27
28/// Initialize runtime user-context switches from the current DTB (FDT).
29///
30/// This function is idempotent; only the first call performs initialization.
31pub fn init_from_fdt() {
32    if INITIALIZED
33        .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
34        .is_err()
35    {
36        return;
37    }
38
39    let (mut enable_fpu, mut enable_vector) = arch_defaults_from_fdt();
40
41    // `/chosen` overrides win.
42    if let Some(fdt) = FdtManager::get_manager().get_fdt() {
43        if let Some(chosen) = fdt.find_node("/chosen") {
44            if let Some(v) = read_boolish_property(&chosen, "scarlet,user-fpu") {
45                enable_fpu = v;
46            }
47            if let Some(v) = read_boolish_property(&chosen, "scarlet,user-vector") {
48                enable_vector = v;
49            }
50        }
51    }
52
53    let mut flags = 0u8;
54    if cfg!(feature = "user-fpu") && enable_fpu {
55        flags |= FLAG_USER_FPU;
56    }
57    if cfg!(feature = "user-vector") && enable_vector {
58        flags |= FLAG_USER_VECTOR;
59    }
60    FLAGS.store(flags, Ordering::Release);
61
62    // Keep this log short; it is useful when bringing up new boards.
63    crate::early_println!(
64        "[userctx] enabled: user-fpu={} user-vector={}",
65        user_fpu_enabled(),
66        user_vector_enabled()
67    );
68}
69
70/// Returns whether user-mode FPU context handling is enabled (feature + DTB).
71#[inline]
72pub fn user_fpu_enabled() -> bool {
73    cfg!(feature = "user-fpu") && (FLAGS.load(Ordering::Acquire) & FLAG_USER_FPU != 0)
74}
75
76/// Returns whether user-mode Vector context handling is enabled (feature + DTB).
77#[inline]
78pub fn user_vector_enabled() -> bool {
79    cfg!(feature = "user-vector") && (FLAGS.load(Ordering::Acquire) & FLAG_USER_VECTOR != 0)
80}
81
82fn read_boolish_property(node: &fdt::node::FdtNode, name: &str) -> Option<bool> {
83    let prop = node.property(name)?;
84
85    // Boolean property: present with empty value.
86    if prop.value.is_empty() {
87        return Some(true);
88    }
89
90    match prop.value.len() {
91        4 => Some(u32::from_be_bytes(prop.value[0..4].try_into().ok()?) != 0),
92        8 => Some(u64::from_be_bytes(prop.value[0..8].try_into().ok()?) != 0),
93        _ => {
94            // Try string forms like "0" / "1".
95            let s = bytes_to_cstr(prop.value)?;
96            match s.trim() {
97                "0" | "false" | "no" | "off" => Some(false),
98                "1" | "true" | "yes" | "on" => Some(true),
99                _ => None,
100            }
101        }
102    }
103}
104
105fn bytes_to_cstr(bytes: &[u8]) -> Option<&str> {
106    let len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
107    core::str::from_utf8(&bytes[..len]).ok()
108}
109
110fn arch_defaults_from_fdt() -> (bool, bool) {
111    // Default policy:
112    // - If we can detect from DTB, use it.
113    // - Otherwise be conservative for Vector (disabled) and permissive for FPU.
114
115    #[cfg(target_arch = "riscv64")]
116    {
117        if let Some(fdt) = FdtManager::get_manager().get_fdt() {
118            if let Some((has_fpu, has_vec)) = riscv_extensions_from_fdt(fdt) {
119                return (has_fpu, has_vec);
120            }
121        }
122        return (true, false);
123    }
124
125    #[cfg(target_arch = "aarch64")]
126    {
127        // Most AArch64 platforms provide FP/SIMD; DTB rarely encodes it.
128        // Keep FP enabled by default (when compiled), and keep "vector" disabled.
129        return (true, false);
130    }
131
132    #[cfg(not(any(target_arch = "riscv64", target_arch = "aarch64")))]
133    {
134        (true, false)
135    }
136}
137
138#[cfg(target_arch = "riscv64")]
139fn riscv_extensions_from_fdt(fdt: &fdt::Fdt) -> Option<(bool, bool)> {
140    let cpus = fdt.find_node("/cpus")?;
141
142    let mut saw_cpu = false;
143    let mut all_have_fpu = true;
144    let mut all_have_vec = true;
145
146    for cpu in cpus.children() {
147        // Filter to CPU nodes when possible.
148        if let Some(dev_type) = cpu.property("device_type") {
149            if bytes_to_cstr(dev_type.value)
150                .map(|s| s != "cpu")
151                .unwrap_or(false)
152            {
153                continue;
154            }
155        }
156
157        // Prefer the modern string list if present; fall back to the legacy ISA string.
158        let (has_fpu, has_vec) = if let Some(p) = cpu.property("riscv,isa-extensions") {
159            riscv_extensions_from_isa_extensions(p.value)?
160        } else if let Some(p) = cpu.property("riscv,isa") {
161            riscv_extensions_from_isa_string(p.value)?
162        } else {
163            continue;
164        };
165
166        saw_cpu = true;
167        all_have_fpu &= has_fpu;
168        all_have_vec &= has_vec;
169    }
170
171    if saw_cpu {
172        Some((all_have_fpu, all_have_vec))
173    } else {
174        None
175    }
176}
177
178#[cfg(target_arch = "riscv64")]
179fn riscv_extensions_from_isa_string(isa: &[u8]) -> Option<(bool, bool)> {
180    let len = isa.iter().position(|&b| b == 0).unwrap_or(isa.len());
181    let isa = &isa[..len];
182    if isa.len() < 4 {
183        return None;
184    }
185
186    // Expect "rv32" or "rv64".
187    let b0 = isa[0] | 0x20;
188    let b1 = isa[1] | 0x20;
189    if b0 != b'r' || b1 != b'v' {
190        return None;
191    }
192
193    // Single-letter extensions are the contiguous run right after rv32/rv64
194    // and before the first '_' token separator.
195    let start = 4;
196    let mut end = isa.len();
197    for (i, &b) in isa[start..].iter().enumerate() {
198        if b == b'_' {
199            end = start + i;
200            break;
201        }
202    }
203
204    let letters = &isa[start..end];
205    let has_fpu = letters.iter().any(|&b| {
206        let c = b | 0x20;
207        c == b'f' || c == b'd'
208    });
209
210    // Vector is either the single-letter 'v' (in the letters block) or an explicit
211    // token like "v" / "zve*" / "zvl*" in the '_' separated extension list.
212    let mut has_vec = letters.iter().any(|&b| (b | 0x20) == b'v');
213
214    if !has_vec && end < isa.len() {
215        has_vec = riscv_isa_suffix_has_vector_tokens(&isa[end + 1..]);
216    }
217
218    Some((has_fpu, has_vec))
219}
220
221#[cfg(target_arch = "riscv64")]
222fn riscv_isa_suffix_has_vector_tokens(mut s: &[u8]) -> bool {
223    // Suffix is '_' separated. Tokens like "svvptc" must NOT be treated as V.
224    while !s.is_empty() {
225        let next = s.iter().position(|&b| b == b'_').unwrap_or(s.len());
226        let tok = &s[..next];
227        if riscv_token_is_vector(tok) {
228            return true;
229        }
230        s = if next < s.len() { &s[next + 1..] } else { &[] };
231    }
232    false
233}
234
235#[cfg(target_arch = "riscv64")]
236fn riscv_extensions_from_isa_extensions(list: &[u8]) -> Option<(bool, bool)> {
237    // DTB string list: multiple NUL-terminated strings packed together.
238    let mut has_fpu = false;
239    let mut has_vec = false;
240
241    let mut i = 0;
242    while i < list.len() {
243        let end = list[i..]
244            .iter()
245            .position(|&b| b == 0)
246            .map(|p| i + p)
247            .unwrap_or(list.len());
248        let tok = &list[i..end];
249        if tok.is_empty() {
250            break;
251        }
252        if riscv_token_is_fpu(tok) {
253            has_fpu = true;
254        }
255        if riscv_token_is_vector(tok) {
256            has_vec = true;
257        }
258        if end == list.len() {
259            break;
260        }
261        i = end + 1;
262    }
263
264    Some((has_fpu, has_vec))
265}
266
267#[cfg(target_arch = "riscv64")]
268fn riscv_token_is_fpu(tok: &[u8]) -> bool {
269    tok.len() == 1 && {
270        let c = tok[0] | 0x20;
271        c == b'f' || c == b'd'
272    }
273}
274
275#[cfg(target_arch = "riscv64")]
276fn riscv_token_is_vector(tok: &[u8]) -> bool {
277    if tok.is_empty() {
278        return false;
279    }
280    // Normalize to lowercase without allocation by checking bytes.
281    if tok.len() == 1 && ((tok[0] | 0x20) == b'v') {
282        return true;
283    }
284    if tok.len() >= 3 {
285        let t0 = tok[0] | 0x20;
286        let t1 = tok[1] | 0x20;
287        let t2 = tok[2] | 0x20;
288        // Any Zve* or Zvl* implies some form of vector support.
289        if t0 == b'z' && t1 == b'v' && (t2 == b'e' || t2 == b'l') {
290            return true;
291        }
292    }
293    false
294}