Skip to main content

vmm_core/
device_builder.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Functions for resolving and building devices.
5
6use anyhow::Context as _;
7use chipset_device_resources::ErasedChipsetDevice;
8use guestmem::DoorbellRegistration;
9use guestmem::GuestMemory;
10use pci_core::dma::DmaTarget;
11use pci_core::msi::MsiConnection;
12use pci_core::msi::SignalMsi;
13use state_unit::StateUnits;
14use std::sync::Arc;
15use vm_resource::Resource;
16use vm_resource::ResourceResolver;
17use vm_resource::kind::PciDeviceHandleKind;
18use vmbus_server::VmbusServerControl;
19use vmcore::vm_task::VmTaskDriverSource;
20use vmcore::vpci_msi::VpciInterruptMapper;
21use vmotherboard::ArcMutexChipsetDeviceBuilder;
22use vmotherboard::ChipsetBuilder;
23use vmotherboard::ChipsetDevices;
24use vmotherboard::DynamicDeviceUnit;
25
26pub use vpci::bus::VpciBusConfig;
27
28/// Common context for resolving and building a PCI device. These parameters
29/// are shared across PCIe and VPCI device construction.
30pub struct PciDeviceResolveContext<'a> {
31    /// The VM's task driver source.
32    pub driver_source: &'a VmTaskDriverSource,
33    /// The resource resolver.
34    pub resolver: &'a ResourceResolver,
35    /// The device resource to resolve.
36    pub resource: Resource<PciDeviceHandleKind>,
37    /// An object with which to register doorbell regions.
38    pub doorbell_registration: Option<Arc<dyn DoorbellRegistration>>,
39    /// An object with which to register shared memory regions.
40    pub shared_mem_mapper: Option<&'a dyn guestmem::MemoryMapper>,
41}
42
43/// A dynamically created VPCI bus and its underlying PCI device.
44pub struct DynamicVpciDevice {
45    pci_unit: DynamicDeviceUnit,
46    vpci_unit: DynamicDeviceUnit,
47    eject: vpci::bus::VpciBusEject,
48    vpci_bus: Arc<closeable_mutex::CloseableMutex<vpci::bus::VpciBus>>,
49}
50
51impl DynamicVpciDevice {
52    /// Requests device ejection and waits for the guest to acknowledge it.
53    pub async fn eject(&self) -> anyhow::Result<()> {
54        self.eject.eject().await
55    }
56
57    /// Removes the VPCI bus before removing its underlying PCI device.
58    pub async fn remove(self) {
59        self.vpci_bus.close().revoke().await;
60        self.vpci_unit.remove().await;
61        self.pci_unit.remove().await;
62    }
63}
64
65/// Resolves a PCI device resource and dynamically creates a VPCI bus to host it.
66pub async fn build_dynamic_vpci_device(
67    ctx: PciDeviceResolveContext<'_>,
68    vmbus: &VmbusServerControl,
69    chipset_devices: &ChipsetDevices,
70    state_units: &mut StateUnits,
71    bus_config: VpciBusConfig,
72    guest_memory: GuestMemory,
73    new_virtual_device: impl FnOnce(u64) -> anyhow::Result<(Arc<dyn SignalMsi>, VpciInterruptMapper)>,
74) -> anyhow::Result<DynamicVpciDevice> {
75    let instance_id = bus_config.instance_id;
76    let device_name = format!("{}:vpci-{instance_id}", ctx.resource.id());
77    let driver_source = ctx.driver_source;
78    let msi_conn = MsiConnection::new();
79    let dma_target = DmaTarget::new(
80        pci_core::bus_range::AssignedBusRange::new(),
81        0,
82        guest_memory,
83        &msi_conn,
84    );
85
86    let (pci_unit, device) = chipset_devices
87        .add_dyn_device(
88            driver_source,
89            state_units,
90            device_name,
91            async |register_mmio| {
92                ctx.resolver
93                    .resolve(
94                        ctx.resource,
95                        pci_resources::ResolvePciDeviceHandleParams {
96                            dma_target: &dma_target,
97                            register_mmio,
98                            driver_source,
99                            doorbell_registration: ctx.doorbell_registration,
100                            shared_mem_mapper: ctx.shared_mem_mapper,
101                        },
102                    )
103                    .await
104                    .map(|r| r.0)
105                    .map_err(anyhow::Error::from)
106            },
107        )
108        .await?;
109
110    let device_id = (instance_id.data2 as u64) << 16 | (instance_id.data3 as u64 & 0xfff8);
111    let mut pending_offer = None;
112    let vpci_unit = chipset_devices
113        .add_dyn_device(
114            driver_source,
115            state_units,
116            format!("vpci:{instance_id}"),
117            async |register_mmio| {
118                let (msi_controller, interrupt_mapper) =
119                    new_virtual_device(device_id).context(format!(
120                        "failed to create virtual device, device_id {device_id} = {} | {}",
121                        instance_id.data2,
122                        instance_id.data3 as u64 & 0xfff8
123                    ))?;
124                msi_conn.connect(msi_controller);
125                let (bus, offer) = vpci::bus::VpciBus::new_unoffered(
126                    bus_config,
127                    device,
128                    register_mmio,
129                    interrupt_mapper,
130                )
131                .map_err(anyhow::Error::from)?;
132                pending_offer = Some(offer);
133                anyhow::Ok(bus)
134            },
135        )
136        .await;
137
138    let (vpci_unit, vpci_bus) = match vpci_unit {
139        Ok(device) => device,
140        Err(error) => {
141            pci_unit.remove().await;
142            return Err(error);
143        }
144    };
145
146    let pending_offer = pending_offer.context("missing deferred VPCI channel offer")?;
147    state_units.start_stopped_units().await;
148    if let Err(error) = pending_offer
149        .offer_registered(&vpci_bus, driver_source, vmbus, state_units.is_running())
150        .await
151    {
152        vpci_unit.remove().await;
153        pci_unit.remove().await;
154        return Err(error);
155    }
156
157    let eject = vpci_bus.lock().eject_control();
158    Ok(DynamicVpciDevice {
159        pci_unit,
160        vpci_unit,
161        eject,
162        vpci_bus,
163    })
164}
165
166/// Resolves a PCI device resource, builds the corresponding device, and builds
167/// a VPCI bus to host it.
168///
169/// VPCI devices deliver interrupts through the vmbus [`VpciInterruptMapper`]
170/// rather than a PCIe [`MsiTarget`](pci_core::msi::MsiTarget), so this builds a
171/// fresh [`DmaTarget`] pairing `guest_memory` with a locally-owned
172/// [`MsiConnection`] that is connected to the virtual device's MSI controller.
173pub async fn build_vpci_device(
174    ctx: PciDeviceResolveContext<'_>,
175    vmbus: &VmbusServerControl,
176    chipset_builder: &ChipsetBuilder<'_>,
177    bus_config: VpciBusConfig,
178    guest_memory: GuestMemory,
179    new_virtual_device: impl FnOnce(u64) -> anyhow::Result<(Arc<dyn SignalMsi>, VpciInterruptMapper)>,
180) -> anyhow::Result<()> {
181    let instance_id = bus_config.instance_id;
182    let device_name = format!("{}:vpci-{instance_id}", ctx.resource.id());
183    let driver_source = ctx.driver_source;
184
185    let device_builder = chipset_builder
186        .arc_mutex_device(device_name)
187        .with_external_pci();
188
189    let msi_conn = MsiConnection::new();
190
191    let dma_target = DmaTarget::new(
192        pci_core::bus_range::AssignedBusRange::new(),
193        0,
194        guest_memory,
195        &msi_conn,
196    );
197    let device = resolve_and_add_pci_device(device_builder, ctx, &dma_target).await?;
198
199    {
200        let device_id = (instance_id.data2 as u64) << 16 | (instance_id.data3 as u64 & 0xfff8);
201        let vpci_bus_name = format!("vpci:{instance_id}");
202        chipset_builder
203            .arc_mutex_device(vpci_bus_name)
204            .try_add_async(async |services| {
205                let (msi_controller, interrupt_mapper) =
206                    new_virtual_device(device_id).context(format!(
207                        "failed to create virtual device, device_id {device_id} = {} | {}",
208                        instance_id.data2,
209                        instance_id.data3 as u64 & 0xfff8
210                    ))?;
211
212                msi_conn.connect(msi_controller);
213
214                let bus = vpci::bus::VpciBus::new(
215                    driver_source,
216                    bus_config,
217                    device,
218                    &mut services.register_mmio(),
219                    vmbus,
220                    interrupt_mapper,
221                )
222                .await?;
223
224                anyhow::Ok(bus)
225            })
226            .await?;
227    }
228
229    Ok(())
230}
231
232/// Resolves a PCI device resource, builds the corresponding device, and attaches
233/// the device at the specified PCIe port.
234pub async fn build_pcie_device(
235    ctx: PciDeviceResolveContext<'_>,
236    chipset_builder: &ChipsetBuilder<'_>,
237    port_name: Arc<str>,
238    dma_target: &DmaTarget,
239) -> anyhow::Result<()> {
240    let dev_name = format!("pcie:{}-{}", port_name, ctx.resource.id());
241    let device_builder = chipset_builder
242        .arc_mutex_device(dev_name)
243        .on_pcie_port(vmotherboard::BusId::new(&port_name));
244
245    resolve_and_add_pci_device(device_builder, ctx, dma_target).await?;
246
247    Ok(())
248}
249
250/// Resolves a PCI device resource and adds it to the specified chipset device
251/// builder.
252pub async fn resolve_and_add_pci_device(
253    device_builder: ArcMutexChipsetDeviceBuilder<'_, '_, ErasedChipsetDevice>,
254    ctx: PciDeviceResolveContext<'_>,
255    dma_target: &DmaTarget,
256) -> anyhow::Result<Arc<closeable_mutex::CloseableMutex<ErasedChipsetDevice>>> {
257    let device = device_builder
258        .try_add_async(async |services| {
259            ctx.resolver
260                .resolve(
261                    ctx.resource,
262                    pci_resources::ResolvePciDeviceHandleParams {
263                        dma_target,
264                        register_mmio: &mut services.register_mmio(),
265                        driver_source: ctx.driver_source,
266                        doorbell_registration: ctx.doorbell_registration,
267                        shared_mem_mapper: ctx.shared_mem_mapper,
268                    },
269                )
270                .await
271                .map(|r| r.0)
272        })
273        .await?;
274
275    Ok(device)
276}