use core::{ffi::c_int, time::Duration};
use alloc::collections::{BTreeMap, VecDeque};
use axerrno::LinuxError;
use axsync::Mutex;
use axtask::{current, AxTaskRef, TaskState, WaitQueue};
use memory_addr::VirtAddr;
use crate::ctypes;
enum FutexFlags {
Wait,
Wake,
Requeue,
Unsupported,
}
impl FutexFlags {
pub fn from(val: c_int) -> Self {
match val & 0x7f {
0 => FutexFlags::Wait,
1 => FutexFlags::Wake,
3 => FutexFlags::Requeue,
_ => FutexFlags::Unsupported,
}
}
}
pub static FUTEX_WAIT_TASK: Mutex<BTreeMap<VirtAddr, VecDeque<(AxTaskRef, c_int)>>> =
Mutex::new(BTreeMap::new());
pub static WAIT_FOR_FUTEX: WaitQueue = WaitQueue::new();
pub fn sys_futex(
uaddr: usize,
op: c_int,
val: c_int,
to: usize,
_uaddr2: c_int,
_val3: c_int,
) -> c_int {
debug!(
"sys_futex <= addr: {:#x}, op: {}, val: {}, to: {}",
uaddr, op, val, to
);
check_dead_wait();
let flag = FutexFlags::from(op);
let current_task = current();
let timeout = if to != 0 {
let dur = unsafe { Duration::from(*(to as *const ctypes::timespec)) };
dur.as_nanos() as u64
} else {
0
};
syscall_body!(sys_futex, {
match flag {
FutexFlags::Wait => {
let real_futex_val = unsafe { (uaddr as *const c_int).read_volatile() };
trace!("real_futex_val: {}, expect: {}", real_futex_val, val);
if real_futex_val != val {
return Err(LinuxError::EAGAIN);
}
let mut futex_wait_task = FUTEX_WAIT_TASK.lock();
let wait_list = if let alloc::collections::btree_map::Entry::Vacant(e) =
futex_wait_task.entry(uaddr.into())
{
e.insert(VecDeque::new());
futex_wait_task.get_mut(&(uaddr.into())).unwrap()
} else {
futex_wait_task.get_mut(&(uaddr.into())).unwrap()
};
let next = current_task.as_task_ref().clone();
wait_list.push_back((next, val));
drop(futex_wait_task);
if timeout == 0 {
axtask::yield_now();
} else {
#[cfg(feature = "irq")]
{
let timeout = WAIT_FOR_FUTEX.wait_timeout(Duration::from_nanos(timeout));
if !timeout {
return Err(LinuxError::EINTR);
}
}
}
Ok(0)
}
FutexFlags::Wake => {
trace!(
"thread id: {}, wake addr: {:#x}",
current_task.id().as_u64(),
uaddr
);
let mut futex_wait_task = FUTEX_WAIT_TASK.lock();
if futex_wait_task.contains_key(&(uaddr.into())) {
let wait_list = futex_wait_task.get_mut(&(uaddr.into())).unwrap();
loop {
if let Some((task, _)) = wait_list.pop_front() {
if !task.is_blocked() {
continue;
}
trace!("Wake task: {}", task.id().as_u64());
drop(futex_wait_task);
WAIT_FOR_FUTEX.notify_task(false, &task);
} else {
drop(futex_wait_task);
}
break;
}
} else {
drop(futex_wait_task);
}
axtask::yield_now();
Ok(val)
}
FutexFlags::Requeue => {
debug!("unimplemented for REQUEUE");
Ok(0)
}
_ => Err(LinuxError::EFAULT),
}
})
}
fn check_dead_wait() {
let mut futex_wait_tast = FUTEX_WAIT_TASK.lock();
for (vaddr, wait_list) in futex_wait_tast.iter_mut() {
let real_futex_val = unsafe { ((*vaddr).as_usize() as *const u32).read_volatile() };
for (task, val) in wait_list.iter() {
if real_futex_val as i32 != *val && task.state() == TaskState::Blocked {
WAIT_FOR_FUTEX.notify_task(false, task);
}
}
wait_list.retain(|(task, val)| {
real_futex_val as i32 == *val && task.state() == TaskState::Blocked
});
}
}