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