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(SchedulerDeadlineDerivationSource::ClockEvent)?;
273 Ok(TaskClockEventOutcome {
274 slice_expired: charge.slice_expired(),
275 deadline_overrun: charge.deadline_overrun(),
276 expired: hard.processed().saturating_add(batch.expired()),
277 update,
278 runtime_deadline: Some(runtime_deadline),
279 scheduler_tick: SchedulerTickStamp {
280 cpu: cpu.owner(),
281 thread: current,
282 observed_ns: clock.task().as_nanos(),
283 },
284 })
285}
286
287const fn clock_event_rq_observation_reusable(
288 rt_period_rescheduled: bool,
289 hard_timers_processed: usize,
290) -> bool {
291 !rt_period_rescheduled && hard_timers_processed == 0
292}
293
294#[derive(Clone, Copy, Debug, Eq, PartialEq)]
295enum ClockEventRqObservationPlan {
296 ReuseAccounted,
297 RefreshAndPublish,
298}
299
300const fn clock_event_rq_observation_plan(
301 rt_period_rescheduled: bool,
302 hard_timers_processed: usize,
303) -> ClockEventRqObservationPlan {
304 if clock_event_rq_observation_reusable(rt_period_rescheduled, hard_timers_processed) {
305 ClockEventRqObservationPlan::ReuseAccounted
306 } else {
307 ClockEventRqObservationPlan::RefreshAndPublish
312 }
313}
314
315#[derive(Clone, Copy, Debug, Eq, PartialEq)]
317pub struct ClaimedSchedulerDeadlines {
318 periodic_tick_ns: Option<NonZeroU64>,
319 scheduler_deadline_elapsed: bool,
320}
321
322#[derive(Clone, Copy, Debug, Eq, PartialEq)]
323enum ClockAccountingKind {
324 RuntimeOnly,
325 SchedulerDeadline,
326 PeriodicTick,
327 PeriodicTickWithSchedulerDeadline,
328}
329
330impl ClaimedSchedulerDeadlines {
331 pub const fn new(
334 periodic_tick_ns: Option<NonZeroU64>,
335 scheduler_deadline_elapsed: bool,
336 ) -> Self {
337 Self {
338 periodic_tick_ns,
339 scheduler_deadline_elapsed,
340 }
341 }
342
343 const fn runs_periodic_task_tick(self) -> bool {
344 self.periodic_tick_ns.is_some()
345 }
346
347 fn periodic_tick_ns(self) -> u64 {
348 match self.periodic_tick_ns {
349 Some(tick_ns) => tick_ns.get(),
350 None => task_runtime::fatal_invariant(0x5251_1013, 0),
351 }
352 }
353
354 const fn accounting_kind(self) -> ClockAccountingKind {
355 match (
356 self.periodic_tick_ns.is_some(),
357 self.scheduler_deadline_elapsed,
358 ) {
359 (false, false) => ClockAccountingKind::RuntimeOnly,
360 (false, true) => ClockAccountingKind::SchedulerDeadline,
361 (true, false) => ClockAccountingKind::PeriodicTick,
362 (true, true) => ClockAccountingKind::PeriodicTickWithSchedulerDeadline,
363 }
364 }
365}
366
367pub fn publish_scheduler_tick(
375 stamp: SchedulerTickStamp,
376 mode: SchedulerTickMode,
377 tick_ns: u64,
378) -> Result<(), TaskError> {
379 if tick_ns == 0 {
380 return Err(TaskError::InvalidConfiguration);
381 }
382 let system = runtime_task_system()?;
383 let mut irq = RuntimeIrqGuard::enter();
384 let cpu = runtime_current_cpu_mut(&mut irq)?;
385 if cpu.owner() != stamp.cpu {
386 return Err(TaskError::CpuOwnerMismatch {
387 expected: stamp.cpu.as_u32(),
388 actual: cpu.owner().as_u32(),
389 });
390 }
391 system.publish_current_scheduler_tick_work(&cpu, stamp.thread, stamp.observed_ns, mode, tick_ns)
392}
393
394pub(crate) fn commit_current_park(
395 current: &Arc<ThreadCore>,
396 ticket: &mut crate::thread::ParkTicket,
397) -> Result<CurrentParkDisposition, TaskError> {
398 let system = runtime_task_system()?;
399 commit_current_park_with_system(system, current, ticket)
400}
401
402fn commit_current_park_with_system(
403 system: &'static TaskSystem,
404 current: &Arc<ThreadCore>,
405 ticket: &mut crate::thread::ParkTicket,
406) -> Result<CurrentParkDisposition, TaskError> {
407 let mut scheduler_frame = RuntimeSchedulerFrameGuard::enter(
413 RuntimeScheduleOrigin::Block,
414 RuntimeSchedulerEntry::Task,
415 )?;
416 let commit = {
417 let mut cpu = runtime_current_cpu_mut(&mut scheduler_frame)?;
418 unsafe { system.commit_park_in_scheduler_frame(cpu.as_mut(), current, ticket)? }
420 };
421 match commit {
422 ParkCommit::Notified => Ok(CurrentParkDisposition::NotifiedBeforeBlock),
423 ParkCommit::Blocked(mut decision) => {
424 execute_switch_plan(&mut scheduler_frame, &mut decision);
425 Ok(CurrentParkDisposition::BlockedAndResumed)
426 }
427 }
428}
429
430pub(crate) fn cancel_current_park(
431 current: &ThreadCore,
432 ticket: &mut crate::thread::ParkTicket,
433) -> Result<(), TaskError> {
434 let mut irq = RuntimeIrqGuard::enter();
435 let mut cpu = runtime_current_cpu_mut(&mut irq)?;
436 runtime_task_system()?.cancel_current_park(cpu.as_mut(), current, ticket)
437}
438
439pub(crate) fn arm_current_park_deadline(
440 thread: &Arc<ThreadCore>,
441 ticket: &mut crate::thread::ParkTicket,
442 deadline: MonotonicDeadline,
443) -> Result<(), TaskError> {
444 let mut irq = RuntimeIrqGuard::enter();
445 let cpu = runtime_current_cpu_mut(&mut irq)?;
446 if ticket.thread() != thread.id()
447 || ticket.is_resolved()
448 || ticket.has_deadline()
449 || cpu.current() != Some(thread.id())
450 {
451 return Err(TaskError::StaleThreadId);
452 }
453 let owner = cpu.owner();
454 let (registration, update) = {
455 let mut deadline_base = cpu
456 .remote()
457 .lock_deadline_activity(DeadlineBaseGuardSource::Registration);
458 let non_timer = deadline_base.non_timer;
459 let kind = TaskDeadlineKind::park_timeout(ticket.generation());
460 let registration = if matches!(
461 thread.base_policy_snapshot(),
462 SchedulePolicy::Fifo { .. }
463 | SchedulePolicy::RoundRobin { .. }
464 | SchedulePolicy::Deadline(_)
465 ) {
466 deadline_base.queue.arm_hard_park(
467 thread.sleep_timer(),
468 deadline,
469 kind,
470 Arc::clone(thread),
471 )
472 } else {
473 deadline_base
474 .queue
475 .arm(thread.sleep_timer(), deadline, kind)
476 }
477 .map_err(|error| match error {
478 crate::time::queue::TaskDeadlineError::Capacity => TaskError::TimerCapacity,
479 crate::time::queue::TaskDeadlineError::GenerationExhausted
480 | crate::time::queue::TaskDeadlineError::KindMismatch => {
481 TaskError::InvalidConfiguration
482 }
483 })?;
484 let token = registration.token();
485 thread.register_sleep_timer(owner, token.generation());
486 let update = match CpuLocal::update_scheduler_deadline_registration_publication(
487 &mut deadline_base,
488 non_timer,
489 ) {
490 Ok(update) => update,
491 Err(error) => {
492 let removed = deadline_base.queue.cancel(®istration);
493 let completed = thread.complete_sleep_timer(token.generation());
494 if !removed || !completed {
495 task_runtime::fatal_invariant(0x5444_0005, thread.id().as_u64() as usize);
496 }
497 return Err(error);
498 }
499 };
500 (registration, update)
501 };
502 task_runtime::publish_scheduler_deadline(update);
503 if ticket.attach_deadline(registration).is_err() {
504 task_runtime::fatal_invariant(0x5444_0002, thread.id().as_u64() as usize);
505 }
506 Ok(())
507}
508
509pub(crate) fn cancel_current_park_deadline(
510 thread: &ThreadCore,
511 ticket: &mut crate::thread::ParkTicket,
512) -> Result<bool, TaskError> {
513 if ticket.thread() != thread.id() {
514 return Err(TaskError::StaleThreadId);
515 }
516 let Some(token) = ticket.deadline().map(|registration| registration.token()) else {
517 return Ok(false);
518 };
519 let system = runtime_task_system()?;
520 let mut irq = RuntimeIrqGuard::enter();
521 let cpu = runtime_current_cpu_mut(&mut irq)?;
522 let actual = cpu.owner();
523 let Some(expected) = thread.sleep_timer_cpu_for(token.generation()) else {
524 if !ticket.clear_deadline(token) {
528 task_runtime::fatal_invariant(0x5444_0003, thread.id().as_u64() as usize);
529 }
530 return Ok(false);
531 };
532 if actual != expected {
533 let remote = system
534 .cpu_remote(expected)
535 .ok_or(TaskError::CpuOffline(expected.as_u32()))?;
536 let registration = ticket
537 .deadline()
538 .expect("the deadline registration remains owned until cancellation");
539 let (cancellation, expired) = {
540 let mut deadline_base =
541 remote.lock_deadline_activity(DeadlineBaseGuardSource::Registration);
542 let cancellation = deadline_base.queue.begin_cancel(registration);
543 let expired = if cancellation.is_none() {
544 deadline_base.cancel_expired_task_deadline(registration)
545 } else {
546 false
547 };
548 (cancellation, expired)
549 };
550 let cancelled = match (cancellation, expired) {
551 (Some(cancellation), _) => {
552 cancellation.commit();
556 true
557 }
558 (None, true) => false,
559 (None, false) if thread.sleep_timer_cpu_for(token.generation()).is_none() => {
560 if !ticket.clear_deadline(token) {
561 task_runtime::fatal_invariant(0x5444_0003, thread.id().as_u64() as usize);
562 }
563 return Ok(false);
564 }
565 (None, false) => {
566 task_runtime::fatal_invariant(0x5444_0006, thread.id().as_u64() as usize)
567 }
568 };
569 if !thread.complete_sleep_timer(token.generation()) || !ticket.clear_deadline(token) {
570 task_runtime::fatal_invariant(0x5444_0004, thread.id().as_u64() as usize);
571 }
572 return Ok(cancelled);
573 }
574 let (cancellation, update) = {
575 let registration = ticket
576 .deadline()
577 .expect("the deadline registration remains owned until cancellation");
578 let mut deadline_base = cpu
579 .remote()
580 .lock_deadline_activity(DeadlineBaseGuardSource::Registration);
581 let non_timer = deadline_base.non_timer;
582 let cancellation = deadline_base.queue.begin_cancel(registration);
583 let expired = if cancellation.is_none() {
584 deadline_base.cancel_expired_task_deadline(registration)
585 } else {
586 false
587 };
588 let cancellation = match (cancellation, expired) {
589 (Some(cancellation), _) => cancellation,
590 (None, true) => {
591 if !thread.complete_sleep_timer(token.generation()) || !ticket.clear_deadline(token)
592 {
593 task_runtime::fatal_invariant(0x5444_0004, thread.id().as_u64() as usize);
594 }
595 return Ok(false);
596 }
597 (None, false) if thread.sleep_timer_cpu_for(token.generation()).is_none() => {
598 if !ticket.clear_deadline(token) {
599 task_runtime::fatal_invariant(0x5444_0003, thread.id().as_u64() as usize);
600 }
601 return Ok(false);
602 }
603 (None, false) => {
604 task_runtime::fatal_invariant(0x5444_0006, thread.id().as_u64() as usize);
605 }
606 };
607 let update = match CpuLocal::update_scheduler_deadline_registration_publication(
608 &mut deadline_base,
609 non_timer,
610 ) {
611 Ok(update) => update,
612 Err(error) => {
613 cancellation.rollback(&mut deadline_base.queue);
614 return Err(error);
615 }
616 };
617 (cancellation, update)
618 };
619 task_runtime::publish_scheduler_deadline(update);
620 cancellation.commit();
621 if !thread.complete_sleep_timer(token.generation()) || !ticket.clear_deadline(token) {
622 task_runtime::fatal_invariant(0x5444_0004, thread.id().as_u64() as usize);
623 }
624 Ok(true)
625}
626
627#[derive(Clone, Copy, Debug, Eq, PartialEq)]
629pub struct TaskClockEventOutcome {
630 slice_expired: bool,
631 deadline_overrun: bool,
632 expired: usize,
633 update: crate::runtime::cpu::SchedulerDeadlineUpdate,
634 runtime_deadline: Option<crate::runtime::cpu::SchedulerRuntimeDeadline>,
635 scheduler_tick: SchedulerTickStamp,
636}
637
638#[derive(Clone, Copy, Debug, Eq, PartialEq)]
640pub struct SchedulerTickStamp {
641 cpu: CpuId,
642 thread: ThreadId,
643 observed_ns: u64,
644}
645
646impl TaskClockEventOutcome {
647 pub const fn slice_expired(self) -> bool {
649 self.slice_expired
650 }
651 pub const fn deadline_overrun(self) -> bool {
653 self.deadline_overrun
654 }
655 pub const fn expired(self) -> usize {
657 self.expired
658 }
659 pub const fn update(self) -> crate::runtime::cpu::SchedulerDeadlineUpdate {
661 self.update
662 }
663 pub const fn runtime_deadline(self) -> Option<crate::runtime::cpu::SchedulerRuntimeDeadline> {
665 self.runtime_deadline
666 }
667 pub const fn scheduler_tick_stamp(self) -> SchedulerTickStamp {
670 self.scheduler_tick
671 }
672 pub const fn next_deadline(self) -> Option<MonotonicDeadline> {
674 self.update.deadline()
675 }
676}
677
678#[cfg(test)]
679mod tests {
680 use core::num::NonZeroU64;
681
682 use super::{
683 ClaimedSchedulerDeadlines, ClockAccountingKind, ClockEventRqObservationPlan,
684 clock_event_rq_observation_plan, clock_event_rq_observation_reusable,
685 };
686
687 const _: () = assert!(matches!(
688 clock_event_rq_observation_plan(false, 1),
689 ClockEventRqObservationPlan::RefreshAndPublish
690 ));
691
692 #[test]
693 fn only_periodic_clock_events_run_the_scheduler_tick() {
694 let tick_ns = NonZeroU64::new(10).unwrap();
695
696 assert!(!ClaimedSchedulerDeadlines::new(None, false).runs_periodic_task_tick());
697 assert!(!ClaimedSchedulerDeadlines::new(None, true).runs_periodic_task_tick());
698 assert!(ClaimedSchedulerDeadlines::new(Some(tick_ns), false).runs_periodic_task_tick());
699 assert!(ClaimedSchedulerDeadlines::new(Some(tick_ns), true).runs_periodic_task_tick());
700 }
701
702 #[test]
703 fn unrelated_physical_clockevent_only_accounts_runtime() {
704 let tick_ns = NonZeroU64::new(10).unwrap();
705
706 assert_eq!(
707 ClaimedSchedulerDeadlines::new(None, false).accounting_kind(),
708 ClockAccountingKind::RuntimeOnly
709 );
710 assert_eq!(
711 ClaimedSchedulerDeadlines::new(None, true).accounting_kind(),
712 ClockAccountingKind::SchedulerDeadline
713 );
714 assert_eq!(
715 ClaimedSchedulerDeadlines::new(Some(tick_ns), false).accounting_kind(),
716 ClockAccountingKind::PeriodicTick
717 );
718 assert_eq!(
719 ClaimedSchedulerDeadlines::new(Some(tick_ns), true).accounting_kind(),
720 ClockAccountingKind::PeriodicTickWithSchedulerDeadline
721 );
722 }
723
724 #[test]
725 fn linux_common_tick_reuses_the_task_tick_rq_observation() {
726 assert!(clock_event_rq_observation_reusable(false, 0));
727 assert!(!clock_event_rq_observation_reusable(true, 0));
728 assert!(!clock_event_rq_observation_reusable(false, 1));
729 }
730
731 #[test]
732 fn plain_hard_timer_rechecks_and_publishes_due_scheduler_work() {
733 assert_eq!(
734 clock_event_rq_observation_plan(false, 1),
735 ClockEventRqObservationPlan::RefreshAndPublish
736 );
737 }
738}