veridian_kernel/sync/rcu.rs
1//! Read-Copy-Update (RCU) Synchronization
2//!
3//! RCU provides extremely fast read-side access to shared data structures
4//! without locks. Readers proceed without synchronization overhead while
5//! writers create copies, update pointers atomically, and reclaim old
6//! versions after a grace period.
7//!
8//! This implementation uses a simplified epoch-based reclamation scheme
9//! suitable for a microkernel with a small number of CPUs:
10//!
11//! - Readers call `rcu_read_lock()` / `rcu_read_unlock()` to mark critical
12//! sections (these compile to atomic counter increments, no actual locks).
13//! - Writers call `synchronize_rcu()` to wait for all pre-existing readers to
14//! complete, or `call_rcu()` to defer cleanup to a callback.
15//! - Grace period detection uses per-CPU counters: when all CPUs have passed
16//! through a quiescent state (context switch or explicit `rcu_quiescent()`),
17//! the grace period is complete.
18
19use alloc::{boxed::Box, vec::Vec};
20use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
21
22use spin::Mutex;
23
24// ---------------------------------------------------------------------------
25// Per-CPU RCU state
26// ---------------------------------------------------------------------------
27
28/// Maximum number of CPUs for RCU tracking.
29const RCU_MAX_CPUS: usize = 16;
30
31/// Per-CPU nesting depth of RCU read-side critical sections.
32///
33/// When > 0, the CPU is inside an RCU read-side critical section and
34/// cannot be considered quiescent.
35#[allow(clippy::declare_interior_mutable_const)]
36static RCU_NESTING: [AtomicUsize; RCU_MAX_CPUS] = {
37 const INIT: AtomicUsize = AtomicUsize::new(0);
38 [INIT; RCU_MAX_CPUS]
39};
40
41/// Global RCU grace period counter. Incremented each time a grace period
42/// completes. Writers snapshot this before waiting; when all CPUs have
43/// observed a quiescent state since the snapshot, the grace period is done.
44static RCU_GP_COUNTER: AtomicU64 = AtomicU64::new(0);
45
46/// Per-CPU last-observed grace period. Updated each time a CPU passes
47/// through a quiescent state.
48#[allow(clippy::declare_interior_mutable_const)]
49static RCU_CPU_GP: [AtomicU64; RCU_MAX_CPUS] = {
50 const INIT: AtomicU64 = AtomicU64::new(0);
51 [INIT; RCU_MAX_CPUS]
52};
53
54// ---------------------------------------------------------------------------
55// Deferred callback queue
56// ---------------------------------------------------------------------------
57
58/// A deferred cleanup callback registered via `call_rcu()`.
59struct RcuCallback {
60 /// The grace period after which this callback can execute.
61 target_gp: u64,
62 /// The callback function.
63 func: Box<dyn FnOnce() + Send>,
64}
65
66/// Queue of deferred RCU callbacks.
67static RCU_CALLBACKS: Mutex<Vec<RcuCallback>> = Mutex::new(Vec::new());
68
69// ---------------------------------------------------------------------------
70// Reader API
71// ---------------------------------------------------------------------------
72
73/// Enter an RCU read-side critical section.
74///
75/// This is extremely lightweight: a single atomic increment. No locks,
76/// no memory barriers beyond the atomic ordering.
77///
78/// Must be paired with `rcu_read_unlock()`. Nesting is supported.
79#[inline]
80pub fn rcu_read_lock() {
81 let cpu = current_cpu();
82 RCU_NESTING[cpu].fetch_add(1, Ordering::Relaxed);
83}
84
85/// Exit an RCU read-side critical section.
86#[inline]
87pub fn rcu_read_unlock() {
88 let cpu = current_cpu();
89 let prev = RCU_NESTING[cpu].fetch_sub(1, Ordering::Relaxed);
90 debug_assert!(prev > 0, "rcu_read_unlock without matching rcu_read_lock");
91}
92
93/// Check whether the current CPU is inside an RCU read-side critical section.
94pub fn rcu_is_reading() -> bool {
95 let cpu = current_cpu();
96 RCU_NESTING[cpu].load(Ordering::Relaxed) > 0
97}
98
99// ---------------------------------------------------------------------------
100// Writer API
101// ---------------------------------------------------------------------------
102
103/// Wait for all pre-existing RCU read-side critical sections to complete.
104///
105/// After this function returns, it is safe to free memory that was visible
106/// to readers before the call. This is the synchronous grace period wait.
107///
108/// On a single-CPU system, a single quiescent state check is sufficient.
109/// On SMP, we spin until all CPUs have reported a quiescent state.
110pub fn synchronize_rcu() {
111 let target_gp = RCU_GP_COUNTER.fetch_add(1, Ordering::SeqCst) + 1;
112
113 // Wait for all CPUs to observe the new grace period.
114 // On a single-CPU system, if we're not in an RCU read section, we're
115 // already past a quiescent point.
116 loop {
117 let mut all_quiescent = true;
118 for cpu in 0..RCU_MAX_CPUS {
119 // A CPU is quiescent if it has no active read-side sections
120 // AND has observed the current grace period.
121 let nesting = RCU_NESTING[cpu].load(Ordering::Acquire);
122 let cpu_gp = RCU_CPU_GP[cpu].load(Ordering::Acquire);
123
124 if nesting > 0 || cpu_gp < target_gp {
125 // CPU 0 is always online; others may not exist.
126 // If a CPU doesn't exist, it's vacuously quiescent.
127 if cpu == 0 || crate::sched::smp::per_cpu(cpu as u8).is_some() {
128 all_quiescent = false;
129 break;
130 }
131 }
132 }
133
134 if all_quiescent {
135 break;
136 }
137
138 // Yield to other tasks while waiting.
139 core::hint::spin_loop();
140 }
141
142 // Process any deferred callbacks that are now ready.
143 process_callbacks(target_gp);
144}
145
146/// Register a deferred callback to be called after the next grace period.
147///
148/// The callback will be invoked after all CPUs have passed through a
149/// quiescent state following this call. The callback must be `Send`
150/// since it may execute on a different CPU.
151pub fn call_rcu<F: FnOnce() + Send + 'static>(func: F) {
152 let target_gp = RCU_GP_COUNTER.load(Ordering::Relaxed) + 1;
153 let mut callbacks = RCU_CALLBACKS.lock();
154 callbacks.push(RcuCallback {
155 target_gp,
156 func: Box::new(func),
157 });
158}
159
160// ---------------------------------------------------------------------------
161// Quiescent State Reporting
162// ---------------------------------------------------------------------------
163
164/// Report that the current CPU has passed through a quiescent state.
165///
166/// Called from the scheduler during context switch, timer tick, or
167/// explicit idle. A CPU in a quiescent state is not holding any RCU
168/// read-side references from before the current grace period.
169pub fn rcu_quiescent() {
170 let cpu = current_cpu();
171 let nesting = RCU_NESTING[cpu].load(Ordering::Relaxed);
172 if nesting == 0 {
173 let current_gp = RCU_GP_COUNTER.load(Ordering::Acquire);
174 RCU_CPU_GP[cpu].store(current_gp, Ordering::Release);
175 }
176}
177
178/// Process deferred callbacks whose grace periods have completed.
179fn process_callbacks(completed_gp: u64) {
180 let mut callbacks = RCU_CALLBACKS.lock();
181 let mut i = 0;
182 while i < callbacks.len() {
183 if callbacks[i].target_gp <= completed_gp {
184 let cb = callbacks.swap_remove(i);
185 // Release lock before executing callback to avoid deadlock.
186 drop(callbacks);
187 (cb.func)();
188 callbacks = RCU_CALLBACKS.lock();
189 // Don't increment i since swap_remove moved the last element here.
190 } else {
191 i += 1;
192 }
193 }
194}
195
196// ---------------------------------------------------------------------------
197// Helpers
198// ---------------------------------------------------------------------------
199
200/// Get the current CPU ID (0 on single-CPU systems).
201fn current_cpu() -> usize {
202 crate::sched::smp::current_cpu_id() as usize
203}