missing_dev/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Exports [`MissingDev`]: a "no-op" device that silently swallows all MMIO,
5//! PIO, and PCI accesses that come its way.
6//!
7//! Useful for claiming known-unimplemented machine resources, in order to cut
8//! down on missing-device logging.
9
10#![forbid(unsafe_code)]
11
12use chipset_device::ChipsetDevice;
13use chipset_device::io::IoResult;
14use chipset_device::mmio::ControlMmioIntercept;
15use chipset_device::mmio::MmioIntercept;
16use chipset_device::mmio::RegisterMmioIntercept;
17use chipset_device::pci::PciConfigSpace;
18use chipset_device::pio::ControlPortIoIntercept;
19use chipset_device::pio::PortIoIntercept;
20use chipset_device::pio::RegisterPortIoIntercept;
21use inspect::Inspect;
22use inspect::InspectMut;
23use pci_core::spec::hwid::ProgrammingInterface;
24use std::ops::RangeInclusive;
25use vmcore::device_state::ChangeDeviceState;
26use vmcore::save_restore::RestoreError;
27use vmcore::save_restore::SaveError;
28use vmcore::save_restore::SaveRestore;
29use vmcore::save_restore::SavedStateNotSupported;
30
31#[derive(Default, Inspect)]
32struct MissingDevPciMetadata {
33    #[inspect(debug)]
34    bdf: (u8, u8, u8),
35    #[inspect(hex)]
36    vendor_id: u16,
37    #[inspect(hex)]
38    device_id: u16,
39}
40
41/// A device that swallows all MMIO, PIO, and PCI accesses that come its way.
42#[derive(Default, InspectMut)]
43pub struct MissingDev {
44    #[inspect(with = "inspect_helpers::io_ranges")]
45    pio: Vec<(Box<str>, RangeInclusive<u16>)>,
46    #[inspect(with = "inspect_helpers::io_ranges")]
47    mmio: Vec<(Box<str>, RangeInclusive<u64>)>,
48    pci: Option<MissingDevPciMetadata>,
49
50    #[inspect(skip)]
51    _mmio_control: Box<[Box<dyn ControlMmioIntercept>]>,
52    #[inspect(skip)]
53    _pio_control: Box<[Box<dyn ControlPortIoIntercept>]>,
54}
55
56impl MissingDev {
57    /// Convert a [`MissingDevManifest`] into a fully instantiated
58    /// [`MissingDev`].
59    pub fn from_manifest(
60        manifest: MissingDevManifest,
61        register_mmio: &mut dyn RegisterMmioIntercept,
62        register_pio: &mut dyn RegisterPortIoIntercept,
63    ) -> MissingDev {
64        let MissingDevManifest { pio, mmio, pci } = manifest;
65
66        let pio_control = pio
67            .iter()
68            .map(|(name, range)| {
69                let mut control = register_pio.new_io_region(name, range.end() - range.start() + 1);
70                control.map(*range.start());
71                control
72            })
73            .collect();
74
75        let mmio_control = mmio
76            .iter()
77            .map(|(name, range)| {
78                let mut control =
79                    register_mmio.new_io_region(name, range.end() - range.start() + 1);
80                control.map(*range.start());
81                control
82            })
83            .collect();
84
85        MissingDev {
86            pio,
87            mmio,
88            pci,
89
90            _pio_control: pio_control,
91            _mmio_control: mmio_control,
92        }
93    }
94}
95
96mod inspect_helpers {
97    use super::*;
98
99    pub(crate) fn io_ranges<T>(ranges: &[(Box<str>, RangeInclusive<T>)]) -> impl Inspect + '_
100    where
101        T: std::fmt::LowerHex,
102    {
103        inspect::adhoc(|req| {
104            let mut res = req.respond();
105            for (label, range) in ranges.iter() {
106                let width = size_of::<T>() * 2 + 2;
107                res.field(
108                    &format!("{:#0width$x}-{:#0width$x}", range.start(), range.end()),
109                    label,
110                );
111            }
112        })
113    }
114}
115
116impl ChangeDeviceState for MissingDev {
117    fn start(&mut self) {}
118
119    async fn stop(&mut self) {}
120
121    async fn reset(&mut self) {}
122}
123
124impl ChipsetDevice for MissingDev {
125    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
126        Some(self)
127    }
128
129    fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
130        Some(self)
131    }
132
133    fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> {
134        // this is technically IDET abuse...
135        if self.pci.is_some() { Some(self) } else { None }
136    }
137}
138
139impl SaveRestore for MissingDev {
140    // This device should be constructed with `omit_saved_state`.
141    type SavedState = SavedStateNotSupported;
142
143    fn save(&mut self) -> Result<Self::SavedState, SaveError> {
144        Err(SaveError::NotSupported)
145    }
146
147    fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
148        match state {}
149    }
150}
151
152impl MmioIntercept for MissingDev {
153    fn mmio_read(&mut self, _addr: u64, data: &mut [u8]) -> IoResult {
154        data.fill(!0);
155        IoResult::Ok
156    }
157
158    fn mmio_write(&mut self, _addr: u64, _data: &[u8]) -> IoResult {
159        IoResult::Ok
160    }
161}
162
163impl PortIoIntercept for MissingDev {
164    fn io_read(&mut self, _addr: u16, data: &mut [u8]) -> IoResult {
165        data.fill(!0);
166        IoResult::Ok
167    }
168
169    fn io_write(&mut self, _addr: u16, _data: &[u8]) -> IoResult {
170        IoResult::Ok
171    }
172}
173
174impl PciConfigSpace for MissingDev {
175    fn pci_cfg_read(&mut self, offset: u16, value: &mut u32) -> IoResult {
176        let pci = self.pci.as_ref().unwrap();
177
178        pci_core::cfg_space_emu::ConfigSpaceType0Emulator::new(
179            pci_core::spec::hwid::HardwareIds {
180                vendor_id: pci.vendor_id,
181                device_id: pci.device_id,
182                revision_id: 0,
183                prog_if: ProgrammingInterface::NONE,
184                sub_class: pci_core::spec::hwid::Subclass::NONE,
185                base_class: pci_core::spec::hwid::ClassCode::UNCLASSIFIED,
186                type0_sub_vendor_id: 0,
187                type0_sub_system_id: 0,
188            },
189            vec![],
190            pci_core::cfg_space_emu::DeviceBars::new(),
191        )
192        .read_u32(offset, value)
193    }
194
195    fn pci_cfg_write(&mut self, _offset: u16, _value: u32) -> IoResult {
196        IoResult::Ok
197    }
198
199    fn suggested_bdf(&mut self) -> Option<(u8, u8, u8)> {
200        let pci = self.pci.as_ref().unwrap();
201        Some(pci.bdf)
202    }
203}
204
205/// A [`MissingDev`] builder.
206#[derive(Default)]
207pub struct MissingDevManifest {
208    pio: Vec<(Box<str>, RangeInclusive<u16>)>,
209    mmio: Vec<(Box<str>, RangeInclusive<u64>)>,
210    pci: Option<MissingDevPciMetadata>,
211}
212
213impl MissingDevManifest {
214    /// Create a new [`MissingDevManifest`].
215    pub fn new() -> Self {
216        Default::default()
217    }
218
219    /// Claim the specified Port IO region.
220    pub fn claim_pio(mut self, region_name: &str, range: RangeInclusive<u16>) -> Self {
221        self.pio.push((region_name.into(), range));
222        self
223    }
224
225    /// Claim the specified MMIO region.
226    pub fn claim_mmio(mut self, region_name: &str, range: RangeInclusive<u64>) -> Self {
227        self.mmio.push((region_name.into(), range));
228        self
229    }
230
231    /// Claim the specified PCI slot.
232    pub fn claim_pci(mut self, bdf: (u8, u8, u8), vendor_id: u16, device_id: u16) -> Self {
233        self.pci = Some(MissingDevPciMetadata {
234            bdf,
235            vendor_id,
236            device_id,
237        });
238        self
239    }
240}
241
242pub mod resolver {
243    //! A resolver for [`MissingDevHandle`] resources.
244
245    use crate::MissingDev;
246    use crate::MissingDevManifest;
247    use chipset_device_resources::ResolveChipsetDeviceHandleParams;
248    use chipset_device_resources::ResolvedChipsetDevice;
249    use missing_dev_resources::MissingDevHandle;
250    use std::convert::Infallible;
251    use vm_resource::ResolveResource;
252    use vm_resource::declare_static_resolver;
253    use vm_resource::kind::ChipsetDeviceHandleKind;
254
255    /// A resolver for [`MissingDevHandle`] resources.
256    pub struct MissingDevResolver;
257
258    declare_static_resolver!(
259        MissingDevResolver,
260        (ChipsetDeviceHandleKind, MissingDevHandle)
261    );
262
263    impl ResolveResource<ChipsetDeviceHandleKind, MissingDevHandle> for MissingDevResolver {
264        type Output = ResolvedChipsetDevice;
265        type Error = Infallible;
266
267        fn resolve(
268            &self,
269            resource: MissingDevHandle,
270            input: ResolveChipsetDeviceHandleParams<'_>,
271        ) -> Result<Self::Output, Self::Error> {
272            input.configure.omit_saved_state();
273            let dev = MissingDev::from_manifest(
274                MissingDevManifest {
275                    pio: resource
276                        .pio
277                        .into_iter()
278                        .map(|(name, start, end)| (name.into(), start..=end))
279                        .collect(),
280                    mmio: resource
281                        .mmio
282                        .into_iter()
283                        .map(|(name, start, end)| (name.into(), start..=end))
284                        .collect(),
285                    pci: None,
286                },
287                input.register_mmio,
288                input.register_pio,
289            );
290            Ok(dev.into())
291        }
292    }
293}