Skip to main content

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