Skip to main content

strat9_kernel/process/scheduler/
timer_ops.rs

1use super::*;
2use core::sync::atomic::{AtomicBool, AtomicU64};
3
4// One-shot bootstrap nudge: ensure each CPU requests at least one preemption
5// after entering Ring 3. This breaks "first task runs forever" scenarios when
6// class accounting has not yet accumulated enough runtime to trigger resched.
7static FIRST_TICK_FORCE_RESCHED: [AtomicBool; crate::arch::percpu::MAX_CPUS] =
8    [const { AtomicBool::new(false) }; crate::arch::percpu::MAX_CPUS];
9
10// Per-CPU local tick counter (incremented every timer tick, independent of
11// the BSP-only global TICK_COUNT). Used for periodic force-resched below.
12static CPU_LOCAL_TICKS: [AtomicU64; crate::arch::percpu::MAX_CPUS] =
13    [const { AtomicU64::new(0) }; crate::arch::percpu::MAX_CPUS];
14
15// Force a reschedule at least every N ticks per CPU, regardless of GLOBAL_SCHED_STATE
16// lock availability. This guarantees kernel tasks (shell, idle) get CPU time
17// even if timer_tick consistently loses the scheduler lock race with the other CPU.
18const PERIODIC_RESCHED_TICKS: u64 = 5;
19
20/// Timer interrupt handler - called from interrupt context.
21///
22/// Increments the global tick counter unconditionally on BSP so wall-clock
23/// time never drifts even when the scheduler lock is contended. Secondary
24/// bookkeeping (interval timers, wake deadlines, per-task accounting) is
25/// deferred when the lock is unavailable.
26///
27/// Lock discipline: `tick_all_timers` and `check_wake_deadlines` each acquire
28/// the scheduler lock themselves via `try_lock`. The per-task block below uses
29/// its own `try_lock`. These are separate acquisitions by design - the inner
30/// functions must not be called while the outer lock is held (that would deadlock).
31pub fn timer_tick() {
32    let _perf = super::perf_counters::PerfScope::new(
33        &super::perf_counters::IRQ_TIMER_TSC,
34        &super::perf_counters::IRQ_TIMER_COUNT,
35    );
36
37    // Feed TSC low bits into the entropy pool (every tick).
38    crate::entropy::add_entropy(2, crate::arch::rdtsc());
39    let cpu_idx = crate::arch::percpu::current_cpu_index();
40
41    if cpu_is_valid(cpu_idx) {
42        CPU_TOTAL_TICKS[cpu_idx].fetch_add(1, Ordering::Relaxed);
43    }
44
45    // BSP wall-clock: ALWAYS advance, regardless of any lock state.
46    if cpu_idx == 0 {
47        TICK_COUNT.fetch_add(1, Ordering::Relaxed);
48    }
49
50    // Per-CPU local tick counter (all CPUs, not just BSP).
51    let local_tick = if cpu_is_valid(cpu_idx) {
52        CPU_LOCAL_TICKS[cpu_idx].fetch_add(1, Ordering::Relaxed) + 1
53    } else {
54        0
55    };
56
57    // Lock-free bootstrap nudge: first local timer tick requests one resched
58    // without touching GLOBAL_SCHED_STATE. This avoids pathological boot windows where
59    // another CPU holds GLOBAL_SCHED_STATE and this CPU would otherwise defer the first
60    // `need_resched` update indefinitely.
61    if cpu_is_valid(cpu_idx) && !FIRST_TICK_FORCE_RESCHED[cpu_idx].swap(true, Ordering::AcqRel) {
62        request_force_resched_hint(cpu_idx);
63    }
64
65    // Periodic force-resched: guarantee every CPU reschedules at least every
66    // PERIODIC_RESCHED_TICKS ticks. This prevents starvation of kernel tasks
67    // when timer_tick fails to acquire the scheduler lock repeatedly, or when
68    // FairClassRq::update_current returns false (e.g., single-task queue).
69    if cpu_is_valid(cpu_idx) && local_tick > 0 && local_tick % PERIODIC_RESCHED_TICKS == 0 {
70        request_force_resched_hint(cpu_idx);
71    }
72
73    // Deferred tick processing: raise work items for processing at safe points
74    // (finish_switch, finish_interrupt_switch, idle loop). This moves heavy
75    // lock-acquiring work out of hardirq context, reducing IRQ latency.
76    //
77    // What remains in hardirq (lock-free only):
78    //   - Counter increments (lock-free atomics)
79    //   - FORCE_RESCHED_HINT (lock-free)
80    //   - Force-resched hint every 5 ticks
81    if cpu_idx == 0 {
82        let tick = TICK_COUNT.load(Ordering::Relaxed);
83        if tick % 200 == 0 {
84            crate::hardware::thermal::poll();
85        }
86    }
87
88    // Raise deferred work for all tick processing.
89    // Timer processing (interval timers, wake deadlines) was previously done
90    // inline on the BSP. Now ALL CPUs raise their own deferred work, and the
91    // processing happens at safe points on each CPU independently.
92    super::deferred_work::raise_tick_deferred_work();
93}
94
95/// Check wake deadlines for all tasks and wake up those whose sleep has expired.
96///
97/// Called from timer_tick() with interrupts disabled.
98/// Uses try_lock() to avoid deadlock if called while scheduler lock is held.
99///
100/// # Lock discipline
101///
102/// The `BLOCKED_TASKS` lock is held **only** during the scan + re-enqueue phase.
103/// The lock is explicitly dropped before sending IPIs (which may acquire
104/// per-CPU data) and before any `Arc<Task>` drop (which reaches
105/// `KernelStack::drop => free_frames => buddy_alloc.lock()`).
106///
107/// To guarantee this, every `Arc<Task>` removed from `blocked_tasks` is
108/// moved into the `deferred_drops` array. Those Arcs are dropped after the
109/// guard goes out of scope, ensuring `free_frames` is never called while the
110/// scheduler lock is held.
111pub(super) fn check_wake_deadlines(current_time_ns: u64) {
112    let mut ipi_targets = [false; crate::arch::percpu::MAX_CPUS];
113    let my_cpu = current_cpu_index();
114
115    const BATCH: usize = 128;
116    let mut deferred_drops: [Option<Arc<Task>>; BATCH] = [const { None }; BATCH];
117    let mut drop_count = 0usize;
118
119    {
120        // Lock order: BLOCKED (rank 3) -> LOCAL (rank 4).
121        lockdep_acquire(LockRank::Blocked, None);
122        let mut blocked = match super::BLOCKED_TASKS.try_lock_no_irqsave() {
123            Some(guard) => guard,
124            None => {
125                lockdep_release(LockRank::Blocked);
126                return;
127            }
128        };
129
130        let mut to_wake = [TaskId::from_u64(0); BATCH];
131        let mut count = 0usize;
132        for (id, task) in blocked.iter() {
133            let deadline = task.wake_deadline_ns.load(Ordering::Relaxed);
134            if deadline != 0 && current_time_ns >= deadline {
135                if count < BATCH {
136                    to_wake[count] = *id;
137                    count += 1;
138                } else {
139                    break;
140                }
141            }
142        }
143
144        for id in to_wake.iter().copied().take(count) {
145            if let Some(blocked_task) = blocked.remove(&id) {
146                blocked_task.wake_deadline_ns.store(0, Ordering::Relaxed);
147                blocked_task.set_state(TaskState::Ready);
148
149                // --- CPU placement: prefer last_cpu, then home_cpu ---
150                let last = blocked_task.last_cpu.load(Ordering::Relaxed);
151                let home = blocked_task.home_cpu.load(Ordering::Relaxed);
152                let n = active_cpu_count();
153                let cpu = if last < n {
154                    // Use try_lock to avoid blocking in IRQ context.
155                    let last_ok = LOCAL_SCHEDULERS[last]
156                        .try_lock_no_irqsave()
157                        .and_then(|guard| {
158                            let ok = guard
159                                .as_ref()
160                                .map(|c| c.class_rqs.runnable_len() <= 2)
161                                .unwrap_or(false);
162                            drop(guard);
163                            Some(ok)
164                        })
165                        .unwrap_or(false);
166                    if last_ok {
167                        last
168                    } else if home < n {
169                        home
170                    } else {
171                        0
172                    }
173                } else if home < n {
174                    home
175                } else {
176                    0
177                };
178                let class = {
179                    use crate::process::sched::SchedClassId;
180                    match blocked_task.sched_policy() {
181                        crate::process::sched::SchedPolicy::RealTimeRR { .. }
182                        | crate::process::sched::SchedPolicy::RealTimeFifo { .. } => {
183                            SchedClassId::RealTime
184                        }
185                        crate::process::sched::SchedPolicy::Fair(_) => SchedClassId::Fair,
186                        crate::process::sched::SchedPolicy::Idle => SchedClassId::Idle,
187                    }
188                };
189                // Use try_lock to avoid blocking in IRQ context. If the target
190                // CPU's LOCAL lock is contended, re-insert the task into
191                // BLOCKED_TASKS so it will be retried on the next tick.
192                match LOCAL_SCHEDULERS[cpu].try_lock_no_irqsave() {
193                    Some(mut guard) => {
194                        lockdep_acquire(LockRank::Local, Some(cpu));
195                        if let Some(ref mut local_cpu) = *guard {
196                            local_cpu.class_rqs.enqueue(class, blocked_task);
197                            local_cpu.need_resched = true;
198                            if cpu != my_cpu && cpu_is_valid(cpu) {
199                                ipi_targets[cpu] = true;
200                            }
201                        } else {
202                            // CPU slot not initialized — drop the task.
203                            if drop_count < BATCH {
204                                deferred_drops[drop_count] = Some(blocked_task);
205                                drop_count += 1;
206                            }
207                        }
208                        lockdep_release(LockRank::Local);
209                        drop(guard);
210                    }
211                    None => {
212                        // LOCAL lock contended — re-insert into BLOCKED_TASKS
213                        // so the next tick retries this wake.
214                        blocked_task.set_state(TaskState::Blocked);
215                        blocked.insert(id, blocked_task);
216                    }
217                }
218            }
219        }
220        lockdep_release(LockRank::Blocked);
221        drop(blocked);
222    }
223    // Drop orphaned task Arcs outside the scheduler lock so that
224    // KernelStack::drop => free_frames => buddy_alloc.lock() does not race
225    // with any other GLOBAL_SCHED_STATE lock acquisition on this or another CPU.
226    for slot in deferred_drops[..drop_count].iter_mut() {
227        drop(slot.take());
228    }
229
230    for (cpu, send) in ipi_targets.iter().copied().enumerate() {
231        if send {
232            send_resched_ipi_to_cpu(cpu);
233        }
234    }
235}
236
237/// Get the current tick count
238pub fn ticks() -> u64 {
239    TICK_COUNT.load(Ordering::Relaxed)
240}
241
242/// Get a list of all tasks in the system (for timer checking).
243/// Returns None if scheduler is not initialized or currently locked.
244pub fn get_all_tasks() -> Option<alloc::vec::Vec<Arc<Task>>> {
245    use alloc::vec::Vec;
246    let scheduler = match GLOBAL_SCHED_STATE.try_lock() {
247        Some(guard) => guard,
248        None => {
249            note_try_lock_fail();
250            return None;
251        }
252    };
253    if let Some(ref sched) = *scheduler {
254        let mut tasks = Vec::with_capacity(sched.all_tasks.len());
255        for (_, task) in sched.all_tasks.iter() {
256            tasks.push(task.clone());
257        }
258        Some(tasks)
259    } else {
260        None
261    }
262}