use bitflags::bitflags;
use bytemuck::{Pod, Zeroable};
use super::MemOps;
bitflags! {
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DescFlags: u16 {
const NEXT = 1 << 0;
const WRITE = 1 << 1;
const INDIRECT = 1 << 2;
const AVAIL = 1 << 7;
const USED = 1 << 15;
}
}
impl DescFlags {
#[inline]
pub fn is_avail(self, wrap: bool) -> bool {
let avail = self.contains(DescFlags::AVAIL);
let used = self.contains(DescFlags::USED);
avail == wrap && used != wrap
}
#[inline]
pub fn is_used(self, wrap: bool) -> bool {
let avail = self.contains(DescFlags::AVAIL);
let used = self.contains(DescFlags::USED);
avail == wrap && used == wrap
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable, PartialEq, Eq, Hash)]
pub struct Descriptor {
pub addr: u64,
pub len: u32,
pub id: u16,
pub flags: u16,
}
const _: () = assert!(core::mem::size_of::<Descriptor>() == 16);
const _: () = assert!(Descriptor::ALIGN == 16);
const _: () = assert!(Descriptor::ADDR_OFFSET == 0);
const _: () = assert!(Descriptor::LEN_OFFSET == 8);
const _: () = assert!(Descriptor::ID_OFFSET == 12);
const _: () = assert!(Descriptor::FLAGS_OFFSET == 14);
impl Descriptor {
pub const ALIGN: usize = 16;
pub const SIZE: usize = core::mem::size_of::<Self>();
pub const ADDR_OFFSET: usize = core::mem::offset_of!(Self, addr);
pub const LEN_OFFSET: usize = core::mem::offset_of!(Self, len);
pub const ID_OFFSET: usize = core::mem::offset_of!(Self, id);
pub const FLAGS_OFFSET: usize = core::mem::offset_of!(Self, flags);
pub fn new(addr: u64, len: u32, id: u16, flags: DescFlags) -> Self {
Self {
addr,
len,
id,
flags: flags.bits(),
}
}
#[inline]
pub fn flags(&self) -> DescFlags {
DescFlags::from_bits_truncate(self.flags)
}
#[inline]
pub fn is_avail(&self, wrap: bool) -> bool {
self.flags().is_avail(wrap)
}
#[inline]
pub fn is_used(&self, wrap: bool) -> bool {
self.flags().is_used(wrap)
}
#[inline]
pub fn is_writable(&self) -> bool {
self.flags().contains(DescFlags::WRITE)
}
#[inline]
pub fn is_next(&self) -> bool {
self.flags().contains(DescFlags::NEXT)
}
#[inline]
pub fn mark_avail(&mut self, wrap: bool) {
if wrap {
self.flags |= DescFlags::AVAIL.bits();
self.flags &= !DescFlags::USED.bits();
} else {
self.flags &= !DescFlags::AVAIL.bits();
self.flags |= DescFlags::USED.bits();
}
}
#[inline]
pub fn mark_used(&mut self, wrap: bool) {
if wrap {
self.flags |= DescFlags::USED.bits();
self.flags |= DescFlags::AVAIL.bits();
} else {
self.flags &= !DescFlags::USED.bits();
self.flags &= !DescFlags::AVAIL.bits();
}
}
pub fn write_release<M: MemOps>(&self, mem: &M, addr: u64) -> Result<(), M::Error> {
mem.write_val(addr + Self::ADDR_OFFSET as u64, self.addr)?;
mem.write_val(addr + Self::LEN_OFFSET as u64, self.len)?;
mem.write_val(addr + Self::ID_OFFSET as u64, self.id)?;
mem.store_release(addr + Self::FLAGS_OFFSET as u64, self.flags)?;
Ok(())
}
pub fn read_flags_acquire<M: MemOps>(mem: &M, addr: u64) -> Result<DescFlags, M::Error> {
let flags = mem.load_acquire(addr + Self::FLAGS_OFFSET as u64)?;
Ok(DescFlags::from_bits_truncate(flags))
}
pub fn read_body<M: MemOps>(mem: &M, addr: u64, flags: DescFlags) -> Result<Self, M::Error> {
let addr_val: u64 = mem.read_val(addr + Self::ADDR_OFFSET as u64)?;
let len: u32 = mem.read_val(addr + Self::LEN_OFFSET as u64)?;
let id: u16 = mem.read_val(addr + Self::ID_OFFSET as u64)?;
Ok(Self {
addr: addr_val,
len,
id,
flags: flags.bits(),
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct DescTable {
base_addr: u64,
len: usize,
}
impl DescTable {
pub const DEFAULT_LEN: usize = 256;
pub unsafe fn from_raw_parts(base_addr: u64, len: usize) -> Self {
debug_assert!(base_addr.is_multiple_of(Descriptor::ALIGN as u64));
debug_assert!(len <= u16::MAX as usize);
Self { base_addr, len }
}
pub fn desc_addr(&self, idx: u16) -> Option<u64> {
if idx >= self.len as u16 {
return None;
}
Some(self.base_addr + (idx as u64 * Descriptor::SIZE as u64))
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub const fn default_len() -> usize {
Self::DEFAULT_LEN
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mark_avail_sets_bits_correctly_wrap_true() {
let mut d = Descriptor::zeroed();
d.flags = DescFlags::WRITE.bits() | DescFlags::NEXT.bits();
d.mark_avail(true);
let f = d.flags();
assert!(f.contains(DescFlags::AVAIL));
assert!(!f.contains(DescFlags::USED));
assert!(f.contains(DescFlags::WRITE));
assert!(f.contains(DescFlags::NEXT));
}
#[test]
fn mark_avail_sets_bits_correctly_wrap_false() {
let mut d = Descriptor::zeroed();
d.mark_avail(false);
let f = d.flags();
assert!(!f.contains(DescFlags::AVAIL));
assert!(f.contains(DescFlags::USED));
}
#[test]
fn mark_used_sets_both_bits_match_wrap_true() {
let mut d = Descriptor::zeroed();
d.mark_used(true);
let f = d.flags();
assert!(f.contains(DescFlags::AVAIL));
assert!(f.contains(DescFlags::USED));
}
#[test]
fn mark_used_sets_both_bits_match_wrap_false() {
let mut d = Descriptor::zeroed();
d.mark_used(false);
let f = d.flags();
assert!(!f.contains(DescFlags::AVAIL));
assert!(!f.contains(DescFlags::USED));
}
#[test]
fn is_avail_and_is_used() {
let mut d = Descriptor::zeroed();
d.mark_avail(true);
assert!(d.is_avail(true));
assert!(!d.is_used(true));
d.mark_used(true);
assert!(d.is_used(true));
assert!(!d.is_avail(true));
d.mark_avail(false);
assert!(d.is_avail(false));
assert!(!d.is_used(false));
d.mark_used(false);
assert!(d.is_used(false));
assert!(!d.is_avail(false));
}
#[test]
fn writable_and_next_helpers() {
let mut d = Descriptor::zeroed();
d.flags = (DescFlags::WRITE | DescFlags::NEXT).bits();
assert!(d.is_writable());
assert!(d.is_next());
d.flags = 0;
assert!(!d.is_writable());
assert!(!d.is_next());
}
#[test]
fn avail_then_used_wrap_flip_sequence() {
let mut d = Descriptor::zeroed();
d.mark_avail(true);
assert!(d.is_avail(true));
d.mark_used(false);
assert!(d.is_used(false));
assert!(!d.is_avail(false));
d.mark_avail(true);
assert!(d.is_avail(true));
}
#[test]
fn desc_table_get_out_of_bounds() {
let mut buf = vec![0u8; 4 * Descriptor::SIZE + Descriptor::ALIGN];
let base = buf.as_mut_ptr() as usize;
let aligned = (base + Descriptor::ALIGN - 1) & !(Descriptor::ALIGN - 1);
let table = unsafe { DescTable::from_raw_parts(aligned as u64, 4) };
assert!(table.desc_addr(3).is_some());
assert!(table.desc_addr(4).is_none());
}
}