Skip to main content

squib_virtio/
slot.rs

1//! MMIO slot allocation for virtio-MMIO devices.
2//!
3//! Per [14-virtio-and-devices.md §
4//! 5](../../../specs/14-virtio-and-devices.md#5-mmio-slot-allocation):
5//!
6//! - 32 slots total (D22 layout headroom).
7//! - Each slot: `0x1000` bytes of MMIO at base `0x0F00_0000 + slot * 0x1000`.
8//! - INTID per slot: `48 + slot` (FDT SPI cell `16 + slot`).
9//!
10//! The allocator hands out slots in insertion order; the per-class API caps
11//! ([10-data-model.md § 2.3](../../../specs/10-data-model.md#23-schema-layer))
12//! ensure a fully-loaded valid configuration consumes ≤ 28 slots, leaving
13//! slack for diagnostic devices like `boot-timer` and runtime-added
14//! virtio-mem regions.
15
16use squib_arch::{IntId, IntIdError};
17use thiserror::Error;
18
19/// MMIO base for slot 0 (D22).
20pub const VIRTIO_MMIO_BASE: u64 = 0x0F00_0000;
21/// Per-slot region size in bytes.
22pub const VIRTIO_MMIO_REGION_SIZE: u64 = 0x1000;
23/// Hard cap on slots (D22 headroom).
24pub const MMIO_SLOT_COUNT: u32 = 32;
25
26/// Errors from the slot allocator.
27#[derive(Debug, Error)]
28#[non_exhaustive]
29pub enum SlotError {
30    /// All 32 slots are taken; the per-class API caps prevent this for valid
31    /// configurations.
32    #[error("MMIO slot allocator exhausted (32 slots already taken)")]
33    Exhausted,
34
35    /// `IntId` derivation failed; should be impossible for `slot < 32` but
36    /// surfaces the error rather than panicking.
37    #[error(transparent)]
38    IntId(#[from] IntIdError),
39}
40
41/// One allocated MMIO slot.
42#[derive(Debug, Clone, Copy)]
43pub struct Slot {
44    /// Slot index (0..32).
45    pub index: u32,
46    /// Guest-physical MMIO base for this slot.
47    pub base: u64,
48    /// SPI INTID for the slot.
49    pub intid: IntId,
50}
51
52impl Slot {
53    /// Construct a slot from its index.
54    ///
55    /// # Errors
56    /// - [`SlotError::Exhausted`] if `index >= 32`.
57    /// - [`SlotError::IntId`] if INTID construction fails.
58    pub fn from_index(index: u32) -> Result<Self, SlotError> {
59        if index >= MMIO_SLOT_COUNT {
60            return Err(SlotError::Exhausted);
61        }
62        let intid = IntId::from_spi_cell(16 + index)?;
63        Ok(Self {
64            index,
65            base: VIRTIO_MMIO_BASE + u64::from(index) * VIRTIO_MMIO_REGION_SIZE,
66            intid,
67        })
68    }
69}
70
71/// Sequential slot allocator. Hands out slots in insertion order.
72#[derive(Debug, Default)]
73pub struct SlotAllocator {
74    next: u32,
75}
76
77impl SlotAllocator {
78    /// Build an allocator starting at slot 0.
79    #[must_use]
80    pub fn new() -> Self {
81        Self { next: 0 }
82    }
83
84    /// Allocate the next slot.
85    ///
86    /// # Errors
87    /// [`SlotError::Exhausted`] if all 32 slots are already taken.
88    pub fn allocate(&mut self) -> Result<Slot, SlotError> {
89        let slot = Slot::from_index(self.next)?;
90        self.next += 1;
91        Ok(slot)
92    }
93
94    /// How many slots have been allocated so far.
95    #[must_use]
96    pub fn allocated(&self) -> u32 {
97        self.next
98    }
99
100    /// How many slots remain.
101    #[must_use]
102    pub fn remaining(&self) -> u32 {
103        MMIO_SLOT_COUNT - self.next
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_should_assign_slot_0_to_base_0x0f00_0000_intid_48() {
113        let s = Slot::from_index(0).unwrap();
114        assert_eq!(s.base, 0x0F00_0000);
115        assert_eq!(s.intid.as_raw(), 48);
116    }
117
118    #[test]
119    fn test_should_assign_slot_1_to_base_0x0f00_1000_intid_49() {
120        let s = Slot::from_index(1).unwrap();
121        assert_eq!(s.base, 0x0F00_1000);
122        assert_eq!(s.intid.as_raw(), 49);
123    }
124
125    #[test]
126    fn test_should_assign_slot_31_to_base_0x0f01_f000_intid_79() {
127        let s = Slot::from_index(31).unwrap();
128        assert_eq!(s.base, 0x0F01_F000);
129        assert_eq!(s.intid.as_raw(), 79);
130    }
131
132    #[test]
133    fn test_should_reject_slot_above_ceiling() {
134        let err = Slot::from_index(32).unwrap_err();
135        assert!(matches!(err, SlotError::Exhausted));
136    }
137
138    #[test]
139    fn test_should_allocate_sequentially_then_fail_after_32_takes() {
140        let mut alloc = SlotAllocator::new();
141        for expected in 0..32 {
142            let s = alloc.allocate().unwrap();
143            assert_eq!(s.index, expected);
144        }
145        assert_eq!(alloc.allocated(), 32);
146        assert_eq!(alloc.remaining(), 0);
147        assert!(matches!(alloc.allocate(), Err(SlotError::Exhausted)));
148    }
149}