Skip to main content

ax_task/timers/
mod.rs

1//! Per-CPU task and kernel timer service.
2
3use alloc::{boxed::Box, format, vec::Vec};
4use core::{
5    sync::atomic::{AtomicBool, AtomicU64, Ordering},
6    task::{Context, Poll},
7};
8
9use ax_hal::time::{TimeValue, monotonic_time};
10use ax_timer_list::{TimerEvent, TimerList};
11
12#[cfg(feature = "smp")]
13use crate::select_run_queue;
14use crate::{
15    AxCpuMask, AxTaskRef, IrqNotify, TaskInner, current_run_queue,
16    future::time::{FutureTimerHandle, TimerRuntime},
17    sync::{PreemptIrqSaveGuard, RawState, SpinLock},
18};
19
20mod clock_event;
21mod kernel;
22
23pub use clock_event::ClockEventControl;
24pub use kernel::{
25    HardKernelTimerAction, HardKernelTimerCallback, KernelTimerAction, KernelTimerCallback,
26    KernelTimerCancelOutcome, KernelTimerError, KernelTimerHandle, MonotonicDeadline,
27    MonotonicInstant, RestartableKernelTimerCallback, TimerCpuId,
28};
29use kernel::{KernelTimerEntry, KernelTimerQueue, KernelTimerQueueCancel};
30
31const KERNEL_TIMER_CAPACITY: usize = 1024;
32const TIMER_IRQ_BUDGET: usize = 64;
33
34static TIMER_TICKET_ID: AtomicU64 = AtomicU64::new(1);
35
36percpu_static! {
37    TIMER_BASE: SpinLock<PerCpuTimerBase> = SpinLock::new(PerCpuTimerBase::new()),
38    TIMER_NOTIFY: IrqNotify = IrqNotify::new(),
39    TIMER_WORKER_STARTED: AtomicBool = AtomicBool::new(false),
40    TIMER_CALLBACKS: Vec<Box<dyn Fn(TimeValue) + Send + Sync>> = Vec::new(),
41}
42
43/// One CPU's logical monotonic timer owner.
44///
45/// The typed queues keep their payload semantics separate while sharing one
46/// owner lock and one earliest-deadline publication path. The physical
47/// comparator is deliberately not part of this object; `ax-runtime` owns it.
48struct PerCpuTimerBase {
49    task_wakeups: TimerList<TaskWakeupEvent>,
50    future_wakeups: TimerRuntime,
51    kernel_timers: KernelTimerQueue,
52}
53
54impl PerCpuTimerBase {
55    const fn new() -> Self {
56        Self {
57            task_wakeups: TimerList::new(),
58            future_wakeups: TimerRuntime::new(),
59            kernel_timers: KernelTimerQueue::new(KERNEL_TIMER_CAPACITY),
60        }
61    }
62
63    fn next_deadline(&self) -> Option<TimeValue> {
64        [
65            self.task_wakeups.next_deadline(),
66            self.future_wakeups.next_deadline(),
67            self.kernel_timers
68                .next_soft_deadline()
69                .map(MonotonicDeadline::as_duration),
70            self.kernel_timers
71                .next_hard_deadline()
72                .map(MonotonicDeadline::as_duration),
73        ]
74        .into_iter()
75        .flatten()
76        .min()
77    }
78}
79
80struct TaskWakeupEvent {
81    ticket_id: u64,
82    task: AxTaskRef,
83}
84
85impl TimerEvent for TaskWakeupEvent {
86    fn callback(self, _now: TimeValue) {
87        // Ignore the timer event if timeout was set but not triggered
88        // (wake up by `WaitQueue::notify()`).
89        // Judge if this timer event is still valid by checking the ticket ID.
90        if self.task.timer_ticket() != self.ticket_id {
91            // Timer ticket ID is not matched.
92            // Just ignore this timer event and return.
93            return;
94        }
95
96        // Timer ticket match. Timers are per-CPU, so prefer waking the task on
97        // the CPU that owns and expires this timer event. Falling back to the
98        // affinity selector is only needed if the task's affinity changed while
99        // it was sleeping.
100        wake_task_from_timer(self.task)
101    }
102}
103
104#[cfg(feature = "smp")]
105fn wake_task_from_timer(task: AxTaskRef) {
106    if task.cpumask().get(ax_hal::percpu::this_cpu_id()) {
107        current_run_queue::<RawState>().unblock_task(task, true);
108    } else {
109        select_run_queue::<RawState>(&task).unblock_task(task, true);
110    }
111}
112
113#[cfg(not(feature = "smp"))]
114fn wake_task_from_timer(task: AxTaskRef) {
115    current_run_queue::<RawState>().unblock_task(task, true);
116}
117
118/// Registers a callback function to be called on each timer tick.
119pub fn register_timer_callback<F>(callback: F)
120where
121    F: Fn(TimeValue) + Send + Sync + 'static,
122{
123    with_local_exclusive(|exclusive| {
124        TIMER_CALLBACKS.with_current_mut(exclusive, |callbacks| callbacks.push(Box::new(callback)))
125    });
126}
127
128fn check_callbacks() {
129    with_local_pin(|pin| {
130        TIMER_CALLBACKS.with_current(pin, |callbacks| {
131            for callback in callbacks {
132                callback(monotonic_time());
133            }
134        })
135    });
136}
137
138fn deadline_to_nanos(deadline: TimeValue) -> u64 {
139    deadline.as_nanos().min(u64::MAX as u128) as u64
140}
141
142pub(crate) fn maybe_reprogram_timer(deadline: TimeValue) {
143    clock_event::publish_earlier_deadline(deadline_to_nanos(deadline));
144}
145
146pub(crate) fn next_deadline_nanos() -> Option<u64> {
147    with_current_timer_base(|timer_base| timer_base.next_deadline().map(deadline_to_nanos))
148}
149
150pub(crate) fn set_alarm_wakeup(deadline: TimeValue, task: AxTaskRef) {
151    let _owner_guard = PreemptIrqSaveGuard::new();
152    with_current_timer_base(|timer_base| {
153        let ticket_id = TIMER_TICKET_ID.fetch_add(1, Ordering::AcqRel);
154        task.set_timer_ticket(ticket_id);
155        timer_base
156            .task_wakeups
157            .set(deadline, TaskWakeupEvent { ticket_id, task });
158    });
159    maybe_reprogram_timer(deadline);
160}
161
162pub(crate) fn register_future_timer(deadline: TimeValue) -> Option<FutureTimerHandle> {
163    let _owner_guard = PreemptIrqSaveGuard::new();
164    let owner_cpu = ax_hal::percpu::this_cpu_id();
165    let key = with_timer_base(owner_cpu, |timer_base| {
166        timer_base.future_wakeups.add(deadline)
167    })?;
168    maybe_reprogram_timer(deadline);
169    Some(FutureTimerHandle::new(owner_cpu, key))
170}
171
172pub(crate) fn poll_future_timer(handle: FutureTimerHandle, context: &mut Context<'_>) -> Poll<()> {
173    with_timer_base(handle.owner_cpu(), |timer_base| {
174        timer_base.future_wakeups.poll(&handle.key(), context)
175    })
176}
177
178pub(crate) fn cancel_future_timer(handle: FutureTimerHandle) {
179    with_timer_base(handle.owner_cpu(), |timer_base| {
180        timer_base.future_wakeups.cancel(&handle.key())
181    });
182}
183
184/// Registers a one-shot callback on the calling CPU's shared timer base.
185///
186/// The callback runs in `ktimers/<cpu>` task context without the timer-base
187/// lock held.
188pub fn register_kernel_timer(
189    deadline: MonotonicDeadline,
190    callback: KernelTimerCallback,
191) -> Result<KernelTimerHandle, KernelTimerError> {
192    validate_kernel_timer_context()?;
193    register_kernel_timer_entry(KernelTimerEntry::new(deadline, callback)?)
194}
195
196/// Registers a task-context callback that can rearm the same timer identity.
197pub fn register_restartable_kernel_timer(
198    deadline: MonotonicDeadline,
199    callback: RestartableKernelTimerCallback,
200) -> Result<KernelTimerHandle, KernelTimerError> {
201    validate_kernel_timer_context()?;
202    register_kernel_timer_entry(KernelTimerEntry::new_restartable(deadline, callback)?)
203}
204
205/// Registers a stable callback with explicit hard-IRQ expiry semantics.
206pub fn register_hard_restartable_kernel_timer(
207    deadline: MonotonicDeadline,
208    callback: HardKernelTimerCallback,
209) -> Result<KernelTimerHandle, KernelTimerError> {
210    validate_kernel_timer_context()?;
211    register_kernel_timer_entry(KernelTimerEntry::new_hard_restartable(deadline, callback)?)
212}
213
214/// Rearms an inactive hard timer on its registration CPU.
215pub fn arm_hard_kernel_timer(
216    handle: KernelTimerHandle,
217    deadline: MonotonicDeadline,
218) -> Result<(), KernelTimerError> {
219    validate_kernel_timer_context()?;
220    let _owner_guard = PreemptIrqSaveGuard::new();
221    let current_cpu = ax_hal::percpu::this_cpu_id();
222    if handle.owner().as_usize() != current_cpu {
223        return Err(KernelTimerError::OwnerMismatch {
224            expected: handle.owner().as_usize(),
225            actual: current_cpu,
226        });
227    }
228    let armed = try_with_timer_base(current_cpu, |timer_base| {
229        timer_base.kernel_timers.arm_hard(handle, deadline)
230    })?;
231    if !armed {
232        return Err(KernelTimerError::StaleHandle);
233    }
234    maybe_reprogram_timer(deadline.as_duration());
235    Ok(())
236}
237
238/// Disarms a stable hard timer without destroying its callback payload.
239pub fn disarm_hard_kernel_timer(handle: KernelTimerHandle) -> Result<(), KernelTimerError> {
240    validate_kernel_timer_context()?;
241    let owner_cpu = handle.owner().as_usize();
242    let found = try_with_timer_base(owner_cpu, |timer_base| {
243        timer_base.kernel_timers.disarm_hard(handle).is_some()
244    })?;
245    found.then_some(()).ok_or(KernelTimerError::StaleHandle)
246}
247
248/// Cancels a kernel-timer registration without waiting for an executing callback.
249///
250/// A callback already claimed for execution may finish, but its tombstone
251/// prevents any restartable action from returning to the active queue.
252pub fn cancel_kernel_timer(
253    handle: KernelTimerHandle,
254) -> Result<KernelTimerCancelOutcome, KernelTimerError> {
255    validate_kernel_timer_context()?;
256    let owner_cpu = handle.owner().as_usize();
257    let outcome = try_with_timer_base(owner_cpu, |timer_base| {
258        timer_base.kernel_timers.cancel(handle)
259    })?;
260    match outcome {
261        KernelTimerQueueCancel::Cancelled(entry) => {
262            drop(entry);
263            Ok(KernelTimerCancelOutcome::Cancelled)
264        }
265        KernelTimerQueueCancel::Executing => Ok(KernelTimerCancelOutcome::NotCancelled),
266        KernelTimerQueueCancel::Stale => Err(KernelTimerError::StaleHandle),
267    }
268}
269
270fn register_kernel_timer_entry(
271    entry: KernelTimerEntry,
272) -> Result<KernelTimerHandle, KernelTimerError> {
273    let _owner_guard = PreemptIrqSaveGuard::new();
274    let owner_cpu = ax_hal::percpu::this_cpu_id();
275    let deadline = entry
276        .deadline_for_registration()
277        .expect("new kernel timer must start armed");
278    let result = try_with_timer_base(owner_cpu, |timer_base| {
279        timer_base
280            .kernel_timers
281            .insert(TimerCpuId::new(owner_cpu), entry)
282    })?;
283    let handle = result.map_err(|_entry| KernelTimerError::Capacity { cpu_id: owner_cpu })?;
284    maybe_reprogram_timer(deadline.as_duration());
285    Ok(handle)
286}
287
288#[derive(Clone, Copy, Debug, Eq, PartialEq)]
289enum KernelTimerCallContext {
290    HardIrq,
291    Thread,
292    ThreadCriticalSection,
293}
294
295fn current_kernel_timer_call_context() -> KernelTimerCallContext {
296    if ax_hal::irq::in_irq_context() {
297        KernelTimerCallContext::HardIrq
298    } else if crate::in_atomic_context() {
299        KernelTimerCallContext::ThreadCriticalSection
300    } else {
301        KernelTimerCallContext::Thread
302    }
303}
304
305fn validate_kernel_timer_call_context(
306    context: KernelTimerCallContext,
307) -> Result<(), KernelTimerError> {
308    if context == KernelTimerCallContext::HardIrq {
309        Err(KernelTimerError::UnsafeContext)
310    } else {
311        Ok(())
312    }
313}
314
315fn validate_kernel_timer_context() -> Result<(), KernelTimerError> {
316    validate_kernel_timer_call_context(current_kernel_timer_call_context())
317}
318
319// SAFETY: only called in timer irq handler, so irq and preemption are
320// both disabled here.
321pub fn check_events(run_callbacks: bool) {
322    if run_callbacks {
323        check_callbacks();
324    }
325    let mut remaining_budget = TIMER_IRQ_BUDGET;
326    let hard_now = MonotonicInstant::from_duration(monotonic_time())
327        .expect("host monotonic clock must remain finite");
328    let mut hard_completed = false;
329    let mut hard_expired = 0;
330    while remaining_budget != 0 {
331        let execution =
332            with_current_timer_base(|timer_base| timer_base.kernel_timers.claim_due_hard(hard_now));
333        let Some(mut execution) = execution else {
334            break;
335        };
336        let action = unsafe {
337            // SAFETY: the timer IRQ owns the current CPU's hard-expiry pass,
338            // local IRQs are disabled, and the base lock was released before
339            // invoking the explicitly audited callback capability.
340            execution.invoke_hard()
341        };
342        hard_completed |= with_current_timer_base(|timer_base| {
343            timer_base
344                .kernel_timers
345                .complete_hard_execution(execution, action)
346        });
347        hard_expired += 1;
348        remaining_budget -= 1;
349    }
350
351    let mut task_expired = 0;
352    while remaining_budget != 0 {
353        let now = monotonic_time();
354        let event = with_current_timer_base(|timer_base| timer_base.task_wakeups.expire_one(now));
355        if let Some((_deadline, event)) = event {
356            event.callback(now);
357            task_expired += 1;
358            remaining_budget -= 1;
359        } else {
360            break;
361        }
362    }
363
364    let soft_now = MonotonicInstant::from_duration(monotonic_time())
365        .expect("host monotonic clock must remain finite");
366    let (soft_due, future_due, soft_promoted, soft_pending) =
367        with_current_timer_base(|timer_base| {
368            let future_due = timer_base
369                .future_wakeups
370                .publish_due_work(soft_now.as_duration());
371            let kernel = timer_base
372                .kernel_timers
373                .expire_due_soft(soft_now, remaining_budget);
374            let soft_due = future_due
375                || kernel.expired() != 0
376                || kernel.pending()
377                || timer_base.kernel_timers.has_expired()
378                || timer_base.kernel_timers.has_completed();
379            (soft_due, future_due, kernel.expired(), kernel.pending())
380        });
381    trace!(
382        "timer IRQ CPU {}: hard_expired={}, task_expired={}, soft_promoted={}, future_due={}, \
383         soft_pending={}, budget_left={}",
384        ax_hal::percpu::this_cpu_id(),
385        hard_expired,
386        task_expired,
387        soft_promoted,
388        future_due,
389        soft_pending,
390        remaining_budget
391    );
392    if soft_due || hard_completed {
393        current_timer_notify().notify_irq();
394    }
395}
396
397/// Starts the owner CPU's shared soft-timer service.
398///
399/// This must run after the scheduler and per-CPU area are initialized, but
400/// before the local timer IRQ is enabled.
401pub fn init_timer_service() {
402    let cpu_id = ax_hal::percpu::this_cpu_id();
403    if timer_worker_started(cpu_id)
404        .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
405        .is_err()
406    {
407        return;
408    }
409
410    with_timer_base(cpu_id, |timer_base| {
411        timer_base
412            .kernel_timers
413            .reserve_transition_capacity(cpu_id)
414            .unwrap_or_else(|error| {
415                panic!("failed to reserve CPU {cpu_id} kernel timer queues: {error}")
416            });
417    });
418
419    let notify = timer_notify(cpu_id);
420    let worker = TaskInner::new(
421        move || loop {
422            notify.wait();
423            drain_soft_timer_events(cpu_id);
424        },
425        format!("ktimers/{cpu_id}"),
426        crate::default_task_stack_size(),
427    );
428    let mut affinity = AxCpuMask::new();
429    affinity.set(cpu_id, true);
430    worker.set_cpumask(affinity);
431    crate::spawn_task(worker);
432}
433
434fn drain_soft_timer_events(cpu_id: usize) {
435    let mut processed = 0;
436    while processed < TIMER_IRQ_BUDGET {
437        if let Some(waker) = with_timer_base(cpu_id, |timer_base| {
438            timer_base.future_wakeups.expire_one(monotonic_time())
439        }) {
440            waker.wake();
441            processed += 1;
442            continue;
443        }
444
445        if let Some(mut execution) = with_timer_base(cpu_id, |timer_base| {
446            timer_base.kernel_timers.claim_expired()
447        }) {
448            let action = execution.invoke_soft();
449            let completed = with_timer_base(cpu_id, |timer_base| {
450                timer_base
451                    .kernel_timers
452                    .complete_soft_execution(execution, action)
453            });
454            drop(completed);
455            processed += 1;
456            continue;
457        }
458
459        if let Some(completed) = with_timer_base(cpu_id, |timer_base| {
460            timer_base.kernel_timers.claim_completed()
461        }) {
462            drop(completed);
463            processed += 1;
464            continue;
465        }
466        break;
467    }
468
469    let now = monotonic_time();
470    let pending = with_timer_base(cpu_id, |timer_base| {
471        timer_base.future_wakeups.finish_due_work(now)
472            || timer_base.kernel_timers.has_expired()
473            || timer_base.kernel_timers.has_completed()
474    });
475    if pending {
476        timer_notify(cpu_id).notify();
477        crate::yield_now();
478    } else if cpu_id == ax_hal::percpu::this_cpu_id()
479        && let Some(deadline) = with_timer_base(cpu_id, |timer_base| timer_base.next_deadline())
480    {
481        maybe_reprogram_timer(deadline);
482    }
483}
484
485fn with_current_timer_base<R>(operation: impl FnOnce(&mut PerCpuTimerBase) -> R) -> R {
486    with_timer_base(ax_hal::percpu::this_cpu_id(), operation)
487}
488
489fn with_timer_base<R>(cpu_id: usize, operation: impl FnOnce(&mut PerCpuTimerBase) -> R) -> R {
490    operation(&mut timer_base(cpu_id).lock_irqsave())
491}
492
493fn try_with_timer_base<R>(
494    cpu_id: usize,
495    operation: impl FnOnce(&mut PerCpuTimerBase) -> R,
496) -> Result<R, KernelTimerError> {
497    let timer_base = try_timer_base(cpu_id)?;
498    Ok(operation(&mut timer_base.lock_irqsave()))
499}
500
501fn timer_base(cpu_id: usize) -> &'static SpinLock<PerCpuTimerBase> {
502    try_timer_base(cpu_id).unwrap_or_else(|error| panic!("timer-base access failed: {error}"))
503}
504
505fn try_timer_base(cpu_id: usize) -> Result<&'static SpinLock<PerCpuTimerBase>, KernelTimerError> {
506    let area = try_timer_cpu_area(cpu_id)?;
507    // SAFETY: every installed CPU area contains a process-lifetime TIMER_BASE
508    // object, including the scheduler-start-to-timer-service window. Allowing
509    // enqueue during that window is required because the GC task is created by
510    // `init_scheduler`; `init_timer_service` starts the worker before timer IRQs
511    // are enabled and the first clockevent publication includes queued work.
512    // All mutable access is serialized by the base's IRQ-save lock.
513    Ok(unsafe { TIMER_BASE.remote_ptr(area).as_ref() })
514}
515
516fn timer_notify(cpu_id: usize) -> &'static IrqNotify {
517    let area = timer_cpu_area(cpu_id);
518    // SAFETY: the per-CPU area remains installed for the kernel lifetime and
519    // IrqNotify provides its own synchronization for cross-context access.
520    unsafe { TIMER_NOTIFY.remote_ptr(area).as_ref() }
521}
522
523fn current_timer_notify() -> &'static IrqNotify {
524    timer_notify(ax_hal::percpu::this_cpu_id())
525}
526
527fn timer_worker_started(cpu_id: usize) -> &'static AtomicBool {
528    let area = timer_cpu_area(cpu_id);
529    // SAFETY: AtomicBool supports concurrent shared access and the per-CPU
530    // storage remains live for the kernel lifetime.
531    unsafe { TIMER_WORKER_STARTED.remote_ptr(area).as_ref() }
532}
533
534fn timer_cpu_area(cpu_id: usize) -> ax_percpu::PerCpuArea {
535    try_timer_cpu_area(cpu_id)
536        .unwrap_or_else(|error| panic!("timer CPU area access failed: {error}"))
537}
538
539fn try_timer_cpu_area(cpu_id: usize) -> Result<ax_percpu::PerCpuArea, KernelTimerError> {
540    let cpu_index = ax_percpu::CpuIndex::try_from(cpu_id)
541        .map_err(|_| KernelTimerError::CpuUnavailable { cpu_id })?;
542    ax_percpu::area(cpu_index).map_err(|_| KernelTimerError::CpuUnavailable { cpu_id })
543}
544
545fn with_local_pin<R>(
546    operation: impl for<'scope> FnOnce(&ax_hal::percpu::CpuPin<'scope>) -> R,
547) -> R {
548    let _guard = PreemptIrqSaveGuard::new();
549    // SAFETY: the guard prevents migration for the complete callback.
550    unsafe { ax_hal::percpu::with_cpu_pin(operation) }
551        .expect("timer access requires an installed CPU-local area")
552}
553
554fn with_local_exclusive<R>(
555    operation: impl for<'exclusive> FnOnce(&ax_hal::percpu::ExclusiveCpu<'exclusive>) -> R,
556) -> R {
557    let _guard = PreemptIrqSaveGuard::new();
558    // SAFETY: the guard excludes migration, local IRQ/re-entry, and conflicting
559    // local access for the complete callback.
560    unsafe {
561        ax_hal::percpu::with_cpu_pin(|pin| ax_hal::percpu::with_exclusive_cpu(pin, operation))
562    }
563    .expect("timer access requires an installed CPU-local area")
564}
565
566#[cfg(test)]
567mod tests {
568    use super::{KernelTimerCallContext, validate_kernel_timer_call_context};
569
570    #[test]
571    fn vcpu_thread_critical_section_may_update_soft_timers() {
572        assert!(
573            validate_kernel_timer_call_context(KernelTimerCallContext::ThreadCriticalSection)
574                .is_ok()
575        );
576    }
577
578    #[test]
579    fn hard_irq_may_not_register_or_cancel_soft_timers() {
580        assert!(validate_kernel_timer_call_context(KernelTimerCallContext::HardIrq).is_err());
581    }
582}