⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

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}