use alloc::{
borrow::{Cow, ToOwned},
sync::Arc,
};
use core::{
sync::atomic::{AtomicBool, AtomicU64, Ordering},
task::Context,
time::Duration,
};
use ax_errno::{AxError, AxResult};
use ax_hal::time::{TimeValue, monotonic_time, wall_time};
use ax_sync::Mutex;
use ax_task::future::{block_on, poll_io, timeout_at};
use axpoll::{IoEvents, PollSet, Pollable};
use event_listener::{Event, listener};
use crate::file::{FileLike, IoDst, IoSrc};
pub const CLOCK_REALTIME: u32 = 0;
pub const CLOCK_MONOTONIC: u32 = 1;
pub const CLOCK_BOOTTIME: u32 = 7;
pub const CLOCK_REALTIME_ALARM: u32 = 8;
pub const CLOCK_BOOTTIME_ALARM: u32 = 9;
pub const TFD_TIMER_ABSTIME: u32 = 1;
pub const TFD_TIMER_CANCEL_ON_SET: u32 = 2;
#[derive(Default)]
struct State {
next_deadline: Option<TimeValue>,
interval: Duration,
shutdown: bool,
}
pub struct Timerfd {
clockid: u32,
state: Mutex<State>,
expire_count: AtomicU64,
poll_rx: PollSet,
non_blocking: AtomicBool,
arm_event: Arc<Event>,
}
impl Timerfd {
pub fn new(clockid: u32) -> AxResult<Arc<Self>> {
match clockid {
CLOCK_REALTIME | CLOCK_MONOTONIC | CLOCK_BOOTTIME | CLOCK_REALTIME_ALARM
| CLOCK_BOOTTIME_ALARM => {}
_ => return Err(AxError::InvalidInput),
}
let this = Arc::new(Self {
clockid,
state: Mutex::new(State::default()),
expire_count: AtomicU64::new(0),
poll_rx: PollSet::new(),
non_blocking: AtomicBool::new(false),
arm_event: Arc::new(Event::new()),
});
let weak = Arc::downgrade(&this);
ax_task::spawn_raw(
move || block_on(run_timer(weak)),
"timerfd".to_owned(),
ax_config::TASK_STACK_SIZE,
);
Ok(this)
}
pub fn settime(
&self,
abstime: bool,
new_value: Duration,
new_interval: Duration,
) -> AxResult<(Duration, Duration)> {
let now = wall_time();
let mut state = self.state.lock();
let old_interval = state.interval;
let old_remaining = state
.next_deadline
.map(|dl| dl.checked_sub(now).unwrap_or(Duration::ZERO))
.unwrap_or(Duration::ZERO);
if new_value.is_zero() {
state.next_deadline = None;
state.interval = Duration::ZERO;
} else {
let deadline = if abstime {
let user_abs = TimeValue::from_secs(new_value.as_secs())
+ Duration::from_nanos(new_value.subsec_nanos() as u64);
match self.clockid {
CLOCK_REALTIME | CLOCK_REALTIME_ALARM => user_abs,
_ => {
let mono = monotonic_time();
let wall_minus_mono = now.checked_sub(mono).unwrap_or(Duration::ZERO);
user_abs.checked_add(wall_minus_mono).unwrap_or(user_abs)
}
}
} else {
now + new_value
};
state.next_deadline = Some(deadline);
state.interval = new_interval;
}
self.expire_count.store(0, Ordering::Release);
drop(state);
self.arm_event.notify(usize::MAX);
Ok((old_interval, old_remaining))
}
pub fn gettime(&self) -> (Duration, Duration) {
let state = self.state.lock();
let interval = state.interval;
let remaining = match state.next_deadline {
None => Duration::ZERO,
Some(dl) => {
let now = wall_time();
dl.checked_sub(now).unwrap_or(Duration::ZERO)
}
};
(interval, remaining)
}
}
impl Drop for Timerfd {
fn drop(&mut self) {
let mut state = self.state.lock();
state.shutdown = true;
drop(state);
self.arm_event.notify(usize::MAX);
}
}
async fn run_timer(weak: alloc::sync::Weak<Timerfd>) {
loop {
let arm_event = {
let Some(tfd) = weak.upgrade() else {
return;
};
tfd.arm_event.clone()
};
listener!(arm_event => listener);
let (deadline, interval, shutdown) = {
let Some(tfd) = weak.upgrade() else {
return;
};
let state = tfd.state.lock();
(state.next_deadline, state.interval, state.shutdown)
};
if shutdown {
return;
}
match deadline {
None => {
listener.await;
}
Some(dl) => {
let fired_timer = timeout_at(Some(dl), listener).await.is_err();
if !fired_timer {
continue;
}
let Some(tfd) = weak.upgrade() else {
return;
};
let now = wall_time();
let mut expirations: u64 = 1;
let mut next_deadline = dl;
if !interval.is_zero() {
if let Some(lag) = now.checked_sub(dl) {
let extra_ticks = lag.as_nanos() / interval.as_nanos().max(1);
let extra = core::cmp::min(extra_ticks, u32::MAX as u128 - 1) as u32;
expirations += extra as u64;
let advance = interval.saturating_mul(extra + 1);
next_deadline += advance;
}
}
let mut state = tfd.state.lock();
if state.shutdown {
return;
}
if state.next_deadline == Some(dl) {
tfd.expire_count.fetch_add(expirations, Ordering::AcqRel);
if interval.is_zero() {
state.next_deadline = None;
} else {
state.next_deadline = Some(next_deadline);
}
drop(state);
tfd.poll_rx.wake();
}
}
}
}
}
impl FileLike for Timerfd {
fn read(&self, dst: &mut IoDst) -> AxResult<usize> {
if dst.remaining_mut() < core::mem::size_of::<u64>() {
return Err(AxError::InvalidInput);
}
block_on(poll_io(self, IoEvents::IN, self.nonblocking(), || {
let n = loop {
let observed = self.expire_count.load(Ordering::Acquire);
if observed == 0 {
return Err(AxError::WouldBlock);
}
if self
.expire_count
.compare_exchange(observed, 0, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
break observed;
}
};
if let Err(e) = dst.write(&n.to_ne_bytes()) {
self.expire_count.fetch_add(n, Ordering::AcqRel);
self.poll_rx.wake();
return Err(e);
}
Ok(core::mem::size_of::<u64>())
}))
}
fn write(&self, _src: &mut IoSrc) -> AxResult<usize> {
Err(AxError::InvalidInput)
}
fn nonblocking(&self) -> bool {
self.non_blocking.load(Ordering::Acquire)
}
fn set_nonblocking(&self, non_blocking: bool) -> AxResult {
self.non_blocking.store(non_blocking, Ordering::Release);
Ok(())
}
fn path(&self) -> Cow<'_, str> {
"anon_inode:[timerfd]".into()
}
}
impl Pollable for Timerfd {
fn poll(&self) -> IoEvents {
let mut events = IoEvents::empty();
events.set(IoEvents::IN, self.expire_count.load(Ordering::Acquire) > 0);
events
}
fn register(&self, context: &mut Context<'_>, events: IoEvents) {
if events.contains(IoEvents::IN) {
self.poll_rx.register(context.waker());
}
}
}