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 ® 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}