kevy_uring/prep.rs
1//! SQE preparation helpers — `prep_*` queue one io_uring submission entry
2//! into the SQ ring. Split out of [`crate::ring`] so that file stays under
3//! the 500-LOC house rule. Each helper returns `false` if the SQ is full;
4//! the caller is expected to submit and retry.
5
6use core::ptr;
7
8use crate::ffi::{
9 IORING_OP_ACCEPT, IORING_OP_NOP, IORING_OP_READ, IORING_OP_RECV, IORING_OP_TIMEOUT,
10 IORING_OP_WRITE, IORING_RECV_MULTISHOT, IOSQE_BUFFER_SELECT, IOSQE_FIXED_FILE, SOCK_CLOEXEC,
11 SOCK_NONBLOCK,
12};
13use crate::layout::{IoUringSqe, KernelTimespec};
14use crate::ring::IoUring;
15
16impl IoUring {
17 /// Queue a `read(fd)` of `len` bytes into `buf`, tagged with `user_data`.
18 /// Returns `false` if the SQ is full.
19 ///
20 /// # Safety
21 /// `buf` must point to `len` writable bytes and stay valid until the matching
22 /// completion is reaped.
23 pub unsafe fn prep_read(&mut self, fd: i32, buf: *mut u8, len: u32, user_data: u64) -> bool {
24 let Some(idx) = self.reserve() else {
25 return false;
26 };
27 // SAFETY: `idx` is a freshly reserved, in-bounds SQE slot we own alone.
28 unsafe {
29 ptr::write(
30 self.sqes_ptr().add(idx),
31 IoUringSqe::new(IORING_OP_READ, fd, buf as u64, len, user_data),
32 );
33 }
34 true
35 }
36
37 /// Queue a `write(fd)` of `len` bytes from `buf`, tagged with `user_data`.
38 /// Returns `false` if the SQ is full.
39 ///
40 /// # Safety
41 /// `buf` must point to `len` readable bytes and stay valid until the matching
42 /// completion is reaped.
43 pub unsafe fn prep_write(&mut self, fd: i32, buf: *const u8, len: u32, user_data: u64) -> bool {
44 let Some(idx) = self.reserve() else {
45 return false;
46 };
47 // SAFETY: `idx` is a freshly reserved, in-bounds SQE slot we own alone.
48 unsafe {
49 ptr::write(
50 self.sqes_ptr().add(idx),
51 IoUringSqe::new(IORING_OP_WRITE, fd, buf as u64, len, user_data),
52 );
53 }
54 true
55 }
56
57 /// Queue a **multishot** `recv(fd)` that draws its destination buffer from
58 /// the provided-buffer group `bgid` (see [`IoUring::register_buf_ring`]): one
59 /// SQE re-fires a completion per arrival, the kernel picking + reporting a
60 /// buffer id each time, until it terminates (error / `ENOBUFS`, signalled by
61 /// [`crate::Completion::has_more`] returning `false`). No per-recv SQE, no read
62 /// buffer to keep alive. Returns `false` if the SQ is full.
63 pub fn prep_recv_multishot(&mut self, fd: i32, bgid: u16, user_data: u64) -> bool {
64 let Some(idx) = self.reserve() else {
65 return false;
66 };
67 // SAFETY: `idx` is a freshly reserved, in-bounds SQE slot we own alone.
68 unsafe {
69 let sqe = self.sqes_ptr().add(idx);
70 // addr/len 0: the buffer comes from the group, not from us.
71 ptr::write(sqe, IoUringSqe::new(IORING_OP_RECV, fd, 0, 0, user_data));
72 (*sqe).ioprio = IORING_RECV_MULTISHOT;
73 (*sqe).flags = IOSQE_BUFFER_SELECT;
74 // `buf_index` aliases `buf_group` in the kernel ABI.
75 (*sqe).buf_index = bgid;
76 }
77 true
78 }
79
80 /// Same as [`Self::prep_write`] but addresses the destination by
81 /// registered-files **slot index** instead of raw fd. Sets
82 /// `IOSQE_FIXED_FILE`; the kernel skips its per-op `fget`/`fput`. Caller
83 /// must have populated `slot` via
84 /// [`crate::IoUring::update_file_slot`].
85 ///
86 /// # Safety
87 /// Same as `prep_write`.
88 pub unsafe fn prep_write_fixed(
89 &mut self,
90 slot: u32,
91 buf: *const u8,
92 len: u32,
93 user_data: u64,
94 ) -> bool {
95 let Some(idx) = self.reserve() else {
96 return false;
97 };
98 // SAFETY: `idx` is a freshly reserved, in-bounds SQE slot we own alone.
99 unsafe {
100 let sqe = self.sqes_ptr().add(idx);
101 ptr::write(
102 sqe,
103 IoUringSqe::new(IORING_OP_WRITE, slot as i32, buf as u64, len, user_data),
104 );
105 (*sqe).flags = IOSQE_FIXED_FILE;
106 }
107 true
108 }
109
110 /// Same as [`Self::prep_recv_multishot`] but addresses the source by
111 /// registered-files **slot index** instead of raw fd. Sets
112 /// `IOSQE_FIXED_FILE | IOSQE_BUFFER_SELECT`; the kernel skips its
113 /// per-op `fget`/`fput`. Caller must have populated `slot` via
114 /// [`crate::IoUring::update_file_slot`].
115 pub fn prep_recv_multishot_fixed(
116 &mut self,
117 slot: u32,
118 bgid: u16,
119 user_data: u64,
120 ) -> bool {
121 let Some(idx) = self.reserve() else {
122 return false;
123 };
124 // SAFETY: `idx` is a freshly reserved, in-bounds SQE slot we own alone.
125 unsafe {
126 let sqe = self.sqes_ptr().add(idx);
127 ptr::write(
128 sqe,
129 IoUringSqe::new(IORING_OP_RECV, slot as i32, 0, 0, user_data),
130 );
131 (*sqe).ioprio = IORING_RECV_MULTISHOT;
132 (*sqe).flags = IOSQE_BUFFER_SELECT | IOSQE_FIXED_FILE;
133 (*sqe).buf_index = bgid;
134 }
135 true
136 }
137
138 /// Queue an `accept` on `listen_fd`; the accepted fd arrives as the
139 /// completion's `res` (already `O_NONBLOCK | O_CLOEXEC`). Returns `false` if
140 /// the SQ is full.
141 pub fn prep_accept(&mut self, listen_fd: i32, user_data: u64) -> bool {
142 let Some(idx) = self.reserve() else {
143 return false;
144 };
145 // SAFETY: `idx` is a freshly reserved, in-bounds SQE slot we own alone.
146 unsafe {
147 let sqe = self.sqes_ptr().add(idx);
148 ptr::write(
149 sqe,
150 IoUringSqe::new(IORING_OP_ACCEPT, listen_fd, 0, 0, user_data),
151 );
152 (*sqe).rw_flags = SOCK_NONBLOCK | SOCK_CLOEXEC;
153 }
154 true
155 }
156
157 /// Queue a relative timeout: the completion (res = `-ETIME`) arrives once
158 /// `ts` elapses, or earlier with res = 0 / `-ECANCELED` if the ring shuts
159 /// down. Bounds a blocking [`IoUring::submit_and_wait`] the way a poller's
160 /// wait-timeout would. Returns `false` if the SQ is full.
161 ///
162 /// # Safety
163 /// `ts` must stay valid (not moved or dropped) until the matching
164 /// completion is reaped — the kernel reads it asynchronously.
165 pub unsafe fn prep_timeout(&mut self, ts: *const KernelTimespec, user_data: u64) -> bool {
166 let Some(idx) = self.reserve() else {
167 return false;
168 };
169 // SAFETY: `idx` is a freshly reserved, in-bounds SQE slot we own alone.
170 unsafe {
171 let sqe = self.sqes_ptr().add(idx);
172 // addr = timespec ptr, len = 1 (one timespec), off = 0 (pure
173 // timeout — no completion-count trigger), rw_flags = 0 (relative).
174 ptr::write(
175 sqe,
176 IoUringSqe::new(IORING_OP_TIMEOUT, -1, ts as u64, 1, user_data),
177 );
178 }
179 true
180 }
181
182 /// Queue a no-op tagged with `user_data` (used to prove the round-trip).
183 /// Returns `false` if the SQ is full.
184 pub fn prep_nop(&mut self, user_data: u64) -> bool {
185 let Some(idx) = self.reserve() else {
186 return false;
187 };
188 // SAFETY: `idx` is a freshly reserved, in-bounds SQE slot we own alone.
189 unsafe {
190 ptr::write(
191 self.sqes_ptr().add(idx),
192 IoUringSqe::new(IORING_OP_NOP, -1, 0, 0, user_data),
193 );
194 }
195 true
196 }
197}