//! Bounded split-virtqueue index ownership with explicit u16 wrap. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { InvalidQueue, InvalidHead, Full, DuplicateHead, StaleObservation, DeviceJump, UnknownHead, NotReset, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Ticket { pub head: u16, pub generation: u64, pub avail_index: u16 } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Snapshot { pub avail_index: u16, pub used_index: u16, pub outstanding: u16, pub next_generation: u64, slots: [Option; N], } pub struct Queue { size: u16, avail_index: u16, used_index: u16, outstanding: u16, next_generation: u64, slots: [Option; N], } impl Queue { pub fn new(size: u16) -> Result { if size == 0 || !size.is_power_of_two() || size > 1024 || usize::from(size) > N { return Err(Error::InvalidQueue); } Ok(Self {size, avail_index: 0, used_index: 0, outstanding: 0, next_generation: 1, slots: [None; N]}) } pub fn snapshot(&self) -> Snapshot { Snapshot {avail_index: self.avail_index, used_index: self.used_index, outstanding: self.outstanding, next_generation: self.next_generation, slots: self.slots} } /// Caller writes descriptors and avail ring slot before publishing this index. pub fn publish(&mut self, head: u16) -> Result { if head >= self.size { return Err(Error::InvalidHead); } if self.outstanding == self.size { return Err(Error::Full); } if self.slots[usize::from(head)].is_some() { return Err(Error::DuplicateHead); } let generation = self.next_generation; self.next_generation = generation.checked_add(1).ok_or(Error::Full)?; self.avail_index = self.avail_index.wrapping_add(1); let ticket = Ticket {head, generation, avail_index: self.avail_index}; self.slots[usize::from(head)] = Some(ticket); self.outstanding += 1; Ok(ticket) } /// Caller observes used idx with acquire ordering before reading used entries. pub fn observe_used(&mut self, observed_index: u16, used_head: u32) -> Result { let delta = observed_index.wrapping_sub(self.used_index); if delta == 0 { return Err(Error::StaleObservation); } if delta != 1 || delta > self.outstanding { return Err(Error::DeviceJump); } if used_head >= u32::from(self.size) { return Err(Error::UnknownHead); } let ticket = self.slots[used_head as usize].ok_or(Error::UnknownHead)?; self.slots[used_head as usize] = None; self.used_index = observed_index; self.outstanding -= 1; Ok(ticket) } pub fn observe_reset(&mut self, status: u8, queue_ready: u16, avail_index: u16, used_index: u16) -> Result<(), Error> { if status != 0 || queue_ready != 0 || avail_index != 0 || used_index != 0 { return Err(Error::NotReset); } self.avail_index = 0; self.used_index = 0; self.outstanding = 0; self.slots = [None; N]; Ok(()) } }