1use squib_arch::{IntId, IntIdError};
17use thiserror::Error;
18
19pub const VIRTIO_MMIO_BASE: u64 = 0x0F00_0000;
21pub const VIRTIO_MMIO_REGION_SIZE: u64 = 0x1000;
23pub const MMIO_SLOT_COUNT: u32 = 32;
25
26#[derive(Debug, Error)]
28#[non_exhaustive]
29pub enum SlotError {
30 #[error("MMIO slot allocator exhausted (32 slots already taken)")]
33 Exhausted,
34
35 #[error(transparent)]
38 IntId(#[from] IntIdError),
39}
40
41#[derive(Debug, Clone, Copy)]
43pub struct Slot {
44 pub index: u32,
46 pub base: u64,
48 pub intid: IntId,
50}
51
52impl Slot {
53 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#[derive(Debug, Default)]
73pub struct SlotAllocator {
74 next: u32,
75}
76
77impl SlotAllocator {
78 #[must_use]
80 pub fn new() -> Self {
81 Self { next: 0 }
82 }
83
84 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 #[must_use]
96 pub fn allocated(&self) -> u32 {
97 self.next
98 }
99
100 #[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}