hermit/scheduler/
mod.rs

1#![allow(clippy::type_complexity)]
2
3use alloc::boxed::Box;
4use alloc::collections::{BTreeMap, VecDeque};
5use alloc::rc::Rc;
6use alloc::sync::Arc;
7#[cfg(feature = "smp")]
8use alloc::vec::Vec;
9use core::cell::RefCell;
10use core::ptr;
11#[cfg(all(target_arch = "x86_64", feature = "smp"))]
12use core::sync::atomic::AtomicBool;
13use core::sync::atomic::{AtomicI32, AtomicU32, Ordering};
14
15use ahash::RandomState;
16use crossbeam_utils::Backoff;
17use hashbrown::HashMap;
18use hermit_sync::*;
19#[cfg(target_arch = "riscv64")]
20use riscv::register::sstatus;
21
22use crate::arch::core_local::*;
23#[cfg(target_arch = "riscv64")]
24use crate::arch::switch::switch_to_task;
25#[cfg(target_arch = "x86_64")]
26use crate::arch::switch::{switch_to_fpu_owner, switch_to_task};
27use crate::arch::{get_processor_count, interrupts};
28use crate::errno::Errno;
29use crate::fd::{FileDescriptor, ObjectInterface};
30use crate::kernel::scheduler::TaskStacks;
31use crate::scheduler::task::*;
32use crate::{arch, io};
33
34pub mod task;
35
36static NO_TASKS: AtomicU32 = AtomicU32::new(0);
37/// Map between Core ID and per-core scheduler
38#[cfg(feature = "smp")]
39static SCHEDULER_INPUTS: SpinMutex<Vec<&InterruptTicketMutex<SchedulerInput>>> =
40	SpinMutex::new(Vec::new());
41#[cfg(all(target_arch = "x86_64", feature = "smp"))]
42static CORE_HLT_STATE: SpinMutex<Vec<&AtomicBool>> = SpinMutex::new(Vec::new());
43/// Map between Task ID and Queue of waiting tasks
44static WAITING_TASKS: InterruptTicketMutex<BTreeMap<TaskId, VecDeque<TaskHandle>>> =
45	InterruptTicketMutex::new(BTreeMap::new());
46/// Map between Task ID and TaskHandle
47static TASKS: InterruptTicketMutex<BTreeMap<TaskId, TaskHandle>> =
48	InterruptTicketMutex::new(BTreeMap::new());
49
50/// Unique identifier for a core.
51pub type CoreId = u32;
52
53#[cfg(feature = "smp")]
54pub(crate) struct SchedulerInput {
55	/// Queue of new tasks
56	new_tasks: VecDeque<NewTask>,
57	/// Queue of task, which are wakeup by another core
58	wakeup_tasks: VecDeque<TaskHandle>,
59}
60
61#[cfg(feature = "smp")]
62impl SchedulerInput {
63	pub fn new() -> Self {
64		Self {
65			new_tasks: VecDeque::new(),
66			wakeup_tasks: VecDeque::new(),
67		}
68	}
69}
70
71#[cfg_attr(any(target_arch = "x86_64", target_arch = "aarch64"), repr(align(128)))]
72#[cfg_attr(
73	not(any(target_arch = "x86_64", target_arch = "aarch64")),
74	repr(align(64))
75)]
76pub(crate) struct PerCoreScheduler {
77	/// Core ID of this per-core scheduler
78	#[cfg(feature = "smp")]
79	core_id: CoreId,
80	/// Task which is currently running
81	current_task: Rc<RefCell<Task>>,
82	/// Idle Task
83	idle_task: Rc<RefCell<Task>>,
84	/// Task that currently owns the FPU
85	#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
86	fpu_owner: Rc<RefCell<Task>>,
87	/// Queue of tasks, which are ready
88	ready_queue: PriorityTaskQueue,
89	/// Queue of tasks, which are finished and can be released
90	finished_tasks: VecDeque<Rc<RefCell<Task>>>,
91	/// Queue of blocked tasks, sorted by wakeup time.
92	blocked_tasks: BlockedTaskQueue,
93}
94
95pub(crate) trait PerCoreSchedulerExt {
96	/// Triggers the scheduler to reschedule the tasks.
97	/// Interrupt flag will be cleared during the reschedule
98	fn reschedule(self);
99
100	#[cfg(feature = "net")]
101	fn add_network_timer(self, wakeup_time: Option<u64>);
102
103	/// Terminate the current task on the current core.
104	fn exit(self, exit_code: i32) -> !;
105}
106
107impl PerCoreSchedulerExt for &mut PerCoreScheduler {
108	#[cfg(target_arch = "x86_64")]
109	fn reschedule(self) {
110		without_interrupts(|| {
111			if let Some(last_stack_pointer) = self.scheduler() {
112				let (new_stack_pointer, is_idle) = {
113					let borrowed = self.current_task.borrow();
114					(
115						borrowed.last_stack_pointer,
116						borrowed.status == TaskStatus::Idle,
117					)
118				};
119
120				if is_idle || Rc::ptr_eq(&self.current_task, &self.fpu_owner) {
121					unsafe {
122						switch_to_fpu_owner(
123							last_stack_pointer,
124							new_stack_pointer.as_u64() as usize,
125						);
126					}
127				} else {
128					unsafe {
129						switch_to_task(last_stack_pointer, new_stack_pointer.as_u64() as usize);
130					}
131				}
132			}
133		});
134	}
135
136	/// Trigger an interrupt to reschedule the system
137	#[cfg(target_arch = "aarch64")]
138	fn reschedule(self) {
139		use core::arch::asm;
140
141		use arm_gic::IntId;
142		use arm_gic::gicv3::{GicV3, SgiTarget, SgiTargetGroup};
143
144		use crate::interrupts::SGI_RESCHED;
145
146		unsafe {
147			asm!("dsb nsh", "isb", options(nostack, nomem, preserves_flags));
148		}
149
150		let reschedid = IntId::sgi(SGI_RESCHED.into());
151		#[cfg(feature = "smp")]
152		let core_id = self.core_id;
153		#[cfg(not(feature = "smp"))]
154		let core_id = 0;
155
156		GicV3::send_sgi(
157			reschedid,
158			SgiTarget::List {
159				affinity3: 0,
160				affinity2: 0,
161				affinity1: 0,
162				target_list: 1 << core_id,
163			},
164			SgiTargetGroup::CurrentGroup1,
165		);
166
167		interrupts::enable();
168	}
169
170	#[cfg(target_arch = "riscv64")]
171	fn reschedule(self) {
172		without_interrupts(|| self.scheduler());
173	}
174
175	#[cfg(feature = "net")]
176	fn add_network_timer(self, wakeup_time: Option<u64>) {
177		without_interrupts(|| {
178			self.blocked_tasks.add_network_timer(wakeup_time);
179		});
180	}
181
182	fn exit(self, exit_code: i32) -> ! {
183		without_interrupts(|| {
184			// Get the current task.
185			let mut current_task_borrowed = self.current_task.borrow_mut();
186			assert_ne!(
187				current_task_borrowed.status,
188				TaskStatus::Idle,
189				"Trying to terminate the idle task"
190			);
191
192			// Finish the task and reschedule.
193			debug!(
194				"Finishing task {} with exit code {}",
195				current_task_borrowed.id, exit_code
196			);
197			current_task_borrowed.status = TaskStatus::Finished;
198			NO_TASKS.fetch_sub(1, Ordering::SeqCst);
199
200			let current_id = current_task_borrowed.id;
201			drop(current_task_borrowed);
202
203			// wakeup tasks, which are waiting for task with the identifier id
204			if let Some(mut queue) = WAITING_TASKS.lock().remove(&current_id) {
205				while let Some(task) = queue.pop_front() {
206					self.custom_wakeup(task);
207				}
208			}
209		});
210
211		self.reschedule();
212		unreachable!()
213	}
214}
215
216struct NewTask {
217	tid: TaskId,
218	func: unsafe extern "C" fn(usize),
219	arg: usize,
220	prio: Priority,
221	core_id: CoreId,
222	stacks: TaskStacks,
223	object_map: Arc<
224		RwSpinLock<
225			HashMap<FileDescriptor, Arc<async_lock::RwLock<dyn ObjectInterface>>, RandomState>,
226		>,
227	>,
228}
229
230impl From<NewTask> for Task {
231	fn from(value: NewTask) -> Self {
232		let NewTask {
233			tid,
234			func,
235			arg,
236			prio,
237			core_id,
238			stacks,
239			object_map,
240		} = value;
241		let mut task = Self::new(tid, core_id, TaskStatus::Ready, prio, stacks, object_map);
242		task.create_stack_frame(func, arg);
243		task
244	}
245}
246
247impl PerCoreScheduler {
248	/// Spawn a new task.
249	pub unsafe fn spawn(
250		func: unsafe extern "C" fn(usize),
251		arg: usize,
252		prio: Priority,
253		core_id: CoreId,
254		stack_size: usize,
255	) -> TaskId {
256		// Create the new task.
257		let tid = get_tid();
258		let stacks = TaskStacks::new(stack_size);
259		let new_task = NewTask {
260			tid,
261			func,
262			arg,
263			prio,
264			core_id,
265			stacks,
266			object_map: core_scheduler().get_current_task_object_map(),
267		};
268
269		// Add it to the task lists.
270		let wakeup = {
271			#[cfg(feature = "smp")]
272			let mut input_locked = get_scheduler_input(core_id).lock();
273			WAITING_TASKS.lock().insert(tid, VecDeque::with_capacity(1));
274			TASKS.lock().insert(
275				tid,
276				TaskHandle::new(
277					tid,
278					prio,
279					#[cfg(feature = "smp")]
280					core_id,
281				),
282			);
283			NO_TASKS.fetch_add(1, Ordering::SeqCst);
284
285			#[cfg(feature = "smp")]
286			if core_id == core_scheduler().core_id {
287				let task = Rc::new(RefCell::new(Task::from(new_task)));
288				core_scheduler().ready_queue.push(task);
289				false
290			} else {
291				input_locked.new_tasks.push_back(new_task);
292				true
293			}
294			#[cfg(not(feature = "smp"))]
295			if core_id == 0 {
296				let task = Rc::new(RefCell::new(Task::from(new_task)));
297				core_scheduler().ready_queue.push(task);
298				false
299			} else {
300				panic!("Invalid  core_id {}!", core_id)
301			}
302		};
303
304		debug!("Creating task {tid} with priority {prio} on core {core_id}");
305
306		if wakeup {
307			arch::wakeup_core(core_id);
308		}
309
310		tid
311	}
312
313	#[cfg(feature = "newlib")]
314	fn clone_impl(&self, func: extern "C" fn(usize), arg: usize) -> TaskId {
315		static NEXT_CORE_ID: AtomicU32 = AtomicU32::new(1);
316
317		// Get the Core ID of the next CPU.
318		let core_id: CoreId = {
319			// Increase the CPU number by 1.
320			let id = NEXT_CORE_ID.fetch_add(1, Ordering::SeqCst);
321
322			// Check for overflow.
323			if id == arch::get_processor_count() {
324				NEXT_CORE_ID.store(0, Ordering::SeqCst);
325				0
326			} else {
327				id
328			}
329		};
330
331		// Get the current task.
332		let current_task_borrowed = self.current_task.borrow();
333
334		// Clone the current task.
335		let tid = get_tid();
336		let clone_task = NewTask {
337			tid,
338			func,
339			arg,
340			prio: current_task_borrowed.prio,
341			core_id,
342			stacks: TaskStacks::new(current_task_borrowed.stacks.get_user_stack_size()),
343			object_map: current_task_borrowed.object_map.clone(),
344		};
345
346		// Add it to the task lists.
347		let wakeup = {
348			#[cfg(feature = "smp")]
349			let mut input_locked = get_scheduler_input(core_id).lock();
350			WAITING_TASKS.lock().insert(tid, VecDeque::with_capacity(1));
351			TASKS.lock().insert(
352				tid,
353				TaskHandle::new(
354					tid,
355					current_task_borrowed.prio,
356					#[cfg(feature = "smp")]
357					core_id,
358				),
359			);
360			NO_TASKS.fetch_add(1, Ordering::SeqCst);
361			#[cfg(feature = "smp")]
362			if core_id == core_scheduler().core_id {
363				let clone_task = Rc::new(RefCell::new(Task::from(clone_task)));
364				core_scheduler().ready_queue.push(clone_task);
365				false
366			} else {
367				input_locked.new_tasks.push_back(clone_task);
368				true
369			}
370			#[cfg(not(feature = "smp"))]
371			if core_id == 0 {
372				let clone_task = Rc::new(RefCell::new(Task::from(clone_task)));
373				core_scheduler().ready_queue.push(clone_task);
374				false
375			} else {
376				panic!("Invalid core_id {}!", core_id);
377			}
378		};
379
380		// Wake up the CPU
381		if wakeup {
382			arch::wakeup_core(core_id);
383		}
384
385		tid
386	}
387
388	#[cfg(feature = "newlib")]
389	pub fn clone(&self, func: extern "C" fn(usize), arg: usize) -> TaskId {
390		without_interrupts(|| self.clone_impl(func, arg))
391	}
392
393	/// Returns `true` if a reschedule is required
394	#[inline]
395	#[cfg(all(any(target_arch = "x86_64", target_arch = "riscv64"), feature = "smp"))]
396	pub fn is_scheduling(&self) -> bool {
397		self.current_task.borrow().prio < self.ready_queue.get_highest_priority()
398	}
399
400	#[inline]
401	pub fn handle_waiting_tasks(&mut self) {
402		without_interrupts(|| {
403			crate::executor::run();
404			self.blocked_tasks
405				.handle_waiting_tasks(&mut self.ready_queue);
406		});
407	}
408
409	#[cfg(not(feature = "smp"))]
410	pub fn custom_wakeup(&mut self, task: TaskHandle) {
411		without_interrupts(|| {
412			let task = self.blocked_tasks.custom_wakeup(task);
413			self.ready_queue.push(task);
414		});
415	}
416
417	#[cfg(feature = "smp")]
418	pub fn custom_wakeup(&mut self, task: TaskHandle) {
419		if task.get_core_id() == self.core_id {
420			without_interrupts(|| {
421				let task = self.blocked_tasks.custom_wakeup(task);
422				self.ready_queue.push(task);
423			});
424		} else {
425			get_scheduler_input(task.get_core_id())
426				.lock()
427				.wakeup_tasks
428				.push_back(task);
429			// Wake up the CPU
430			arch::wakeup_core(task.get_core_id());
431		}
432	}
433
434	#[inline]
435	pub fn block_current_task(&mut self, wakeup_time: Option<u64>) {
436		without_interrupts(|| {
437			self.blocked_tasks
438				.add(self.current_task.clone(), wakeup_time);
439		});
440	}
441
442	#[inline]
443	pub fn get_current_task_handle(&self) -> TaskHandle {
444		without_interrupts(|| {
445			let current_task_borrowed = self.current_task.borrow();
446
447			TaskHandle::new(
448				current_task_borrowed.id,
449				current_task_borrowed.prio,
450				#[cfg(feature = "smp")]
451				current_task_borrowed.core_id,
452			)
453		})
454	}
455
456	#[inline]
457	pub fn get_current_task_id(&self) -> TaskId {
458		without_interrupts(|| self.current_task.borrow().id)
459	}
460
461	#[inline]
462	pub fn get_current_task_object_map(
463		&self,
464	) -> Arc<
465		RwSpinLock<
466			HashMap<FileDescriptor, Arc<async_lock::RwLock<dyn ObjectInterface>>, RandomState>,
467		>,
468	> {
469		without_interrupts(|| self.current_task.borrow().object_map.clone())
470	}
471
472	/// Map a file descriptor to their IO interface and returns
473	/// the shared reference
474	#[inline]
475	pub fn get_object(
476		&self,
477		fd: FileDescriptor,
478	) -> io::Result<Arc<async_lock::RwLock<dyn ObjectInterface>>> {
479		without_interrupts(|| {
480			let current_task = self.current_task.borrow();
481			let object_map = current_task.object_map.read();
482			object_map.get(&fd).cloned().ok_or(Errno::Badf)
483		})
484	}
485
486	/// Creates a new map between file descriptor and their IO interface and
487	/// clone the standard descriptors.
488	#[cfg(feature = "common-os")]
489	#[cfg_attr(not(target_arch = "x86_64"), expect(dead_code))]
490	pub fn recreate_objmap(&self) -> io::Result<()> {
491		let mut map = HashMap::<
492			FileDescriptor,
493			Arc<async_lock::RwLock<dyn ObjectInterface>>,
494			RandomState,
495		>::with_hasher(RandomState::with_seeds(0, 0, 0, 0));
496
497		without_interrupts(|| {
498			let mut current_task = self.current_task.borrow_mut();
499			let object_map = current_task.object_map.read();
500
501			// clone standard file descriptors
502			for i in 0..3 {
503				if let Some(obj) = object_map.get(&i) {
504					map.insert(i, obj.clone());
505				}
506			}
507
508			drop(object_map);
509			current_task.object_map = Arc::new(RwSpinLock::new(map));
510		});
511
512		Ok(())
513	}
514
515	/// Insert a new IO interface and returns a file descriptor as
516	/// identifier to this object
517	pub fn insert_object(
518		&self,
519		obj: Arc<async_lock::RwLock<dyn ObjectInterface>>,
520	) -> io::Result<FileDescriptor> {
521		without_interrupts(|| {
522			let current_task = self.current_task.borrow();
523			let mut object_map = current_task.object_map.write();
524
525			let new_fd = || -> io::Result<FileDescriptor> {
526				let mut fd: FileDescriptor = 0;
527				loop {
528					if !object_map.contains_key(&fd) {
529						break Ok(fd);
530					} else if fd == FileDescriptor::MAX {
531						break Err(Errno::Overflow);
532					}
533
534					fd = fd.saturating_add(1);
535				}
536			};
537
538			let fd = new_fd()?;
539			let _ = object_map.insert(fd, obj.clone());
540			Ok(fd)
541		})
542	}
543
544	/// Duplicate a IO interface and returns a new file descriptor as
545	/// identifier to the new copy
546	pub fn dup_object(&self, fd: FileDescriptor) -> io::Result<FileDescriptor> {
547		without_interrupts(|| {
548			let current_task = self.current_task.borrow();
549			let mut object_map = current_task.object_map.write();
550
551			let obj = (*(object_map.get(&fd).ok_or(Errno::Inval)?)).clone();
552
553			let new_fd = || -> io::Result<FileDescriptor> {
554				let mut fd: FileDescriptor = 0;
555				loop {
556					if !object_map.contains_key(&fd) {
557						break Ok(fd);
558					} else if fd == FileDescriptor::MAX {
559						break Err(Errno::Overflow);
560					}
561
562					fd = fd.saturating_add(1);
563				}
564			};
565
566			let fd = new_fd()?;
567			if object_map.try_insert(fd, obj).is_err() {
568				Err(Errno::Mfile)
569			} else {
570				Ok(fd)
571			}
572		})
573	}
574
575	pub fn dup_object2(
576		&self,
577		fd1: FileDescriptor,
578		fd2: FileDescriptor,
579	) -> io::Result<FileDescriptor> {
580		without_interrupts(|| {
581			let current_task = self.current_task.borrow();
582			let mut object_map = current_task.object_map.write();
583
584			let obj = object_map.get(&fd1).cloned().ok_or(Errno::Badf)?;
585
586			if object_map.try_insert(fd2, obj).is_err() {
587				Err(Errno::Mfile)
588			} else {
589				Ok(fd2)
590			}
591		})
592	}
593
594	/// Remove a IO interface, which is named by the file descriptor
595	pub fn remove_object(
596		&self,
597		fd: FileDescriptor,
598	) -> io::Result<Arc<async_lock::RwLock<dyn ObjectInterface>>> {
599		without_interrupts(|| {
600			let current_task = self.current_task.borrow();
601			let mut object_map = current_task.object_map.write();
602
603			object_map.remove(&fd).ok_or(Errno::Badf)
604		})
605	}
606
607	#[inline]
608	pub fn get_current_task_prio(&self) -> Priority {
609		without_interrupts(|| self.current_task.borrow().prio)
610	}
611
612	/// Returns reference to prio_bitmap
613	#[allow(dead_code)]
614	#[inline]
615	pub fn get_priority_bitmap(&self) -> &u64 {
616		self.ready_queue.get_priority_bitmap()
617	}
618
619	#[cfg(target_arch = "x86_64")]
620	pub fn set_current_kernel_stack(&self) {
621		let current_task_borrowed = self.current_task.borrow();
622		let tss = unsafe { &mut *CoreLocal::get().tss.get() };
623
624		let rsp = current_task_borrowed.stacks.get_kernel_stack()
625			+ current_task_borrowed.stacks.get_kernel_stack_size() as u64
626			- TaskStacks::MARKER_SIZE as u64;
627		tss.privilege_stack_table[0] = rsp.into();
628		CoreLocal::get().kernel_stack.set(rsp.as_mut_ptr());
629		let ist_start = current_task_borrowed.stacks.get_interrupt_stack()
630			+ current_task_borrowed.stacks.get_interrupt_stack_size() as u64
631			- TaskStacks::MARKER_SIZE as u64;
632		tss.interrupt_stack_table[0] = ist_start.into();
633	}
634
635	pub fn set_current_task_priority(&mut self, prio: Priority) {
636		without_interrupts(|| {
637			trace!("Change priority of the current task");
638			self.current_task.borrow_mut().prio = prio;
639		});
640	}
641
642	pub fn set_priority(&mut self, id: TaskId, prio: Priority) -> Result<(), ()> {
643		trace!("Change priority of task {id} to priority {prio}");
644
645		without_interrupts(|| {
646			let task = get_task_handle(id).ok_or(())?;
647			#[cfg(feature = "smp")]
648			let other_core = task.get_core_id() != self.core_id;
649			#[cfg(not(feature = "smp"))]
650			let other_core = false;
651
652			if other_core {
653				warn!("Have to change the priority on another core");
654			} else if self.current_task.borrow().id == task.get_id() {
655				self.current_task.borrow_mut().prio = prio;
656			} else {
657				self.ready_queue
658					.set_priority(task, prio)
659					.expect("Do not find valid task in ready queue");
660			}
661
662			Ok(())
663		})
664	}
665
666	#[cfg(target_arch = "riscv64")]
667	pub fn set_current_kernel_stack(&self) {
668		let current_task_borrowed = self.current_task.borrow();
669
670		let stack = (current_task_borrowed.stacks.get_kernel_stack()
671			+ current_task_borrowed.stacks.get_kernel_stack_size() as u64
672			- TaskStacks::MARKER_SIZE as u64)
673			.as_u64();
674		CoreLocal::get().kernel_stack.set(stack);
675	}
676
677	/// Save the FPU context for the current FPU owner and restore it for the current task,
678	/// which wants to use the FPU now.
679	#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
680	pub fn fpu_switch(&mut self) {
681		if !Rc::ptr_eq(&self.current_task, &self.fpu_owner) {
682			debug!(
683				"Switching FPU owner from task {} to {}",
684				self.fpu_owner.borrow().id,
685				self.current_task.borrow().id
686			);
687
688			self.fpu_owner.borrow_mut().last_fpu_state.save();
689			self.current_task.borrow().last_fpu_state.restore();
690			self.fpu_owner = self.current_task.clone();
691		}
692	}
693
694	/// Check if a finished task could be deleted.
695	fn cleanup_tasks(&mut self) {
696		// Pop the first finished task and remove it from the TASKS list, which implicitly deallocates all associated memory.
697		while let Some(finished_task) = self.finished_tasks.pop_front() {
698			debug!("Cleaning up task {}", finished_task.borrow().id);
699		}
700	}
701
702	#[cfg(feature = "smp")]
703	pub fn check_input(&mut self) {
704		let mut input_locked = CoreLocal::get().scheduler_input.lock();
705
706		while let Some(task) = input_locked.wakeup_tasks.pop_front() {
707			let task = self.blocked_tasks.custom_wakeup(task);
708			self.ready_queue.push(task);
709		}
710
711		while let Some(new_task) = input_locked.new_tasks.pop_front() {
712			let task = Rc::new(RefCell::new(Task::from(new_task)));
713			self.ready_queue.push(task.clone());
714		}
715	}
716
717	/// Only the idle task should call this function.
718	/// Set the idle task to halt state if not another
719	/// available.
720	pub fn run() -> ! {
721		let backoff = Backoff::new();
722
723		loop {
724			let core_scheduler = core_scheduler();
725			interrupts::disable();
726
727			// run async tasks
728			crate::executor::run();
729
730			// do housekeeping
731			#[cfg(feature = "smp")]
732			core_scheduler.check_input();
733			core_scheduler.cleanup_tasks();
734
735			if core_scheduler.ready_queue.is_empty() {
736				if backoff.is_completed() {
737					interrupts::enable_and_wait();
738					backoff.reset();
739				} else {
740					interrupts::enable();
741					backoff.snooze();
742				}
743			} else {
744				interrupts::enable();
745				core_scheduler.reschedule();
746				backoff.reset();
747			}
748		}
749	}
750
751	#[inline]
752	#[cfg(target_arch = "aarch64")]
753	pub fn get_last_stack_pointer(&self) -> memory_addresses::VirtAddr {
754		self.current_task.borrow().last_stack_pointer
755	}
756
757	/// Triggers the scheduler to reschedule the tasks.
758	/// Interrupt flag must be cleared before calling this function.
759	pub fn scheduler(&mut self) -> Option<*mut usize> {
760		// run background tasks
761		crate::executor::run();
762
763		// Someone wants to give up the CPU
764		// => we have time to cleanup the system
765		self.cleanup_tasks();
766
767		// Get information about the current task.
768		let (id, last_stack_pointer, prio, status) = {
769			let mut borrowed = self.current_task.borrow_mut();
770			(
771				borrowed.id,
772				ptr::from_mut(&mut borrowed.last_stack_pointer).cast::<usize>(),
773				borrowed.prio,
774				borrowed.status,
775			)
776		};
777
778		let mut new_task = None;
779
780		if status == TaskStatus::Running {
781			// A task is currently running.
782			// Check if a task with a equal or higher priority is available.
783			if let Some(task) = self.ready_queue.pop_with_prio(prio) {
784				new_task = Some(task);
785			}
786		} else {
787			if status == TaskStatus::Finished {
788				// Mark the finished task as invalid and add it to the finished tasks for a later cleanup.
789				self.current_task.borrow_mut().status = TaskStatus::Invalid;
790				self.finished_tasks.push_back(self.current_task.clone());
791			}
792
793			// No task is currently running.
794			// Check if there is any available task and get the one with the highest priority.
795			if let Some(task) = self.ready_queue.pop() {
796				// This available task becomes the new task.
797				debug!("Task is available.");
798				new_task = Some(task);
799			} else if status != TaskStatus::Idle {
800				// The Idle task becomes the new task.
801				debug!("Only Idle Task is available.");
802				new_task = Some(self.idle_task.clone());
803			}
804		}
805
806		if let Some(task) = new_task {
807			// There is a new task we want to switch to.
808
809			// Handle the current task.
810			if status == TaskStatus::Running {
811				// Mark the running task as ready again and add it back to the queue.
812				self.current_task.borrow_mut().status = TaskStatus::Ready;
813				self.ready_queue.push(self.current_task.clone());
814			}
815
816			// Handle the new task and get information about it.
817			let (new_id, new_stack_pointer) = {
818				let mut borrowed = task.borrow_mut();
819				if borrowed.status != TaskStatus::Idle {
820					// Mark the new task as running.
821					borrowed.status = TaskStatus::Running;
822				}
823
824				(borrowed.id, borrowed.last_stack_pointer)
825			};
826
827			if id != new_id {
828				// Tell the scheduler about the new task.
829				debug!(
830					"Switching task from {} to {} (stack {:#X} => {:p})",
831					id,
832					new_id,
833					unsafe { *last_stack_pointer },
834					new_stack_pointer
835				);
836				#[cfg(not(target_arch = "riscv64"))]
837				{
838					self.current_task = task;
839				}
840
841				// Finally return the context of the new task.
842				#[cfg(not(target_arch = "riscv64"))]
843				return Some(last_stack_pointer);
844
845				#[cfg(target_arch = "riscv64")]
846				{
847					if sstatus::read().fs() == sstatus::FS::Dirty {
848						self.current_task.borrow_mut().last_fpu_state.save();
849					}
850					task.borrow().last_fpu_state.restore();
851					self.current_task = task;
852					unsafe {
853						switch_to_task(last_stack_pointer, new_stack_pointer.as_usize());
854					}
855				}
856			}
857		}
858
859		None
860	}
861}
862
863fn get_tid() -> TaskId {
864	static TID_COUNTER: AtomicI32 = AtomicI32::new(0);
865	let guard = TASKS.lock();
866
867	loop {
868		let id = TaskId::from(TID_COUNTER.fetch_add(1, Ordering::SeqCst));
869		if !guard.contains_key(&id) {
870			return id;
871		}
872	}
873}
874
875#[inline]
876pub(crate) fn abort() -> ! {
877	core_scheduler().exit(-1)
878}
879
880/// Add a per-core scheduler for the current core.
881pub(crate) fn add_current_core() {
882	// Create an idle task for this core.
883	let core_id = core_id();
884	let tid = get_tid();
885	let idle_task = Rc::new(RefCell::new(Task::new_idle(tid, core_id)));
886
887	// Add the ID -> Task mapping.
888	WAITING_TASKS.lock().insert(tid, VecDeque::with_capacity(1));
889	TASKS.lock().insert(
890		tid,
891		TaskHandle::new(
892			tid,
893			IDLE_PRIO,
894			#[cfg(feature = "smp")]
895			core_id,
896		),
897	);
898	// Initialize a scheduler for this core.
899	debug!("Initializing scheduler for core {core_id} with idle task {tid}");
900	let boxed_scheduler = Box::new(PerCoreScheduler {
901		#[cfg(feature = "smp")]
902		core_id,
903		current_task: idle_task.clone(),
904		#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
905		fpu_owner: idle_task.clone(),
906		idle_task,
907		ready_queue: PriorityTaskQueue::new(),
908		finished_tasks: VecDeque::new(),
909		blocked_tasks: BlockedTaskQueue::new(),
910	});
911
912	let scheduler = Box::into_raw(boxed_scheduler);
913	set_core_scheduler(scheduler);
914	#[cfg(feature = "smp")]
915	{
916		SCHEDULER_INPUTS.lock().insert(
917			core_id.try_into().unwrap(),
918			&CoreLocal::get().scheduler_input,
919		);
920		#[cfg(target_arch = "x86_64")]
921		CORE_HLT_STATE
922			.lock()
923			.insert(core_id.try_into().unwrap(), &CoreLocal::get().hlt);
924	}
925}
926
927#[inline]
928#[cfg(all(target_arch = "x86_64", feature = "smp", not(feature = "idle-poll")))]
929pub(crate) fn take_core_hlt_state(core_id: CoreId) -> bool {
930	CORE_HLT_STATE.lock()[usize::try_from(core_id).unwrap()].swap(false, Ordering::Acquire)
931}
932
933#[inline]
934#[cfg(feature = "smp")]
935fn get_scheduler_input(core_id: CoreId) -> &'static InterruptTicketMutex<SchedulerInput> {
936	SCHEDULER_INPUTS.lock()[usize::try_from(core_id).unwrap()]
937}
938
939pub unsafe fn spawn(
940	func: unsafe extern "C" fn(usize),
941	arg: usize,
942	prio: Priority,
943	stack_size: usize,
944	selector: isize,
945) -> TaskId {
946	static CORE_COUNTER: AtomicU32 = AtomicU32::new(1);
947
948	let core_id = if selector < 0 {
949		// use Round Robin to schedule the cores
950		CORE_COUNTER.fetch_add(1, Ordering::SeqCst) % get_processor_count()
951	} else {
952		selector as u32
953	};
954
955	unsafe { PerCoreScheduler::spawn(func, arg, prio, core_id, stack_size) }
956}
957
958#[allow(clippy::result_unit_err)]
959pub fn join(id: TaskId) -> Result<(), ()> {
960	let core_scheduler = core_scheduler();
961
962	debug!(
963		"Task {} is waiting for task {}",
964		core_scheduler.get_current_task_id(),
965		id
966	);
967
968	loop {
969		let mut waiting_tasks_guard = WAITING_TASKS.lock();
970
971		if let Some(queue) = waiting_tasks_guard.get_mut(&id) {
972			queue.push_back(core_scheduler.get_current_task_handle());
973			core_scheduler.block_current_task(None);
974
975			// Switch to the next task.
976			drop(waiting_tasks_guard);
977			core_scheduler.reschedule();
978		} else {
979			return Ok(());
980		}
981	}
982}
983
984pub fn shutdown(arg: i32) -> ! {
985	crate::syscalls::shutdown(arg)
986}
987
988fn get_task_handle(id: TaskId) -> Option<TaskHandle> {
989	TASKS.lock().get(&id).copied()
990}
991
992#[cfg(all(target_arch = "x86_64", feature = "common-os"))]
993pub(crate) static BOOT_ROOT_PAGE_TABLE: OnceCell<usize> = OnceCell::new();
994
995#[cfg(all(target_arch = "x86_64", feature = "common-os"))]
996pub(crate) fn get_root_page_table() -> usize {
997	let current_task_borrowed = core_scheduler().current_task.borrow_mut();
998	current_task_borrowed.root_page_table
999}