use std::sync::atomic::{AtomicU32, Ordering};
#[derive(Debug, Clone, Copy)]
pub struct RingOffset {
pub producer: u64,
pub consumer: u64,
pub desc: u64,
pub flags: u64,
}
impl From<&libc::xdp_ring_offset> for RingOffset {
fn from(o: &libc::xdp_ring_offset) -> RingOffset {
RingOffset {
producer: o.producer,
consumer: o.consumer,
desc: o.desc,
flags: o.flags,
}
}
}
#[derive(Debug)]
struct Cursors {
producer: *const AtomicU32,
consumer: *const AtomicU32,
flags: *const AtomicU32,
mask: u32,
size: u32,
}
impl Cursors {
unsafe fn new(mem: *mut u8, off: RingOffset, size: u32) -> Cursors {
let flags = if off.flags != 0 {
mem.add(off.flags as usize) as *const AtomicU32
} else {
std::ptr::null()
};
Cursors {
producer: mem.add(off.producer as usize) as *const AtomicU32,
consumer: mem.add(off.consumer as usize) as *const AtomicU32,
flags,
mask: size - 1,
size,
}
}
#[inline]
fn producer(&self) -> &AtomicU32 {
unsafe { &*self.producer }
}
#[inline]
fn consumer(&self) -> &AtomicU32 {
unsafe { &*self.consumer }
}
#[inline]
fn need_wakeup(&self) -> bool {
if self.flags.is_null() {
return true;
}
let f = unsafe { &*self.flags };
f.load(Ordering::Acquire) & XDP_RING_NEED_WAKEUP != 0
}
}
const XDP_RING_NEED_WAKEUP: u32 = 1 << 0;
#[derive(Debug)]
pub struct AddrRing {
cur: Cursors,
addrs: *mut u64,
}
unsafe impl Send for AddrRing {}
unsafe impl Sync for AddrRing {}
impl AddrRing {
pub unsafe fn new(mem: *mut u8, off: RingOffset, size: u32) -> AddrRing {
AddrRing {
cur: Cursors::new(mem, off, size),
addrs: mem.add(off.desc as usize) as *mut u64,
}
}
#[inline]
fn slot(&self, idx: u32) -> *mut u64 {
unsafe { self.addrs.add((idx & self.cur.mask) as usize) }
}
pub fn produce(&self, addrs: &[u64]) -> usize {
let prod = self.cur.producer().load(Ordering::Relaxed);
let cons = self.cur.consumer().load(Ordering::Acquire);
let free = self.cur.size - prod.wrapping_sub(cons);
let n = (addrs.len() as u32).min(free);
if n == 0 {
return 0;
}
for i in 0..n {
unsafe { *self.slot(prod.wrapping_add(i)) = addrs[i as usize] };
}
self.cur
.producer()
.store(prod.wrapping_add(n), Ordering::Release);
n as usize
}
pub fn consume(&self, out: &mut [u64]) -> usize {
let cons = self.cur.consumer().load(Ordering::Relaxed);
let prod = self.cur.producer().load(Ordering::Acquire);
let avail = prod.wrapping_sub(cons);
let n = (out.len() as u32).min(avail);
if n == 0 {
return 0;
}
for i in 0..n {
out[i as usize] = unsafe { *self.slot(cons.wrapping_add(i)) };
}
self.cur
.consumer()
.store(cons.wrapping_add(n), Ordering::Release);
n as usize
}
}
#[derive(Debug)]
pub struct DescRing {
cur: Cursors,
descs: *mut libc::xdp_desc,
}
unsafe impl Send for DescRing {}
unsafe impl Sync for DescRing {}
impl DescRing {
pub unsafe fn new(mem: *mut u8, off: RingOffset, size: u32) -> DescRing {
DescRing {
cur: Cursors::new(mem, off, size),
descs: mem.add(off.desc as usize) as *mut libc::xdp_desc,
}
}
#[inline]
fn slot(&self, idx: u32) -> *mut libc::xdp_desc {
unsafe { self.descs.add((idx & self.cur.mask) as usize) }
}
pub fn produce(&self, descs: &[libc::xdp_desc]) -> usize {
let prod = self.cur.producer().load(Ordering::Relaxed);
let cons = self.cur.consumer().load(Ordering::Acquire);
let free = self.cur.size - prod.wrapping_sub(cons);
let n = (descs.len() as u32).min(free);
if n == 0 {
return 0;
}
for i in 0..n {
let d = &descs[i as usize];
unsafe {
*self.slot(prod.wrapping_add(i)) = libc::xdp_desc {
addr: d.addr,
len: d.len,
options: d.options,
};
}
}
self.cur
.producer()
.store(prod.wrapping_add(n), Ordering::Release);
n as usize
}
pub fn consume(&self, out: &mut [libc::xdp_desc]) -> usize {
let cons = self.cur.consumer().load(Ordering::Relaxed);
let prod = self.cur.producer().load(Ordering::Acquire);
let avail = prod.wrapping_sub(cons);
let n = (out.len() as u32).min(avail);
if n == 0 {
return 0;
}
for i in 0..n {
let d = unsafe { &*self.slot(cons.wrapping_add(i)) };
out[i as usize] = libc::xdp_desc {
addr: d.addr,
len: d.len,
options: d.options,
};
}
self.cur
.consumer()
.store(cons.wrapping_add(n), Ordering::Release);
n as usize
}
#[inline]
pub fn need_wakeup(&self) -> bool {
self.cur.need_wakeup()
}
}
#[cfg(test)]
mod tests {
use super::*;
const PRODUCER_OFF: u64 = 0;
const CONSUMER_OFF: u64 = 64;
const FLAGS_OFF: u64 = 128;
const DESC_OFF: u64 = 192;
fn offsets() -> RingOffset {
RingOffset {
producer: PRODUCER_OFF,
consumer: CONSUMER_OFF,
desc: DESC_OFF,
flags: FLAGS_OFF,
}
}
fn backing(size: u32, elem: usize) -> Vec<u8> {
vec![0u8; DESC_OFF as usize + size as usize * elem]
}
#[test]
fn addr_ring_produce_consume_roundtrip() {
let size = 8u32;
let mut mem = backing(size, 8);
let ring = unsafe { AddrRing::new(mem.as_mut_ptr(), offsets(), size) };
let in_addrs = [4096u64, 8192, 12288];
assert_eq!(ring.produce(&in_addrs), 3);
let mut out = [0u64; 8];
assert_eq!(ring.consume(&mut out), 3);
assert_eq!(&out[..3], &in_addrs);
assert_eq!(ring.consume(&mut out), 0);
}
#[test]
fn addr_ring_respects_capacity() {
let size = 4u32;
let mut mem = backing(size, 8);
let ring = unsafe { AddrRing::new(mem.as_mut_ptr(), offsets(), size) };
let many: Vec<u64> = (0..10).map(|i| i as u64 * 64).collect();
assert_eq!(ring.produce(&many), 4);
assert_eq!(ring.produce(&[999]), 0);
let mut out = [0u64; 2];
assert_eq!(ring.consume(&mut out), 2);
assert_eq!(out, [0, 64]);
assert_eq!(ring.produce(&[1000, 2000, 3000]), 2);
}
#[test]
fn addr_ring_wraps_around_mask() {
let size = 4u32;
let mut mem = backing(size, 8);
let ring = unsafe { AddrRing::new(mem.as_mut_ptr(), offsets(), size) };
let mut next = 0u64;
for _ in 0..5 {
let batch = [next, next + 1];
assert_eq!(ring.produce(&batch), 2);
let mut out = [0u64; 2];
assert_eq!(ring.consume(&mut out), 2);
assert_eq!(out, batch);
next += 2;
}
}
#[test]
fn desc_ring_produce_consume_roundtrip() {
let size = 8u32;
let mut mem = backing(size, std::mem::size_of::<libc::xdp_desc>());
let ring = unsafe { DescRing::new(mem.as_mut_ptr(), offsets(), size) };
let descs = [
libc::xdp_desc {
addr: 0,
len: 60,
options: 0,
},
libc::xdp_desc {
addr: 4096,
len: 1514,
options: 0,
},
];
assert_eq!(ring.produce(&descs), 2);
let mut out = [libc::xdp_desc {
addr: 0,
len: 0,
options: 0,
}; 8];
assert_eq!(ring.consume(&mut out), 2);
assert_eq!(out[0].addr, 0);
assert_eq!(out[0].len, 60);
assert_eq!(out[1].addr, 4096);
assert_eq!(out[1].len, 1514);
}
#[test]
fn empty_consume_is_zero() {
let size = 8u32;
let mut mem = backing(size, 8);
let ring = unsafe { AddrRing::new(mem.as_mut_ptr(), offsets(), size) };
let mut out = [0u64; 4];
assert_eq!(ring.consume(&mut out), 0);
}
#[test]
fn need_wakeup_reads_flags_word() {
let size = 4u32;
let mut mem = backing(size, std::mem::size_of::<libc::xdp_desc>());
let ring = unsafe { DescRing::new(mem.as_mut_ptr(), offsets(), size) };
assert!(!ring.need_wakeup());
let flags = &mut mem[FLAGS_OFF as usize..FLAGS_OFF as usize + 4];
flags.copy_from_slice(&XDP_RING_NEED_WAKEUP.to_ne_bytes());
let ring = unsafe { DescRing::new(mem.as_mut_ptr(), offsets(), size) };
assert!(ring.need_wakeup());
}
#[test]
fn need_wakeup_true_without_flags() {
let size = 4u32;
let mut mem = backing(size, std::mem::size_of::<libc::xdp_desc>());
let off = RingOffset {
flags: 0,
..offsets()
};
let ring = unsafe { DescRing::new(mem.as_mut_ptr(), off, size) };
assert!(ring.need_wakeup());
}
}