Skip to main content

air_sys_syscall/
time.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//! Wrappers de la famille `time`.
6//!
7//! Cf. `docs/specs/layer-0/family-time.md`.
8
9use air_sys_types::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd};
10use core::num::NonZeroI32;
11use core::time::Duration;
12
13use air_sys_types::{
14    Clock, Errno, Instant, SleepDeadline, SleepError, TimerFdFlags, TimerFdSpecification,
15    TimerSetFlags,
16};
17
18#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
19compile_error!("air-sys-syscall::time supporte uniquement x86_64 et aarch64 (ADR-014).");
20
21const NANOS_PER_SECOND_U32: u32 = 1_000_000_000;
22const TIMER_ABSTIME: i32 = 1;
23
24#[repr(C)]
25#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
26struct KernelTimespec {
27    tv_sec: i64,
28    tv_nsec: i64,
29}
30
31#[repr(C)]
32#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
33struct KernelItimerspec {
34    it_interval: KernelTimespec,
35    it_value: KernelTimespec,
36}
37
38/// FD `timerfd` (cf. `timerfd_create(2)`).
39#[derive(Debug)]
40pub struct TimerFd(OwnedFd);
41
42impl TimerFd {
43    /// Construit depuis un `OwnedFd` déjà ouvert sur un timerfd.
44    #[must_use]
45    #[inline]
46    pub const fn from_owned_fd(fd: OwnedFd) -> Self {
47        Self(fd)
48    }
49
50    /// Vue empruntée du FD sous-jacent.
51    #[must_use]
52    pub fn as_fd(&self) -> BorrowedFd<'_> {
53        use air_sys_types::fd::AsFd;
54        self.0.as_fd()
55    }
56
57    /// Consomme et restitue le `OwnedFd` sous-jacent.
58    #[must_use]
59    pub fn into_fd(self) -> OwnedFd {
60        self.0
61    }
62
63    /// Arme le timer et retourne l'ancienne spécification.
64    ///
65    /// # Errors
66    ///
67    /// - `EINVAL` : durée hors-plage (dépassement `i64` lors de la conversion
68    ///   en `KernelTimespec`), ou combinaison de `flags` refusée par le kernel.
69    /// - `EBADF` : le FD sous-jacent n'est pas un timerfd valide.
70    pub fn arm(
71        &self,
72        spec: &TimerFdSpecification,
73        flags: TimerSetFlags,
74    ) -> Result<TimerFdSpecification, Errno> {
75        let new_value = kernel_itimerspec_from_spec(*spec)?;
76        let mut old_value = KernelItimerspec::default();
77        // SAFETY: `new_value` et `old_value` sont des `KernelItimerspec` locaux
78        // vivants ; on passe leurs adresses (lecture pour `new_value`, écriture pour
79        // `old_value`) — préconditions de `raw_syscall_timerfd_settime` satisfaites.
80        let ret = unsafe {
81            raw_syscall_timerfd_settime(
82                self.0.as_raw_fd(),
83                flags.bits(),
84                (&new_value as *const KernelItimerspec) as u64,
85                (&mut old_value as *mut KernelItimerspec) as u64,
86            )
87        };
88        if ret < 0 {
89            return Err(errno_from_negative_syscall_ret(ret));
90        }
91        kernel_itimerspec_to_spec(old_value)
92    }
93
94    /// Désarme le timer et retourne l'ancienne spécification.
95    ///
96    /// # Errors
97    ///
98    /// Délègue à [`TimerFd::arm`] avec une spécification nulle — seul `EBADF`
99    /// peut survenir. `EINVAL` ne peut pas survenir ici car la spécification
100    /// `Duration::ZERO` est toujours valide.
101    pub fn disarm(&self) -> Result<TimerFdSpecification, Errno> {
102        self.arm(
103            &TimerFdSpecification {
104                initial: Duration::ZERO,
105                interval: Duration::ZERO,
106            },
107            TimerSetFlags::empty(),
108        )
109    }
110
111    /// Lit l'état courant du timer.
112    ///
113    /// # Errors
114    ///
115    /// - `EBADF` : le FD sous-jacent n'est pas un timerfd valide.
116    /// - `EINVAL` : valeur renvoyée par le kernel hors-plage (ne devrait pas
117    ///   survenir sur un timerfd bien formé).
118    pub fn current(&self) -> Result<TimerFdSpecification, Errno> {
119        let mut current = KernelItimerspec::default();
120        // SAFETY: `current` est un `KernelItimerspec` local vivant ; on passe son
121        // adresse au kernel qui y écrit l'échéance courante — précondition de
122        // `raw_syscall_timerfd_gettime` satisfaite.
123        let ret = unsafe {
124            raw_syscall_timerfd_gettime(
125                self.0.as_raw_fd(),
126                (&mut current as *mut KernelItimerspec) as u64,
127            )
128        };
129        if ret < 0 {
130            return Err(errno_from_negative_syscall_ret(ret));
131        }
132        kernel_itimerspec_to_spec(current)
133    }
134
135    /// Lit le nombre d'expirations accumulées.
136    ///
137    /// # Errors
138    ///
139    /// - `EAGAIN` : aucune expiration disponible et le FD a été créé avec
140    ///   `TFD_NONBLOCK` (cf. test `timerfd_nonblocking_read_without_expiration_returns_eagain`).
141    /// - `EINTR` : interrompu par un signal avant toute expiration. Conforme
142    ///   à la politique EINTR de la couche 0 : l'appelant doit retry
143    ///   explicitement s'il le souhaite.
144    /// - `EBADF` : le FD sous-jacent n'est pas un timerfd valide.
145    /// - `ECANCELED` : l'horloge `CLOCK_REALTIME` (ou `CLOCK_REALTIME_ALARM`) a
146    ///   subi un changement discontinu alors que le timer absolu avait été armé
147    ///   avec `TFD_TIMER_CANCEL_ON_SET` (cf. `man 2 timerfd_create`).
148    pub fn read(&self) -> Result<u64, Errno> {
149        let mut count = 0_u64;
150        // SAFETY: `count` est un `u64` local vivant ; on passe son adresse et la
151        // taille exacte (`size_of::<u64>()`) — le kernel y écrit au plus 8 octets,
152        // précondition de `raw_syscall_read` satisfaite.
153        let ret = unsafe {
154            raw_syscall_read(
155                self.0.as_raw_fd(),
156                (&mut count as *mut u64) as u64,
157                u64::try_from(core::mem::size_of::<u64>()).expect("u64 size fits in u64"),
158            )
159        };
160        if ret < 0 {
161            return Err(errno_from_negative_syscall_ret(ret));
162        }
163        debug_assert_eq!(ret, 8);
164        Ok(count)
165    }
166}
167
168/// Lit le temps courant pour l'horloge ciblée.
169///
170/// # Errors
171///
172/// - `EINVAL` : identifiant d'horloge inconnu du kernel, ou valeur renvoyée
173///   hors-plage (nanoseconde négative ou ≥ 10⁹).
174pub fn clock_gettime(clock: Clock) -> Result<Instant, Errno> {
175    let ts = syscall_clock_gettime(clock.as_raw())?;
176    kernel_timespec_to_instant(clock, ts)
177}
178
179/// Modifie le temps courant d'une horloge ajustable.
180///
181/// # Errors
182///
183/// - `EINVAL` : `instant.clock() != clock` (vérifié avant l'appel syscall),
184///   ou identifiant d'horloge refusé par le kernel.
185/// - `EPERM` : `CAP_SYS_TIME` manquante. Échoue systématiquement en
186///   environnement non privilégié (cf. test `clock_settime_realtime_current_time_is_ok_or_eperm`).
187pub fn clock_settime(clock: Clock, instant: Instant) -> Result<(), Errno> {
188    if instant.clock() != clock {
189        return Err(Errno::EINVAL);
190    }
191    let ts = kernel_timespec_from_instant(instant);
192    // Note coverage:
193    // - Le chemin `Ok(())` de `clock_settime` n'est généralement pas couvrable
194    //   en CI ni en développement courant, car `clock_settime(2)` exige des
195    //   privilèges élevés (`CAP_SYS_TIME`) pour réussir sur les horloges
196    //   ajustables.
197    // - Nous savons donc que la branche succès peut rester non couverte sur un
198    //   environnement non privilégié, même si le wrapper est correct.
199    // - Une manière d'atteindre artificiellement 100 % ici serait
200    //   d'introduire un point d'injection / seam test-only pour simuler un
201    //   succès du syscall. Ce choix est refusé dans Air : on n'ouvre pas de
202    //   brèche structurelle dans le code de production uniquement pour flatter
203    //   la couverture. Le projet vise une couverture maximale, mais reste
204    //   pragmatique plutôt que paranoïaque.
205    // SAFETY: `ts` est un `KernelTimespec` local vivant ; on passe son adresse en
206    // lecture seule — précondition de `raw_syscall_clock_settime` satisfaite.
207    let ret =
208        unsafe { raw_syscall_clock_settime(clock.as_raw(), (&ts as *const KernelTimespec) as u64) };
209    if ret < 0 {
210        return Err(errno_from_negative_syscall_ret(ret));
211    }
212    Ok(())
213}
214
215/// Résolution de l'horloge ciblée.
216///
217/// # Errors
218///
219/// - `EINVAL` : identifiant d'horloge inconnu du kernel.
220pub fn clock_getres(clock: Clock) -> Result<Duration, Errno> {
221    let ts = syscall_clock_getres(clock.as_raw())?;
222    kernel_timespec_to_duration(ts)
223}
224
225/// Endort le thread appelant jusqu'à l'échéance demandée.
226pub fn clock_nanosleep(clock: Clock, deadline: SleepDeadline) -> Result<(), SleepError> {
227    let mut rem = KernelTimespec::default();
228    let (flags, req, rem_ptr) = match deadline {
229        SleepDeadline::Relative(duration) => {
230            let req = match kernel_timespec_from_duration(duration) {
231                Ok(req) => req,
232                Err(err) => return Err(SleepError::Other(err)),
233            };
234            (0_i32, req, (&mut rem as *mut KernelTimespec) as u64)
235        }
236        SleepDeadline::AbsoluteInstant(instant) => {
237            if instant.clock() != clock {
238                return Err(SleepError::Other(Errno::EINVAL));
239            }
240            (TIMER_ABSTIME, kernel_timespec_from_instant(instant), 0_u64)
241        }
242    };
243
244    // SAFETY: `req` est un `KernelTimespec` local vivant (lecture) ; `rem_ptr` est
245    // soit nul (sommeil absolu), soit l'adresse du `rem` local vivant (écriture du
246    // reste) — préconditions de `raw_syscall_clock_nanosleep` satisfaites.
247    let ret = unsafe {
248        raw_syscall_clock_nanosleep(
249            clock.as_raw(),
250            flags,
251            (&req as *const KernelTimespec) as u64,
252            rem_ptr,
253        )
254    };
255    decode_clock_nanosleep_result(deadline, rem, ret)
256}
257
258/// Crée un `timerfd` pour l'horloge ciblée.
259///
260/// `TFD_CLOEXEC` est toujours positionné en interne, quelle que soit la valeur
261/// de `flags` (sécurité par défaut).
262///
263/// # Errors
264///
265/// - `EINVAL` : identifiant d'horloge non supporté par `timerfd`, ou
266///   combinaison de `flags` invalide.
267/// - `EMFILE` : quota de FD du processus atteint.
268/// - `ENFILE` : quota de FD système atteint.
269/// - `ENOMEM` : mémoire kernel insuffisante.
270/// - `ENODEV` : impossible de monter le pseudo-périphérique anonyme interne.
271pub fn timerfd_create(clock: Clock, flags: TimerFdFlags) -> Result<TimerFd, Errno> {
272    let kernel_flags = flags.bits() | TimerFdFlags::CLOEXEC.bits();
273    let owned = syscall_timerfd_create(clock.as_raw(), kernel_flags)?;
274    Ok(TimerFd::from_owned_fd(owned))
275}
276
277fn syscall_clock_gettime(clock: i32) -> Result<KernelTimespec, Errno> {
278    let mut ts = KernelTimespec::default();
279    // SAFETY: `ts` est un `KernelTimespec` local vivant ; on passe son adresse en
280    // écriture — précondition de `raw_syscall_clock_gettime` satisfaite.
281    let ret = unsafe { raw_syscall_clock_gettime(clock, (&mut ts as *mut KernelTimespec) as u64) };
282    if ret < 0 {
283        return Err(errno_from_negative_syscall_ret(ret));
284    }
285    Ok(ts)
286}
287
288fn syscall_clock_getres(clock: i32) -> Result<KernelTimespec, Errno> {
289    let mut ts = KernelTimespec::default();
290    // SAFETY: `ts` est un `KernelTimespec` local vivant ; on passe son adresse en
291    // écriture — précondition de `raw_syscall_clock_getres` satisfaite.
292    let ret = unsafe { raw_syscall_clock_getres(clock, (&mut ts as *mut KernelTimespec) as u64) };
293    if ret < 0 {
294        return Err(errno_from_negative_syscall_ret(ret));
295    }
296    Ok(ts)
297}
298
299fn syscall_timerfd_create(clock: i32, flags: i32) -> Result<OwnedFd, Errno> {
300    // SAFETY: `raw_syscall_timerfd_create` ne prend que des scalaires et ne touche
301    // à aucune mémoire utilisateur — aucune précondition de pointeur à honorer.
302    let ret = unsafe { raw_syscall_timerfd_create(clock, flags) };
303    if ret < 0 {
304        return Err(errno_from_negative_syscall_ret(ret));
305    }
306    #[allow(clippy::cast_possible_truncation)]
307    let fd = ret as i32;
308    // SAFETY: `ret >= 0` ici, donc `fd` est un descripteur fraîchement créé par
309    // `timerfd_create`, non possédé ailleurs : on en prend la propriété exclusive.
310    let owned = unsafe { OwnedFd::from_raw_fd(fd) };
311    Ok(owned)
312}
313
314fn decode_clock_nanosleep_result(
315    deadline: SleepDeadline,
316    rem: KernelTimespec,
317    ret: i64,
318) -> Result<(), SleepError> {
319    if ret == 0 {
320        return Ok(());
321    }
322    let errno = errno_from_negative_syscall_ret(ret);
323    if errno == Errno::EINTR
324        && let SleepDeadline::Relative(_) = deadline
325    {
326        let remaining = kernel_timespec_to_duration(rem).map_err(SleepError::Other)?;
327        return Err(SleepError::Interrupted { remaining });
328    }
329    Err(SleepError::Other(errno))
330}
331
332fn kernel_timespec_from_duration(duration: Duration) -> Result<KernelTimespec, Errno> {
333    let secs = i64::try_from(duration.as_secs()).map_err(|_| Errno::EINVAL)?;
334    Ok(KernelTimespec {
335        tv_sec: secs,
336        tv_nsec: i64::from(duration.subsec_nanos()),
337    })
338}
339
340fn kernel_timespec_to_duration(ts: KernelTimespec) -> Result<Duration, Errno> {
341    if ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= i64::from(NANOS_PER_SECOND_U32) {
342        return Err(Errno::EINVAL);
343    }
344    let secs = u64::try_from(ts.tv_sec).map_err(|_| Errno::EINVAL)?;
345    let nanos = u32::try_from(ts.tv_nsec).map_err(|_| Errno::EINVAL)?;
346    Ok(Duration::new(secs, nanos))
347}
348
349fn kernel_timespec_from_instant(instant: Instant) -> KernelTimespec {
350    KernelTimespec {
351        tv_sec: instant.seconds(),
352        tv_nsec: i64::from(instant.nanoseconds()),
353    }
354}
355
356fn kernel_timespec_to_instant(clock: Clock, ts: KernelTimespec) -> Result<Instant, Errno> {
357    if ts.tv_nsec < 0 || ts.tv_nsec >= i64::from(NANOS_PER_SECOND_U32) {
358        return Err(Errno::EINVAL);
359    }
360    let nanos = u32::try_from(ts.tv_nsec).map_err(|_| Errno::EINVAL)?;
361    Instant::try_new(clock, ts.tv_sec, nanos).ok_or(Errno::EINVAL)
362}
363
364fn kernel_itimerspec_from_spec(spec: TimerFdSpecification) -> Result<KernelItimerspec, Errno> {
365    Ok(KernelItimerspec {
366        it_interval: kernel_timespec_from_duration(spec.interval)?,
367        it_value: kernel_timespec_from_duration(spec.initial)?,
368    })
369}
370
371fn kernel_itimerspec_to_spec(spec: KernelItimerspec) -> Result<TimerFdSpecification, Errno> {
372    Ok(TimerFdSpecification {
373        interval: kernel_timespec_to_duration(spec.it_interval)?,
374        initial: kernel_timespec_to_duration(spec.it_value)?,
375    })
376}
377
378fn errno_from_negative_syscall_ret(ret: i64) -> Errno {
379    debug_assert!(ret < 0 && ret > -4096);
380    #[allow(clippy::cast_possible_truncation)]
381    let raw = ret.wrapping_neg() as i32;
382    let nz = NonZeroI32::new(raw).expect("errno strictement positif par construction");
383    Errno::from_nonzero(nz)
384}
385
386#[cfg(target_arch = "x86_64")]
387#[inline]
388unsafe fn raw_syscall_clock_gettime(clock: i32, ts: u64) -> i64 {
389    let ret: i64;
390    // SAFETY: SYS_clock_gettime (x86_64 = 228). Le kernel écrit l'instant courant
391    // à l'adresse `ts`, qui doit pointer un `KernelTimespec` valide et inscriptible
392    // pour la durée de l'appel (précondition de cette fn unsafe, tenue par l'appelant).
393    unsafe {
394        core::arch::asm!(
395            "syscall",
396            in("rax") 228_i64,
397            in("rdi") i64::from(clock),
398            in("rsi") ts,
399            lateout("rax") ret,
400            lateout("rcx") _,
401            lateout("r11") _,
402            options(nostack, preserves_flags),
403        );
404    }
405    ret
406}
407
408#[cfg(target_arch = "x86_64")]
409#[inline]
410unsafe fn raw_syscall_clock_settime(clock: i32, ts: u64) -> i64 {
411    let ret: i64;
412    // SAFETY: SYS_clock_settime (x86_64 = 227). Le kernel lit l'instant à poser
413    // à l'adresse `ts`, qui doit pointer un `KernelTimespec` valide pour la durée
414    // de l'appel. `readonly` : aucune mémoire utilisateur n'est écrite.
415    unsafe {
416        core::arch::asm!(
417            "syscall",
418            in("rax") 227_i64,
419            in("rdi") i64::from(clock),
420            in("rsi") ts,
421            lateout("rax") ret,
422            lateout("rcx") _,
423            lateout("r11") _,
424            options(nostack, preserves_flags, readonly),
425        );
426    }
427    ret
428}
429
430#[cfg(target_arch = "x86_64")]
431#[inline]
432unsafe fn raw_syscall_clock_getres(clock: i32, ts: u64) -> i64 {
433    let ret: i64;
434    // SAFETY: SYS_clock_getres (x86_64 = 229). Le kernel écrit la résolution de
435    // l'horloge à l'adresse `ts`, qui doit pointer un `KernelTimespec` valide et
436    // inscriptible pour la durée de l'appel (précondition de cette fn unsafe).
437    unsafe {
438        core::arch::asm!(
439            "syscall",
440            in("rax") 229_i64,
441            in("rdi") i64::from(clock),
442            in("rsi") ts,
443            lateout("rax") ret,
444            lateout("rcx") _,
445            lateout("r11") _,
446            options(nostack, preserves_flags),
447        );
448    }
449    ret
450}
451
452#[cfg(target_arch = "x86_64")]
453#[inline]
454unsafe fn raw_syscall_clock_nanosleep(clock: i32, flags: i32, req: u64, rem: u64) -> i64 {
455    let ret: i64;
456    // SAFETY: SYS_clock_nanosleep (x86_64 = 230). Le kernel lit la durée demandée
457    // à l'adresse `req` et, en cas d'interruption d'un sommeil relatif, écrit le
458    // reste à l'adresse `rem`. `req` doit pointer un `KernelTimespec` valide ; `rem`
459    // doit être nul ou pointer un `KernelTimespec` inscriptible (préconditions de
460    // cette fn unsafe, tenues par l'appelant).
461    unsafe {
462        core::arch::asm!(
463            "syscall",
464            in("rax") 230_i64,
465            in("rdi") i64::from(clock),
466            in("rsi") i64::from(flags),
467            in("rdx") req,
468            in("r10") rem,
469            lateout("rax") ret,
470            lateout("rcx") _,
471            lateout("r11") _,
472            options(nostack, preserves_flags),
473        );
474    }
475    ret
476}
477
478#[cfg(target_arch = "x86_64")]
479#[inline]
480unsafe fn raw_syscall_timerfd_create(clock: i32, flags: i32) -> i64 {
481    let ret: i64;
482    // SAFETY: SYS_timerfd_create (x86_64 = 283). Ne prend que des scalaires (clock,
483    // flags) et ne touche à aucune mémoire utilisateur ; retourne un FD ≥ 0 ou un
484    // errno négatif. `readonly` : aucune écriture mémoire.
485    unsafe {
486        core::arch::asm!(
487            "syscall",
488            in("rax") 283_i64,
489            in("rdi") i64::from(clock),
490            in("rsi") i64::from(flags),
491            lateout("rax") ret,
492            lateout("rcx") _,
493            lateout("r11") _,
494            options(nostack, preserves_flags, readonly),
495        );
496    }
497    ret
498}
499
500#[cfg(target_arch = "x86_64")]
501#[inline]
502unsafe fn raw_syscall_timerfd_settime(fd: i32, flags: i32, new_value: u64, old_value: u64) -> i64 {
503    let ret: i64;
504    // SAFETY: SYS_timerfd_settime (x86_64 = 286). Le kernel lit la nouvelle
505    // échéance à l'adresse `new_value` et, si `old_value` est non nul, y écrit
506    // l'ancienne. `new_value` doit pointer un `KernelItimerspec` valide ; `old_value`
507    // doit être nul ou pointer un `KernelItimerspec` inscriptible (préconditions de
508    // cette fn unsafe).
509    unsafe {
510        core::arch::asm!(
511            "syscall",
512            in("rax") 286_i64,
513            in("rdi") i64::from(fd),
514            in("rsi") i64::from(flags),
515            in("rdx") new_value,
516            in("r10") old_value,
517            lateout("rax") ret,
518            lateout("rcx") _,
519            lateout("r11") _,
520            options(nostack, preserves_flags),
521        );
522    }
523    ret
524}
525
526#[cfg(target_arch = "x86_64")]
527#[inline]
528unsafe fn raw_syscall_timerfd_gettime(fd: i32, curr_value: u64) -> i64 {
529    let ret: i64;
530    // SAFETY: SYS_timerfd_gettime (x86_64 = 287). Le kernel écrit l'échéance
531    // courante à l'adresse `curr_value`, qui doit pointer un `KernelItimerspec`
532    // valide et inscriptible pour la durée de l'appel (précondition de cette fn unsafe).
533    unsafe {
534        core::arch::asm!(
535            "syscall",
536            in("rax") 287_i64,
537            in("rdi") i64::from(fd),
538            in("rsi") curr_value,
539            lateout("rax") ret,
540            lateout("rcx") _,
541            lateout("r11") _,
542            options(nostack, preserves_flags),
543        );
544    }
545    ret
546}
547
548#[cfg(target_arch = "x86_64")]
549#[inline]
550unsafe fn raw_syscall_read(fd: i32, buffer: u64, count: u64) -> i64 {
551    let ret: i64;
552    // SAFETY: SYS_read (x86_64 = 0). Le kernel écrit jusqu'à `count` octets à
553    // l'adresse `buffer`, qui doit pointer une zone valide et inscriptible d'au
554    // moins `count` octets (précondition de cette fn unsafe, tenue par l'appelant).
555    unsafe {
556        core::arch::asm!(
557            "syscall",
558            in("rax") 0_i64,
559            in("rdi") i64::from(fd),
560            in("rsi") buffer,
561            in("rdx") count,
562            lateout("rax") ret,
563            lateout("rcx") _,
564            lateout("r11") _,
565            options(nostack, preserves_flags),
566        );
567    }
568    ret
569}
570
571#[cfg(target_arch = "aarch64")]
572#[inline]
573unsafe fn raw_syscall_clock_gettime(clock: i32, ts: u64) -> i64 {
574    let ret: i64;
575    // SAFETY: SYS_clock_gettime (aarch64 = 113). Le kernel écrit l'instant courant
576    // à l'adresse `ts`, qui doit pointer un `KernelTimespec` valide et inscriptible
577    // pour la durée de l'appel (précondition de cette fn unsafe, tenue par l'appelant).
578    unsafe {
579        core::arch::asm!(
580            "svc 0",
581            in("x8") 113_i64,
582            inout("x0") i64::from(clock) => ret,
583            in("x1") ts,
584            options(nostack, preserves_flags),
585        );
586    }
587    ret
588}
589
590#[cfg(target_arch = "aarch64")]
591#[inline]
592unsafe fn raw_syscall_clock_settime(clock: i32, ts: u64) -> i64 {
593    let ret: i64;
594    // SAFETY: SYS_clock_settime (aarch64 = 112). Le kernel lit l'instant à poser
595    // à l'adresse `ts`, qui doit pointer un `KernelTimespec` valide pour la durée
596    // de l'appel. `readonly` : aucune mémoire utilisateur n'est écrite.
597    unsafe {
598        core::arch::asm!(
599            "svc 0",
600            in("x8") 112_i64,
601            inout("x0") i64::from(clock) => ret,
602            in("x1") ts,
603            options(nostack, preserves_flags, readonly),
604        );
605    }
606    ret
607}
608
609#[cfg(target_arch = "aarch64")]
610#[inline]
611unsafe fn raw_syscall_clock_getres(clock: i32, ts: u64) -> i64 {
612    let ret: i64;
613    // SAFETY: SYS_clock_getres (aarch64 = 114). Le kernel écrit la résolution de
614    // l'horloge à l'adresse `ts`, qui doit pointer un `KernelTimespec` valide et
615    // inscriptible pour la durée de l'appel (précondition de cette fn unsafe).
616    unsafe {
617        core::arch::asm!(
618            "svc 0",
619            in("x8") 114_i64,
620            inout("x0") i64::from(clock) => ret,
621            in("x1") ts,
622            options(nostack, preserves_flags),
623        );
624    }
625    ret
626}
627
628#[cfg(target_arch = "aarch64")]
629#[inline]
630unsafe fn raw_syscall_clock_nanosleep(clock: i32, flags: i32, req: u64, rem: u64) -> i64 {
631    let ret: i64;
632    // SAFETY: SYS_clock_nanosleep (aarch64 = 115). Le kernel lit la durée demandée
633    // à l'adresse `req` et, en cas d'interruption d'un sommeil relatif, écrit le
634    // reste à l'adresse `rem`. `req` doit pointer un `KernelTimespec` valide ; `rem`
635    // doit être nul ou pointer un `KernelTimespec` inscriptible (préconditions de
636    // cette fn unsafe, tenues par l'appelant).
637    unsafe {
638        core::arch::asm!(
639            "svc 0",
640            in("x8") 115_i64,
641            inout("x0") i64::from(clock) => ret,
642            in("x1") i64::from(flags),
643            in("x2") req,
644            in("x3") rem,
645            options(nostack, preserves_flags),
646        );
647    }
648    ret
649}
650
651#[cfg(target_arch = "aarch64")]
652#[inline]
653unsafe fn raw_syscall_timerfd_create(clock: i32, flags: i32) -> i64 {
654    let ret: i64;
655    // SAFETY: SYS_timerfd_create (aarch64 = 85). Ne prend que des scalaires (clock,
656    // flags) et ne touche à aucune mémoire utilisateur ; retourne un FD ≥ 0 ou un
657    // errno négatif. `readonly` : aucune écriture mémoire.
658    unsafe {
659        core::arch::asm!(
660            "svc 0",
661            in("x8") 85_i64,
662            inout("x0") i64::from(clock) => ret,
663            in("x1") i64::from(flags),
664            options(nostack, preserves_flags, readonly),
665        );
666    }
667    ret
668}
669
670#[cfg(target_arch = "aarch64")]
671#[inline]
672unsafe fn raw_syscall_timerfd_settime(fd: i32, flags: i32, new_value: u64, old_value: u64) -> i64 {
673    let ret: i64;
674    // SAFETY: SYS_timerfd_settime (aarch64 = 86). Le kernel lit la nouvelle
675    // échéance à l'adresse `new_value` et, si `old_value` est non nul, y écrit
676    // l'ancienne. `new_value` doit pointer un `KernelItimerspec` valide ; `old_value`
677    // doit être nul ou pointer un `KernelItimerspec` inscriptible (préconditions de
678    // cette fn unsafe).
679    unsafe {
680        core::arch::asm!(
681            "svc 0",
682            in("x8") 86_i64,
683            inout("x0") i64::from(fd) => ret,
684            in("x1") i64::from(flags),
685            in("x2") new_value,
686            in("x3") old_value,
687            options(nostack, preserves_flags),
688        );
689    }
690    ret
691}
692
693#[cfg(target_arch = "aarch64")]
694#[inline]
695unsafe fn raw_syscall_timerfd_gettime(fd: i32, curr_value: u64) -> i64 {
696    let ret: i64;
697    // SAFETY: SYS_timerfd_gettime (aarch64 = 87). Le kernel écrit l'échéance
698    // courante à l'adresse `curr_value`, qui doit pointer un `KernelItimerspec`
699    // valide et inscriptible pour la durée de l'appel (précondition de cette fn unsafe).
700    unsafe {
701        core::arch::asm!(
702            "svc 0",
703            in("x8") 87_i64,
704            inout("x0") i64::from(fd) => ret,
705            in("x1") curr_value,
706            options(nostack, preserves_flags),
707        );
708    }
709    ret
710}
711
712#[cfg(target_arch = "aarch64")]
713#[inline]
714unsafe fn raw_syscall_read(fd: i32, buffer: u64, count: u64) -> i64 {
715    let ret: i64;
716    // SAFETY: SYS_read (aarch64 = 63). Le kernel écrit jusqu'à `count` octets à
717    // l'adresse `buffer`, qui doit pointer une zone valide et inscriptible d'au
718    // moins `count` octets (précondition de cette fn unsafe, tenue par l'appelant).
719    unsafe {
720        core::arch::asm!(
721            "svc 0",
722            in("x8") 63_i64,
723            inout("x0") i64::from(fd) => ret,
724            in("x1") buffer,
725            in("x2") count,
726            options(nostack, preserves_flags),
727        );
728    }
729    ret
730}
731
732#[cfg(test)]
733mod tests;