chipset_arc_mutex_device/
test_chipset.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! A simple chipset that only supports MMIO intercepts.
5
6use crate::device::ArcMutexChipsetDeviceBuilder;
7use crate::device::ArcMutexChipsetServicesFinalize;
8use crate::services::ChipsetServices;
9use crate::services::ChipsetServicesMeta;
10use crate::services::MmioInterceptServices;
11use crate::services::Unimplemented;
12use chipset_device::ChipsetDevice;
13use chipset_device::io::IoResult;
14use chipset_device::mmio::ControlMmioIntercept;
15use chipset_device::mmio::RegisterMmioIntercept;
16use closeable_mutex::CloseableMutex;
17use parking_lot::RwLock;
18use range_map_vec::RangeMap;
19use std::cell::Cell;
20use std::sync::Arc;
21use std::sync::Weak;
22
23type MmioRanges = Arc<RwLock<RangeMap<u64, (Box<str>, Weak<CloseableMutex<dyn ChipsetDevice>>)>>>;
24
25/// A concrete type which implements [`RegisterMmioIntercept`]
26pub struct TestMmioRangeMapper {
27    dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
28    map: MmioRanges,
29}
30
31// Implementation detail - the concrete type returned by TestMmioRangeMapper's
32// `new_io_region` implementation
33struct TestDeviceRange {
34    map: MmioRanges,
35    region_name: Box<str>,
36    len: u64,
37    addr: Option<u64>,
38    io: Weak<CloseableMutex<dyn ChipsetDevice>>,
39}
40
41impl RegisterMmioIntercept for TestMmioRangeMapper {
42    fn new_io_region(&mut self, region_name: &str, len: u64) -> Box<dyn ControlMmioIntercept> {
43        Box::new(TestDeviceRange {
44            map: self.map.clone(),
45            region_name: region_name.into(),
46            len,
47            addr: None,
48            io: self.dev.clone(),
49        })
50    }
51}
52impl ControlMmioIntercept for TestDeviceRange {
53    fn region_name(&self) -> &str {
54        &self.region_name
55    }
56
57    fn map(&mut self, addr: u64) {
58        self.unmap();
59        if self.map.write().insert(
60            addr..=addr
61                .checked_add(self.len - 1)
62                .expect("overflow during addition, not possible in real hardware"),
63            (self.region_name.clone(), self.io.clone()),
64        ) {
65            self.addr = Some(addr);
66        } else {
67            panic!("conflict!")
68        }
69    }
70
71    fn unmap(&mut self) {
72        if let Some(addr) = self.addr.take() {
73            let _entry = self.map.write().remove(&addr).unwrap();
74        }
75    }
76
77    fn addr(&self) -> Option<u64> {
78        self.addr
79    }
80
81    fn len(&self) -> u64 {
82        self.len
83    }
84
85    fn offset_of(&self, addr: u64) -> Option<u64> {
86        let base = self.addr?;
87
88        (base..(base + self.len))
89            .contains(&addr)
90            .then(|| addr - base)
91    }
92}
93
94/// A simple chipset that only models MMIO intercepts.
95#[derive(Default)]
96pub struct TestChipset {
97    mmio_ranges: MmioRanges,
98}
99
100impl TestChipset {
101    /// Return a device builder associated with the chipset
102    pub fn device_builder<T: ChipsetDevice>(
103        &self,
104        name: &'static str,
105    ) -> ArcMutexChipsetDeviceBuilder<TestChipsetServicesImpl<'_>, T> {
106        ArcMutexChipsetDeviceBuilder::new(name.into(), |dev, _name| TestChipsetServicesImpl {
107            vm_chipset: self,
108            dev,
109            took_mmio: false.into(),
110        })
111    }
112
113    /// Dispatch a MMIO read to the given address.
114    pub fn mmio_read(&self, addr: u64, data: &mut [u8]) -> Option<()> {
115        let dev = self.mmio_ranges.read().get(&addr)?.1.upgrade()?;
116        // devices might want to map/unmap ranges as part of a MMIO access,
117        // so don't hold the range lock for any longer than we need to
118        match dev
119            .lock()
120            .supports_mmio()
121            .expect("objects on the mmio bus support mmio")
122            .mmio_read(addr, data)
123        {
124            IoResult::Ok => {}
125            IoResult::Err(_) => {
126                data.fill(!0);
127            }
128            IoResult::Defer(_) => unreachable!(),
129        }
130        Some(())
131    }
132
133    /// Dispatch a MMIO write to the given address.
134    pub fn mmio_write(&self, addr: u64, data: &[u8]) -> Option<()> {
135        let dev = self.mmio_ranges.read().get(&addr)?.1.upgrade()?;
136        // devices might want to map/unmap ranges as part of a MMIO access,
137        // so don't hold the range lock for any longer than we need to
138        let _ = dev
139            .lock()
140            .supports_mmio()
141            .expect("objects on the mmio bus support mmio")
142            .mmio_write(addr, data);
143        Some(())
144    }
145}
146
147/// Implementation of [`ChipsetServices`] associated with [`TestChipset`]
148pub struct TestChipsetServicesImpl<'a> {
149    vm_chipset: &'a TestChipset,
150    dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
151    took_mmio: Cell<bool>,
152}
153
154/// Compile-time type metadata used by [`TestChipsetServicesImpl`]'s
155/// [`ChipsetServices`] impl
156pub enum TestChipsetServicesMeta {}
157impl ChipsetServicesMeta for TestChipsetServicesMeta {
158    type RegisterMmioIntercept = TestMmioRangeMapper;
159    type RegisterPortIoIntercept = Unimplemented;
160}
161
162impl ChipsetServices for TestChipsetServicesImpl<'_> {
163    type M = TestChipsetServicesMeta;
164
165    #[inline(always)]
166    fn supports_mmio(&mut self) -> Option<&mut dyn MmioInterceptServices<M = Self::M>> {
167        Some(self)
168    }
169}
170
171impl<T> ArcMutexChipsetServicesFinalize<T> for TestChipsetServicesImpl<'_> {
172    fn finalize(self, _dev: &Arc<CloseableMutex<T>>, _name: Arc<str>) {}
173}
174
175impl MmioInterceptServices for TestChipsetServicesImpl<'_> {
176    /// Obtain an instance of [`RegisterMmioIntercept`]
177    fn register_mmio(&self) -> TestMmioRangeMapper {
178        self.took_mmio.set(true);
179        TestMmioRangeMapper {
180            dev: self.dev.clone(),
181            map: self.vm_chipset.mmio_ranges.clone(),
182        }
183    }
184
185    fn is_being_used(&self) -> bool {
186        self.took_mmio.get()
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    mod sample_dev {
195        use super::*;
196        use chipset_device::io::IoResult;
197        use chipset_device::mmio::MmioIntercept;
198        use std::ops::RangeInclusive;
199
200        pub struct SampleDevice {
201            pub mmio_control: Box<dyn ControlMmioIntercept>,
202            pub mmio_read_log: Vec<u64>,
203        }
204
205        impl SampleDevice {
206            pub fn new(
207                register_mmio: &mut dyn RegisterMmioIntercept,
208            ) -> Result<Self, std::convert::Infallible> {
209                Ok(SampleDevice {
210                    mmio_control: register_mmio.new_io_region("dynamic", 1),
211                    mmio_read_log: Vec::new(),
212                })
213            }
214        }
215
216        impl ChipsetDevice for SampleDevice {
217            fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
218                Some(self)
219            }
220        }
221
222        impl MmioIntercept for SampleDevice {
223            fn mmio_read(&mut self, addr: u64, _: &mut [u8]) -> IoResult {
224                self.mmio_read_log.push(addr);
225                IoResult::Ok
226            }
227
228            fn mmio_write(&mut self, _: u64, _: &[u8]) -> IoResult {
229                IoResult::Ok
230            }
231
232            fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u64>)] {
233                &[("static", 10..=10)]
234            }
235        }
236    }
237
238    #[test]
239    fn closure() -> Result<(), Box<dyn std::error::Error>> {
240        let vm_chipset = TestChipset::default();
241
242        let devices_builder = ArcMutexChipsetDeviceBuilder::new("sample".into(), |dev, _name| {
243            TestChipsetServicesImpl {
244                vm_chipset: &vm_chipset,
245                dev,
246                took_mmio: false.into(),
247            }
248        });
249
250        let sample_dev: Arc<CloseableMutex<sample_dev::SampleDevice>> = devices_builder
251            .try_add(|services| sample_dev::SampleDevice::new(&mut services.register_mmio()))?;
252
253        // give it a go
254        assert!(vm_chipset.mmio_read(10, &mut []).is_some());
255        assert!(vm_chipset.mmio_read(11, &mut []).is_none());
256        sample_dev.lock().mmio_control.map(11);
257        assert!(vm_chipset.mmio_read(11, &mut []).is_some());
258        sample_dev.lock().mmio_control.unmap();
259        assert!(vm_chipset.mmio_read(11, &mut []).is_none());
260
261        assert_eq!(sample_dev.lock().mmio_read_log, [10, 11]);
262
263        Ok(())
264    }
265}