Skip to main content

ukernel_sys/
submit_ring.rs

1//! Submission ring for batched kernel operations.
2//!
3//! Userspace queues [`SubmitEntry`] operations into the ring and flushes
4//! them with a single `SYS_SUBMIT` syscall. The kernel processes all entries,
5//! capability-checks each one, and writes [`CompleteEntry`] results.
6//!
7//! One ring transition for N operations. Security enforcement is per-operation
8//! (every capability is checked), but the transition cost is amortized.
9//!
10//! # Memory layout
11//!
12//! The ring is a shared page mapped into both kernel and domain address space.
13//! Synchronization uses atomic indices with acquire/release ordering.
14
15/// Number of entries in the ring. Sized to fit in a page with completions.
16pub const RING_SIZE: usize = 64;
17
18/// Fixed virtual address for the ring in domain address space.
19pub const RING_VADDR: u64 = 0x7FFF_0000_0000;
20
21/// Submission entry — a single kernel operation request.
22///
23/// Written by userspace, read by kernel. Each entry is cache-line aligned
24/// to prevent false sharing.
25#[repr(C, align(64))]
26#[derive(Clone, Copy)]
27pub struct SubmitEntry {
28    /// Operation code ([`KernelOp`](crate::KernelOp) discriminant).
29    pub opcode: u16,
30    /// Per-operation flags (see [`submit_flags`]).
31    pub flags: u16,
32    /// Reserved.
33    pub _reserved: u32,
34    /// Operation-specific arguments. Meaning depends on opcode.
35    pub args: [u64; 5],
36    /// Opaque user token, returned in the corresponding [`CompleteEntry`].
37    pub user_data: u64,
38}
39
40impl Default for SubmitEntry {
41    fn default() -> Self {
42        Self {
43            opcode: 0,
44            flags: 0,
45            _reserved: 0,
46            args: [0; 5],
47            user_data: 0,
48        }
49    }
50}
51
52impl SubmitEntry {
53    /// Create a submission entry.
54    #[inline]
55    pub const fn new(opcode: u16, args: [u64; 5], user_data: u64) -> Self {
56        Self {
57            opcode,
58            flags: 0,
59            _reserved: 0,
60            args,
61            user_data,
62        }
63    }
64}
65
66/// Submission entry flags.
67pub mod submit_flags {
68    /// Request completion notification even on success.
69    pub const NEED_COMPLETION: u16 = 1 << 0;
70    /// Barrier — flush all pending operations before this one.
71    pub const BARRIER: u16 = 1 << 1;
72    /// Hint that more operations follow.
73    pub const MORE_COMING: u16 = 1 << 2;
74}
75
76/// Completion entry — kernel's response to a submission.
77///
78/// Written by kernel, read by userspace. The `user_data` field matches
79/// the value from the corresponding [`SubmitEntry`].
80#[repr(C, align(16))]
81#[derive(Clone, Copy, Default)]
82pub struct CompleteEntry {
83    /// Result value. Positive/zero = success, negative = -errno.
84    pub result: i64,
85    /// User token from the corresponding [`SubmitEntry`].
86    pub user_data: u64,
87    /// Completion flags (see [`complete_flags`]).
88    pub flags: u16,
89    /// Reserved.
90    pub _reserved: [u8; 6],
91}
92
93impl CompleteEntry {
94    /// Create a completion entry.
95    #[inline]
96    pub const fn new(result: i64, user_data: u64) -> Self {
97        Self {
98            result,
99            user_data,
100            flags: 0,
101            _reserved: [0; 6],
102        }
103    }
104
105    /// Whether the operation succeeded.
106    #[inline]
107    pub fn is_ok(&self) -> bool {
108        self.result >= 0
109    }
110
111    /// Error code if the operation failed.
112    #[inline]
113    pub fn error(&self) -> Option<i32> {
114        if self.result < 0 {
115            Some((-self.result) as i32)
116        } else {
117            None
118        }
119    }
120}
121
122/// Completion flags (kernel → domain notifications).
123pub mod complete_flags {
124    /// A signal is pending — check signal state after processing.
125    pub const SIGNAL_PENDING: u16 = 1 << 0;
126    /// A timer expired — check timer state.
127    pub const TIMER_EXPIRED: u16 = 1 << 1;
128}
129
130/// Shared-memory ring buffer mapped into both kernel and domain.
131///
132/// This struct defines the memory layout. The actual ring is initialized
133/// by the kernel and mapped at [`RING_VADDR`] in the domain's address space.
134///
135/// Synchronization protocol:
136/// 1. Domain writes entries, advances `submit_head` (Release)
137/// 2. Domain calls `SYS_SUBMIT`
138/// 3. Kernel reads entries from `submit_tail` to `submit_head` (Acquire)
139/// 4. Kernel writes completions, advances `complete_head` (Release)
140/// 5. Domain reads completions from `complete_tail` to `complete_head` (Acquire)
141#[repr(C)]
142pub struct SubmitRing {
143    /// Next slot for userspace to write (producer).
144    pub submit_head: u32,
145    /// Next slot for kernel to process (consumer).
146    pub submit_tail: u32,
147    /// Next completion slot for kernel to write (producer).
148    pub complete_head: u32,
149    /// Next completion slot for userspace to read (consumer).
150    pub complete_tail: u32,
151    /// Padding to cache-line boundary.
152    pub _pad: [u8; 48],
153    // Followed by:
154    //   [SubmitEntry; RING_SIZE]    — submission entries
155    //   [CompleteEntry; RING_SIZE]  — completion entries
156}