use std::{
ffi::c_void,
io,
mem::{ManuallyDrop, size_of},
os::fd::{AsRawFd, OwnedFd},
ptr::{self, addr_of_mut},
slice,
sync::Arc,
sync::atomic::{AtomicU16, Ordering},
};
use super::{
Ring,
sys::{
IORING_REGISTER_PBUF_RING, IORING_UNREGISTER_PBUF_RING, IoUringBuf, IoUringBufReg, Mapping,
cqe_buffer_id, io_uring_register,
},
};
const MAX_ENTRIES: u16 = 1 << 15;
const TAIL_OFFSET: usize = 14;
#[derive(Debug)]
pub(crate) struct BufMem {
map: Mapping,
entries: u16,
buf_size: u32,
}
unsafe impl Send for BufMem {}
unsafe impl Sync for BufMem {}
impl BufMem {
pub(crate) unsafe fn bytes(&self, bid: u16, start: u32, end: u32) -> Option<&[u8]> {
if bid >= self.entries || start > end || end > self.buf_size {
return None;
}
let at = usize::from(bid) * self.buf_size as usize + start as usize;
Some(unsafe { slice::from_raw_parts(self.map.as_ptr().add(at), (end - start) as usize) })
}
}
#[derive(Debug)]
pub(crate) struct BufRing {
ring_fd: OwnedFd,
bgid: u16,
entries: u16,
buf_size: u32,
tail: u16,
pending: u16,
maps: ManuallyDrop<(Mapping, Arc<BufMem>)>,
}
impl BufRing {
pub(crate) fn new(ring: &Ring, bgid: u16, entries: u16, buf_size: u32) -> io::Result<BufRing> {
if !entries.is_power_of_two() || entries > MAX_ENTRIES {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"a buffer ring needs a power-of-two size of at most 32768",
));
}
if buf_size == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"buffers need at least one byte",
));
}
let buf_bytes = usize::from(entries)
.checked_mul(buf_size as usize)
.ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "the buffer pool is too large")
})?;
let ring_map = Mapping::anon(usize::from(entries) * size_of::<IoUringBuf>())?;
let buf_map = Mapping::anon(buf_bytes)?;
let ring_fd = ring.fd.try_clone()?;
let reg = IoUringBufReg {
ring_addr: ring_map.as_ptr().expose_provenance() as u64,
ring_entries: u32::from(entries),
bgid,
..IoUringBufReg::default()
};
unsafe {
io_uring_register(
ring_fd.as_raw_fd(),
IORING_REGISTER_PBUF_RING,
ptr::from_ref(®).cast(),
1,
)
}?;
Ok(Self {
ring_fd,
bgid,
entries,
buf_size,
tail: 0,
pending: 0,
maps: ManuallyDrop::new((
ring_map,
Arc::new(BufMem {
map: buf_map,
entries,
buf_size,
}),
)),
})
}
pub(crate) fn memory(&self) -> &Arc<BufMem> {
&self.maps.1
}
pub(crate) fn bgid(&self) -> u16 {
self.bgid
}
pub(crate) fn entries(&self) -> u16 {
self.entries
}
pub(crate) fn buf_size(&self) -> u32 {
self.buf_size
}
pub(crate) fn buffer_id(cqe_flags: u32) -> Option<u16> {
cqe_buffer_id(cqe_flags)
}
fn tail_view(&self) -> &AtomicU16 {
unsafe { &*self.maps.0.as_ptr().add(TAIL_OFFSET).cast::<AtomicU16>() }
}
pub(crate) fn add(&mut self, bid: u16) -> io::Result<()> {
if bid >= self.entries {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"the buffer id is outside the pool",
));
}
if self.pending >= self.entries {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"more buffers were added than the ring holds",
));
}
let slot = self.tail.wrapping_add(self.pending) & (self.entries - 1);
let buf = self
.maps
.1
.map
.as_ptr()
.wrapping_add(usize::from(bid) * self.buf_size as usize);
unsafe {
let desc = self
.maps
.0
.as_ptr()
.cast::<IoUringBuf>()
.add(usize::from(slot));
addr_of_mut!((*desc).addr).write(buf.expose_provenance() as u64);
addr_of_mut!((*desc).len).write(self.buf_size);
addr_of_mut!((*desc).bid).write(bid);
}
self.pending += 1;
Ok(())
}
pub(crate) fn commit(&mut self) {
if self.pending == 0 {
return;
}
self.tail = self.tail.wrapping_add(self.pending);
self.pending = 0;
self.tail_view().store(self.tail, Ordering::Release);
}
pub(crate) fn buffer(&self, bid: u16, len: u32) -> Option<&[u8]> {
if bid >= self.entries || len > self.buf_size {
return None;
}
let start = usize::from(bid) * self.buf_size as usize;
Some(unsafe { slice::from_raw_parts(self.maps.1.map.as_ptr().add(start), len as usize) })
}
}
#[cfg(test)]
impl BufRing {
pub(super) fn published_tail(&self) -> u16 {
self.tail_view().load(Ordering::Acquire)
}
pub(super) fn descriptor(&self, slot: u16) -> IoUringBuf {
assert!(slot < self.entries);
unsafe {
ptr::read(
self.maps
.0
.as_ptr()
.cast::<IoUringBuf>()
.add(usize::from(slot)),
)
}
}
}
impl Drop for BufRing {
fn drop(&mut self) {
let reg = IoUringBufReg {
bgid: self.bgid,
..IoUringBufReg::default()
};
let unregistered = unsafe {
io_uring_register(
self.ring_fd.as_raw_fd(),
IORING_UNREGISTER_PBUF_RING,
ptr::from_ref(®).cast::<c_void>(),
1,
)
};
if unregistered.is_ok() {
unsafe { ManuallyDrop::drop(&mut self.maps) };
}
}
}