//! K430: fixed-capacity packet buffer credits over already owned DMA pages. //! This metadata never allocates; the caller retains the physical DMA leases. use crate::memory::PAGE_SIZE; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { Empty, InvalidFrame, DuplicateFrame, Full, InvalidSlot, NotClaimed, } pub struct PacketPool { frames: [u64; N], claimed: [bool; N], live: usize, } impl PacketPool { pub fn new(frames: [u64; N], ram_base: u64, ram_bytes: u64) -> Result { if N == 0 { return Err(Error::Empty); } let ram_end = ram_base.checked_add(ram_bytes).ok_or(Error::InvalidFrame)?; for (index, frame) in frames.iter().enumerate() { if frame % PAGE_SIZE != 0 || *frame < ram_base || frame.checked_add(PAGE_SIZE).is_none_or(|end| end > ram_end) { return Err(Error::InvalidFrame); } if frames[..index].contains(frame) { return Err(Error::DuplicateFrame); } } Ok(Self { frames, claimed: [false; N], live: 0, }) } pub fn claim(&mut self) -> Result<(usize, u64), Error> { let index = self .claimed .iter() .position(|used| !used) .ok_or(Error::Full)?; self.claimed[index] = true; self.live += 1; Ok((index, self.frames[index])) } pub fn release(&mut self, index: usize) -> Result<(), Error> { let claimed = self.claimed.get_mut(index).ok_or(Error::InvalidSlot)?; if !*claimed { return Err(Error::NotClaimed); } *claimed = false; self.live -= 1; Ok(()) } pub fn live(&self) -> usize { self.live } pub const fn capacity(&self) -> usize { N } } #[cfg(test)] mod tests { use super::*; const BASE: u64 = 0x4000_0000; #[test] fn k430_full_pool_rejects_fifth_credit_without_changing_ownership() { let mut pool = PacketPool::new( [ BASE, BASE + PAGE_SIZE, BASE + 2 * PAGE_SIZE, BASE + 3 * PAGE_SIZE, ], BASE, 8 * PAGE_SIZE, ) .unwrap(); for index in 0..4 { assert_eq!(pool.claim(), Ok((index, BASE + index as u64 * PAGE_SIZE))); } assert_eq!(pool.live(), pool.capacity()); assert_eq!(pool.claim(), Err(Error::Full)); assert_eq!(pool.live(), 4); pool.release(2).unwrap(); assert_eq!(pool.claim(), Ok((2, BASE + 2 * PAGE_SIZE))); assert_eq!(pool.live(), 4); } #[test] fn k430_rejects_invalid_frames_and_double_release() { assert!(matches!( PacketPool::<0>::new([], BASE, PAGE_SIZE), Err(Error::Empty) )); assert!(matches!( PacketPool::new([BASE + 1], BASE, 2 * PAGE_SIZE), Err(Error::InvalidFrame) )); assert!(matches!( PacketPool::new([BASE + PAGE_SIZE], BASE, PAGE_SIZE), Err(Error::InvalidFrame) )); assert!(matches!( PacketPool::new([BASE, BASE], BASE, 2 * PAGE_SIZE), Err(Error::DuplicateFrame) )); let mut pool = PacketPool::new([BASE], BASE, PAGE_SIZE).unwrap(); assert_eq!(pool.release(0), Err(Error::NotClaimed)); assert_eq!(pool.release(1), Err(Error::InvalidSlot)); pool.claim().unwrap(); pool.release(0).unwrap(); assert_eq!(pool.release(0), Err(Error::NotClaimed)); assert_eq!(pool.live(), 0); } }