air_sys_syscall/raw_syscall.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! `raw_syscall` — **trappe de sortie (escape hatch) syscall générique**.
6//!
7//! # ⚠️ EXCEPTION ASSUMÉE — À RETIRER (cf. `docs/EXCEPTIONS.md`, ADR-087)
8//!
9//! Cette fonction est une **entorse délibérée et temporaire** à deux règles :
10//! - **ADR-021 conv. 3** (« pas de wrapper générique pour syscalls multiplexés ;
11//! chaque opération = une fonction dédiée typée ») ;
12//! - **la règle « la libc binde la couche 1, jamais la couche 0 »** (le seul
13//! consommateur, le shim libc `syscall`, tape ici **directement**).
14//!
15//! **Raison d'être** : la libc C-ABI d'Air **doit** fournir la fonction POSIX/Linux
16//! `long syscall(long number, ...)`, que le **pal `unix` de Rust `std`** appelle **en
17//! direct** pour deux services **sans** fonction libc typée : `futex` (aucune fonction
18//! libc n'a jamais existé — base des `Mutex`/`Condvar`/`park` de std) et `gettid`
19//! (pas de wrapper garanti avant glibc 2.30). `syscall(2)` est, **par nature**, une
20//! trappe **non typée** — on ne peut pas la rendre typée sans la dénaturer.
21//!
22//! **Plan de retrait (action item amont)** : faire que le pal `std` n'ait **pas
23//! besoin** d'un `syscall` générique sur `*-linux-air` (router `futex`/`gettid` vers
24//! des fonctions typées, ou pal Air dédié) — **avec l'équipe Rust**. Cette fonction
25//! disparaît dès que l'amont est résolu.
26//!
27//! **Périmètre exercé par M5** : `hello-std` étant mono-thread, `futex` n'est **jamais
28//! appelé** au runtime (invoqué uniquement sur contention de verrou) ; seul `gettid`
29//! l'est éventuellement au démarrage. Le symbole doit néanmoins **exister** pour lier.
30
31use core::ffi::c_long;
32
33/// Exécute un **syscall arbitraire** : `number` dans le registre de numéro, `a1..a6`
34/// dans les six registres d'arguments (ABI System V Linux). Rend la valeur **brute** du
35/// noyau (une erreur est un `-errno` dans `[-4095, -1]` — la **conversion** en
36/// `-1`/`errno` est laissée à l'appelant libc, comme le `__syscall_ret` de musl).
37///
38/// # ⚠️ Escape hatch — cf. en-tête de module (exception à retirer)
39///
40/// # Safety
41///
42/// L'appelant garantit que `(number, a1..a6)` forment un **appel syscall valide** : tout
43/// argument interprété par le noyau comme un pointeur doit être valide pour l'accès
44/// (lecture/écriture) qu'implique `number`, et l'invariant mémoire du syscall visé doit
45/// être respecté. Aucune vérification n'est possible ici (numéro et sémantique opaques).
46#[cfg(target_arch = "x86_64")]
47#[inline]
48pub unsafe fn raw_syscall(
49 number: c_long,
50 a1: c_long,
51 a2: c_long,
52 a3: c_long,
53 a4: c_long,
54 a5: c_long,
55 a6: c_long,
56) -> c_long {
57 let ret: c_long;
58 // SAFETY: instruction `syscall` (x86_64). `number`→rax, `a1..a6`→rdi/rsi/rdx/r10/r8/r9
59 // (ABI syscall Linux). Le syscall peut lire/écrire la mémoire utilisateur selon `number`
60 // (aucune option `readonly`/`nomem`, ni `preserves_flags` : impossible à garantir pour un
61 // appel générique). `rcx`/`r11` sont clobberés par l'instruction. Préconditions =
62 // contrat de sûreté de l'appelant (cf. `# Safety`).
63 unsafe {
64 core::arch::asm!(
65 "syscall",
66 inout("rax") number => ret,
67 in("rdi") a1,
68 in("rsi") a2,
69 in("rdx") a3,
70 in("r10") a4,
71 in("r8") a5,
72 in("r9") a6,
73 lateout("rcx") _,
74 lateout("r11") _,
75 options(nostack),
76 );
77 }
78 ret
79}
80
81/// Exécute un **syscall arbitraire** (aarch64) — cf. la variante x86_64 pour le contrat.
82///
83/// # ⚠️ Escape hatch — cf. en-tête de module (exception à retirer)
84///
85/// # Safety
86///
87/// Identique à la variante x86_64 : `(number, a1..a6)` forment un appel syscall valide.
88#[cfg(target_arch = "aarch64")]
89#[inline]
90pub unsafe fn raw_syscall(
91 number: c_long,
92 a1: c_long,
93 a2: c_long,
94 a3: c_long,
95 a4: c_long,
96 a5: c_long,
97 a6: c_long,
98) -> c_long {
99 let ret: c_long;
100 // SAFETY: instruction `svc 0` (aarch64). `number`→x8, `a1..a6`→x0..x5 (ABI syscall
101 // Linux). Comme sur x86_64, aucune option mémoire/flags n'est posée (appel générique) ;
102 // les préconditions relèvent du contrat de sûreté de l'appelant (cf. `# Safety`).
103 unsafe {
104 core::arch::asm!(
105 "svc 0",
106 in("x8") number,
107 inout("x0") a1 => ret,
108 in("x1") a2,
109 in("x2") a3,
110 in("x3") a4,
111 in("x4") a5,
112 in("x5") a6,
113 options(nostack),
114 );
115 }
116 ret
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::process::getpid;
123
124 /// `SYS_getpid` (sans argument, effet nul, valeur connue) — numéro par arche.
125 #[cfg(target_arch = "x86_64")]
126 const SYS_GETPID: c_long = 39;
127 #[cfg(target_arch = "aarch64")]
128 const SYS_GETPID: c_long = 172;
129
130 #[test]
131 fn raw_syscall_getpid_matches_typed_wrapper() {
132 // `getpid` : syscall sans argument, sans effet de bord, à valeur stable.
133 // SAFETY: `SYS_getpid` n'interprète aucun argument comme pointeur ⇒ `a1..a6`
134 // ignorés ; appel toujours valide.
135 let raw = unsafe { raw_syscall(SYS_GETPID, 0, 0, 0, 0, 0, 0) };
136 let expected = c_long::from(getpid().as_raw());
137 assert_eq!(raw, expected);
138 assert!(raw > 0, "pid strictement positif");
139 }
140}