io_uring/uring.rs
1// Copyright 2020 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This file makes several casts from u8 pointers into more-aligned pointer types.
6// We assume that the kernel will give us suitably aligned memory.
7#![allow(clippy::cast_ptr_alignment)]
8
9use std::collections::BTreeMap;
10use std::fs::File;
11use std::io;
12use std::os::unix::io::AsRawFd;
13use std::os::unix::io::FromRawFd;
14use std::os::unix::io::RawFd;
15use std::pin::Pin;
16use std::ptr::null;
17use std::sync::atomic::AtomicPtr;
18use std::sync::atomic::AtomicU32;
19use std::sync::atomic::Ordering;
20
21use base::AsRawDescriptor;
22use base::EventType;
23use base::IoBufMut;
24use base::MappedRegion;
25use base::MemoryMapping;
26use base::MemoryMappingBuilder;
27use base::Protection;
28use base::RawDescriptor;
29use libc::c_void;
30use remain::sorted;
31use sync::Mutex;
32use thiserror::Error as ThisError;
33
34use crate::bindings::*;
35use crate::syscalls::*;
36
37/// Holds per-operation, user specified data. The usage is up to the caller. The most common use is
38/// for callers to identify each request.
39pub type UserData = u64;
40
41#[sorted]
42#[derive(Debug, ThisError)]
43pub enum Error {
44 /// Failed to map the completion ring.
45 #[error("Failed to mmap completion ring {0}")]
46 MappingCompleteRing(base::MmapError),
47 /// Failed to map submit entries.
48 #[error("Failed to mmap submit entries {0}")]
49 MappingSubmitEntries(base::MmapError),
50 /// Failed to map the submit ring.
51 #[error("Failed to mmap submit ring {0}")]
52 MappingSubmitRing(base::MmapError),
53 /// Too many ops are already queued.
54 #[error("No space for more ring entries, try increasing the size passed to `new`")]
55 NoSpace,
56 /// The call to `io_uring_enter` failed with the given errno.
57 #[error("Failed to enter io uring: {0}")]
58 RingEnter(libc::c_int),
59 /// The call to `io_uring_register` failed with the given errno.
60 #[error("Failed to register operations for io uring: {0}")]
61 RingRegister(libc::c_int),
62 /// The call to `io_uring_setup` failed with the given errno.
63 #[error("Failed to setup io uring {0}")]
64 Setup(libc::c_int),
65}
66pub type Result<T> = std::result::Result<T, Error>;
67
68impl From<Error> for io::Error {
69 fn from(e: Error) -> Self {
70 use Error::*;
71 match e {
72 RingEnter(errno) => io::Error::from_raw_os_error(errno),
73 Setup(errno) => io::Error::from_raw_os_error(errno),
74 e => io::Error::other(e),
75 }
76 }
77}
78
79pub struct SubmitQueue {
80 submit_ring: SubmitQueueState,
81 submit_queue_entries: SubmitQueueEntries,
82 submitting: usize, // The number of ops in the process of being submitted.
83 pub added: usize, // The number of ops added since the last call to `io_uring_enter`.
84 num_sqes: usize, // The total number of sqes allocated in shared memory.
85}
86
87// Helper functions to set io_uring_sqe bindgen union members in a less verbose manner.
88impl io_uring_sqe {
89 pub fn set_addr(&mut self, val: u64) {
90 self.__bindgen_anon_2.addr = val;
91 }
92 pub fn set_off(&mut self, val: u64) {
93 self.__bindgen_anon_1.off = val;
94 }
95
96 pub fn set_buf_index(&mut self, val: u16) {
97 self.__bindgen_anon_4.buf_index = val;
98 }
99
100 pub fn set_rw_flags(&mut self, val: libc::c_int) {
101 self.__bindgen_anon_3.rw_flags = val;
102 }
103
104 pub fn set_poll_events(&mut self, val: u32) {
105 let val = if cfg!(target_endian = "big") {
106 // Swap words on big-endian platforms to match the original ABI where poll_events was 16
107 // bits wide.
108 val.rotate_left(16)
109 } else {
110 val
111 };
112 self.__bindgen_anon_3.poll32_events = val;
113 }
114}
115
116// Convert a file offset to the raw io_uring offset format.
117// Some => explicit offset
118// None => use current file position
119fn file_offset_to_raw_offset(offset: Option<u64>) -> u64 {
120 // File offsets are interpretted as off64_t inside io_uring, with -1 representing the current
121 // file position.
122 const USE_CURRENT_FILE_POS: libc::off64_t = -1;
123 offset.unwrap_or(USE_CURRENT_FILE_POS as u64)
124}
125
126impl SubmitQueue {
127 // Call `f` with the next available sqe or return an error if none are available.
128 // After `f` returns, the sqe is appended to the kernel's queue.
129 fn prep_next_sqe<F>(&mut self, mut f: F) -> Result<()>
130 where
131 F: FnMut(&mut io_uring_sqe),
132 {
133 if self.added == self.num_sqes {
134 return Err(Error::NoSpace);
135 }
136
137 // Find the next free submission entry in the submit ring and fill it with an iovec.
138 // The below raw pointer derefs are safe because the memory the pointers use lives as long
139 // as the mmap in self.
140 let tail = self.submit_ring.pointers.tail(Ordering::Relaxed);
141 let next_tail = tail.wrapping_add(1);
142 if next_tail == self.submit_ring.pointers.head(Ordering::Acquire) {
143 return Err(Error::NoSpace);
144 }
145 // `tail` is the next sqe to use.
146 let index = (tail & self.submit_ring.ring_mask) as usize;
147 let sqe = self.submit_queue_entries.get_mut(index).unwrap();
148
149 f(sqe);
150
151 // Tells the kernel to use the new index when processing the entry at that index.
152 self.submit_ring.set_array_entry(index, index as u32);
153 // Ensure the above writes to sqe are seen before the tail is updated.
154 // set_tail uses Release ordering when storing to the ring.
155 self.submit_ring.pointers.set_tail(next_tail);
156
157 self.added += 1;
158
159 Ok(())
160 }
161
162 // Returns the number of entries that have been added to this SubmitQueue since the last time
163 // `prepare_submit` was called.
164 fn prepare_submit(&mut self) -> usize {
165 let out = self.added - self.submitting;
166 self.submitting = self.added;
167
168 out
169 }
170
171 // Indicates that we failed to submit `count` entries to the kernel and that they should be
172 // retried.
173 fn fail_submit(&mut self, count: usize) {
174 debug_assert!(count <= self.submitting);
175 self.submitting -= count;
176 }
177
178 // Indicates that `count` entries have been submitted to the kernel and so the space may be
179 // reused for new entries.
180 fn complete_submit(&mut self, count: usize) {
181 debug_assert!(count <= self.submitting);
182 self.submitting -= count;
183 self.added -= count;
184 }
185}
186
187/// Enum to represent all io_uring operations
188#[repr(u32)]
189pub enum URingOperation {
190 Nop = io_uring_op_IORING_OP_NOP,
191 Readv = io_uring_op_IORING_OP_READV,
192 Writev = io_uring_op_IORING_OP_WRITEV,
193 Fsync = io_uring_op_IORING_OP_FSYNC,
194 ReadFixed = io_uring_op_IORING_OP_READ_FIXED,
195 WriteFixed = io_uring_op_IORING_OP_WRITE_FIXED,
196 PollAdd = io_uring_op_IORING_OP_POLL_ADD,
197 PollRemove = io_uring_op_IORING_OP_POLL_REMOVE,
198 SyncFileRange = io_uring_op_IORING_OP_SYNC_FILE_RANGE,
199 Sendmsg = io_uring_op_IORING_OP_SENDMSG,
200 Recvmsg = io_uring_op_IORING_OP_RECVMSG,
201 Timeout = io_uring_op_IORING_OP_TIMEOUT,
202 TimeoutRemove = io_uring_op_IORING_OP_TIMEOUT_REMOVE,
203 Accept = io_uring_op_IORING_OP_ACCEPT,
204 AsyncCancel = io_uring_op_IORING_OP_ASYNC_CANCEL,
205 LinkTimeout = io_uring_op_IORING_OP_LINK_TIMEOUT,
206 Connect = io_uring_op_IORING_OP_CONNECT,
207 Fallocate = io_uring_op_IORING_OP_FALLOCATE,
208 Openat = io_uring_op_IORING_OP_OPENAT,
209 Close = io_uring_op_IORING_OP_CLOSE,
210 FilesUpdate = io_uring_op_IORING_OP_FILES_UPDATE,
211 Statx = io_uring_op_IORING_OP_STATX,
212 Read = io_uring_op_IORING_OP_READ,
213 Write = io_uring_op_IORING_OP_WRITE,
214 Fadvise = io_uring_op_IORING_OP_FADVISE,
215 Madvise = io_uring_op_IORING_OP_MADVISE,
216 Send = io_uring_op_IORING_OP_SEND,
217 Recv = io_uring_op_IORING_OP_RECV,
218 Openat2 = io_uring_op_IORING_OP_OPENAT2,
219 EpollCtl = io_uring_op_IORING_OP_EPOLL_CTL,
220 Splice = io_uring_op_IORING_OP_SPLICE,
221 ProvideBuffers = io_uring_op_IORING_OP_PROVIDE_BUFFERS,
222 RemoveBuffers = io_uring_op_IORING_OP_REMOVE_BUFFERS,
223 Tee = io_uring_op_IORING_OP_TEE,
224 Shutdown = io_uring_op_IORING_OP_SHUTDOWN,
225 Renameat = io_uring_op_IORING_OP_RENAMEAT,
226 Unlinkat = io_uring_op_IORING_OP_UNLINKAT,
227 Mkdirat = io_uring_op_IORING_OP_MKDIRAT,
228 Symlinkat = io_uring_op_IORING_OP_SYMLINKAT,
229 Linkat = io_uring_op_IORING_OP_LINKAT,
230}
231
232/// Represents an allowlist of the restrictions to be registered to a uring.
233#[derive(Default)]
234pub struct URingAllowlist(Vec<io_uring_restriction>);
235
236impl URingAllowlist {
237 /// Create a new `UringAllowList` which allows no operation.
238 pub fn new() -> Self {
239 URingAllowlist::default()
240 }
241
242 /// Allow `operation` to be submitted to the submit queue of the io_uring.
243 pub fn allow_submit_operation(&mut self, operation: URingOperation) -> &mut Self {
244 self.0.push(io_uring_restriction {
245 opcode: io_uring_register_restriction_op_IORING_RESTRICTION_SQE_OP as u16,
246 __bindgen_anon_1: io_uring_restriction__bindgen_ty_1 {
247 sqe_op: operation as u8,
248 },
249 ..Default::default()
250 });
251 self
252 }
253}
254
255/// Unsafe wrapper for the kernel's io_uring interface. Allows for queueing multiple I/O operations
256/// to the kernel and asynchronously handling the completion of these operations.
257/// Use the various `add_*` functions to configure operations, then call `wait` to start
258/// the operations and get any completed results. Each op is given a u64 user_data argument that is
259/// used to identify the result when returned in the iterator provided by `wait`.
260///
261/// # Example polling an FD for readable status.
262///
263/// ```no_run
264/// # use std::fs::File;
265/// # use std::os::unix::io::AsRawFd;
266/// # use std::path::Path;
267/// # use base::EventType;
268/// # use io_uring::URingContext;
269/// let f = File::open(Path::new("/dev/zero")).unwrap();
270/// let uring = URingContext::new(16, None).unwrap();
271/// uring
272/// .add_poll_fd(f.as_raw_fd(), EventType::Read, 454)
273/// .unwrap();
274/// let (user_data, res) = uring.wait().unwrap().next().unwrap();
275/// assert_eq!(user_data, 454 as io_uring::UserData);
276/// assert_eq!(res.unwrap(), 1 as u32);
277/// ```
278pub struct URingContext {
279 ring_file: File, // Holds the io_uring context FD returned from io_uring_setup.
280 pub submit_ring: Mutex<SubmitQueue>,
281 pub complete_ring: CompleteQueueState,
282}
283
284impl URingContext {
285 /// Creates a `URingContext` where the underlying uring has a space for `num_entries`
286 /// simultaneous operations. If `allowlist` is given, all operations other
287 /// than those explicitly permitted by `allowlist` are prohibited.
288 pub fn new(num_entries: usize, allowlist: Option<&URingAllowlist>) -> Result<URingContext> {
289 let mut ring_params = io_uring_params::default();
290 if allowlist.is_some() {
291 // To register restrictions, a uring must start in a disabled state.
292 ring_params.flags |= IORING_SETUP_R_DISABLED;
293 }
294
295 // SAFETY:
296 // The below unsafe block isolates the creation of the URingContext. Each step on it's own
297 // is unsafe. Using the uring FD for the mapping and the offsets returned by the kernel for
298 // base addresses maintains safety guarantees assuming the kernel API guarantees are
299 // trusted.
300 unsafe {
301 // Safe because the kernel is trusted to only modify params and `File` is created with
302 // an FD that it takes complete ownership of.
303 let fd = io_uring_setup(num_entries, &mut ring_params).map_err(Error::Setup)?;
304 let ring_file = File::from_raw_fd(fd);
305
306 // Register the restrictions if it's given
307 if let Some(restrictions) = allowlist {
308 // safe because IORING_REGISTER_RESTRICTIONS does not modify the memory and
309 // `restrictions` contains a valid pointer and length.
310 io_uring_register(
311 fd,
312 io_uring_register_op_IORING_REGISTER_RESTRICTIONS,
313 restrictions.0.as_ptr() as *const c_void,
314 restrictions.0.len() as u32,
315 )
316 .map_err(Error::RingRegister)?;
317
318 // enables the URingContext since it was started in a disabled state.
319 // safe because IORING_REGISTER_RESTRICTIONS does not modify the memory
320 io_uring_register(
321 fd,
322 io_uring_register_op_IORING_REGISTER_ENABLE_RINGS,
323 null::<c_void>(),
324 0,
325 )
326 .map_err(Error::RingRegister)?;
327 }
328
329 // Mmap the submit and completion queues.
330 // Safe because we trust the kernel to set valid sizes in `io_uring_setup` and any error
331 // is checked.
332 let submit_ring = SubmitQueueState::new(
333 MemoryMappingBuilder::new(
334 ring_params.sq_off.array as usize
335 + ring_params.sq_entries as usize * std::mem::size_of::<u32>(),
336 )
337 .from_file(&ring_file)
338 .offset(u64::from(IORING_OFF_SQ_RING))
339 .protection(Protection::read_write())
340 .populate()
341 .build()
342 .map_err(Error::MappingSubmitRing)?,
343 &ring_params,
344 );
345
346 let num_sqe = ring_params.sq_entries as usize;
347 let submit_queue_entries = SubmitQueueEntries {
348 mmap: MemoryMappingBuilder::new(
349 ring_params.sq_entries as usize * std::mem::size_of::<io_uring_sqe>(),
350 )
351 .from_file(&ring_file)
352 .offset(u64::from(IORING_OFF_SQES))
353 .protection(Protection::read_write())
354 .populate()
355 .build()
356 .map_err(Error::MappingSubmitEntries)?,
357 len: num_sqe,
358 };
359
360 let complete_ring = CompleteQueueState::new(
361 MemoryMappingBuilder::new(
362 ring_params.cq_off.cqes as usize
363 + ring_params.cq_entries as usize * std::mem::size_of::<io_uring_cqe>(),
364 )
365 .from_file(&ring_file)
366 .offset(u64::from(IORING_OFF_CQ_RING))
367 .protection(Protection::read_write())
368 .populate()
369 .build()
370 .map_err(Error::MappingCompleteRing)?,
371 &ring_params,
372 );
373
374 Ok(URingContext {
375 ring_file,
376 submit_ring: Mutex::new(SubmitQueue {
377 submit_ring,
378 submit_queue_entries,
379 submitting: 0,
380 added: 0,
381 num_sqes: ring_params.sq_entries as usize,
382 }),
383 complete_ring,
384 })
385 }
386 }
387
388 /// # Safety
389 /// See 'writev' but accepts an iterator instead of a vector if there isn't already a vector in
390 /// existence.
391 pub unsafe fn add_writev_iter<I>(
392 &self,
393 iovecs: I,
394 fd: RawFd,
395 offset: Option<u64>,
396 user_data: UserData,
397 ) -> Result<()>
398 where
399 I: Iterator<Item = libc::iovec>,
400 {
401 self.add_writev(
402 Pin::from(
403 // Safe because the caller is required to guarantee that the memory pointed to by
404 // `iovecs` lives until the transaction is complete and the completion has been
405 // returned from `wait()`.
406 iovecs
407 .map(|iov| IoBufMut::from_raw_parts(iov.iov_base as *mut u8, iov.iov_len))
408 .collect::<Vec<_>>()
409 .into_boxed_slice(),
410 ),
411 fd,
412 offset,
413 user_data,
414 )
415 }
416
417 /// Asynchronously writes to `fd` from the addresses given in `iovecs`.
418 /// # Safety
419 /// `add_writev` will write to the address given by `iovecs`. This is only safe if the caller
420 /// guarantees there are no other references to that memory and that the memory lives until the
421 /// transaction is complete and that completion has been returned from the `wait` function. In
422 /// addition there must not be any mutable references to the data pointed to by `iovecs` until
423 /// the operation completes. Ensure that the fd remains open until the op completes as well.
424 /// The iovecs reference must be kept alive until the op returns.
425 pub unsafe fn add_writev(
426 &self,
427 iovecs: Pin<Box<[IoBufMut<'static>]>>,
428 fd: RawFd,
429 offset: Option<u64>,
430 user_data: UserData,
431 ) -> Result<()> {
432 self.submit_ring.lock().prep_next_sqe(|sqe| {
433 sqe.opcode = io_uring_op_IORING_OP_WRITEV as u8;
434 sqe.set_addr(iovecs.as_ptr() as *const _ as *const libc::c_void as u64);
435 sqe.len = iovecs.len() as u32;
436 sqe.set_off(file_offset_to_raw_offset(offset));
437 sqe.set_buf_index(0);
438 sqe.ioprio = 0;
439 sqe.user_data = user_data;
440 sqe.flags = 0;
441 sqe.fd = fd;
442 })?;
443 self.complete_ring.add_op_data(user_data, iovecs);
444 Ok(())
445 }
446
447 /// Asynchronously writes `len` bytes to `fd` from the buffer at `ptr`.
448 /// # Safety
449 /// `add_write` will read the memory pointed to by `ptr`. This is only safe if the caller
450 /// guarantees there are no mutable references to that memory and that the memory lives until
451 /// the transaction is complete and that completion has been returned from the `wait` function.
452 /// Ensure that the fd remains open until the op completes as well.
453 pub unsafe fn add_write(
454 &self,
455 ptr: *const u8,
456 len: u32,
457 fd: RawFd,
458 offset: Option<u64>,
459 user_data: UserData,
460 ) -> Result<()> {
461 self.submit_ring.lock().prep_next_sqe(|sqe| {
462 sqe.opcode = io_uring_op_IORING_OP_WRITE as u8;
463 sqe.set_addr(ptr as u64);
464 sqe.len = len;
465 sqe.set_off(file_offset_to_raw_offset(offset));
466 sqe.set_buf_index(0);
467 sqe.ioprio = 0;
468 sqe.user_data = user_data;
469 sqe.flags = 0;
470 sqe.fd = fd;
471 })
472 }
473
474 /// # Safety
475 /// See 'readv' but accepts an iterator instead of a vector if there isn't already a vector in
476 /// existence.
477 pub unsafe fn add_readv_iter<I>(
478 &self,
479 iovecs: I,
480 fd: RawFd,
481 offset: Option<u64>,
482 user_data: UserData,
483 ) -> Result<()>
484 where
485 I: Iterator<Item = libc::iovec>,
486 {
487 self.add_readv(
488 Pin::from(
489 // Safe because the caller is required to guarantee that the memory pointed to by
490 // `iovecs` lives until the transaction is complete and the completion has been
491 // returned from `wait()`.
492 iovecs
493 .map(|iov| IoBufMut::from_raw_parts(iov.iov_base as *mut u8, iov.iov_len))
494 .collect::<Vec<_>>()
495 .into_boxed_slice(),
496 ),
497 fd,
498 offset,
499 user_data,
500 )
501 }
502
503 /// Asynchronously reads from `fd` to the addresses given in `iovecs`.
504 /// # Safety
505 /// `add_readv` will write to the address given by `iovecs`. This is only safe if the caller
506 /// guarantees there are no other references to that memory and that the memory lives until the
507 /// transaction is complete and that completion has been returned from the `wait` function. In
508 /// addition there must not be any references to the data pointed to by `iovecs` until the
509 /// operation completes. Ensure that the fd remains open until the op completes as well.
510 /// The iovecs reference must be kept alive until the op returns.
511 pub unsafe fn add_readv(
512 &self,
513 iovecs: Pin<Box<[IoBufMut<'static>]>>,
514 fd: RawFd,
515 offset: Option<u64>,
516 user_data: UserData,
517 ) -> Result<()> {
518 self.submit_ring.lock().prep_next_sqe(|sqe| {
519 sqe.opcode = io_uring_op_IORING_OP_READV as u8;
520 sqe.set_addr(iovecs.as_ptr() as *const _ as *const libc::c_void as u64);
521 sqe.len = iovecs.len() as u32;
522 sqe.set_off(file_offset_to_raw_offset(offset));
523 sqe.set_buf_index(0);
524 sqe.ioprio = 0;
525 sqe.user_data = user_data;
526 sqe.flags = 0;
527 sqe.fd = fd;
528 })?;
529 self.complete_ring.add_op_data(user_data, iovecs);
530 Ok(())
531 }
532
533 /// Asynchronously reads `len` bytes from `fd` into the buffer at `ptr`.
534 /// # Safety
535 /// `add_read` will write to the memory pointed to by `ptr`. This is only safe if the caller
536 /// guarantees there are no other references to that memory and that the memory lives until the
537 /// transaction is complete and that completion has been returned from the `wait` function.
538 /// Ensure that the fd remains open until the op completes as well.
539 pub unsafe fn add_read(
540 &self,
541 ptr: *mut u8,
542 len: u32,
543 fd: RawFd,
544 offset: Option<u64>,
545 user_data: UserData,
546 ) -> Result<()> {
547 self.submit_ring.lock().prep_next_sqe(|sqe| {
548 sqe.opcode = io_uring_op_IORING_OP_READ as u8;
549 sqe.set_addr(ptr as u64);
550 sqe.len = len;
551 sqe.set_off(file_offset_to_raw_offset(offset));
552 sqe.set_buf_index(0);
553 sqe.ioprio = 0;
554 sqe.user_data = user_data;
555 sqe.flags = 0;
556 sqe.fd = fd;
557 })
558 }
559
560 /// Add a no-op operation that doesn't perform any IO. Useful for testing the performance of the
561 /// io_uring itself and for waking up a thread that's blocked inside a wait() call.
562 pub fn add_nop(&self, user_data: UserData) -> Result<()> {
563 self.submit_ring.lock().prep_next_sqe(|sqe| {
564 sqe.opcode = io_uring_op_IORING_OP_NOP as u8;
565 sqe.fd = -1;
566 sqe.user_data = user_data;
567
568 sqe.set_addr(0);
569 sqe.len = 0;
570 sqe.set_off(0);
571 sqe.set_buf_index(0);
572 sqe.set_rw_flags(0);
573 sqe.ioprio = 0;
574 sqe.flags = 0;
575 })
576 }
577
578 /// Syncs all completed operations, the ordering with in-flight async ops is not
579 /// defined.
580 pub fn add_fsync(&self, fd: RawFd, user_data: UserData) -> Result<()> {
581 self.submit_ring.lock().prep_next_sqe(|sqe| {
582 sqe.opcode = io_uring_op_IORING_OP_FSYNC as u8;
583 sqe.fd = fd;
584 sqe.user_data = user_data;
585
586 sqe.set_addr(0);
587 sqe.len = 0;
588 sqe.set_off(0);
589 sqe.set_buf_index(0);
590 sqe.set_rw_flags(0);
591 sqe.ioprio = 0;
592 sqe.flags = 0;
593 })
594 }
595
596 /// See the usage of `fallocate`, this asynchronously performs the same operations.
597 pub fn add_fallocate(
598 &self,
599 fd: RawFd,
600 offset: u64,
601 len: u64,
602 mode: u32,
603 user_data: UserData,
604 ) -> Result<()> {
605 // Note that len for fallocate in passed in the addr field of the sqe and the mode uses the
606 // len field.
607 self.submit_ring.lock().prep_next_sqe(|sqe| {
608 sqe.opcode = io_uring_op_IORING_OP_FALLOCATE as u8;
609
610 sqe.fd = fd;
611 sqe.set_addr(len);
612 sqe.len = mode;
613 sqe.set_off(offset);
614 sqe.user_data = user_data;
615
616 sqe.set_buf_index(0);
617 sqe.set_rw_flags(0);
618 sqe.ioprio = 0;
619 sqe.flags = 0;
620 })
621 }
622
623 /// Adds an FD to be polled based on the given flags.
624 /// The user must keep the FD open until the operation completion is returned from
625 /// `wait`.
626 /// Note that io_uring is always a one shot poll. After the fd is returned, it must be re-added
627 /// to get future events.
628 pub fn add_poll_fd(&self, fd: RawFd, events: EventType, user_data: UserData) -> Result<()> {
629 self.submit_ring.lock().prep_next_sqe(|sqe| {
630 sqe.opcode = io_uring_op_IORING_OP_POLL_ADD as u8;
631 sqe.fd = fd;
632 sqe.user_data = user_data;
633 sqe.set_poll_events(events.into());
634
635 sqe.set_addr(0);
636 sqe.len = 0;
637 sqe.set_off(0);
638 sqe.set_buf_index(0);
639 sqe.ioprio = 0;
640 sqe.flags = 0;
641 })
642 }
643
644 /// Removes an FD that was previously added with `add_poll_fd`.
645 pub fn remove_poll_fd(&self, fd: RawFd, events: EventType, user_data: UserData) -> Result<()> {
646 self.submit_ring.lock().prep_next_sqe(|sqe| {
647 sqe.opcode = io_uring_op_IORING_OP_POLL_REMOVE as u8;
648 sqe.fd = fd;
649 sqe.user_data = user_data;
650 sqe.set_poll_events(events.into());
651
652 sqe.set_addr(0);
653 sqe.len = 0;
654 sqe.set_off(0);
655 sqe.set_buf_index(0);
656 sqe.ioprio = 0;
657 sqe.flags = 0;
658 })
659 }
660
661 /// Attempt to cancel an already issued request. addr must contain the user_data field of the
662 /// request that should be cancelled. The cancellation request will complete with one of the
663 /// following results codes. If found, the res field of the cqe will contain 0. If not found,
664 /// res will contain -ENOENT. If found and attempted cancelled, the res field will contain
665 /// -EALREADY. In this case, the request may or may not terminate. In general, requests that
666 /// are interruptible (like socket IO) will get cancelled, while disk IO requests cannot be
667 /// cancelled if already started.
668 pub fn async_cancel(&self, addr: UserData, user_data: UserData) -> Result<()> {
669 self.submit_ring.lock().prep_next_sqe(|sqe| {
670 sqe.opcode = io_uring_op_IORING_OP_ASYNC_CANCEL as u8;
671 sqe.user_data = user_data;
672 sqe.set_addr(addr);
673
674 sqe.len = 0;
675 sqe.fd = 0;
676 sqe.set_off(0);
677 sqe.set_buf_index(0);
678 sqe.ioprio = 0;
679 sqe.flags = 0;
680 })
681 }
682
683 // Calls io_uring_enter, submitting any new sqes that have been added to the submit queue and
684 // waiting for `wait_nr` operations to complete.
685 fn enter(&self, wait_nr: u64) -> Result<()> {
686 let added = self.submit_ring.lock().prepare_submit();
687 if added == 0 && wait_nr == 0 {
688 return Ok(());
689 }
690
691 let flags = if wait_nr > 0 {
692 IORING_ENTER_GETEVENTS
693 } else {
694 0
695 };
696 let res =
697 // SAFETY:
698 // Safe because the only memory modified is in the completion queue.
699 unsafe { io_uring_enter(self.ring_file.as_raw_fd(), added as u64, wait_nr, flags) };
700
701 // An EINTR means we did successfully submit the events.
702 if res.is_ok() || res == Err(libc::EINTR) {
703 self.submit_ring.lock().complete_submit(added);
704 } else {
705 self.submit_ring.lock().fail_submit(added);
706 }
707
708 match res {
709 Ok(()) => Ok(()),
710 // EBUSY means that some completed events need to be processed before more can
711 // be submitted, so wait for some sqes to finish without submitting new ones.
712 // EINTR means we were interrupted while waiting, so start waiting again.
713 Err(libc::EBUSY) | Err(libc::EINTR) if wait_nr != 0 => {
714 loop {
715 let res =
716 // SAFETY:
717 // Safe because the only memory modified is in the completion queue.
718 unsafe { io_uring_enter(self.ring_file.as_raw_fd(), 0, wait_nr, flags) };
719 if res != Err(libc::EINTR) {
720 return res.map_err(Error::RingEnter);
721 }
722 }
723 }
724 Err(e) => Err(Error::RingEnter(e)),
725 }
726 }
727
728 /// Sends operations added with the `add_*` functions to the kernel.
729 pub fn submit(&self) -> Result<()> {
730 self.enter(0)
731 }
732
733 /// Sends operations added with the `add_*` functions to the kernel and return an iterator to
734 /// any completed operations. `wait` blocks until at least one completion is ready. If
735 /// called without any new events added, this simply waits for any existing events to
736 /// complete and returns as soon an one or more is ready.
737 pub fn wait(&self) -> Result<impl Iterator<Item = (UserData, std::io::Result<u32>)> + '_> {
738 // We only want to wait for events if there aren't already events in the completion queue.
739 let wait_nr = if self.complete_ring.num_ready() > 0 {
740 0
741 } else {
742 1
743 };
744
745 // The CompletionQueue will iterate all completed ops.
746 match self.enter(wait_nr) {
747 Ok(()) => Ok(&self.complete_ring),
748 // If we cannot submit any more entries then we need to pull stuff out of the completion
749 // ring, so just return the completion ring. This can only happen when `wait_nr` is 0 so
750 // we know there are already entries in the completion queue.
751 Err(Error::RingEnter(libc::EBUSY)) => Ok(&self.complete_ring),
752 Err(e) => Err(e),
753 }
754 }
755}
756
757impl AsRawFd for URingContext {
758 fn as_raw_fd(&self) -> RawFd {
759 self.ring_file.as_raw_fd()
760 }
761}
762
763impl AsRawDescriptor for URingContext {
764 fn as_raw_descriptor(&self) -> RawDescriptor {
765 self.ring_file.as_raw_descriptor()
766 }
767}
768
769struct SubmitQueueEntries {
770 mmap: MemoryMapping,
771 len: usize,
772}
773
774impl SubmitQueueEntries {
775 fn get_mut(&mut self, index: usize) -> Option<&mut io_uring_sqe> {
776 if index >= self.len {
777 return None;
778 }
779 // SAFETY:
780 // Safe because the mut borrow of self resticts to one mutable reference at a time and
781 // we trust that the kernel has returned enough memory in io_uring_setup and mmap.
782 let mut_ref = unsafe { &mut *(self.mmap.as_ptr() as *mut io_uring_sqe).add(index) };
783 // Clear any state.
784 *mut_ref = io_uring_sqe::default();
785 Some(mut_ref)
786 }
787}
788
789struct SubmitQueueState {
790 _mmap: MemoryMapping,
791 pointers: QueuePointers,
792 ring_mask: u32,
793 array: AtomicPtr<u32>,
794}
795
796impl SubmitQueueState {
797 // # Safety
798 // Safe iff `mmap` is created by mapping from a uring FD at the SQ_RING offset and params is
799 // the params struct passed to io_uring_setup.
800 unsafe fn new(mmap: MemoryMapping, params: &io_uring_params) -> SubmitQueueState {
801 let ptr = mmap.as_ptr();
802 // Transmutes are safe because a u32 is atomic on all supported architectures and the
803 // pointer will live until after self is dropped because the mmap is owned.
804 let head = ptr.add(params.sq_off.head as usize) as *const AtomicU32;
805 let tail = ptr.add(params.sq_off.tail as usize) as *const AtomicU32;
806 // This offset is guaranteed to be within the mmap so unwrap the result.
807 let ring_mask = mmap.read_obj(params.sq_off.ring_mask as usize).unwrap();
808 let array = AtomicPtr::new(ptr.add(params.sq_off.array as usize) as *mut u32);
809 SubmitQueueState {
810 _mmap: mmap,
811 pointers: QueuePointers { head, tail },
812 ring_mask,
813 array,
814 }
815 }
816
817 // Sets the kernel's array entry at the given `index` to `value`.
818 fn set_array_entry(&self, index: usize, value: u32) {
819 // SAFETY:
820 // Safe because self being constructed from the correct mmap guaratees that the memory is
821 // valid to written.
822 unsafe {
823 std::ptr::write_volatile(self.array.load(Ordering::Relaxed).add(index), value);
824 }
825 }
826}
827
828#[derive(Default)]
829struct CompleteQueueData {
830 //For ops that pass in arrays of iovecs, they need to be valid for the duration of the
831 //operation because the kernel might read them at any time.
832 pending_op_addrs: BTreeMap<UserData, Pin<Box<[IoBufMut<'static>]>>>,
833}
834
835pub struct CompleteQueueState {
836 mmap: MemoryMapping,
837 pointers: QueuePointers,
838 ring_mask: u32,
839 cqes_offset: u32,
840 data: Mutex<CompleteQueueData>,
841}
842
843impl CompleteQueueState {
844 /// # Safety
845 /// Safe iff `mmap` is created by mapping from a uring FD at the CQ_RING offset and params is
846 /// the params struct passed to io_uring_setup.
847 unsafe fn new(mmap: MemoryMapping, params: &io_uring_params) -> CompleteQueueState {
848 let ptr = mmap.as_ptr();
849 let head = ptr.add(params.cq_off.head as usize) as *const AtomicU32;
850 let tail = ptr.add(params.cq_off.tail as usize) as *const AtomicU32;
851 let ring_mask = mmap.read_obj(params.cq_off.ring_mask as usize).unwrap();
852 CompleteQueueState {
853 mmap,
854 pointers: QueuePointers { head, tail },
855 ring_mask,
856 cqes_offset: params.cq_off.cqes,
857 data: Default::default(),
858 }
859 }
860
861 fn add_op_data(&self, user_data: UserData, addrs: Pin<Box<[IoBufMut<'static>]>>) {
862 self.data.lock().pending_op_addrs.insert(user_data, addrs);
863 }
864
865 fn get_cqe(&self, head: u32) -> &io_uring_cqe {
866 // SAFETY:
867 // Safe because we trust that the kernel has returned enough memory in io_uring_setup
868 // and mmap and index is checked within range by the ring_mask.
869 unsafe {
870 let cqes = (self.mmap.as_ptr() as *const u8).add(self.cqes_offset as usize)
871 as *const io_uring_cqe;
872
873 let index = head & self.ring_mask;
874
875 &*cqes.add(index as usize)
876 }
877 }
878
879 pub fn num_ready(&self) -> u32 {
880 let tail = self.pointers.tail(Ordering::Acquire);
881 let head = self.pointers.head(Ordering::Relaxed);
882
883 tail.saturating_sub(head)
884 }
885
886 fn pop_front(&self) -> Option<(UserData, std::io::Result<u32>)> {
887 // Take the lock on self.data first so that 2 threads don't try to pop the same completed op
888 // from the queue.
889 let mut data = self.data.lock();
890
891 // Safe because the pointers to the atomics are valid and the cqe must be in range
892 // because the kernel provided mask is applied to the index.
893 let head = self.pointers.head(Ordering::Relaxed);
894
895 // Synchronize the read of tail after the read of head.
896 if head == self.pointers.tail(Ordering::Acquire) {
897 return None;
898 }
899
900 let cqe = self.get_cqe(head);
901 let user_data = cqe.user_data;
902 let res = cqe.res;
903
904 // free the addrs saved for this op.
905 let _ = data.pending_op_addrs.remove(&user_data);
906
907 // Store the new head and ensure the reads above complete before the kernel sees the
908 // update to head, `set_head` uses `Release` ordering
909 let new_head = head.wrapping_add(1);
910 self.pointers.set_head(new_head);
911
912 let io_res = match res {
913 r if r < 0 => Err(std::io::Error::from_raw_os_error(-r)),
914 r => Ok(r as u32),
915 };
916 Some((user_data, io_res))
917 }
918}
919
920// Return the completed ops with their result.
921impl Iterator for &CompleteQueueState {
922 type Item = (UserData, std::io::Result<u32>);
923
924 fn next(&mut self) -> Option<Self::Item> {
925 self.pop_front()
926 }
927}
928
929struct QueuePointers {
930 head: *const AtomicU32,
931 tail: *const AtomicU32,
932}
933
934// SAFETY:
935// Rust pointers don't implement Send or Sync but in this case both fields are atomics and so it's
936// safe to send the pointers between threads or access them concurrently from multiple threads.
937unsafe impl Send for QueuePointers {}
938// SAFETY: See safety comments for impl Send
939unsafe impl Sync for QueuePointers {}
940
941impl QueuePointers {
942 // Loads the tail pointer atomically with the given ordering.
943 fn tail(&self, ordering: Ordering) -> u32 {
944 // SAFETY:
945 // Safe because self being constructed from the correct mmap guaratees that the memory is
946 // valid to read.
947 unsafe { (*self.tail).load(ordering) }
948 }
949
950 // Stores the new value of the tail in the submit queue. This allows the kernel to start
951 // processing entries that have been added up until the given tail pointer.
952 // Always stores with release ordering as that is the only valid way to use the pointer.
953 fn set_tail(&self, next_tail: u32) {
954 // SAFETY:
955 // Safe because self being constructed from the correct mmap guaratees that the memory is
956 // valid to read and it's used as an atomic to cover mutability concerns.
957 unsafe { (*self.tail).store(next_tail, Ordering::Release) }
958 }
959
960 // Loads the head pointer atomically with the given ordering.
961 fn head(&self, ordering: Ordering) -> u32 {
962 // SAFETY:
963 // Safe because self being constructed from the correct mmap guaratees that the memory is
964 // valid to read.
965 unsafe { (*self.head).load(ordering) }
966 }
967
968 // Stores the new value of the head in the submit queue. This allows the kernel to start
969 // processing entries that have been added up until the given head pointer.
970 // Always stores with release ordering as that is the only valid way to use the pointer.
971 fn set_head(&self, next_head: u32) {
972 // SAFETY:
973 // Safe because self being constructed from the correct mmap guaratees that the memory is
974 // valid to read and it's used as an atomic to cover mutability concerns.
975 unsafe { (*self.head).store(next_head, Ordering::Release) }
976 }
977}