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

veridian_kernel/ipc/
registry.rs

1//! Global IPC registry for managing channels and endpoints
2//!
3//! This module provides O(1) lookup for IPC endpoints and channels,
4//! managing the global namespace for IPC operations.
5
6#![allow(dead_code, clippy::explicit_auto_deref)]
7
8#[cfg(feature = "alloc")]
9extern crate alloc;
10
11#[cfg(feature = "alloc")]
12use alloc::collections::BTreeMap;
13use core::sync::atomic::{AtomicU64, Ordering};
14
15use spin::Mutex;
16
17use super::{
18    capability::{EndpointId, IpcCapability, IpcPermissions, ProcessId},
19    channel::{Channel, Endpoint},
20    error::{IpcError, Result},
21    Message,
22};
23
24/// Global IPC registry using OnceLock for safe initialization.
25static IPC_REGISTRY: crate::sync::once_lock::OnceLock<Mutex<IpcRegistry>> =
26    crate::sync::once_lock::OnceLock::new();
27
28/// Initialize the IPC registry
29pub fn init() {
30    #[allow(unused_imports)]
31    use crate::println;
32
33    println!("[IPC-REG] Initializing IPC registry...");
34    let registry = IpcRegistry::new();
35    let registry_mutex = Mutex::new(registry);
36
37    match IPC_REGISTRY.set(registry_mutex) {
38        Ok(()) => println!("[IPC-REG] Registry initialized successfully"),
39        Err(_) => println!("[IPC-REG] Registry already initialized, skipping..."),
40    }
41}
42
43/// IPC registry for managing all endpoints and channels
44pub struct IpcRegistry {
45    /// Endpoint lookup table
46    #[cfg(feature = "alloc")]
47    endpoints: BTreeMap<EndpointId, Endpoint>,
48    /// Channel lookup table
49    #[cfg(feature = "alloc")]
50    channels: BTreeMap<EndpointId, Channel>,
51    /// Process to endpoints mapping
52    #[cfg(feature = "alloc")]
53    process_endpoints: BTreeMap<ProcessId, BTreeMap<EndpointId, IpcCapability>>,
54    /// Next endpoint ID
55    next_endpoint_id: AtomicU64,
56    /// Statistics
57    stats: RegistryStats,
58}
59
60/// Registry statistics
61struct RegistryStats {
62    endpoints_created: AtomicU64,
63    endpoints_destroyed: AtomicU64,
64    channels_created: AtomicU64,
65    channels_destroyed: AtomicU64,
66    capability_lookups: AtomicU64,
67    capability_hits: AtomicU64,
68}
69
70impl IpcRegistry {
71    /// Create a new IPC registry
72    fn new() -> Self {
73        Self {
74            #[cfg(feature = "alloc")]
75            endpoints: BTreeMap::new(),
76            #[cfg(feature = "alloc")]
77            channels: BTreeMap::new(),
78            #[cfg(feature = "alloc")]
79            process_endpoints: BTreeMap::new(),
80            next_endpoint_id: AtomicU64::new(1),
81            stats: RegistryStats {
82                endpoints_created: AtomicU64::new(0),
83                endpoints_destroyed: AtomicU64::new(0),
84                channels_created: AtomicU64::new(0),
85                channels_destroyed: AtomicU64::new(0),
86                capability_lookups: AtomicU64::new(0),
87                capability_hits: AtomicU64::new(0),
88            },
89        }
90    }
91
92    /// Create a new endpoint
93    #[cfg(feature = "alloc")]
94    pub fn create_endpoint(&mut self, owner: ProcessId) -> Result<(EndpointId, IpcCapability)> {
95        let endpoint_id = self.next_endpoint_id.fetch_add(1, Ordering::Relaxed);
96        let endpoint = Endpoint::new(owner);
97
98        // Create capability for the endpoint
99        let capability = IpcCapability::new(endpoint_id, IpcPermissions::all());
100
101        // Insert into tables
102        self.endpoints.insert(endpoint_id, endpoint);
103
104        // Add to process's endpoint list
105        self.process_endpoints
106            .entry(owner)
107            .or_default()
108            .insert(endpoint_id, capability);
109
110        self.stats.endpoints_created.fetch_add(1, Ordering::Relaxed);
111
112        Ok((endpoint_id, capability))
113    }
114
115    #[cfg(not(feature = "alloc"))]
116    pub fn create_endpoint(&mut self, _owner: ProcessId) -> Result<(EndpointId, IpcCapability)> {
117        Err(IpcError::OutOfMemory)
118    }
119
120    /// Create a new channel
121    #[cfg(feature = "alloc")]
122    pub fn create_channel(
123        &mut self,
124        owner: ProcessId,
125        capacity: usize,
126    ) -> Result<(EndpointId, EndpointId, IpcCapability, IpcCapability)> {
127        let channel = Channel::new(owner, capacity);
128        let send_id = channel.send_id();
129        let recv_id = channel.receive_id();
130
131        // Create capabilities
132        let send_cap = IpcCapability::new(send_id, IpcPermissions::send_only());
133        let recv_cap = IpcCapability::new(recv_id, IpcPermissions::receive_only());
134
135        // Insert into registry
136        self.channels.insert(send_id, channel);
137
138        // Add to process's endpoint list
139        let process_eps = self.process_endpoints.entry(owner).or_default();
140        process_eps.insert(send_id, send_cap);
141        process_eps.insert(recv_id, recv_cap);
142
143        self.stats.channels_created.fetch_add(1, Ordering::Relaxed);
144
145        Ok((send_id, recv_id, send_cap, recv_cap))
146    }
147
148    #[cfg(not(feature = "alloc"))]
149    pub fn create_channel(
150        &mut self,
151        _owner: ProcessId,
152        _capacity: usize,
153    ) -> Result<(EndpointId, EndpointId, IpcCapability, IpcCapability)> {
154        Err(IpcError::OutOfMemory)
155    }
156
157    /// Lookup an endpoint by ID
158    #[cfg(feature = "alloc")]
159    pub fn lookup_endpoint(&self, id: EndpointId) -> Option<&Endpoint> {
160        self.stats
161            .capability_lookups
162            .fetch_add(1, Ordering::Relaxed);
163
164        if let Some(endpoint) = self.endpoints.get(&id) {
165            self.stats.capability_hits.fetch_add(1, Ordering::Relaxed);
166            Some(endpoint)
167        } else {
168            None
169        }
170    }
171
172    #[cfg(not(feature = "alloc"))]
173    pub fn lookup_endpoint(&self, _id: EndpointId) -> Option<&Endpoint> {
174        None
175    }
176
177    /// Validate a capability (Level 1: registry, Level 2: process capability
178    /// space)
179    #[cfg(feature = "alloc")]
180    pub fn validate_capability(
181        &self,
182        process: ProcessId,
183        capability: &IpcCapability,
184    ) -> Result<()> {
185        self.stats
186            .capability_lookups
187            .fetch_add(1, Ordering::Relaxed);
188
189        // Level 1: Check if process owns this capability in the registry
190        if let Some(process_caps) = self.process_endpoints.get(&process) {
191            if let Some(stored_cap) = process_caps.get(&capability.target()) {
192                // Verify generation matches
193                if stored_cap.generation() == capability.generation() {
194                    self.stats.capability_hits.fetch_add(1, Ordering::Relaxed);
195
196                    // Level 2: Cross-validate against process's capability space
197                    if let Some(real_process) = crate::process::table::get_process(process) {
198                        let cap_space = real_process.capability_space.lock();
199                        let cap_token = crate::cap::CapabilityToken::from_u64(capability.id());
200                        if cap_space.lookup(cap_token).is_some() {
201                            return Ok(());
202                        }
203                        // Cap space doesn't have it - fall through to error
204                    } else {
205                        // Process table not available (early boot) - trust Level 1
206                        return Ok(());
207                    }
208                }
209            }
210        }
211
212        Err(IpcError::InvalidCapability)
213    }
214
215    #[cfg(not(feature = "alloc"))]
216    pub fn validate_capability(
217        &self,
218        _process: ProcessId,
219        _capability: &IpcCapability,
220    ) -> Result<()> {
221        Err(IpcError::InvalidCapability)
222    }
223
224    /// Remove an endpoint
225    #[cfg(feature = "alloc")]
226    pub fn remove_endpoint(&mut self, id: EndpointId, owner: ProcessId) -> Result<()> {
227        // Verify ownership
228        if let Some(endpoint) = self.endpoints.get(&id) {
229            if endpoint.owner != owner {
230                return Err(IpcError::PermissionDenied);
231            }
232        } else {
233            return Err(IpcError::EndpointNotFound);
234        }
235
236        // Remove from registry
237        self.endpoints.remove(&id);
238
239        // Remove from process's endpoint list
240        if let Some(process_eps) = self.process_endpoints.get_mut(&owner) {
241            process_eps.remove(&id);
242        }
243
244        self.stats
245            .endpoints_destroyed
246            .fetch_add(1, Ordering::Relaxed);
247
248        Ok(())
249    }
250
251    #[cfg(not(feature = "alloc"))]
252    pub fn remove_endpoint(&mut self, _id: EndpointId, _owner: ProcessId) -> Result<()> {
253        Err(IpcError::EndpointNotFound)
254    }
255
256    /// Get registry statistics
257    pub fn get_stats(&self) -> RegistryStatsSummary {
258        RegistryStatsSummary {
259            endpoints_created: self.stats.endpoints_created.load(Ordering::Relaxed),
260            endpoints_destroyed: self.stats.endpoints_destroyed.load(Ordering::Relaxed),
261            channels_created: self.stats.channels_created.load(Ordering::Relaxed),
262            channels_destroyed: self.stats.channels_destroyed.load(Ordering::Relaxed),
263            capability_lookups: self.stats.capability_lookups.load(Ordering::Relaxed),
264            capability_hits: self.stats.capability_hits.load(Ordering::Relaxed),
265            cache_hit_rate: {
266                let lookups = self.stats.capability_lookups.load(Ordering::Relaxed);
267                let hits = self.stats.capability_hits.load(Ordering::Relaxed);
268                if lookups > 0 {
269                    (hits * 100) / lookups
270                } else {
271                    0
272                }
273            },
274        }
275    }
276}
277
278/// Registry statistics summary
279pub struct RegistryStatsSummary {
280    pub endpoints_created: u64,
281    pub endpoints_destroyed: u64,
282    pub channels_created: u64,
283    pub channels_destroyed: u64,
284    pub capability_lookups: u64,
285    pub capability_hits: u64,
286    pub cache_hit_rate: u64,
287}
288
289/// Helper functions for cross-architecture registry access
290/// Get a mutable reference to the global registry
291fn with_registry_mut<T, F>(f: F) -> Result<T>
292where
293    F: FnOnce(&mut IpcRegistry) -> Result<T>,
294{
295    let registry_mutex = IPC_REGISTRY.get().ok_or(IpcError::NotInitialized)?;
296    let mut registry_guard = registry_mutex.lock();
297    f(&mut *registry_guard)
298}
299
300/// Get an immutable reference to the global registry
301fn with_registry<T, F>(f: F) -> Result<T>
302where
303    F: FnOnce(&IpcRegistry) -> Result<T>,
304{
305    let registry_mutex = IPC_REGISTRY.get().ok_or(IpcError::NotInitialized)?;
306    let registry_guard = registry_mutex.lock();
307    f(&*registry_guard)
308}
309
310/// Global registry access functions
311/// Create an endpoint through the global registry
312pub fn create_endpoint(owner: ProcessId) -> Result<(EndpointId, IpcCapability)> {
313    with_registry_mut(|registry| registry.create_endpoint(owner))
314}
315
316/// Create a channel through the global registry
317pub fn create_channel(
318    owner: ProcessId,
319    capacity: usize,
320) -> Result<(EndpointId, EndpointId, IpcCapability, IpcCapability)> {
321    with_registry_mut(|registry| registry.create_channel(owner, capacity))
322}
323
324/// Remove a channel from the global registry
325pub fn remove_channel(channel_id: u64) -> Result<()> {
326    with_registry_mut(|registry| {
327        // For now, treat channel_id as endpoint_id
328        // In a real implementation, we'd track channel IDs separately
329        let endpoint_id = channel_id;
330
331        // Remove the channel if it exists
332        #[cfg(feature = "alloc")]
333        {
334            if registry.channels.remove(&endpoint_id).is_some() {
335                registry
336                    .stats
337                    .channels_destroyed
338                    .fetch_add(1, Ordering::Relaxed);
339                Ok(())
340            } else {
341                // Try removing as endpoint
342                if registry.endpoints.remove(&endpoint_id).is_some() {
343                    registry
344                        .stats
345                        .endpoints_destroyed
346                        .fetch_add(1, Ordering::Relaxed);
347                    Ok(())
348                } else {
349                    Err(IpcError::EndpointNotFound)
350                }
351            }
352        }
353
354        #[cfg(not(feature = "alloc"))]
355        Err(IpcError::EndpointNotFound)
356    })
357}
358
359/// Remove all endpoints owned by a process (used during process cleanup)
360pub fn remove_process_endpoints(owner: ProcessId) -> Result<usize> {
361    with_registry_mut(|registry| {
362        #[cfg(feature = "alloc")]
363        {
364            // Get all endpoint IDs owned by this process
365            let endpoint_ids: alloc::vec::Vec<EndpointId> = registry
366                .process_endpoints
367                .get(&owner)
368                .map(|eps| eps.keys().cloned().collect())
369                .unwrap_or_default();
370
371            let mut removed_count = 0;
372
373            // Remove each endpoint from the registry
374            for endpoint_id in &endpoint_ids {
375                // Remove from endpoints table
376                if registry.endpoints.remove(endpoint_id).is_some() {
377                    registry
378                        .stats
379                        .endpoints_destroyed
380                        .fetch_add(1, Ordering::Relaxed);
381                    removed_count += 1;
382                }
383
384                // Remove from channels table (in case it's a channel endpoint)
385                if registry.channels.remove(endpoint_id).is_some() {
386                    registry
387                        .stats
388                        .channels_destroyed
389                        .fetch_add(1, Ordering::Relaxed);
390                }
391            }
392
393            // Remove the process's endpoint mapping entirely
394            registry.process_endpoints.remove(&owner);
395
396            Ok(removed_count)
397        }
398
399        #[cfg(not(feature = "alloc"))]
400        {
401            let _ = owner;
402            Ok(0)
403        }
404    })
405}
406
407/// Lookup an endpoint by ID
408pub fn lookup_endpoint(id: EndpointId) -> Result<&'static Endpoint> {
409    with_registry(|registry| {
410        // SAFETY: We cast the registry reference to a raw pointer and dereference
411        // it to obtain a &'static Endpoint. This is sound because:
412        // 1. The registry is heap-allocated via Box::leak and lives for the kernel's
413        //    lifetime, so the Endpoint data it contains also has 'static lifetime.
414        // 2. The Endpoint reference is derived from data owned by the registry's
415        //    BTreeMap, which persists as long as the entry is not removed.
416        // CAVEAT: If the endpoint is removed from the registry while a &'static
417        // reference is held, this becomes a dangling reference. Production code
418        // should use reference counting (Arc) to prevent use-after-free.
419        unsafe {
420            let registry_ptr = registry as *const IpcRegistry;
421            (*registry_ptr)
422                .lookup_endpoint(id)
423                .ok_or(IpcError::EndpointNotFound)
424                .map(|ep| &*(ep as *const Endpoint))
425        }
426    })
427}
428
429/// Try to receive a message from an endpoint without blocking.
430///
431/// Looks up the endpoint by ID in the registry and attempts to dequeue
432/// a message from its receive queue. Returns None if the endpoint doesn't
433/// exist or has no pending messages.
434pub fn try_receive_from_endpoint(endpoint_id: u64) -> Option<Message> {
435    with_registry(|registry| {
436        Ok(if let Some(ep) = registry.lookup_endpoint(endpoint_id) {
437            ep.try_receive().ok()
438        } else {
439            None
440        })
441    })
442    .ok()
443    .flatten()
444}
445
446/// Validate a capability
447pub fn validate_capability(process: ProcessId, capability: &IpcCapability) -> Result<()> {
448    with_registry(|registry| registry.validate_capability(process, capability))
449}
450
451/// Get registry statistics
452pub fn get_registry_stats() -> Result<RegistryStatsSummary> {
453    with_registry(|registry| Ok(registry.get_stats()))
454}
455
456#[cfg(all(test, not(target_os = "none")))]
457mod tests {
458    use super::*;
459    use crate::process::ProcessId;
460
461    #[test]
462    fn test_registry_init() {
463        init();
464        let result = create_endpoint(ProcessId(1));
465        assert!(result.is_ok());
466    }
467
468    #[test]
469    fn test_endpoint_creation() {
470        init();
471        let (id, cap) = create_endpoint(ProcessId(1)).unwrap();
472        assert_eq!(cap.target(), id);
473        assert!(cap.has_permission(super::super::capability::Permission::Send));
474    }
475
476    #[test]
477    fn test_channel_creation() {
478        init();
479        let (send_id, recv_id, send_cap, recv_cap) = create_channel(ProcessId(1), 100).unwrap();
480        assert_ne!(send_id, recv_id);
481        assert!(send_cap.has_permission(super::super::capability::Permission::Send));
482        assert!(!send_cap.has_permission(super::super::capability::Permission::Receive));
483        assert!(recv_cap.has_permission(super::super::capability::Permission::Receive));
484        assert!(!recv_cap.has_permission(super::super::capability::Permission::Send));
485    }
486}