1use alloc::sync::Arc;
2use core::num::NonZeroU64;
3
4use crate::{
5 runtime::{
6 TaskSystem,
7 context::{
8 RuntimeIrqGuard, RuntimeSchedulerFrameGuard, runtime_current_cpu_mut,
9 runtime_task_system,
10 },
11 cpu::CpuLocal,
12 lock::PreemptScope,
13 service::SchedulerTickMode,
14 switch::{RuntimeScheduleOrigin, RuntimeSchedulerEntry, dispatch::execute_switch_plan},
15 task_runtime,
16 },
17 sched::{
18 CpuId, SchedulePolicy,
19 system::{DeadlineBaseGuardSource, SchedulerDeadlineDerivationSource},
20 },
21 thread::{
22 ParkCommit, ParkPrepare, TaskError, ThreadCore, ThreadId, ThreadWakeHandle,
23 current::{BlockingPermit, acquire_blocking_permit, current_thread_core_arc},
24 },
25 time::{MonotonicDeadline, MonotonicInstant, queue::TaskDeadlineKind},
26};
27
28#[derive(Debug)]
35pub enum CurrentParkStart {
36 Notified,
38 Prepared(PreparedCurrentPark),
40}
41
42#[must_use = "a prepared current-thread park must be committed or cancelled"]
44#[derive(Debug)]
45pub struct PreparedCurrentPark {
46 thread: Arc<ThreadCore>,
47 ticket: Option<crate::thread::ParkTicket>,
48 system: &'static TaskSystem,
51}
52
53#[derive(Clone, Copy, Debug, Eq, PartialEq)]
55pub struct CurrentParkResume {
56 generation: u64,
57 deadline_expired: bool,
58 disposition: CurrentParkDisposition,
59}
60
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub enum CurrentParkDisposition {
64 NotifiedBeforeBlock,
66 BlockedAndResumed,
68}
69
70impl CurrentParkResume {
71 pub const fn generation(self) -> u64 {
73 self.generation
74 }
75
76 pub const fn deadline_expired(self) -> bool {
78 self.deadline_expired
79 }
80
81 pub const fn disposition(self) -> CurrentParkDisposition {
83 self.disposition
84 }
85
86 pub const fn was_notified_before_block(self) -> bool {
88 matches!(
89 self.disposition,
90 CurrentParkDisposition::NotifiedBeforeBlock
91 )
92 }
93}
94
95impl PreparedCurrentPark {
96 pub fn thread_id(&self) -> ThreadId {
98 self.thread.id()
99 }
100
101 pub fn wake_handle(&self) -> ThreadWakeHandle {
106 ThreadWakeHandle::from_core(Arc::clone(&self.thread))
107 }
108
109 pub fn generation(&self) -> u64 {
111 self.ticket()
112 .expect("prepared park ticket remains owned")
113 .generation()
114 }
115
116 pub fn arm_deadline(&mut self, deadline: MonotonicDeadline) -> Result<(), TaskError> {
118 let ticket = self
119 .ticket
120 .as_mut()
121 .expect("prepared park ticket remains owned");
122 arm_current_park_deadline(&self.thread, ticket, deadline)
123 }
124
125 pub fn commit(mut self) -> Result<CurrentParkResume, TaskError> {
127 let mut ticket = self
128 .ticket
129 .take()
130 .expect("prepared park ticket remains owned");
131 let generation = ticket.generation();
132 let deadline_armed = ticket.has_deadline();
133 let disposition =
134 match commit_current_park_with_system(self.system, &self.thread, &mut ticket) {
135 Ok(disposition) => disposition,
136 Err(error) => {
137 let deadline_result = cancel_current_park_deadline(&self.thread, &mut ticket);
138 if cancel_current_park(&self.thread, &mut ticket).is_err() {
139 task_runtime::fatal_invariant(
140 0x5041_0002,
141 self.thread.id().as_u64() as usize,
142 );
143 }
144 let _cancelled = deadline_result?;
145 return Err(error);
146 }
147 };
148 let deadline_cancelled = cancel_current_park_deadline(&self.thread, &mut ticket)?;
149 Ok(CurrentParkResume {
150 generation,
151 deadline_expired: deadline_armed && !deadline_cancelled,
152 disposition,
153 })
154 }
155
156 pub fn cancel(mut self) -> Result<(), TaskError> {
158 let mut ticket = self
159 .ticket
160 .take()
161 .expect("prepared park ticket remains owned");
162 let deadline_result = cancel_current_park_deadline(&self.thread, &mut ticket);
163 let park_result = cancel_current_park(&self.thread, &mut ticket);
164 let _cancelled = deadline_result?;
165 park_result
166 }
167
168 fn ticket(&self) -> Option<&crate::thread::ParkTicket> {
169 self.ticket.as_ref()
170 }
171}
172
173impl Drop for PreparedCurrentPark {
174 fn drop(&mut self) {
175 if self
176 .ticket
177 .as_ref()
178 .is_some_and(|ticket| !ticket.is_resolved())
179 {
180 task_runtime::fatal_invariant(0x5041_0003, self.thread.id().as_u64() as usize);
181 }
182 }
183}
184
185pub fn begin_current_park() -> Result<CurrentParkStart, TaskError> {
192 let permit = acquire_blocking_permit()?;
193 begin_current_park_with_permit(&permit)
194}
195
196pub(crate) fn begin_current_park_with_permit(
197 _permit: &BlockingPermit,
198) -> Result<CurrentParkStart, TaskError> {
199 let system = runtime_task_system()?;
200 let _current_pin = PreemptScope::enter();
205 let thread = current_thread_core_arc()?;
206 let prepare = system.prepare_current_park(&thread);
207 match prepare? {
208 ParkPrepare::Notified => Ok(CurrentParkStart::Notified),
209 ParkPrepare::Prepared(ticket) => Ok(CurrentParkStart::Prepared(PreparedCurrentPark {
210 thread,
211 ticket: Some(ticket),
212 system,
213 })),
214 }
215}
216
217pub fn on_clock_event(
219 now: MonotonicInstant,
220 budget: usize,
221 scheduler_event: ClaimedSchedulerDeadlines,
222) -> Result<TaskClockEventOutcome, TaskError> {
223 let system = runtime_task_system()?;
224 let mut irq = RuntimeIrqGuard::enter();
225 let mut cpu = runtime_current_cpu_mut(&mut irq)?;
226 let periodic_tick = scheduler_event.runs_periodic_task_tick();
227 if periodic_tick && cpu.promote_lazy_reschedule() {
228 let _self_serviced = task_runtime::publish_local_scheduler_work();
234 }
235 let (charge, clock, current, task_tick_rq_observation) = match scheduler_event.accounting_kind()
240 {
241 ClockAccountingKind::RuntimeOnly => {
242 system.charge_current_until_with_clock(cpu.as_mut(), 0)?
243 }
244 ClockAccountingKind::SchedulerDeadline => {
245 system.clock_event_current_until_with_clock(cpu.as_mut(), 0)?
246 }
247 ClockAccountingKind::PeriodicTick => system.task_tick_current_until_with_clock(
248 cpu.as_mut(),
249 0,
250 scheduler_event.periodic_tick_ns(),
251 )?,
252 ClockAccountingKind::PeriodicTickWithSchedulerDeadline => system
253 .task_tick_and_clock_event_current_until_with_clock(
254 cpu.as_mut(),
255 0,
256 scheduler_event.periodic_tick_ns(),
257 )?,
258 };
259 let rt_period_rescheduled = system.service_rt_period(&cpu, now);
260 let hard = system.service_due_hard_timers(cpu.as_mut(), now, budget)?;
261 let batch = hard.soft();
262 let rq_observation =
263 match clock_event_rq_observation_plan(rt_period_rescheduled, hard.processed()) {
264 ClockEventRqObservationPlan::ReuseAccounted => cpu
265 .as_mut()
266 .scheduler_work_due_from_rq_observation(now, task_tick_rq_observation),
267 ClockEventRqObservationPlan::RefreshAndPublish => cpu.as_mut().scheduler_work_due(now),
268 };
269 let runtime_deadline = cpu.scheduler_runtime_deadline_for_rq_observation(rq_observation);
270 let update = cpu
271 .as_mut()
272 .next_scheduler_deadline_update_from_rq_observation(
273 rq_observation,
274 SchedulerDeadlineDerivationSource::ClockEvent,
275 )?;
276 Ok(TaskClockEventOutcome {
277 slice_expired: charge.slice_expired(),
278 deadline_overrun: charge.deadline_overrun(),
279 expired: hard.processed().saturating_add(batch.expired()),
280 update,
281 runtime_deadline: Some(runtime_deadline),
282 scheduler_tick: SchedulerTickStamp {
283 cpu: cpu.owner(),
284 thread: current,
285 observed_ns: clock.task().as_nanos(),
286 },
287 })
288}
289
290const fn clock_event_rq_observation_reusable(
291 rt_period_rescheduled: bool,
292 hard_timers_processed: usize,
293) -> bool {
294 !rt_period_rescheduled && hard_timers_processed == 0
295}
296
297#[derive(Clone, Copy, Debug, Eq, PartialEq)]
298enum ClockEventRqObservationPlan {
299 ReuseAccounted,
300 RefreshAndPublish,
301}
302
303const fn clock_event_rq_observation_plan(
304 rt_period_rescheduled: bool,
305 hard_timers_processed: usize,
306) -> ClockEventRqObservationPlan {
307 if clock_event_rq_observation_reusable(rt_period_rescheduled, hard_timers_processed) {
308 ClockEventRqObservationPlan::ReuseAccounted
309 } else {
310 ClockEventRqObservationPlan::RefreshAndPublish
315 }
316}
317
318#[derive(Clone, Copy, Debug, Eq, PartialEq)]
320pub struct ClaimedSchedulerDeadlines {
321 periodic_tick_ns: Option<NonZeroU64>,
322 scheduler_deadline_elapsed: bool,
323}
324
325#[derive(Clone, Copy, Debug, Eq, PartialEq)]
326enum ClockAccountingKind {
327 RuntimeOnly,
328 SchedulerDeadline,
329 PeriodicTick,
330 PeriodicTickWithSchedulerDeadline,
331}
332
333impl ClaimedSchedulerDeadlines {
334 pub const fn new(
337 periodic_tick_ns: Option<NonZeroU64>,
338 scheduler_deadline_elapsed: bool,
339 ) -> Self {
340 Self {
341 periodic_tick_ns,
342 scheduler_deadline_elapsed,
343 }
344 }
345
346 const fn runs_periodic_task_tick(self) -> bool {
347 self.periodic_tick_ns.is_some()
348 }
349
350 fn periodic_tick_ns(self) -> u64 {
351 match self.periodic_tick_ns {
352 Some(tick_ns) => tick_ns.get(),
353 None => task_runtime::fatal_invariant(0x5251_1013, 0),
354 }
355 }
356
357 const fn accounting_kind(self) -> ClockAccountingKind {
358 match (
359 self.periodic_tick_ns.is_some(),
360 self.scheduler_deadline_elapsed,
361 ) {
362 (false, false) => ClockAccountingKind::RuntimeOnly,
363 (false, true) => ClockAccountingKind::SchedulerDeadline,
364 (true, false) => ClockAccountingKind::PeriodicTick,
365 (true, true) => ClockAccountingKind::PeriodicTickWithSchedulerDeadline,
366 }
367 }
368}
369
370pub fn publish_scheduler_tick(
378 stamp: SchedulerTickStamp,
379 mode: SchedulerTickMode,
380 tick_ns: u64,
381) -> Result<(), TaskError> {
382 if tick_ns == 0 {
383 return Err(TaskError::InvalidConfiguration);
384 }
385 let system = runtime_task_system()?;
386 let mut irq = RuntimeIrqGuard::enter();
387 let cpu = runtime_current_cpu_mut(&mut irq)?;
388 if cpu.owner() != stamp.cpu {
389 return Err(TaskError::CpuOwnerMismatch {
390 expected: stamp.cpu.as_u32(),
391 actual: cpu.owner().as_u32(),
392 });
393 }
394 system.publish_current_scheduler_tick_work(&cpu, stamp.thread, stamp.observed_ns, mode, tick_ns)
395}
396
397pub(crate) fn commit_current_park(
398 current: &Arc<ThreadCore>,
399 ticket: &mut crate::thread::ParkTicket,
400) -> Result<CurrentParkDisposition, TaskError> {
401 let system = runtime_task_system()?;
402 commit_current_park_with_system(system, current, ticket)
403}
404
405fn commit_current_park_with_system(
406 system: &'static TaskSystem,
407 current: &Arc<ThreadCore>,
408 ticket: &mut crate::thread::ParkTicket,
409) -> Result<CurrentParkDisposition, TaskError> {
410 let mut scheduler_frame = RuntimeSchedulerFrameGuard::enter(
416 RuntimeScheduleOrigin::Block,
417 RuntimeSchedulerEntry::Task,
418 )?;
419 let commit = {
420 let mut cpu = runtime_current_cpu_mut(&mut scheduler_frame)?;
421 unsafe { system.commit_park_in_scheduler_frame(cpu.as_mut(), current, ticket)? }
423 };
424 match commit {
425 ParkCommit::Notified => Ok(CurrentParkDisposition::NotifiedBeforeBlock),
426 ParkCommit::Blocked(mut decision) => {
427 execute_switch_plan(&mut scheduler_frame, &mut decision);
428 Ok(CurrentParkDisposition::BlockedAndResumed)
429 }
430 }
431}
432
433pub(crate) fn cancel_current_park(
434 current: &ThreadCore,
435 ticket: &mut crate::thread::ParkTicket,
436) -> Result<(), TaskError> {
437 let mut irq = RuntimeIrqGuard::enter();
438 let mut cpu = runtime_current_cpu_mut(&mut irq)?;
439 runtime_task_system()?.cancel_current_park(cpu.as_mut(), current, ticket)
440}
441
442pub(crate) fn arm_current_park_deadline(
443 thread: &Arc<ThreadCore>,
444 ticket: &mut crate::thread::ParkTicket,
445 deadline: MonotonicDeadline,
446) -> Result<(), TaskError> {
447 let mut irq = RuntimeIrqGuard::enter();
448 let cpu = runtime_current_cpu_mut(&mut irq)?;
449 if ticket.thread() != thread.id()
450 || ticket.is_resolved()
451 || ticket.has_deadline()
452 || cpu.current() != Some(thread.id())
453 {
454 return Err(TaskError::StaleThreadId);
455 }
456 let owner = cpu.owner();
457 let (registration, update) = {
458 let mut deadline_base = cpu
459 .remote()
460 .lock_deadline_activity(DeadlineBaseGuardSource::Registration);
461 let non_timer = deadline_base.non_timer;
462 let kind = TaskDeadlineKind::park_timeout(ticket.generation());
463 let registration = if matches!(
464 thread.base_policy_snapshot(),
465 SchedulePolicy::Fifo { .. }
466 | SchedulePolicy::RoundRobin { .. }
467 | SchedulePolicy::Deadline(_)
468 ) {
469 deadline_base.queue.arm_hard_park(
470 thread.sleep_timer(),
471 deadline,
472 kind,
473 Arc::clone(thread),
474 )
475 } else {
476 deadline_base
477 .queue
478 .arm(thread.sleep_timer(), deadline, kind)
479 }
480 .map_err(|error| match error {
481 crate::time::queue::TaskDeadlineError::Capacity => TaskError::TimerCapacity,
482 crate::time::queue::TaskDeadlineError::GenerationExhausted
483 | crate::time::queue::TaskDeadlineError::KindMismatch => {
484 TaskError::InvalidConfiguration
485 }
486 })?;
487 let token = registration.token();
488 thread.register_sleep_timer(owner, token.generation());
489 let update = match CpuLocal::update_scheduler_deadline_registration_publication(
490 &mut deadline_base,
491 non_timer,
492 ) {
493 Ok(update) => update,
494 Err(error) => {
495 let removed = deadline_base.queue.cancel(®istration);
496 let completed = thread.complete_sleep_timer(token.generation());
497 if !removed || !completed {
498 task_runtime::fatal_invariant(0x5444_0005, thread.id().as_u64() as usize);
499 }
500 return Err(error);
501 }
502 };
503 (registration, update)
504 };
505 task_runtime::publish_scheduler_deadline(update);
506 if ticket.attach_deadline(registration).is_err() {
507 task_runtime::fatal_invariant(0x5444_0002, thread.id().as_u64() as usize);
508 }
509 Ok(())
510}
511
512pub(crate) fn cancel_current_park_deadline(
513 thread: &ThreadCore,
514 ticket: &mut crate::thread::ParkTicket,
515) -> Result<bool, TaskError> {
516 if ticket.thread() != thread.id() {
517 return Err(TaskError::StaleThreadId);
518 }
519 let Some(token) = ticket.deadline().map(|registration| registration.token()) else {
520 return Ok(false);
521 };
522 let system = runtime_task_system()?;
523 let mut irq = RuntimeIrqGuard::enter();
524 let cpu = runtime_current_cpu_mut(&mut irq)?;
525 let actual = cpu.owner();
526 let Some(expected) = thread.sleep_timer_cpu_for(token.generation()) else {
527 if !ticket.clear_deadline(token) {
531 task_runtime::fatal_invariant(0x5444_0003, thread.id().as_u64() as usize);
532 }
533 return Ok(false);
534 };
535 if actual != expected {
536 let remote = system
537 .cpu_remote(expected)
538 .ok_or(TaskError::CpuOffline(expected.as_u32()))?;
539 let registration = ticket
540 .deadline()
541 .expect("the deadline registration remains owned until cancellation");
542 let (cancellation, expired) = {
543 let mut deadline_base =
544 remote.lock_deadline_activity(DeadlineBaseGuardSource::Registration);
545 let cancellation = deadline_base.queue.begin_cancel(registration);
546 let expired = if cancellation.is_none() {
547 deadline_base.cancel_expired_task_deadline(registration)
548 } else {
549 false
550 };
551 (cancellation, expired)
552 };
553 let cancelled = match (cancellation, expired) {
554 (Some(cancellation), _) => {
555 cancellation.commit();
559 true
560 }
561 (None, true) => false,
562 (None, false) if thread.sleep_timer_cpu_for(token.generation()).is_none() => {
563 if !ticket.clear_deadline(token) {
564 task_runtime::fatal_invariant(0x5444_0003, thread.id().as_u64() as usize);
565 }
566 return Ok(false);
567 }
568 (None, false) => {
569 task_runtime::fatal_invariant(0x5444_0006, thread.id().as_u64() as usize)
570 }
571 };
572 if !thread.complete_sleep_timer(token.generation()) || !ticket.clear_deadline(token) {
573 task_runtime::fatal_invariant(0x5444_0004, thread.id().as_u64() as usize);
574 }
575 return Ok(cancelled);
576 }
577 let (cancellation, update) = {
578 let registration = ticket
579 .deadline()
580 .expect("the deadline registration remains owned until cancellation");
581 let mut deadline_base = cpu
582 .remote()
583 .lock_deadline_activity(DeadlineBaseGuardSource::Registration);
584 let non_timer = deadline_base.non_timer;
585 let cancellation = deadline_base.queue.begin_cancel(registration);
586 let expired = if cancellation.is_none() {
587 deadline_base.cancel_expired_task_deadline(registration)
588 } else {
589 false
590 };
591 let cancellation = match (cancellation, expired) {
592 (Some(cancellation), _) => cancellation,
593 (None, true) => {
594 if !thread.complete_sleep_timer(token.generation()) || !ticket.clear_deadline(token)
595 {
596 task_runtime::fatal_invariant(0x5444_0004, thread.id().as_u64() as usize);
597 }
598 return Ok(false);
599 }
600 (None, false) if thread.sleep_timer_cpu_for(token.generation()).is_none() => {
601 if !ticket.clear_deadline(token) {
602 task_runtime::fatal_invariant(0x5444_0003, thread.id().as_u64() as usize);
603 }
604 return Ok(false);
605 }
606 (None, false) => {
607 task_runtime::fatal_invariant(0x5444_0006, thread.id().as_u64() as usize);
608 }
609 };
610 let update = match CpuLocal::update_scheduler_deadline_registration_publication(
611 &mut deadline_base,
612 non_timer,
613 ) {
614 Ok(update) => update,
615 Err(error) => {
616 cancellation.rollback(&mut deadline_base.queue);
617 return Err(error);
618 }
619 };
620 (cancellation, update)
621 };
622 task_runtime::publish_scheduler_deadline(update);
623 cancellation.commit();
624 if !thread.complete_sleep_timer(token.generation()) || !ticket.clear_deadline(token) {
625 task_runtime::fatal_invariant(0x5444_0004, thread.id().as_u64() as usize);
626 }
627 Ok(true)
628}
629
630#[derive(Clone, Copy, Debug, Eq, PartialEq)]
632pub struct TaskClockEventOutcome {
633 slice_expired: bool,
634 deadline_overrun: bool,
635 expired: usize,
636 update: crate::runtime::cpu::SchedulerDeadlineUpdate,
637 runtime_deadline: Option<crate::runtime::cpu::SchedulerRuntimeDeadline>,
638 scheduler_tick: SchedulerTickStamp,
639}
640
641#[derive(Clone, Copy, Debug, Eq, PartialEq)]
643pub struct SchedulerTickStamp {
644 cpu: CpuId,
645 thread: ThreadId,
646 observed_ns: u64,
647}
648
649impl TaskClockEventOutcome {
650 pub const fn slice_expired(self) -> bool {
652 self.slice_expired
653 }
654 pub const fn deadline_overrun(self) -> bool {
656 self.deadline_overrun
657 }
658 pub const fn expired(self) -> usize {
660 self.expired
661 }
662 pub const fn update(self) -> crate::runtime::cpu::SchedulerDeadlineUpdate {
664 self.update
665 }
666 pub const fn runtime_deadline(self) -> Option<crate::runtime::cpu::SchedulerRuntimeDeadline> {
668 self.runtime_deadline
669 }
670 pub const fn scheduler_tick_stamp(self) -> SchedulerTickStamp {
673 self.scheduler_tick
674 }
675 pub const fn next_deadline(self) -> Option<MonotonicDeadline> {
677 self.update.deadline()
678 }
679}
680
681#[cfg(test)]
682mod tests {
683 use core::num::NonZeroU64;
684
685 use super::{
686 ClaimedSchedulerDeadlines, ClockAccountingKind, ClockEventRqObservationPlan,
687 clock_event_rq_observation_plan, clock_event_rq_observation_reusable,
688 };
689
690 const _: () = assert!(matches!(
691 clock_event_rq_observation_plan(false, 1),
692 ClockEventRqObservationPlan::RefreshAndPublish
693 ));
694
695 #[test]
696 fn only_periodic_clock_events_run_the_scheduler_tick() {
697 let tick_ns = NonZeroU64::new(10).unwrap();
698
699 assert!(!ClaimedSchedulerDeadlines::new(None, false).runs_periodic_task_tick());
700 assert!(!ClaimedSchedulerDeadlines::new(None, true).runs_periodic_task_tick());
701 assert!(ClaimedSchedulerDeadlines::new(Some(tick_ns), false).runs_periodic_task_tick());
702 assert!(ClaimedSchedulerDeadlines::new(Some(tick_ns), true).runs_periodic_task_tick());
703 }
704
705 #[test]
706 fn unrelated_physical_clockevent_only_accounts_runtime() {
707 let tick_ns = NonZeroU64::new(10).unwrap();
708
709 assert_eq!(
710 ClaimedSchedulerDeadlines::new(None, false).accounting_kind(),
711 ClockAccountingKind::RuntimeOnly
712 );
713 assert_eq!(
714 ClaimedSchedulerDeadlines::new(None, true).accounting_kind(),
715 ClockAccountingKind::SchedulerDeadline
716 );
717 assert_eq!(
718 ClaimedSchedulerDeadlines::new(Some(tick_ns), false).accounting_kind(),
719 ClockAccountingKind::PeriodicTick
720 );
721 assert_eq!(
722 ClaimedSchedulerDeadlines::new(Some(tick_ns), true).accounting_kind(),
723 ClockAccountingKind::PeriodicTickWithSchedulerDeadline
724 );
725 }
726
727 #[test]
728 fn linux_common_tick_reuses_the_task_tick_rq_observation() {
729 assert!(clock_event_rq_observation_reusable(false, 0));
730 assert!(!clock_event_rq_observation_reusable(true, 0));
731 assert!(!clock_event_rq_observation_reusable(false, 1));
732 }
733
734 #[test]
735 fn plain_hard_timer_rechecks_and_publishes_due_scheduler_work() {
736 assert_eq!(
737 clock_event_rq_observation_plan(false, 1),
738 ClockEventRqObservationPlan::RefreshAndPublish
739 );
740 }
741}