Skip to main content

kevy_uring/
register.rs

1//! `io_uring_register` helpers — provided-buffer ring, sparse registered-files
2//! table, file-slot updates, and ring-fd self-register. Split out of
3//! [`crate::ring`] so that file stays under the 500-LOC house rule. Each
4//! method is on the same `impl IoUring` and accesses `ring_fd` /
5//! `enter_ring` (pub(crate) fields) directly.
6
7use core::ffi::c_long;
8
9use crate::ffi::{
10    self, IORING_ENTER_REGISTERED_RING, IORING_REGISTER_FILES2, IORING_REGISTER_FILES_UPDATE,
11    IORING_REGISTER_RING_FDS, SYS_IO_URING_REGISTER,
12};
13use crate::layout::{
14    IORING_RSRC_REGISTER_SPARSE, IoUringFilesUpdate, IoUringRsrcRegister, IoUringRsrcUpdate,
15};
16use crate::pbr::ProvidedBufRing;
17use crate::ring::IoUring;
18use std::io;
19
20impl IoUring {
21    /// Register a **provided-buffer ring** of `entries` (power of two) buffers of
22    /// `buf_size` bytes each under group id `bgid`, for multishot
23    /// `prep_recv_multishot`. The kernel draws a buffer per arrival and
24    /// reports its id; the application recycles it via
25    /// [`ProvidedBufRing::recycle`]. The registration is auto-released when
26    /// the ring fd closes; the returned handle also unregisters + unmaps on
27    /// drop.
28    pub fn register_buf_ring(
29        &self,
30        entries: u16,
31        buf_size: u32,
32        bgid: u16,
33    ) -> io::Result<ProvidedBufRing> {
34        ProvidedBufRing::new(self.ring_fd, entries, buf_size, bgid)
35    }
36
37    /// Self-register `self.ring_fd` into the **current thread's** io_uring
38    /// registered-rings table (`IORING_REGISTER_RING_FDS`, Linux 5.18+) and
39    /// flip the `enter_ring` mode on success. Future `submit_and_wait`
40    /// syscalls then pass the table index + `IORING_ENTER_REGISTERED_RING`
41    /// instead of the raw fd, skipping the kernel's per-syscall
42    /// `fget`/`fput` on the ring. Failure (older kernel / disabled feature)
43    /// silently leaves `enter_ring = None`; behavior unchanged.
44    ///
45    /// **Thread affinity caveat**: registered-rings entries are per
46    /// **thread**, not per process. The ring must be moved to the thread
47    /// that will call `submit_and_wait` before this registration; in kevy
48    /// that already holds — the shard's `run_uring` owns the ring and
49    /// stays on one OS thread for the reactor's life.
50    pub(crate) fn try_register_ring_fd(&mut self) {
51        let mut upd = IoUringRsrcUpdate {
52            offset: u32::MAX,
53            resv: 0,
54            data: self.ring_fd as u32 as u64,
55        };
56        // SAFETY: `upd` lives through the syscall; ring_fd is valid.
57        let ret = unsafe {
58            ffi::syscall(
59                SYS_IO_URING_REGISTER,
60                self.ring_fd as c_long,
61                IORING_REGISTER_RING_FDS as c_long,
62                &mut upd as *mut _ as c_long,
63                1 as c_long,
64            )
65        };
66        if ret == 1 {
67            self.enter_ring = Some((upd.offset, IORING_ENTER_REGISTERED_RING));
68        }
69    }
70
71    /// Register a **sparse registered-files table** of `nr` slots — empty
72    /// initially; slots are filled later via [`Self::update_file_slot`] and
73    /// referenced from SQEs by setting `IOSQE_FIXED_FILE` and putting the
74    /// slot index in the SQE's `fd` field. The kernel skips the per-op
75    /// `fget`/`fput` fd-table lookup for any SQE that uses a fixed file.
76    ///
77    /// Requires Linux 5.13+ for the rsrc-struct API. The table is
78    /// auto-released when the ring fd closes. The kernel rejects
79    /// `nr > RLIMIT_NOFILE`; callers that need >1024 slots must bump
80    /// ulimit themselves.
81    ///
82    /// **Call shape**: `IORING_REGISTER_FILES2` (#13) with
83    /// `IoUringRsrcRegister { nr, flags: IORING_RSRC_REGISTER_SPARSE, .. }`
84    /// + `nr_args = sizeof::<IoUringRsrcRegister>() = 32`. There is no
85    /// stand-alone "FILES_SPARSE" opcode in mainline.
86    pub fn register_files_sparse(&self, nr: u32) -> io::Result<()> {
87        let reg = IoUringRsrcRegister {
88            nr,
89            flags: IORING_RSRC_REGISTER_SPARSE,
90            ..Default::default()
91        };
92        // SAFETY: `reg` lives through the syscall; ring_fd is valid.
93        let ret = unsafe {
94            ffi::syscall(
95                SYS_IO_URING_REGISTER,
96                self.ring_fd as c_long,
97                IORING_REGISTER_FILES2 as c_long,
98                &reg as *const _ as c_long,
99                core::mem::size_of::<IoUringRsrcRegister>() as c_long,
100            )
101        };
102        if ret < 0 {
103            return Err(io::Error::last_os_error());
104        }
105        Ok(())
106    }
107
108    /// Place `fd` into registered-files slot `index`, or unmap the slot if
109    /// `fd < 0`. After a successful call SQEs referencing `IOSQE_FIXED_FILE`
110    /// + `fd = index` will skip the fd-table lookup. Cheaper than the full
111    /// per-op fget/fput once amortised across many ops per conn.
112    pub fn update_file_slot(&self, index: u32, fd: i32) -> io::Result<()> {
113        let fd_val: i32 = fd;
114        let upd = IoUringFilesUpdate {
115            offset: index,
116            resv: 0,
117            fds: (&fd_val as *const i32) as u64,
118        };
119        // SAFETY: `upd` and `fd_val` both live through the syscall; ring_fd
120        // came from io_uring_setup.
121        let ret = unsafe {
122            ffi::syscall(
123                SYS_IO_URING_REGISTER,
124                self.ring_fd as c_long,
125                IORING_REGISTER_FILES_UPDATE as c_long,
126                &upd as *const _ as c_long,
127                1 as c_long,
128            )
129        };
130        if ret < 0 {
131            return Err(io::Error::last_os_error());
132        }
133        Ok(())
134    }
135}