Skip to main content

ax_task/timers/kernel/
mod.rs

1//! Kernel callbacks sharing the scheduler clockevent owner.
2
3use alloc::{boxed::Box, vec::Vec};
4use core::{
5    fmt,
6    num::NonZeroU64,
7    sync::atomic::{AtomicU64, Ordering},
8    time::Duration,
9};
10
11static NEXT_KERNEL_TIMER_ID: AtomicU64 = AtomicU64::new(1);
12
13/// Dense logical CPU identity owning one kernel timer registration.
14#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
15pub struct TimerCpuId(usize);
16
17impl TimerCpuId {
18    pub const fn new(cpu_id: usize) -> Self {
19        Self(cpu_id)
20    }
21
22    pub const fn as_usize(self) -> usize {
23        self.0
24    }
25}
26
27/// Finite absolute deadline in the host monotonic clock domain.
28#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
29pub struct MonotonicDeadline(Duration);
30
31impl MonotonicDeadline {
32    pub fn from_duration(deadline: Duration) -> Result<Self, KernelTimerError> {
33        let nanos = deadline.as_nanos();
34        if nanos >= u128::from(u64::MAX) {
35            return Err(KernelTimerError::InvalidDeadline);
36        }
37        Ok(Self(deadline))
38    }
39
40    pub fn from_nanos(nanos: u64) -> Result<Self, KernelTimerError> {
41        Self::from_duration(Duration::from_nanos(nanos))
42    }
43
44    pub const fn as_duration(self) -> Duration {
45        self.0
46    }
47}
48
49/// Sample from the same monotonic clock domain as [`MonotonicDeadline`].
50#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
51pub struct MonotonicInstant(Duration);
52
53impl MonotonicInstant {
54    pub fn from_duration(now: Duration) -> Result<Self, KernelTimerError> {
55        let nanos = now.as_nanos();
56        if nanos >= u128::from(u64::MAX) {
57            return Err(KernelTimerError::InvalidDeadline);
58        }
59        Ok(Self(now))
60    }
61
62    pub fn from_nanos(nanos: u64) -> Result<Self, KernelTimerError> {
63        Self::from_duration(Duration::from_nanos(nanos))
64    }
65
66    pub const fn as_duration(self) -> Duration {
67        self.0
68    }
69
70    pub fn reached(self, deadline: MonotonicDeadline) -> bool {
71        self.0 >= deadline.0
72    }
73}
74
75/// Errors from the shared host kernel-timer service.
76#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
77pub enum KernelTimerError {
78    #[error("kernel timer registration and cancellation are unavailable in hard IRQ context")]
79    UnsafeContext,
80    #[error("timer deadline must be finite")]
81    InvalidDeadline,
82    #[error("kernel timer identity space is exhausted")]
83    GenerationExhausted,
84    #[error("CPU {cpu_id} has no initialized timer base")]
85    CpuUnavailable { cpu_id: usize },
86    #[error("kernel timer capacity is exhausted on CPU {cpu_id}")]
87    Capacity { cpu_id: usize },
88    #[error("kernel timer handle is stale")]
89    StaleHandle,
90    #[error("kernel timer owner mismatch: expected CPU {expected}, current CPU {actual}")]
91    OwnerMismatch { expected: usize, actual: usize },
92}
93
94/// Callback executed by the owner CPU's `ktimers/%u` service thread.
95pub type KernelTimerCallback = Box<dyn FnOnce(MonotonicInstant) + Send + 'static>;
96
97/// Callback for a stable timer registration that may restart itself.
98pub type RestartableKernelTimerCallback =
99    Box<dyn FnMut(MonotonicInstant) -> KernelTimerAction + Send + 'static>;
100/// Owned callback for an explicitly hard-expiry kernel timer.
101pub type HardRestartableKernelTimerCallback =
102    Box<dyn FnMut(MonotonicInstant) -> HardKernelTimerAction + Send + 'static>;
103
104/// Explicit capability for a bounded callback that may execute in hard IRQ.
105///
106/// The callback allocation is created and destroyed in task context. The
107/// timer base invokes it without allocating, freeing, sleeping, performing a
108/// registry lookup, or holding the deadline-base lock. Completion is moved to
109/// `ktimers/%u` before the callback payload can be dropped.
110pub struct HardKernelTimerCallback {
111    callback: HardRestartableKernelTimerCallback,
112}
113
114impl HardKernelTimerCallback {
115    /// Creates one hard-expiry callback capability.
116    ///
117    /// # Safety
118    ///
119    /// Every invocation must be bounded, non-panicking, allocation-free and
120    /// valid in hard IRQ context. It must use only IRQ-safe synchronization
121    /// and prebound capabilities; it may not sleep, perform registry lookup,
122    /// invoke an untyped external callback, or clone/drop owning references.
123    pub unsafe fn new(callback: HardRestartableKernelTimerCallback) -> Self {
124        Self { callback }
125    }
126
127    fn invoke(&mut self, expired_at: MonotonicInstant) -> HardKernelTimerAction {
128        (self.callback)(expired_at)
129    }
130}
131
132/// Result returned by an explicitly hard-expiry callback.
133#[derive(Clone, Copy, Debug, Eq, PartialEq)]
134pub enum HardKernelTimerAction {
135    /// Destroy this registration after task-context reclamation.
136    Complete,
137    /// Keep the stable registration inactive until task context arms it again.
138    Disarm,
139    /// Reinsert the same registration at a new absolute deadline.
140    Rearm(MonotonicDeadline),
141}
142
143/// Result returned by a restartable kernel-timer callback.
144#[derive(Clone, Copy, Debug, Eq, PartialEq)]
145pub enum KernelTimerAction {
146    /// Finish this registration after the current callback.
147    Complete,
148    /// Reinsert the same registration at a new absolute deadline.
149    Rearm(MonotonicDeadline),
150}
151
152/// Stable identity of one host kernel-timer registration.
153#[derive(Clone, Copy, Debug, Eq, PartialEq)]
154pub struct KernelTimerHandle {
155    owner: TimerCpuId,
156    identity: NonZeroU64,
157}
158
159impl KernelTimerHandle {
160    pub(crate) const fn new(owner: TimerCpuId, identity: NonZeroU64) -> Self {
161        Self { owner, identity }
162    }
163
164    /// Returns the CPU deadline base that owns this registration.
165    pub const fn owner(self) -> TimerCpuId {
166        self.owner
167    }
168
169    pub(crate) const fn identity(self) -> NonZeroU64 {
170        self.identity
171    }
172}
173
174/// Outcome of a non-blocking kernel-timer cancellation attempt.
175#[derive(Clone, Copy, Debug, Eq, PartialEq)]
176pub enum KernelTimerCancelOutcome {
177    /// The queued or expired callback was removed before execution.
178    Cancelled,
179    /// The handle was already cancelled, claimed for execution, or completed.
180    NotCancelled,
181}
182
183pub(crate) enum KernelTimerQueueCancel {
184    Cancelled(KernelTimerEntry),
185    Executing,
186    Stale,
187}
188
189pub(crate) struct KernelTimerEntry {
190    identity: NonZeroU64,
191    deadline: Option<MonotonicDeadline>,
192    expired_at: Option<MonotonicInstant>,
193    callback: KernelTimerCallbackState,
194}
195
196enum KernelTimerCallbackState {
197    OneShot(Option<KernelTimerCallback>),
198    Restartable(RestartableKernelTimerCallback),
199    HardRestartable(HardKernelTimerCallback),
200}
201
202impl KernelTimerEntry {
203    pub(crate) fn new(
204        deadline: MonotonicDeadline,
205        callback: KernelTimerCallback,
206    ) -> Result<Self, KernelTimerError> {
207        Ok(Self {
208            identity: next_kernel_timer_identity()?,
209            deadline: Some(deadline),
210            expired_at: None,
211            callback: KernelTimerCallbackState::OneShot(Some(callback)),
212        })
213    }
214
215    pub(crate) fn new_restartable(
216        deadline: MonotonicDeadline,
217        callback: RestartableKernelTimerCallback,
218    ) -> Result<Self, KernelTimerError> {
219        Ok(Self {
220            identity: next_kernel_timer_identity()?,
221            deadline: Some(deadline),
222            expired_at: None,
223            callback: KernelTimerCallbackState::Restartable(callback),
224        })
225    }
226
227    pub(crate) fn new_hard_restartable(
228        deadline: MonotonicDeadline,
229        callback: HardKernelTimerCallback,
230    ) -> Result<Self, KernelTimerError> {
231        Ok(Self {
232            identity: next_kernel_timer_identity()?,
233            deadline: Some(deadline),
234            expired_at: None,
235            callback: KernelTimerCallbackState::HardRestartable(callback),
236        })
237    }
238
239    fn deadline(&self) -> MonotonicDeadline {
240        self.deadline
241            .expect("only an armed kernel timer has a deadline")
242    }
243
244    pub(crate) const fn deadline_for_registration(&self) -> Option<MonotonicDeadline> {
245        self.deadline
246    }
247
248    const fn identity(&self) -> NonZeroU64 {
249        self.identity
250    }
251
252    fn expire(&mut self, now: MonotonicInstant) {
253        assert!(self.expired_at.replace(now).is_none());
254    }
255
256    fn rearm(&mut self, deadline: MonotonicDeadline) {
257        self.deadline = Some(deadline);
258        self.expired_at = None;
259    }
260
261    fn disarm(&mut self) -> MonotonicDeadline {
262        self.expired_at = None;
263        self.deadline
264            .take()
265            .expect("only an armed kernel timer can be disarmed")
266    }
267
268    const fn is_armed(&self) -> bool {
269        self.deadline.is_some()
270    }
271
272    const fn is_hard(&self) -> bool {
273        matches!(self.callback, KernelTimerCallbackState::HardRestartable(_))
274    }
275}
276
277fn next_kernel_timer_identity() -> Result<NonZeroU64, KernelTimerError> {
278    let identity = NEXT_KERNEL_TIMER_ID
279        .try_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
280            current.checked_add(1)
281        })
282        .map_err(|_| KernelTimerError::GenerationExhausted)?;
283    NonZeroU64::new(identity).ok_or(KernelTimerError::GenerationExhausted)
284}
285
286impl fmt::Debug for KernelTimerEntry {
287    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
288        formatter
289            .debug_struct("KernelTimerEntry")
290            .field("identity", &self.identity)
291            .field("deadline", &self.deadline)
292            .field("expired_at", &self.expired_at)
293            .finish_non_exhaustive()
294    }
295}
296
297/// One callback claimed by the ktimer worker.
298///
299/// Cancellation while the callback runs leaves a tombstone that prevents a
300/// restartable callback from returning the entry to the active queue.
301pub(crate) struct KernelTimerExecution {
302    entry: KernelTimerEntry,
303}
304
305impl KernelTimerExecution {
306    pub(crate) fn invoke_soft(&mut self) -> KernelTimerAction {
307        let expired_at = self
308            .entry
309            .expired_at
310            .expect("claimed kernel timer must have an expiry sample");
311        match &mut self.entry.callback {
312            KernelTimerCallbackState::OneShot(callback) => {
313                callback
314                    .take()
315                    .expect("kernel timer callback may execute only once")(
316                    expired_at
317                );
318                KernelTimerAction::Complete
319            }
320            KernelTimerCallbackState::Restartable(callback) => callback(expired_at),
321            KernelTimerCallbackState::HardRestartable(_) => {
322                panic!("hard kernel timer must not execute in ktimers/%u")
323            }
324        }
325    }
326
327    /// Invokes an explicitly hard-IRQ-safe callback.
328    ///
329    /// # Safety
330    ///
331    /// The caller must own the CPU's hard-timer execution context with local
332    /// IRQs excluded. The deadline-base lock must not be held.
333    pub(crate) unsafe fn invoke_hard(&mut self) -> HardKernelTimerAction {
334        let expired_at = self
335            .entry
336            .expired_at
337            .expect("claimed hard kernel timer must have an expiry sample");
338        match &mut self.entry.callback {
339            KernelTimerCallbackState::HardRestartable(callback) => callback.invoke(expired_at),
340            KernelTimerCallbackState::OneShot(_) | KernelTimerCallbackState::Restartable(_) => {
341                panic!("task-context kernel timer must not execute in hard IRQ")
342            }
343        }
344    }
345
346    const fn is_hard(&self) -> bool {
347        self.entry.is_hard()
348    }
349}
350
351#[derive(Clone, Copy, Debug, Eq, PartialEq)]
352struct ExecutingKernelTimer {
353    identity: NonZeroU64,
354    disposition: ExecutingKernelTimerDisposition,
355}
356
357#[derive(Clone, Copy, Debug, Eq, PartialEq)]
358enum ExecutingKernelTimerDisposition {
359    Continue,
360    Disarm,
361    Rearm(MonotonicDeadline),
362    Destroy,
363}
364
365/// Result of one bounded hard-IRQ promotion pass.
366#[derive(Clone, Copy, Debug, Eq, PartialEq)]
367pub(crate) struct KernelTimerExpireBatch {
368    expired: usize,
369    pending: bool,
370}
371
372impl KernelTimerExpireBatch {
373    pub(crate) const fn expired(self) -> usize {
374        self.expired
375    }
376
377    pub(crate) const fn pending(self) -> bool {
378        self.pending
379    }
380}
381
382/// Fixed-capacity kernel callback clock base.
383///
384/// Callback ownership is allocated before this queue is locked. Expiry only
385/// moves entries between preallocated vectors, so hard IRQ never
386/// allocates, frees, or invokes arbitrary code.
387pub(crate) struct KernelTimerQueue {
388    active: Vec<KernelTimerEntry>,
389    inactive: Vec<KernelTimerEntry>,
390    expired: Vec<KernelTimerEntry>,
391    executing: Vec<ExecutingKernelTimer>,
392    completed: Vec<KernelTimerEntry>,
393    capacity: usize,
394}
395
396impl KernelTimerQueue {
397    pub(crate) const fn new(capacity: usize) -> Self {
398        Self {
399            active: Vec::new(),
400            inactive: Vec::new(),
401            expired: Vec::new(),
402            executing: Vec::new(),
403            completed: Vec::new(),
404            capacity,
405        }
406    }
407
408    /// Reserves every transition queue before timer IRQs can move entries.
409    pub(crate) fn reserve_transition_capacity(
410        &mut self,
411        cpu_id: usize,
412    ) -> Result<(), KernelTimerError> {
413        self.active
414            .try_reserve_exact(self.capacity)
415            .map_err(|_| KernelTimerError::Capacity { cpu_id })?;
416        self.inactive
417            .try_reserve_exact(self.capacity)
418            .map_err(|_| KernelTimerError::Capacity { cpu_id })?;
419        self.expired
420            .try_reserve_exact(self.capacity)
421            .map_err(|_| KernelTimerError::Capacity { cpu_id })?;
422        self.executing
423            .try_reserve_exact(self.capacity)
424            .map_err(|_| KernelTimerError::Capacity { cpu_id })?;
425        self.completed
426            .try_reserve_exact(self.capacity)
427            .map_err(|_| KernelTimerError::Capacity { cpu_id })?;
428        Ok(())
429    }
430
431    pub(crate) fn insert(
432        &mut self,
433        owner: TimerCpuId,
434        entry: KernelTimerEntry,
435    ) -> Result<KernelTimerHandle, KernelTimerEntry> {
436        if self.active.len()
437            + self.inactive.len()
438            + self.expired.len()
439            + self.executing.len()
440            + self.completed.len()
441            >= self.capacity
442        {
443            return Err(entry);
444        }
445        let handle = KernelTimerHandle::new(owner, entry.identity());
446        self.insert_entry(entry);
447        Ok(handle)
448    }
449
450    pub(crate) fn cancel(&mut self, handle: KernelTimerHandle) -> KernelTimerQueueCancel {
451        if let Some(index) = self
452            .active
453            .iter()
454            .position(|entry| entry.identity() == handle.identity())
455        {
456            return KernelTimerQueueCancel::Cancelled(self.active.remove(index));
457        }
458        if let Some(index) = self
459            .inactive
460            .iter()
461            .position(|entry| entry.identity() == handle.identity())
462        {
463            return KernelTimerQueueCancel::Cancelled(self.inactive.remove(index));
464        }
465        let removed = self
466            .expired
467            .iter()
468            .position(|entry| entry.identity() == handle.identity())
469            .map(|index| self.expired.remove(index));
470        if let Some(entry) = removed {
471            return KernelTimerQueueCancel::Cancelled(entry);
472        }
473        if let Some(executing) = self
474            .executing
475            .iter_mut()
476            .find(|entry| entry.identity == handle.identity())
477        {
478            executing.disposition = ExecutingKernelTimerDisposition::Destroy;
479            return KernelTimerQueueCancel::Executing;
480        }
481        KernelTimerQueueCancel::Stale
482    }
483
484    pub(crate) fn arm_hard(
485        &mut self,
486        handle: KernelTimerHandle,
487        deadline: MonotonicDeadline,
488    ) -> bool {
489        if let Some(index) = self
490            .inactive
491            .iter()
492            .position(|entry| entry.identity() == handle.identity() && entry.is_hard())
493        {
494            let mut entry = self.inactive.remove(index);
495            entry.rearm(deadline);
496            self.insert_at(entry);
497            return true;
498        }
499        if let Some(executing) = self
500            .executing
501            .iter_mut()
502            .find(|entry| entry.identity == handle.identity())
503            && executing.disposition != ExecutingKernelTimerDisposition::Destroy
504        {
505            // Like hrtimer_start() racing a running callback, task context
506            // publishes the next arm on the stable identity. Completion owns
507            // the only transition back into the active base.
508            executing.disposition = ExecutingKernelTimerDisposition::Rearm(deadline);
509            return true;
510        }
511        false
512    }
513
514    /// Disarms one stable hard registration without releasing its payload.
515    ///
516    /// `Some(Some(deadline))` reports an active entry that moved to inactive,
517    /// `Some(None)` reports an already inactive or executing entry, and `None`
518    /// reports a stale or non-hard handle.
519    pub(crate) fn disarm_hard(
520        &mut self,
521        handle: KernelTimerHandle,
522    ) -> Option<Option<MonotonicDeadline>> {
523        if self
524            .inactive
525            .iter()
526            .any(|entry| entry.identity() == handle.identity() && entry.is_hard())
527        {
528            return Some(None);
529        }
530        if let Some(index) = self
531            .active
532            .iter()
533            .position(|entry| entry.identity() == handle.identity() && entry.is_hard())
534        {
535            let mut entry = self.active.remove(index);
536            let deadline = entry.disarm();
537            self.inactive.push(entry);
538            return Some(Some(deadline));
539        }
540        if let Some(executing) = self
541            .executing
542            .iter_mut()
543            .find(|entry| entry.identity == handle.identity())
544        {
545            if executing.disposition != ExecutingKernelTimerDisposition::Destroy {
546                executing.disposition = ExecutingKernelTimerDisposition::Disarm;
547            }
548            return Some(None);
549        }
550        None
551    }
552
553    pub(crate) fn expire_due_soft(
554        &mut self,
555        now: MonotonicInstant,
556        budget: usize,
557    ) -> KernelTimerExpireBatch {
558        let mut expired = 0;
559        while expired < budget {
560            let Some(index) = self.next_active_index(false) else {
561                break;
562            };
563            if !now.reached(self.active[index].deadline()) {
564                break;
565            }
566            let mut entry = self.active.remove(index);
567            entry.expire(now);
568            self.expired.push(entry);
569            expired += 1;
570        }
571        KernelTimerExpireBatch {
572            expired,
573            pending: self.has_due_soft(now),
574        }
575    }
576
577    pub(crate) fn claim_due_hard(&mut self, now: MonotonicInstant) -> Option<KernelTimerExecution> {
578        let index = self.next_active_index(true)?;
579        if !now.reached(self.active[index].deadline()) {
580            return None;
581        }
582        let mut entry = self.active.remove(index);
583        entry.expire(now);
584        self.executing.push(ExecutingKernelTimer {
585            identity: entry.identity(),
586            disposition: ExecutingKernelTimerDisposition::Continue,
587        });
588        Some(KernelTimerExecution { entry })
589    }
590
591    pub(crate) fn claim_expired(&mut self) -> Option<KernelTimerExecution> {
592        if self.expired.is_empty() {
593            return None;
594        }
595        let entry = self.expired.remove(0);
596        self.executing.push(ExecutingKernelTimer {
597            identity: entry.identity(),
598            disposition: ExecutingKernelTimerDisposition::Continue,
599        });
600        Some(KernelTimerExecution { entry })
601    }
602
603    pub(crate) fn complete_soft_execution(
604        &mut self,
605        mut execution: KernelTimerExecution,
606        action: KernelTimerAction,
607    ) -> Option<KernelTimerEntry> {
608        assert!(!execution.is_hard());
609        let position = self
610            .executing
611            .iter()
612            .position(|entry| entry.identity == execution.entry.identity())
613            .expect("completed kernel timer must remain in executing state");
614        let executing = self.executing.swap_remove(position);
615        if executing.disposition == ExecutingKernelTimerDisposition::Continue
616            && let KernelTimerAction::Rearm(deadline) = action
617        {
618            execution.entry.rearm(deadline);
619            self.insert_at(execution.entry);
620            return None;
621        }
622        Some(execution.entry)
623    }
624
625    /// Completes one hard callback without dropping its payload in hard IRQ.
626    ///
627    /// Returns `true` when task-context reclamation was queued.
628    pub(crate) fn complete_hard_execution(
629        &mut self,
630        mut execution: KernelTimerExecution,
631        action: HardKernelTimerAction,
632    ) -> bool {
633        assert!(execution.is_hard());
634        let position = self
635            .executing
636            .iter()
637            .position(|entry| entry.identity == execution.entry.identity())
638            .expect("completed hard kernel timer must remain in executing state");
639        let executing = self.executing.swap_remove(position);
640        match (executing.disposition, action) {
641            (ExecutingKernelTimerDisposition::Destroy, _) => {
642                self.completed.push(execution.entry);
643                true
644            }
645            (ExecutingKernelTimerDisposition::Disarm, _) => {
646                execution.entry.disarm();
647                self.inactive.push(execution.entry);
648                false
649            }
650            (ExecutingKernelTimerDisposition::Rearm(deadline), _) => {
651                execution.entry.rearm(deadline);
652                self.insert_at(execution.entry);
653                false
654            }
655            (ExecutingKernelTimerDisposition::Continue, HardKernelTimerAction::Complete) => {
656                self.completed.push(execution.entry);
657                true
658            }
659            (ExecutingKernelTimerDisposition::Continue, HardKernelTimerAction::Disarm) => {
660                execution.entry.disarm();
661                self.inactive.push(execution.entry);
662                false
663            }
664            (ExecutingKernelTimerDisposition::Continue, HardKernelTimerAction::Rearm(deadline)) => {
665                execution.entry.rearm(deadline);
666                self.insert_at(execution.entry);
667                false
668            }
669        }
670    }
671
672    pub(crate) fn claim_completed(&mut self) -> Option<KernelTimerEntry> {
673        (!self.completed.is_empty()).then(|| self.completed.remove(0))
674    }
675
676    fn insert_at(&mut self, entry: KernelTimerEntry) {
677        debug_assert!(entry.is_armed());
678        let position = self.active.partition_point(|candidate| {
679            (candidate.deadline(), candidate.identity()) > (entry.deadline(), entry.identity())
680        });
681        self.active.insert(position, entry);
682    }
683
684    fn insert_entry(&mut self, entry: KernelTimerEntry) {
685        if entry.is_armed() {
686            self.insert_at(entry);
687        } else {
688            self.inactive.push(entry);
689        }
690    }
691
692    pub(crate) fn next_soft_deadline(&self) -> Option<MonotonicDeadline> {
693        self.next_active_entry(false)
694            .map(KernelTimerEntry::deadline)
695    }
696
697    pub(crate) fn next_hard_deadline(&self) -> Option<MonotonicDeadline> {
698        self.next_active_entry(true).map(KernelTimerEntry::deadline)
699    }
700
701    pub(crate) fn has_due_soft(&self, now: MonotonicInstant) -> bool {
702        self.next_soft_deadline()
703            .is_some_and(|deadline| now.reached(deadline))
704    }
705
706    pub(crate) fn has_expired(&self) -> bool {
707        !self.expired.is_empty()
708    }
709
710    pub(crate) fn has_completed(&self) -> bool {
711        !self.completed.is_empty()
712    }
713
714    #[cfg(test)]
715    pub(crate) fn has_inactive(&self) -> bool {
716        !self.inactive.is_empty()
717    }
718
719    #[cfg(test)]
720    pub(crate) fn has_active_work(&self) -> bool {
721        !self.active.is_empty()
722            || !self.expired.is_empty()
723            || !self.executing.is_empty()
724            || !self.completed.is_empty()
725    }
726
727    fn next_active_index(&self, hard: bool) -> Option<usize> {
728        self.active
729            .iter()
730            .rposition(|entry| entry.is_hard() == hard)
731    }
732
733    fn next_active_entry(&self, hard: bool) -> Option<&KernelTimerEntry> {
734        self.next_active_index(hard)
735            .map(|index| &self.active[index])
736    }
737}
738
739impl fmt::Debug for KernelTimerQueue {
740    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
741        formatter
742            .debug_struct("KernelTimerQueue")
743            .field("active", &self.active)
744            .field("inactive", &self.inactive)
745            .field("expired", &self.expired)
746            .field("executing", &self.executing)
747            .field("completed", &self.completed)
748            .field("capacity", &self.capacity)
749            .finish()
750    }
751}
752
753#[cfg(test)]
754mod tests;