Skip to main content

rustubs/proc/
sync.rs

1//! the sync module defines the OOStuBS prologue/epilogue synchronization model
2//! for interrupt and preemptive scheduling. Read `docs/sync_model.md` for
3//! details
4#![doc = include_str!("../../docs/sync_model.md")]
5pub mod bellringer;
6pub mod irq;
7pub mod semaphore;
8pub mod spin;
9use crate::arch::x86_64::{gdt, is_int_enabled};
10use crate::black_magic::Void;
11use crate::proc::task::Task;
12use core::cell::SyncUnsafeCell;
13use core::ops::{Deref, DerefMut};
14use core::sync::atomic::{AtomicBool, Ordering};
15pub use irq::*;
16/// indicates whether a task is running in L2. Maybe make it L3SyncCell as well.
17static L2_AVAILABLE: AtomicBool = AtomicBool::new(true);
18/// RAII lock guard for the global L2 flag, the u64 is not to be used.
19static L2_GUARD: L2Sync<Void> = L2Sync::new(Void::new());
20
21#[inline(always)]
22#[allow(non_snake_case)]
23pub fn IS_L2_AVAILABLE() -> bool {
24	return L2_AVAILABLE.load(Ordering::Relaxed);
25}
26
27/// ENTER_L2 mirrors `enter` for oostubs
28#[allow(non_snake_case)]
29#[inline(always)]
30pub fn ENTER_L2() {
31	let r = L2_AVAILABLE.compare_exchange(
32		true,
33		false,
34		Ordering::Relaxed,
35		Ordering::Relaxed,
36	);
37	debug_assert_eq!(r, Ok(true));
38}
39
40// called by the asm code `child_task_entry` to 1) leave L2, and 2) set the
41// correct tss.rsp0, since the newly forked child doesn't return via the
42// trap_gate path.
43#[no_mangle]
44extern "C" fn child_entry_cleanup() {
45	unsafe { gdt::set_tss_ksp(Task::current().unwrap().get_init_kernel_sp()) };
46	LEAVE_L2();
47}
48
49/// LEAVE_L2() mirrors `retne` for oostubs. It's important to note that we do
50/// not mirror or implement oostubs's `leave()` funciton on purpose: the origin
51/// is IMHO too much coupling. We want to keep our implementation as flat as
52/// possible. The internal logic of clearing Epilogue queue (which includes a.
53/// disabling and re-enabling irq at proper places and b. reschedule decisions)
54/// is explicit and flattened in the irq handling routine. See `handle_irq`
55#[inline(always)]
56#[allow(non_snake_case)]
57pub fn LEAVE_L2() {
58	let r = L2_AVAILABLE.compare_exchange(
59		false,
60		true,
61		Ordering::Relaxed,
62		Ordering::Relaxed,
63	);
64	debug_assert_eq!(r, Ok(false));
65}
66
67/// also clear the epilogue queue before really leaving.
68#[inline(always)]
69#[allow(non_snake_case)]
70pub fn LEAVE_L2_CLEAR_QUEUE() {
71	todo!();
72}
73
74/// RAII guard for L2Sync objects
75pub struct L2Guard<'a, T: 'a> {
76	lock: &'a L2Sync<T>,
77	// poison is implicit (using the L2_AVAILABLE flag)
78}
79
80impl<T> Deref for L2Guard<'_, T> {
81	type Target = T;
82	fn deref(&self) -> &T { unsafe { &*self.lock.data.get() } }
83}
84
85impl<T> DerefMut for L2Guard<'_, T> {
86	fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.lock.data.get() } }
87}
88
89impl<T> Drop for L2Guard<'_, T> {
90	fn drop(&mut self) { LEAVE_L2(); }
91}
92
93/// All L2Sync objects are guaranteed to be synchronized on the epilogue level.
94pub struct L2Sync<T> {
95	data: SyncUnsafeCell<T>,
96}
97
98impl<T> L2Sync<T> {
99	pub const fn new(data: T) -> Self {
100		Self { data: SyncUnsafeCell::new(data) }
101	}
102	pub fn lock(&self) -> L2Guard<'_, T> {
103		ENTER_L2();
104		L2Guard { lock: self }
105	}
106
107	/// This breaks synchronization, the caller is responsible of checking the
108	/// global L2_AVAILABLE flag, and do other stuffs (like relaying) when
109	/// epilogue level is occupied.
110	pub unsafe fn get_ref_unguarded(&self) -> &T { &*self.data.get() }
111
112	pub unsafe fn get_ref_mut_unguarded(&self) -> &mut T {
113		&mut *self.data.get()
114	}
115}
116
117/// L3Sync is like RefCell, instead of counting the reference numbers, we check
118/// that the interrupt must be disabled. e.g. epilogue queue
119///
120/// TODO: implement reference counting to make sure the sync model is followed
121pub struct L3Sync<T> {
122	data: SyncUnsafeCell<T>,
123}
124
125impl<T> L3Sync<T> {
126	pub const fn new(data: T) -> Self {
127		Self { data: SyncUnsafeCell::new(data) }
128	}
129	/// get a readonly reference to the protected data. It should be fine to get
130	/// a read only ref without masking interrupts but we haven't implemented
131	/// reference counting yet so ...
132	pub fn l3_get_ref(&self) -> &T {
133		debug_assert!(
134			!is_int_enabled(),
135			"trying to get a ref to L3 synced object with interrupt enabled"
136		);
137		unsafe { &*self.data.get() }
138	}
139	/// get a mutable reference to the protected data. will panic if called with
140	/// interrupt enabled
141	pub fn l3_get_ref_mut(&self) -> &mut T {
142		debug_assert!(
143			!is_int_enabled(),
144			"trying to get a mut ref to L3 synced object with interrupt enabled"
145		);
146		unsafe { &mut *self.data.get() }
147	}
148	/// get a mutable reference without checking sync/borrow conditions.
149	pub unsafe fn l3_get_ref_mut_unchecked(&self) -> &mut T {
150		unsafe { &mut *self.data.get() }
151	}
152}