Skip to main content

ax_task/sched/system/thread_sched/
mod.rs

1//! Per-thread scheduler state independent from the generation registry.
2
3mod deadline_state;
4mod pi_state;
5mod placement;
6mod policy_state;
7mod runtime_state;
8
9use alloc::sync::{Arc, Weak};
10
11pub(in crate::sched::system) use pi_state::PiScheduleUpdate;
12pub(in crate::sched::system) use placement::SchedulerPlacement;
13
14use crate::{
15    runtime::{
16        lock::{IrqTicketGuard, IrqTicketLock},
17        resource::{AddressSpaceHandle, ExecutionContextHandle},
18    },
19    sched::{
20        CpuId, CpuSet, SchedulePolicy, SchedulerTimestamp,
21        algorithm::{
22            ActiveSchedulingState, DetachedActiveGuard, DetachedActivePublication,
23            DetachedActiveState, SchedulingEntity,
24        },
25    },
26    thread::{DeadlineServer, TaskError, ThreadCore, ThreadId, ThreadLifecycle, ThreadState},
27    time::queue::TaskDeadlineRegistration,
28};
29
30/// GRUB activity of one admitted Deadline reservation.
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub enum DeadlineActivity {
33    /// Runnable or executing, and therefore contributing active utilization.
34    ActiveContending,
35    /// Blocked before zero-lag while still contributing active utilization.
36    ActiveNonContending,
37    /// Blocked past zero-lag and eligible to donate inactive utilization.
38    Inactive,
39}
40
41/// Stable scheduler ownership anchor retained by every runnable reference.
42///
43/// Owner CPUs operate on this cell through queued, current, and inbox-held
44/// `ThreadCore` references. Registry locking is reserved for lifecycle lookup,
45/// admission, and PI graph changes rather than owner runqueue progress.
46#[derive(Debug)]
47pub(crate) struct ThreadSchedCell {
48    id: ThreadId,
49    lifecycle: alloc::sync::Arc<ThreadLifecycle>,
50    placement: alloc::sync::Arc<placement::SchedulerPlacement>,
51    deadline_server: DeadlineServer,
52    detached_active: DetachedActiveState,
53    state: IrqTicketLock<ThreadSchedState>,
54}
55
56impl ThreadSchedCell {
57    pub(super) fn new(id: ThreadId, init: ThreadSchedInit) -> Self {
58        let (state, active) = ThreadSchedState::new(init);
59        let lifecycle = alloc::sync::Arc::clone(&state.lifecycle);
60        let placement = alloc::sync::Arc::clone(&state.placement);
61        let deadline_server = state.deadline.server.clone();
62        Self {
63            id,
64            lifecycle,
65            placement,
66            deadline_server,
67            detached_active: DetachedActiveState::new(active),
68            state: IrqTicketLock::new(state),
69        }
70    }
71
72    pub(crate) const fn id(&self) -> ThreadId {
73        self.id
74    }
75
76    pub(super) fn lock(&self) -> IrqTicketGuard<'_, ThreadSchedState> {
77        loop {
78            let guard = self
79                .state
80                .lock(crate::runtime::IrqGuardSource::ThreadSchedTicket);
81            if !self.detached_active.publication_in_progress() {
82                return guard;
83            }
84            // The rq owner publishing a detached entity never needs this task
85            // lock. Release it before waiting so one delayed CPU cannot turn
86            // the move-only entity handoff into a task-wide lock stall. After
87            // the Acquire wait, retry the lock and every protected predicate.
88            drop(guard);
89            self.detached_active.wait_for_publication();
90        }
91    }
92
93    /// Locks scheduler state below the runtime's IRQ-off scheduler baton.
94    ///
95    /// # Safety
96    ///
97    /// The scheduler frame must remain active until the returned guard is
98    /// dropped. Ordinary task context must use [`Self::lock`].
99    pub(super) unsafe fn lock_scheduler_frame(&self) -> IrqTicketGuard<'_, ThreadSchedState> {
100        loop {
101            // SAFETY: forwarded from this method's scheduler-baton contract.
102            let guard = unsafe { self.state.lock_irq_disabled() };
103            if !self.detached_active.publication_in_progress() {
104                return guard;
105            }
106            drop(guard);
107            self.detached_active.wait_for_publication();
108        }
109    }
110
111    /// Tries to lock task state while its owner rq lock is active.
112    ///
113    /// This is the sole inverse-order acquisition used to finish Linux Fair
114    /// delayed dequeue. It never waits: a concurrent `p->pi_lock -> rq` owner
115    /// wins and the rq picker skips that delayed entity for this pass.
116    ///
117    /// # Safety
118    ///
119    /// The owner rq guard must keep local IRQs disabled for the complete
120    /// returned-guard lifetime.
121    pub(super) unsafe fn try_lock_from_owner_rq(
122        &self,
123    ) -> Option<IrqTicketGuard<'_, ThreadSchedState>> {
124        // SAFETY: forwarded from this method's owner-rq/IRQ-off contract.
125        let guard = unsafe { self.state.try_lock_irq_disabled() }?;
126        if self.detached_active.publication_in_progress() {
127            drop(guard);
128            return None;
129        }
130        Some(guard)
131    }
132
133    /// Locks scheduler state during offline CPU bootstrap.
134    ///
135    /// # Safety
136    ///
137    /// The caller must retain raw local IRQ exclusion and the boot CPU's
138    /// `PREEMPT_DISABLED` ownership for the complete guard lifetime.
139    pub(super) unsafe fn lock_bootstrap(&self) -> IrqTicketGuard<'_, ThreadSchedState> {
140        loop {
141            // SAFETY: forwarded from this method's offline boot-owner contract.
142            let guard = unsafe { self.state.lock_irq_disabled() };
143            if !self.detached_active.publication_in_progress() {
144                return guard;
145            }
146            drop(guard);
147            self.detached_active.wait_for_publication();
148        }
149    }
150
151    /// Borrows the off-rq entity under this task's scheduler lock.
152    pub(super) fn active(&self, _sched: &ThreadSchedState) -> DetachedActiveGuard<'_> {
153        self.detached_active.active()
154    }
155
156    /// Borrows the off-rq entity when task placement says it may be detached.
157    pub(super) fn active_option(
158        &self,
159        _sched: &ThreadSchedState,
160    ) -> Option<DetachedActiveGuard<'_>> {
161        self.detached_active.active_option()
162    }
163
164    /// Moves off-rq entity ownership into an rq/current representation.
165    pub(super) fn take_active(&self, _sched: &mut ThreadSchedState) -> ActiveSchedulingState {
166        self.detached_active
167            .take()
168            .expect("active scheduling state must have exactly one owner")
169    }
170
171    /// Returns rq/current entity ownership to this task's stable slot.
172    pub(super) fn install_active(
173        &self,
174        _sched: &mut ThreadSchedState,
175        active: ActiveSchedulingState,
176    ) {
177        self.detached_active.install(active);
178    }
179
180    /// Reserves detached ownership for an rq-only block publication.
181    pub(super) fn begin_active_publication(&self) -> Option<DetachedActivePublication<'_>> {
182        self.detached_active.begin_publication()
183    }
184
185    pub(crate) fn scheduler_fence_cpu(&self) -> Option<CpuId> {
186        self.placement.on_cpu()
187    }
188
189    pub(crate) fn assigned_cpu(&self) -> Option<CpuId> {
190        self.placement.assigned_cpu()
191    }
192
193    pub(in crate::sched::system) fn placement(&self) -> &placement::SchedulerPlacement {
194        self.placement.as_ref()
195    }
196
197    pub(crate) fn lifecycle(&self) -> &alloc::sync::Arc<ThreadLifecycle> {
198        &self.lifecycle
199    }
200
201    pub(crate) fn deadline_server(&self) -> DeadlineServer {
202        self.deadline_server.clone()
203    }
204}
205
206#[derive(Debug)]
207pub(super) struct ThreadSchedState {
208    pub(super) lifecycle: alloc::sync::Arc<ThreadLifecycle>,
209    pub(super) policy: policy_state::ThreadPolicyState,
210    pub(super) placement: alloc::sync::Arc<placement::SchedulerPlacement>,
211    pub(super) affinity: placement::ThreadAffinityState,
212    pub(super) deadline: deadline_state::ThreadDeadlineState,
213    pub(super) pi: pi_state::ThreadPiState,
214    pub(super) runtime: runtime_state::ThreadRuntimeState,
215}
216
217pub(super) struct ThreadPolicyInit {
218    pub(super) policy: SchedulePolicy,
219    pub(super) entity: SchedulingEntity,
220}
221
222pub(super) struct ThreadPlacementInit {
223    pub(super) initial_cpu: CpuId,
224    pub(super) affinity: CpuSet,
225}
226
227pub(super) struct ThreadDeadlineInit {
228    pub(super) server: DeadlineServer,
229    pub(super) reservation_scaled: u64,
230}
231
232pub(super) struct ThreadRuntimeInit {
233    pub(super) context: ExecutionContextHandle,
234    pub(super) address_space: AddressSpaceHandle,
235}
236
237pub(super) struct ThreadSchedInit {
238    pub(super) policy: ThreadPolicyInit,
239    pub(super) placement: ThreadPlacementInit,
240    pub(super) deadline: ThreadDeadlineInit,
241    pub(super) runtime: ThreadRuntimeInit,
242}
243
244impl ThreadSchedState {
245    pub(super) fn new(init: ThreadSchedInit) -> (Self, ActiveSchedulingState) {
246        let active = ActiveSchedulingState::new(init.policy.policy, init.policy.entity);
247        (
248            Self {
249                lifecycle: alloc::sync::Arc::new(ThreadLifecycle::new()),
250                policy: policy_state::ThreadPolicyState::new(init.policy.policy),
251                placement: alloc::sync::Arc::new(placement::SchedulerPlacement::new(
252                    init.placement.initial_cpu,
253                )),
254                affinity: placement::ThreadAffinityState::new(init.placement.affinity),
255                deadline: deadline_state::ThreadDeadlineState::new(
256                    init.deadline.server,
257                    init.deadline.reservation_scaled,
258                ),
259                pi: pi_state::ThreadPiState::new(),
260                runtime: runtime_state::ThreadRuntimeState::new(
261                    init.runtime.context,
262                    init.runtime.address_space,
263                ),
264            },
265            active,
266        )
267    }
268
269    pub(super) fn transition(
270        &mut self,
271        core: &ThreadCore,
272        state: ThreadState,
273    ) -> Result<(), TaskError> {
274        core.transition_state(state)
275    }
276
277    pub(super) fn is_pi_boosted_rt_owner_for(&self, policy: SchedulePolicy) -> bool {
278        !self.pi.donors.is_empty()
279            && self.is_pi_boosted()
280            && matches!(
281                policy,
282                SchedulePolicy::Fifo { .. } | SchedulePolicy::RoundRobin { .. }
283            )
284    }
285
286    pub(super) const fn is_pi_boosted(&self) -> bool {
287        self.pi.donor.is_some()
288    }
289
290    /// Returns the root-domain reservation retained by the applied policy or
291    /// by the one not-yet-applied owner transaction.
292    ///
293    /// Admission accounts the maximum, not the sum: publishing a replacement
294    /// transaction transfers one reservation to another without admitting two
295    /// Deadline entities for the same task.
296    pub(super) fn held_deadline_reservation(&self) -> u64 {
297        self.deadline.bandwidth.reservation_scaled().max(
298            self.policy
299                .pending_update()
300                .map_or(0, |pending| pending.reservation_scaled),
301        )
302    }
303
304    /// Builds the task-control snapshot published with an rq entity.
305    ///
306    /// Callers hold this task's scheduler lock and then acquire the owner rq,
307    /// matching Linux's `p->pi_lock` to rq publication order.
308    pub(super) fn rq_task_metadata(
309        &self,
310    ) -> Result<crate::sched::algorithm::RqTaskMetadata, TaskError> {
311        Ok(crate::sched::algorithm::RqTaskMetadata {
312            affinity: Arc::clone(&self.affinity.affinity),
313            deadline_bandwidth_scaled: self.deadline.bandwidth.reservation_scaled(),
314            runtime_binding: self.runtime.binding(),
315        })
316    }
317}