use squib_arch::{IntId, IntIdError};
use thiserror::Error;
pub const VIRTIO_MMIO_BASE: u64 = 0x0F00_0000;
pub const VIRTIO_MMIO_REGION_SIZE: u64 = 0x1000;
pub const MMIO_SLOT_COUNT: u32 = 32;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum SlotError {
#[error("MMIO slot allocator exhausted (32 slots already taken)")]
Exhausted,
#[error(transparent)]
IntId(#[from] IntIdError),
}
#[derive(Debug, Clone, Copy)]
pub struct Slot {
pub index: u32,
pub base: u64,
pub intid: IntId,
}
impl Slot {
pub fn from_index(index: u32) -> Result<Self, SlotError> {
if index >= MMIO_SLOT_COUNT {
return Err(SlotError::Exhausted);
}
let intid = IntId::from_spi_cell(16 + index)?;
Ok(Self {
index,
base: VIRTIO_MMIO_BASE + u64::from(index) * VIRTIO_MMIO_REGION_SIZE,
intid,
})
}
}
#[derive(Debug, Default)]
pub struct SlotAllocator {
next: u32,
}
impl SlotAllocator {
#[must_use]
pub fn new() -> Self {
Self { next: 0 }
}
pub fn allocate(&mut self) -> Result<Slot, SlotError> {
let slot = Slot::from_index(self.next)?;
self.next += 1;
Ok(slot)
}
#[must_use]
pub fn allocated(&self) -> u32 {
self.next
}
#[must_use]
pub fn remaining(&self) -> u32 {
MMIO_SLOT_COUNT - self.next
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_assign_slot_0_to_base_0x0f00_0000_intid_48() {
let s = Slot::from_index(0).unwrap();
assert_eq!(s.base, 0x0F00_0000);
assert_eq!(s.intid.as_raw(), 48);
}
#[test]
fn test_should_assign_slot_1_to_base_0x0f00_1000_intid_49() {
let s = Slot::from_index(1).unwrap();
assert_eq!(s.base, 0x0F00_1000);
assert_eq!(s.intid.as_raw(), 49);
}
#[test]
fn test_should_assign_slot_31_to_base_0x0f01_f000_intid_79() {
let s = Slot::from_index(31).unwrap();
assert_eq!(s.base, 0x0F01_F000);
assert_eq!(s.intid.as_raw(), 79);
}
#[test]
fn test_should_reject_slot_above_ceiling() {
let err = Slot::from_index(32).unwrap_err();
assert!(matches!(err, SlotError::Exhausted));
}
#[test]
fn test_should_allocate_sequentially_then_fail_after_32_takes() {
let mut alloc = SlotAllocator::new();
for expected in 0..32 {
let s = alloc.allocate().unwrap();
assert_eq!(s.index, expected);
}
assert_eq!(alloc.allocated(), 32);
assert_eq!(alloc.remaining(), 0);
assert!(matches!(alloc.allocate(), Err(SlotError::Exhausted)));
}
}