Skip to main content

vmotherboard/chipset/backing/arc_mutex/
device.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Infrastructure to wire up [`ChipsetDevice`] instances to the
5//! [`Chipset`](crate::Chipset).
6
7use super::services::ArcMutexChipsetServices;
8use crate::BusIdPci;
9use crate::BusIdPcieDownstreamPort;
10use crate::BusIdPcieEnumerator;
11use crate::VmmChipsetDevice;
12use arc_cyclic_builder::ArcCyclicBuilder;
13use arc_cyclic_builder::ArcCyclicBuilderExt;
14use chipset_device::mmio::RegisterMmioIntercept;
15use chipset_device::pio::RegisterPortIoIntercept;
16use closeable_mutex::CloseableMutex;
17use std::sync::Arc;
18use std::sync::Weak;
19use thiserror::Error;
20use tracing::instrument;
21
22#[derive(Debug, Error)]
23pub(crate) enum AddDeviceErrorKind {
24    #[error("could not construct device")]
25    DeviceError(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
26
27    #[error("no pci bus address provided")]
28    NoPciBusAddress,
29    #[error("no pci bus specified; call on_pci_bus(...) when adding this PCI device")]
30    NoPciBusSpecified,
31    #[error("error finalizing device")]
32    Finalize(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
33}
34
35impl AddDeviceErrorKind {
36    pub(crate) fn with_dev_name(self, dev_name: Arc<str>) -> AddDeviceError {
37        AddDeviceError {
38            dev_name,
39            inner: self,
40        }
41    }
42}
43
44/// Errors that may occur while adding a device to the chipset.
45#[derive(Debug, Error)]
46#[error("could not initialize {dev_name}")]
47pub struct AddDeviceError {
48    dev_name: Arc<str>,
49    #[source]
50    inner: AddDeviceErrorKind,
51}
52
53/// Additional trait implemented by Arc + CloseableMutex [`ChipsetServices`] that gives
54/// the services an opportunity to perform any chipset-specific wiring of the
55/// constructed `Arc<CloseableMutex<T: ChipsetDevice>>`.
56///
57/// This is a separate trait from [`ChipsetServices`] because it is specific to
58/// the ArcMutex infrastructure.
59pub trait ArcMutexChipsetServicesFinalize<T> {
60    /// The error type returned by the `finalize` method.
61    type Error;
62
63    /// Called to finish wiring up the device after it has been completely
64    /// constructed.
65    fn finalize(self, dev: &Arc<CloseableMutex<T>>, name: Arc<str>) -> Result<(), Self::Error>;
66}
67
68/// A builder to streamline the construction of `Arc + CloseableMutex` wrapped
69/// `ChipsetDevice` instances.
70pub struct ArcMutexChipsetDeviceBuilder<'a, 'b, T> {
71    services: ArcMutexChipsetServices<'a, 'b>,
72    arc_builder: ArcCyclicBuilder<CloseableMutex<T>>,
73
74    dev_name: Arc<str>,
75
76    pci_addr: Option<(u8, u8, u8)>,
77    pci_bus_id: Option<BusIdPci>,
78    pcie_port: Option<BusIdPcieDownstreamPort>,
79    pcie_rciep: Option<(BusIdPcieEnumerator, u8)>,
80    external_pci: bool,
81}
82
83impl<'a, 'b, T> ArcMutexChipsetDeviceBuilder<'a, 'b, T>
84where
85    T: VmmChipsetDevice,
86{
87    /// Create a new [`ArcMutexChipsetDeviceBuilder`]
88    pub fn new(
89        name: Arc<str>,
90        new_device_services: impl FnOnce(
91            Weak<CloseableMutex<T>>,
92            Arc<str>,
93        ) -> ArcMutexChipsetServices<'a, 'b>,
94    ) -> Self {
95        let arc_builder: ArcCyclicBuilder<CloseableMutex<T>> = Arc::new_cyclic_builder();
96        let services = (new_device_services)(arc_builder.weak(), name.clone());
97
98        ArcMutexChipsetDeviceBuilder {
99            services,
100            arc_builder,
101
102            dev_name: name,
103
104            pci_addr: None,
105            pci_bus_id: None,
106            pcie_port: None,
107            pcie_rciep: None,
108            external_pci: false,
109        }
110    }
111
112    /// Omit device saved state. Be careful when using this! Currently only used
113    /// for `MissingDev`!
114    pub fn omit_saved_state(mut self) -> Self {
115        self.services.omit_saved_state();
116        self
117    }
118
119    /// For PCI devices: place the device at the following PCI address
120    pub fn with_pci_addr(mut self, bus: u8, device: u8, function: u8) -> Self {
121        self.pci_addr = Some((bus, device, function));
122        self
123    }
124
125    /// For PCI devices: place the device on the specific bus
126    pub fn on_pci_bus(mut self, id: BusIdPci) -> Self {
127        self.pci_bus_id = Some(id);
128        self
129    }
130
131    /// For PCIe devices: place the device on the specified downstream port
132    pub fn on_pcie_port(mut self, id: BusIdPcieDownstreamPort) -> Self {
133        self.pcie_port = Some(id);
134        self
135    }
136
137    /// For PCIe devices: place the device as a Root Complex Integrated
138    /// Endpoint (RCiEP) on the start bus of the specified root complex.
139    ///
140    /// RCiEPs are Type 0 PCI functions that sit directly on the start bus
141    /// alongside root ports, without a downstream port above them (e.g., an
142    /// AMD IOMMU). `devfn` is `device << 3 | function`.
143    pub fn on_pcie_root_complex(mut self, enumerator_id: BusIdPcieEnumerator, devfn: u8) -> Self {
144        self.pcie_rciep = Some((enumerator_id, devfn));
145        self
146    }
147
148    /// For PCI devices: do not register the device with any PCI bus. This is
149    /// used when the device is hooked up to a bus (such as a VPCI bus) outside
150    /// of the vmotherboard infrastructure.
151    pub fn with_external_pci(mut self) -> Self {
152        self.external_pci = true;
153        self
154    }
155
156    fn inner_add(
157        mut self,
158        typed_dev: Result<T, AddDeviceError>,
159    ) -> Result<Arc<CloseableMutex<T>>, AddDeviceError> {
160        let mut typed_dev = typed_dev?;
161
162        if let Some(dev) = typed_dev.supports_mmio() {
163            // static mmio registration
164            for (label, range) in dev.get_static_regions() {
165                self.services
166                    .register_mmio()
167                    .new_io_region(label, range.end() - range.start() + 1)
168                    .map(*range.start());
169            }
170        }
171
172        if let Some(dev) = typed_dev.supports_pio() {
173            // static pio registration
174            for (label, range) in dev.get_static_regions() {
175                self.services
176                    .register_pio()
177                    .new_io_region(label, range.end() - range.start() + 1)
178                    .map(*range.start());
179            }
180        }
181
182        if !self.external_pci {
183            if let Some(dev) = typed_dev.supports_pci() {
184                let bus_options = [
185                    self.pci_bus_id.is_some(),
186                    self.pcie_port.is_some(),
187                    self.pcie_rciep.is_some(),
188                ];
189                if bus_options.iter().filter(|&&v| v).count() > 1 {
190                    panic!(
191                        "wiring error: invoked multiple bus placement methods for `{}`",
192                        self.dev_name
193                    );
194                }
195
196                if let Some(bus_id_port) = self.pcie_port {
197                    self.services.register_static_pcie(bus_id_port);
198                } else if let Some((enumerator_id, devfn)) = self.pcie_rciep {
199                    self.services
200                        .register_static_pcie_rciep(enumerator_id, devfn);
201                } else {
202                    // static pci registration
203                    let bdf = match (self.pci_addr, dev.suggested_bdf()) {
204                        (Some(override_bdf), Some(suggested_bdf)) => {
205                            let (ob, od, of) = override_bdf;
206                            let (sb, sd, sf) = suggested_bdf;
207                            tracing::info!(
208                                "overriding suggested bdf: using {:02x}:{:02x}:{} instead of {:02x}:{:02x}:{}",
209                                ob,
210                                od,
211                                of,
212                                sb,
213                                sd,
214                                sf
215                            );
216                            override_bdf
217                        }
218                        (Some(override_bdf), None) => override_bdf,
219                        (None, Some(suggested_bdf)) => suggested_bdf,
220                        (None, None) => {
221                            return Err(
222                                AddDeviceErrorKind::NoPciBusAddress.with_dev_name(self.dev_name)
223                            );
224                        }
225                    };
226
227                    let bus_id = if let Some(bus_id) = self.pci_bus_id.take() {
228                        bus_id
229                    } else {
230                        return Err(
231                            AddDeviceErrorKind::NoPciBusSpecified.with_dev_name(self.dev_name)
232                        );
233                    };
234
235                    self.services.register_static_pci(bus_id, bdf);
236                }
237            }
238        }
239
240        let dev = self.arc_builder.build(CloseableMutex::new(typed_dev));
241
242        // Now ask the services to finish wiring up the device.
243        self.services
244            .finalize(&dev, self.dev_name.clone())
245            .map_err(|err| AddDeviceErrorKind::Finalize(err.into()).with_dev_name(self.dev_name))?;
246
247        Ok(dev)
248    }
249
250    /// Construct a new device.
251    ///
252    /// If the device can fail during initialization, use
253    /// [`try_add`](Self::try_add) instead.
254    ///
255    /// Includes some basic validation that returns an error if a device
256    /// attempts to use a service without also implementing the service's
257    /// corresponding `ChipsetDevice::supports_` method.
258    #[instrument(name = "add_device", skip_all, fields(device = self.dev_name.as_ref()))]
259    #[expect(clippy::should_implement_trait)]
260    pub fn add<F>(mut self, f: F) -> Result<Arc<CloseableMutex<T>>, AddDeviceError>
261    where
262        F: FnOnce(&mut ArcMutexChipsetServices<'a, 'b>) -> T,
263    {
264        let dev = (f)(&mut self.services);
265        self.inner_add(Ok(dev))
266    }
267
268    /// Just like [`add`](Self::add), except async.
269    #[instrument(name = "add_device", skip_all, fields(device = self.dev_name.as_ref()))]
270    pub async fn add_async<F>(mut self, f: F) -> Result<Arc<CloseableMutex<T>>, AddDeviceError>
271    where
272        F: AsyncFnOnce(&mut ArcMutexChipsetServices<'a, 'b>) -> T,
273    {
274        let dev = (f)(&mut self.services).await;
275        self.inner_add(Ok(dev))
276    }
277
278    /// Just like [`add`](Self::add), except fallible.
279    #[instrument(name = "add_device", skip_all, fields(device = self.dev_name.as_ref()))]
280    pub fn try_add<F, E>(mut self, f: F) -> Result<Arc<CloseableMutex<T>>, AddDeviceError>
281    where
282        F: FnOnce(&mut ArcMutexChipsetServices<'a, 'b>) -> Result<T, E>,
283        E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
284    {
285        let dev = match (f)(&mut self.services) {
286            Ok(dev) => dev,
287            Err(e) => {
288                return Err(AddDeviceErrorKind::DeviceError(e.into()).with_dev_name(self.dev_name));
289            }
290        };
291        self.inner_add(Ok(dev))
292    }
293
294    /// Just like [`try_add`](Self::try_add), except async.
295    #[instrument(name = "add_device", skip_all, fields(device = self.dev_name.as_ref()))]
296    pub async fn try_add_async<F, E>(
297        mut self,
298        f: F,
299    ) -> Result<Arc<CloseableMutex<T>>, AddDeviceError>
300    where
301        F: AsyncFnOnce(&mut ArcMutexChipsetServices<'a, 'b>) -> Result<T, E>,
302        E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
303    {
304        let dev = match (f)(&mut self.services).await {
305            Ok(dev) => dev,
306            Err(e) => {
307                return Err(AddDeviceErrorKind::DeviceError(e.into()).with_dev_name(self.dev_name));
308            }
309        };
310        self.inner_add(Ok(dev))
311    }
312}