kernel/abi/linux/riscv64/
futex.rs

1use crate::abi::linux::riscv64::LinuxRiscv64Abi;
2use crate::arch::Trapframe;
3use crate::sync::waker::Waker;
4use alloc::collections::BTreeMap;
5use spin::{Mutex, Once};
6
7// Minimal FUTEX op codes (match Linux)
8const FUTEX_WAIT: u32 = 0;
9const FUTEX_WAKE: u32 = 1;
10// Extended ops commonly used by musl
11const FUTEX_WAIT_BITSET: u32 = 9;
12const FUTEX_WAKE_BITSET: u32 = 10;
13const FUTEX_CMD_MASK: u32 = 0x3f; // per Linux uapi
14
15// Global registry of futex wakers keyed by user address
16static FUTEX_WAKERS: Once<Mutex<BTreeMap<usize, Waker>>> = Once::new();
17
18fn init_futex_wakers() -> Mutex<BTreeMap<usize, Waker>> {
19    Mutex::new(BTreeMap::new())
20}
21
22fn get_futex_waker(uaddr: usize) -> &'static Waker {
23    let futex_map_mutex = FUTEX_WAKERS.call_once(init_futex_wakers);
24    let mut map = futex_map_mutex.lock();
25    if !map.contains_key(&uaddr) {
26        // Leak a static name for diagnostics
27        let name = alloc::format!("futex_{:#x}", uaddr);
28        let static_name = alloc::boxed::Box::leak(name.into_boxed_str());
29        map.insert(uaddr, Waker::new_interruptible(static_name));
30    }
31    unsafe {
32        let ptr = map.get(&uaddr).unwrap() as *const Waker;
33        &*ptr
34    }
35}
36
37/// Wake up to `max` waiters on a futex at `uaddr`.
38pub fn wake_address(uaddr: usize, max: usize) -> usize {
39    let waker = get_futex_waker(uaddr);
40    if max == 0 {
41        return 0;
42    }
43    let mut woken = 0;
44    if max == usize::MAX {
45        // treat as wake_all
46        woken = waker.wake_all();
47    } else {
48        for _ in 0..max {
49            if waker.wake_one() {
50                woken += 1;
51            } else {
52                break;
53            }
54        }
55    }
56    woken
57}
58
59/// Linux futex syscall (minimal implementation: WAIT/WAKE)
60pub fn sys_futex(_abi: &mut LinuxRiscv64Abi, trapframe: &mut Trapframe) -> usize {
61    let task = match crate::task::mytask() {
62        Some(t) => t,
63        None => return super::errno::to_result(super::errno::EPERM),
64    };
65
66    // args: uaddr, op, val, timeout, uaddr2, val3
67    let uaddr = trapframe.get_arg(0) as usize;
68    let op_raw = trapframe.get_arg(1) as u32;
69    let val = trapframe.get_arg(2) as i32;
70    let _timeout = trapframe.get_arg(3) as usize; // TODO: implement timeout
71    let _uaddr2 = trapframe.get_arg(4) as usize;
72    let _val3 = trapframe.get_arg(5) as u32; // e.g., bitset for *_BITSET ops
73
74    // Always advance PC to avoid re-executing syscall on resume
75    trapframe.increment_pc_next(task);
76
77    let cmd = op_raw & FUTEX_CMD_MASK; // strip PRIVATE/other flags
78    match cmd {
79        FUTEX_WAIT | FUTEX_WAIT_BITSET => {
80            // Validate user address and expected value
81            let paddr = match task.vm_manager.translate_vaddr(uaddr) {
82                Some(pa) => pa,
83                None => return super::errno::to_result(super::errno::EFAULT),
84            };
85            let cur_val = unsafe { *(paddr as *const i32) };
86            if cur_val != val {
87                // Expected value mismatch -> EAGAIN (common benign fast-path)
88                return super::errno::to_result(super::errno::EAGAIN);
89            }
90
91            // Block current task on futex queue
92            let waker = get_futex_waker(uaddr);
93            let tid = task.get_id();
94            waker.wait(tid, trapframe);
95            // When resumed, report success (Linux may report -EINTR if interrupted; TBD)
96            0
97        }
98        FUTEX_WAKE | FUTEX_WAKE_BITSET => {
99            let max = if val < 0 { 0 } else { val as usize };
100            let woken = wake_address(uaddr, max);
101            // Return number of woken tasks
102            woken
103        }
104        _ => {
105            // Not implemented ops
106            super::errno::to_result(super::errno::ENOSYS)
107        }
108    }
109}