ax_task/sched/system/cpu/remote/
scheduler.rs1use core::sync::atomic::fence;
2
3use super::*;
4
5const REQUEST_PREEMPT: u64 = 1 << 0;
6const REQUEST_OWNER_WORK: u64 = 1 << 1;
7const REQUEST_PREEMPT_LAZY: u64 = 1 << 2;
8const REQUEST_REASON_MASK: u64 = REQUEST_PREEMPT | REQUEST_PREEMPT_LAZY | REQUEST_OWNER_WORK;
9const REQUEST_IDLE_POLLING: u64 = 1 << 3;
10const REQUEST_PARK_PREEMPT_DEFERRED: u64 = 1 << 4;
11const REQUEST_PARK_PREEMPT_LAZY_DEFERRED: u64 = 1 << 5;
12const DEFERRED_SCHEDULER_WORK_OFFLINE_INVARIANT: u32 = 0x4453_574f;
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub(super) enum SchedulerRequestDelivery {
16 PollingOwner,
19 DoorbellRequired,
25}
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub(crate) enum RescheduleKind {
30 Immediate,
32 Lazy,
35}
36
37impl RescheduleKind {
38 const fn request_bit(self) -> u64 {
39 match self {
40 Self::Immediate => REQUEST_PREEMPT,
41 Self::Lazy => REQUEST_PREEMPT_LAZY,
42 }
43 }
44}
45
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48pub(crate) enum SchedulerRequestScope {
49 Immediate,
52 All,
54}
55
56impl SchedulerRequestScope {
57 const fn claim_mask(self) -> u64 {
58 match self {
59 Self::Immediate => REQUEST_PREEMPT | REQUEST_OWNER_WORK,
60 Self::All => REQUEST_REASON_MASK,
61 }
62 }
63}
64
65#[derive(Clone, Copy, Debug, Eq, PartialEq)]
66pub(super) struct SchedulerRequestPublication {
67 delivery: SchedulerRequestDelivery,
68}
69
70impl SchedulerRequestPublication {}
71
72#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
73pub(crate) struct SchedulerRequestClaim {
74 immediate_preempt: bool,
75 lazy_preempt: bool,
76 owner_work: bool,
77}
78
79impl SchedulerRequestClaim {
80 pub(crate) const fn immediate_preempt_requested(self) -> bool {
81 self.immediate_preempt
82 }
83
84 pub(crate) const fn lazy_preempt_requested(self) -> bool {
85 self.lazy_preempt
86 }
87
88 pub(crate) const fn preemption_requested(self) -> bool {
89 self.immediate_preempt || self.lazy_preempt
90 }
91
92 pub(crate) const fn owner_work_requested(self) -> bool {
93 self.owner_work
94 }
95
96 pub(crate) const fn merge(self, other: Self) -> Self {
97 Self {
98 immediate_preempt: self.immediate_preempt || other.immediate_preempt,
99 lazy_preempt: self.lazy_preempt || other.lazy_preempt,
100 owner_work: self.owner_work || other.owner_work,
101 }
102 }
103}
104
105#[derive(Debug)]
106pub(super) struct SchedulerRequestState {
107 request: AtomicU64,
108}
109
110impl SchedulerRequestState {
111 pub(super) const fn new() -> Self {
112 Self {
113 request: AtomicU64::new(0),
114 }
115 }
116
117 fn publish(&self, reason: u64) -> Option<u64> {
118 debug_assert_ne!(reason & REQUEST_REASON_MASK, 0);
119 let mut observed = self.request.load(Ordering::Acquire);
120 loop {
121 let publish = if reason & REQUEST_PREEMPT_LAZY != 0
127 && reason & REQUEST_PREEMPT == 0
128 && observed & REQUEST_PREEMPT != 0
129 {
130 reason & !REQUEST_PREEMPT_LAZY
131 } else {
132 reason
133 };
134 if publish & REQUEST_REASON_MASK == 0 || observed & publish == publish {
135 return None;
136 }
137 match self.request.compare_exchange_weak(
138 observed,
139 observed | publish,
140 Ordering::AcqRel,
141 Ordering::Acquire,
142 ) {
143 Ok(previous) => return Some(previous),
144 Err(updated) => observed = updated,
145 }
146 }
147 }
148
149 fn publish_remote(&self, reason: u64) -> Option<u64> {
150 let published = self.publish(reason);
151 if reason & (REQUEST_PREEMPT | REQUEST_OWNER_WORK) == 0 {
152 return published;
153 }
154
155 published.or_else(|| Some(self.request.load(Ordering::Acquire)))
163 }
164
165 fn publish_rq_delivery(&self, reason: u64) -> Option<u64> {
166 self.publish_remote(reason)
170 }
171
172 fn claim(&self, scope: SchedulerRequestScope) -> SchedulerRequestClaim {
173 let request = self
174 .request
175 .fetch_and(!scope.claim_mask(), Ordering::AcqRel);
176 SchedulerRequestClaim {
177 immediate_preempt: request & REQUEST_PREEMPT != 0,
178 lazy_preempt: request & REQUEST_PREEMPT_LAZY != 0
179 && scope == SchedulerRequestScope::All,
180 owner_work: request & REQUEST_OWNER_WORK != 0,
181 }
182 }
183
184 fn promote_lazy(&self) -> bool {
185 let mut observed = self.request.load(Ordering::Acquire);
186 loop {
187 if observed & REQUEST_PREEMPT_LAZY == 0 {
188 return false;
189 }
190 let promoted = (observed | REQUEST_PREEMPT) & !REQUEST_PREEMPT_LAZY;
194 match self.request.compare_exchange_weak(
195 observed,
196 promoted,
197 Ordering::AcqRel,
198 Ordering::Acquire,
199 ) {
200 Ok(_) => return true,
201 Err(updated) => observed = updated,
202 }
203 }
204 }
205
206 fn defer_park_preemption(&self, request: SchedulerRequestClaim) {
207 let mut deferred = 0;
208 if request.immediate_preempt_requested() {
209 deferred |= REQUEST_PARK_PREEMPT_DEFERRED;
210 }
211 if request.lazy_preempt_requested() {
212 deferred |= REQUEST_PARK_PREEMPT_LAZY_DEFERRED;
213 }
214 if deferred != 0 {
215 self.request.fetch_or(deferred, Ordering::Release);
216 }
217 }
218
219 fn finish_park_preemption(&self, resume_running: bool) {
220 let deferred = self.request.fetch_and(
221 !(REQUEST_PARK_PREEMPT_DEFERRED | REQUEST_PARK_PREEMPT_LAZY_DEFERRED),
222 Ordering::AcqRel,
223 );
224 if !resume_running {
225 return;
226 }
227 if deferred & REQUEST_PARK_PREEMPT_DEFERRED != 0 {
228 let _ = self.publish(REQUEST_PREEMPT);
229 }
230 if deferred & REQUEST_PARK_PREEMPT_LAZY_DEFERRED != 0 {
231 let _ = self.publish(REQUEST_PREEMPT_LAZY);
232 }
233 }
234
235 fn restore_claimed_park_preemption(&self, request: SchedulerRequestClaim) {
236 self.defer_park_preemption(request);
237 self.finish_park_preemption(true);
238 }
239}
240
241impl CpuRemote {
242 pub(crate) fn is_scheduler_ready(&self) -> bool {
243 self.is_online() && self.idle_thread().is_some()
248 }
249
250 pub(crate) fn request_reschedule(&self, kind: RescheduleKind) {
252 let Some(_publication) = self.begin_publication() else {
253 return;
254 };
255 let _ = self.request_reschedule_owned(kind);
256 }
257
258 fn request_reschedule_owned(
259 &self,
260 kind: RescheduleKind,
261 ) -> Option<SchedulerRequestPublication> {
262 self.publish_scheduler_request_owned(kind.request_bit())
263 }
264
265 fn publish_scheduler_reasons_owned(
266 &self,
267 reschedule: Option<RescheduleKind>,
268 owner_work: bool,
269 ) {
270 let mut reasons = reschedule.map_or(0, RescheduleKind::request_bit);
271 if owner_work {
272 reasons |= REQUEST_OWNER_WORK;
273 }
274 let publication = self
275 .scheduler_request
276 .publish_remote(reasons)
277 .map(Self::scheduler_request_publication);
278 if owner_work || reschedule == Some(RescheduleKind::Immediate) {
279 self.deliver_scheduler_work_owned(
280 publication.expect("immediate remote scheduler work must retain a publication"),
281 );
282 }
283 }
284
285 pub(crate) fn publish_rq_scheduler_reasons(
293 &self,
294 reschedule: Option<RescheduleKind>,
295 owner_work: bool,
296 producer: CpuId,
297 irq_owner: &IrqOwner<'_>,
298 ) {
299 if reschedule.is_none() && !owner_work {
300 return;
301 }
302 let _ = irq_owner;
308 let mut reasons = reschedule.map_or(0, RescheduleKind::request_bit);
309 if owner_work {
310 reasons |= REQUEST_OWNER_WORK;
311 }
312 if let Some(publication) = self
313 .scheduler_request
314 .publish_rq_delivery(reasons)
315 .map(Self::scheduler_request_publication)
316 && (owner_work || reschedule == Some(RescheduleKind::Immediate))
317 {
318 if publication.delivery == SchedulerRequestDelivery::PollingOwner {
319 return;
320 }
321 if producer == self.owner {
322 let _self_serviced = task_runtime::publish_local_scheduler_work();
323 } else {
324 self.ring_scheduler_doorbell();
325 }
326 }
327 }
328
329 pub(crate) fn request_remote_reschedule(&self, kind: RescheduleKind) {
335 let Some(_publication) = self.begin_owner_delivery() else {
336 return;
337 };
338 let _irq = IrqScope::enter();
339 self.publish_scheduler_reasons_owned(Some(kind), false);
340 }
341
342 pub(crate) fn request_remote_reschedule_with_scheduler_work(&self, kind: RescheduleKind) {
349 let Some(_publication) = self.begin_owner_delivery() else {
350 return;
351 };
352 let _irq = IrqScope::enter();
353 self.publish_scheduler_reasons_owned(Some(kind), true);
354 }
355
356 pub(crate) fn request_scheduler_work(&self) {
357 let _delivered = self.request_scheduler_work_delivery();
358 }
359
360 fn request_scheduler_work_delivery(&self) -> bool {
361 let Some(_publication) = self.begin_owner_delivery() else {
362 return false;
363 };
364 let _irq = IrqScope::enter();
365 self.request_scheduler_work_owned()
366 .is_none_or(|publication| self.deliver_scheduler_work_owned(publication))
367 }
368
369 pub(super) fn request_scheduler_work_owned(&self) -> Option<SchedulerRequestPublication> {
370 self.publish_scheduler_request_owned(REQUEST_OWNER_WORK)
371 }
372
373 pub(super) fn publish_owner_inbox_head_owned(&self) -> SchedulerRequestPublication {
380 let previous = self
381 .scheduler_request
382 .request
383 .fetch_or(REQUEST_OWNER_WORK, Ordering::AcqRel);
384 Self::scheduler_request_publication(previous)
385 }
386
387 fn publish_scheduler_request_owned(&self, reason: u64) -> Option<SchedulerRequestPublication> {
388 self.scheduler_request
389 .publish(reason)
390 .map(Self::scheduler_request_publication)
391 }
392
393 fn scheduler_request_publication(previous: u64) -> SchedulerRequestPublication {
394 let delivery = if previous & REQUEST_IDLE_POLLING != 0 {
395 SchedulerRequestDelivery::PollingOwner
396 } else {
397 SchedulerRequestDelivery::DoorbellRequired
398 };
399 SchedulerRequestPublication { delivery }
400 }
401
402 pub(crate) fn kick_scheduler_work(&self) -> bool {
403 let Some(_publication) = self.begin_owner_delivery() else {
404 return false;
405 };
406 let _irq = IrqScope::enter();
407 self.kick_scheduler_work_owned()
408 }
409
410 pub(super) fn kick_scheduler_work_owned(&self) -> bool {
411 self.request_scheduler_work_owned()
412 .is_none_or(|publication| self.deliver_scheduler_work_owned(publication))
413 }
414
415 pub(crate) fn defer_scheduler_work(&self) {
422 let Some(_publication) = self.begin_owner_delivery() else {
423 task_runtime::fatal_invariant(
424 DEFERRED_SCHEDULER_WORK_OFFLINE_INVARIANT,
425 self.owner.as_u32() as usize,
426 );
427 };
428 let _irq = IrqScope::enter();
429 self.scheduler_request
430 .request
431 .fetch_or(REQUEST_OWNER_WORK, Ordering::Release);
432 self.ring_scheduler_doorbell();
433 }
434
435 pub(super) fn deliver_scheduler_work_owned(
436 &self,
437 publication: SchedulerRequestPublication,
438 ) -> bool {
439 if publication.delivery == SchedulerRequestDelivery::PollingOwner
440 || self.current_cpu_will_service_local_work()
441 {
442 return true;
443 }
444 self.ring_scheduler_doorbell()
445 }
446
447 fn ring_scheduler_doorbell(&self) -> bool {
448 match task_runtime::notify_scheduler_cpu(RuntimeCpuId::new(self.owner.as_u32())) {
449 RuntimeStatus::Success => true,
450 status => task_runtime::fatal_invariant(
451 0x4950_4900 | status as u32,
452 self.owner.as_u32() as usize,
453 ),
454 }
455 }
456
457 fn current_cpu_will_service_local_work(&self) -> bool {
458 let current = unsafe { task_runtime::current_cpu_id() };
461 if current.as_u32() != self.owner.as_u32() {
462 return false;
463 }
464 task_runtime::publish_local_scheduler_work()
469 }
470
471 pub fn needs_reschedule(&self) -> bool {
473 self.scheduler_request.request.load(Ordering::Acquire) & REQUEST_REASON_MASK != 0
474 }
475
476 pub(crate) fn needs_immediate_scheduler_work(&self) -> bool {
480 self.scheduler_request.request.load(Ordering::Acquire)
481 & (REQUEST_PREEMPT | REQUEST_OWNER_WORK)
482 != 0
483 }
484
485 pub(crate) fn scheduler_request_pending(&self, scope: SchedulerRequestScope) -> bool {
486 self.scheduler_request.request.load(Ordering::Acquire) & scope.claim_mask() != 0
487 }
488
489 pub(crate) fn immediate_preemption_requested(&self) -> bool {
494 self.scheduler_request.request.load(Ordering::Acquire) & REQUEST_PREEMPT != 0
495 }
496
497 pub(crate) fn claim_scheduler_request(
498 &self,
499 scope: SchedulerRequestScope,
500 ) -> SchedulerRequestClaim {
501 self.scheduler_request.claim(scope)
502 }
503
504 pub(crate) fn promote_lazy_reschedule(&self) -> bool {
509 self.scheduler_request.promote_lazy()
510 }
511
512 pub(crate) fn finish_scheduler_request(&self) {
513 let request = self.scheduler_request.request.load(Ordering::Acquire);
514 if self.has_remote_work() && request & REQUEST_OWNER_WORK == 0 {
515 self.request_scheduler_work();
516 }
517 }
518
519 pub(crate) fn defer_park_preemption(&self, request: SchedulerRequestClaim) {
520 self.scheduler_request.defer_park_preemption(request);
521 }
522
523 pub(crate) fn finish_park_preemption(&self, resume_running: bool) {
524 self.scheduler_request
525 .finish_park_preemption(resume_running);
526 }
527
528 pub(crate) fn restore_claimed_park_preemption(&self, request: SchedulerRequestClaim) {
531 self.scheduler_request
532 .restore_claimed_park_preemption(request);
533 }
534
535 pub(crate) fn prepare_idle_wait(&self) -> bool {
536 let previous = self
537 .scheduler_request
538 .request
539 .fetch_or(REQUEST_IDLE_POLLING, Ordering::AcqRel);
540 let may_wait = previous & REQUEST_REASON_MASK == 0
541 && !self.needs_reschedule()
542 && !self.has_remote_work()
543 && self.queued_summary() == 0;
544 if !may_wait {
545 self.finish_idle_wait();
546 }
547 may_wait
548 }
549
550 pub(crate) fn finish_idle_wait(&self) {
551 self.scheduler_request
552 .request
553 .fetch_and(!REQUEST_IDLE_POLLING, Ordering::Release);
554 fence(Ordering::SeqCst);
559 }
560
561 pub(crate) fn is_idle_polling(&self) -> bool {
562 self.scheduler_request.request.load(Ordering::Acquire) & REQUEST_IDLE_POLLING != 0
563 }
564
565 pub(super) fn reset_scheduler_for_offline(&self) {
566 self.scheduler_request.request.store(0, Ordering::Relaxed);
567 }
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573
574 #[test]
575 fn ordinary_request_folds_a_later_lazy_request() {
576 let request = SchedulerRequestState::new();
577 assert!(request.publish(REQUEST_PREEMPT).is_some());
578 assert!(
579 request.publish(REQUEST_PREEMPT_LAZY).is_none(),
580 "a live ordinary request must cover the lazy reason"
581 );
582
583 let immediate = request.request.fetch_and(
584 !SchedulerRequestScope::Immediate.claim_mask(),
585 Ordering::AcqRel,
586 );
587 assert_ne!(immediate & REQUEST_PREEMPT, 0);
588 assert_eq!(
589 request.request.load(Ordering::Acquire) & REQUEST_PREEMPT_LAZY,
590 0,
591 "the ordinary scheduling pass must not leave lazy residue"
592 );
593
594 assert!(
595 request.publish(REQUEST_PREEMPT_LAZY).is_some(),
596 "a lazy reason published after ordinary consumption is new work"
597 );
598 assert_ne!(
599 request.request.load(Ordering::Acquire) & REQUEST_PREEMPT_LAZY,
600 0
601 );
602 }
603
604 #[test]
605 fn periodic_tick_moves_lazy_request_into_ordinary_class() {
606 let request = SchedulerRequestState::new();
607 assert!(request.publish(REQUEST_PREEMPT_LAZY).is_some());
608 assert!(request.promote_lazy());
609
610 let claim = request.claim(SchedulerRequestScope::Immediate);
611 assert!(claim.immediate_preempt_requested());
612 assert!(
613 !request
614 .claim(SchedulerRequestScope::All)
615 .preemption_requested(),
616 "the ordinary scheduling pass must consume the lazy reason promoted by this tick"
617 );
618 }
619
620 #[test]
621 fn notified_park_restores_its_claimed_preemption_request() {
622 let ordinary = SchedulerRequestState::new();
623 let _ = ordinary.publish(REQUEST_PREEMPT);
624 let claim = ordinary.claim(SchedulerRequestScope::All);
625 ordinary.restore_claimed_park_preemption(claim);
626
627 assert_ne!(
628 ordinary.request.load(Ordering::Acquire) & REQUEST_PREEMPT,
629 0,
630 "a notified park must restore the ordinary request claimed before cancellation"
631 );
632
633 let lazy = SchedulerRequestState::new();
634 let _ = lazy.publish(REQUEST_PREEMPT_LAZY);
635 let claim = lazy.claim(SchedulerRequestScope::All);
636 lazy.restore_claimed_park_preemption(claim);
637 assert_ne!(
638 lazy.request.load(Ordering::Acquire) & REQUEST_PREEMPT_LAZY,
639 0,
640 "a notified park must restore the lazy request claimed before cancellation"
641 );
642 }
643
644 #[test]
645 fn repeated_remote_preemption_retains_a_delivery_attempt() {
646 let request = SchedulerRequestState::new();
647
648 assert!(request.publish_remote(REQUEST_PREEMPT).is_some());
649 assert!(
650 request.publish_remote(REQUEST_PREEMPT).is_some(),
651 "a fresh remote rq decision must reach DeliveryEdge even while the CPU bit is sticky"
652 );
653 assert!(
654 request.publish_remote(REQUEST_PREEMPT_LAZY).is_none(),
655 "a repeated lazy request must remain logical-only"
656 );
657 }
658}