chipset_arc_mutex_device/
device.rs

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