mod mem;
use mem::UmemRegion;
pub mod frame;
use frame::{Data, DataMut, FrameDesc, Headroom, HeadroomMut};
mod fill_queue;
pub use fill_queue::FillQueue;
mod comp_queue;
pub use comp_queue::CompQueue;
use libxdp_sys::{xsk_socket, xsk_umem};
use log::error;
use std::{
error::Error,
fmt, io,
num::NonZeroU32,
ptr::{self, NonNull},
sync::{Arc, Mutex, PoisonError},
};
use crate::{
config::UmemConfig,
ring::{XskRingCons, XskRingConsHandle, XskRingProd, XskRingProdHandle},
};
#[derive(Debug)]
struct UmemInner {
ptr: NonNull<xsk_umem>,
saved_fq_and_cq: Option<(XskRingProd, XskRingCons)>,
ring_handles: Vec<(XskRingProdHandle, XskRingConsHandle)>,
_mem: UmemRegion,
}
unsafe impl Send for UmemInner {}
impl UmemInner {
unsafe fn new(
ptr: NonNull<xsk_umem>,
saved_fq_and_cq: (XskRingProd, XskRingCons),
mem: UmemRegion,
) -> Self {
Self {
ptr,
saved_fq_and_cq: Some(saved_fq_and_cq),
ring_handles: Vec::new(),
_mem: mem,
}
}
fn as_mut_ptr(&self) -> *mut xsk_umem {
self.ptr.as_ptr()
}
}
impl Drop for UmemInner {
fn drop(&mut self) {
let err = unsafe { libxdp_sys::xsk_umem__delete(self.as_mut_ptr()) };
if err != 0 {
error!(
"failed to delete UMEM with error: {}",
io::Error::from_raw_os_error(-err)
);
}
}
}
#[derive(Debug, Clone)]
pub struct Umem {
inner: Arc<Mutex<UmemInner>>,
mem: UmemRegion,
}
impl Umem {
pub fn new(
config: UmemConfig,
frame_count: NonZeroU32,
use_huge_pages: bool,
) -> Result<(Self, Vec<FrameDesc>), UmemCreateError> {
let frame_layout = config.into();
let mem = UmemRegion::new(frame_count, frame_layout, use_huge_pages).map_err(|e| {
UmemCreateError {
reason: "failed to create mmap'd UMEM region",
err: Some(e),
}
})?;
let mut umem_ptr = ptr::null_mut();
let fq = XskRingProd::default();
let cq = XskRingCons::default();
let err = unsafe {
libxdp_sys::xsk_umem__create(
&mut umem_ptr,
mem.as_ptr(),
mem.len() as u64,
fq.as_ptr(),
cq.as_ptr(),
&config.into(),
)
};
if err != 0 {
return Err(UmemCreateError {
reason: "non-zero error code returned when creating UMEM",
err: Some(io::Error::from_raw_os_error(-err)),
});
}
let Some(umem_ptr) = NonNull::new(umem_ptr) else {
return Err(UmemCreateError {
reason: "UMEM is null",
err: None,
});
};
let null_ring = if fq.is_ring_null() {
Some("fill queue ring is null")
} else if cq.is_ring_null() {
Some("comp queue ring is null")
} else {
None
};
let inner = unsafe { UmemInner::new(umem_ptr, (fq, cq), mem.clone()) };
if let Some(reason) = null_ring {
return Err(UmemCreateError { reason, err: None });
}
let frame_count = frame_count.get() as usize;
let mut frame_descs: Vec<FrameDesc> = Vec::with_capacity(frame_count);
for i in 0..frame_count {
let addr = (i * frame_layout.frame_size())
+ frame_layout.xdp_headroom
+ frame_layout.frame_headroom;
frame_descs.push(FrameDesc::new(addr));
}
let umem = Umem {
inner: Arc::new(Mutex::new(inner)),
mem,
};
Ok((umem, frame_descs))
}
#[inline]
pub unsafe fn frame(&self, desc: &FrameDesc) -> (Headroom<'_>, Data<'_>) {
unsafe { self.mem.frame(desc) }
}
#[inline]
pub unsafe fn headroom(&self, desc: &FrameDesc) -> Headroom<'_> {
unsafe { self.mem.headroom(desc) }
}
#[inline]
pub unsafe fn data(&self, desc: &FrameDesc) -> Data<'_> {
unsafe { self.mem.data(desc) }
}
#[inline]
pub unsafe fn frame_mut<'a>(
&'a self,
desc: &'a mut FrameDesc,
) -> (HeadroomMut<'a>, DataMut<'a>) {
unsafe { self.mem.frame_mut(desc) }
}
#[inline]
pub unsafe fn headroom_mut<'a>(&'a self, desc: &'a mut FrameDesc) -> HeadroomMut<'a> {
unsafe { self.mem.headroom_mut(desc) }
}
#[inline]
pub unsafe fn data_mut<'a>(&'a self, desc: &'a mut FrameDesc) -> DataMut<'a> {
unsafe { self.mem.data_mut(desc) }
}
#[inline]
pub(crate) fn with_ptr_and_fq_and_cq<F>(&self, f: F) -> Result<CtxRings, i32>
where
F: FnOnce(*mut xsk_umem, &mut XskRingProd, &mut XskRingCons) -> i32,
{
let mut inner = self.inner.lock().unwrap();
let mut fresh = (XskRingProd::default(), XskRingCons::default());
let umem_ptr = inner.as_mut_ptr();
let (fq, cq) = inner.saved_fq_and_cq.as_mut().unwrap_or(&mut fresh);
let err = f(umem_ptr, fq, cq);
if err != 0 {
return Err(err);
}
let (fq_null, cq_null) = (fq.is_ring_null(), cq.is_ring_null());
let (fq, cq) = inner.saved_fq_and_cq.take().unwrap_or(fresh);
if !(fq_null && cq_null) {
inner.ring_handles.push((fq.handle(), cq.handle()));
}
Ok(match (fq_null, cq_null) {
(false, false) => CtxRings::New(fq, cq),
(true, true) => CtxRings::Existing,
_ => CtxRings::Mismatched,
})
}
#[inline]
pub(crate) unsafe fn delete_socket(&self, ptr: *mut xsk_socket) {
let _guard = self.inner.lock().unwrap_or_else(PoisonError::into_inner);
unsafe { libxdp_sys::xsk_socket__delete(ptr) };
}
}
#[derive(Debug)]
pub(crate) enum CtxRings {
New(XskRingProd, XskRingCons),
Existing,
Mismatched,
}
#[derive(Debug)]
pub struct UmemCreateError {
reason: &'static str,
err: Option<io::Error>,
}
impl fmt::Display for UmemCreateError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.reason)
}
}
impl Error for UmemCreateError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.err.as_ref().map(|err| err as _)
}
}
#[derive(Debug, Clone, Copy)]
struct FrameLayout {
xdp_headroom: usize,
frame_headroom: usize,
mtu: usize,
}
impl FrameLayout {
fn frame_size(&self) -> usize {
self.xdp_headroom + self.frame_headroom + self.mtu
}
}
impl From<UmemConfig> for FrameLayout {
fn from(c: UmemConfig) -> Self {
Self {
xdp_headroom: c.xdp_headroom() as usize,
frame_headroom: c.frame_headroom() as usize,
mtu: c.mtu() as usize,
}
}
}
#[cfg(test)]
mod tests {
use std::convert::TryInto;
use crate::config::{UmemConfigBuilder, XDP_UMEM_MIN_CHUNK_SIZE};
use super::*;
#[test]
fn config_frame_size_equals_layout_frame_size() {
let config = UmemConfigBuilder::new()
.frame_headroom(512)
.frame_size(XDP_UMEM_MIN_CHUNK_SIZE.try_into().unwrap())
.build()
.unwrap();
let layout: FrameLayout = config.into();
assert_eq!(config.frame_size().get() as usize, layout.frame_size())
}
}