use std::num::NonZeroUsize;
use std::os::fd::AsFd;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicU32, Ordering};
use nix::sys::mman::{MapFlags, ProtFlags};
use crate::afpacket::ffi;
use crate::error::Error;
pub(crate) struct MmapRing {
base: NonNull<u8>,
size: usize,
block_size: usize,
block_count: usize,
}
impl MmapRing {
pub(crate) fn new(
fd: impl AsFd,
size: usize,
block_size: usize,
block_count: usize,
) -> Result<Self, Error> {
debug_assert_eq!(size, block_size * block_count);
let length =
NonZeroUsize::new(size).ok_or_else(|| Error::Config("ring size is 0".into()))?;
let prot = ProtFlags::PROT_READ | ProtFlags::PROT_WRITE;
let flags = MapFlags::MAP_SHARED | MapFlags::MAP_LOCKED | MapFlags::MAP_POPULATE;
let result = unsafe {
nix::sys::mman::mmap(None, length, prot, flags, &fd, 0)
};
let ptr = match result {
Ok(p) => p,
Err(
e @ (nix::errno::Errno::EPERM
| nix::errno::Errno::ENOMEM
| nix::errno::Errno::EAGAIN),
) => {
let hint = match e {
nix::errno::Errno::EPERM => "missing CAP_IPC_LOCK",
nix::errno::Errno::ENOMEM => "RLIMIT_MEMLOCK exhausted or genuine OOM",
nix::errno::Errno::EAGAIN => "RLIMIT_MEMLOCK exhausted",
_ => unreachable!(),
};
tracing::warn!(
error = %e,
hint,
"mmap MAP_LOCKED failed; retrying without MAP_LOCKED"
);
let flags_no_lock = MapFlags::MAP_SHARED | MapFlags::MAP_POPULATE;
unsafe { nix::sys::mman::mmap(None, length, prot, flags_no_lock, &fd, 0) }
.map_err(|e| Error::Mmap(e.into()))?
}
Err(e) => return Err(Error::Mmap(e.into())),
};
Ok(Self {
base: ptr.cast(),
size,
block_size,
block_count,
})
}
#[inline]
pub(crate) fn block_ptr(&self, index: usize) -> NonNull<ffi::tpacket_block_desc> {
assert!(
index < self.block_count,
"block index {index} out of range (count: {})",
self.block_count
);
let offset = index * self.block_size;
let ptr = self.base.as_ptr().map_addr(|a| a + offset);
unsafe { NonNull::new_unchecked(ptr.cast::<ffi::tpacket_block_desc>()) }
}
pub(crate) fn base(&self) -> NonNull<u8> {
self.base
}
pub(crate) fn size(&self) -> usize {
self.size
}
pub(crate) fn block_count(&self) -> usize {
self.block_count
}
}
impl Drop for MmapRing {
fn drop(&mut self) {
let _ = unsafe { nix::sys::mman::munmap(self.base.cast(), self.size) };
}
}
unsafe impl Send for MmapRing {}
#[inline]
pub(crate) unsafe fn read_block_status(bd: NonNull<ffi::tpacket_block_desc>) -> u32 {
let status_ptr =
unsafe { core::ptr::addr_of!((*bd.as_ptr()).hdr.bh1.block_status) as *const AtomicU32 };
unsafe { &*status_ptr }.load(Ordering::Acquire)
}
#[inline]
pub(crate) unsafe fn release_block(bd: NonNull<ffi::tpacket_block_desc>) {
let status_ptr =
unsafe { core::ptr::addr_of!((*bd.as_ptr()).hdr.bh1.block_status) as *const AtomicU32 };
unsafe { &*status_ptr }.store(ffi::TP_STATUS_KERNEL, Ordering::Release);
}