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 closeable_mutex::CloseableMutex;
9use guestmem::DoorbellRegistration;
10use guestmem::GuestMemory;
11use pci_core::msi::MsiConnection;
12use pci_core::msi::SignalMsi;
13use std::sync::Arc;
14use vm_resource::Resource;
15use vm_resource::ResourceResolver;
16use vm_resource::kind::PciDeviceHandleKind;
17use vmbus_server::Guid;
18use vmbus_server::VmbusServerControl;
19use vmcore::vm_task::VmTaskDriverSource;
20use vmcore::vpci_msi::VpciInterruptMapper;
21use vmotherboard::ArcMutexChipsetDeviceBuilder;
22use vmotherboard::ChipsetBuilder;
23
24/// Resolves a PCI device resource, builds the corresponding device, and builds
25/// a VPCI bus to host it.
26pub async fn build_vpci_device(
27    driver_source: &VmTaskDriverSource,
28    resolver: &ResourceResolver,
29    guest_memory: &GuestMemory,
30    vmbus: &VmbusServerControl,
31    instance_id: Guid,
32    resource: Resource<PciDeviceHandleKind>,
33    chipset_builder: &mut ChipsetBuilder<'_>,
34    doorbell_registration: Option<Arc<dyn DoorbellRegistration>>,
35    mapper: Option<&dyn guestmem::MemoryMapper>,
36    new_virtual_device: impl FnOnce(u64) -> anyhow::Result<(Arc<dyn SignalMsi>, VpciInterruptMapper)>,
37    vtom: Option<u64>,
38) -> anyhow::Result<()> {
39    let device_name = format!("{}:vpci-{instance_id}", resource.id());
40
41    let device_builder = chipset_builder
42        .arc_mutex_device(device_name)
43        .with_external_pci();
44
45    let (device, msi_conn) = resolve_and_add_pci_device(
46        device_builder,
47        driver_source,
48        resolver,
49        guest_memory,
50        resource,
51        doorbell_registration,
52        mapper,
53    )
54    .await?;
55
56    {
57        let device_id = (instance_id.data2 as u64) << 16 | (instance_id.data3 as u64 & 0xfff8);
58        let vpci_bus_name = format!("vpci:{instance_id}");
59        chipset_builder
60            .arc_mutex_device(vpci_bus_name)
61            .try_add_async(async |services| {
62                let (msi_controller, interrupt_mapper) =
63                    new_virtual_device(device_id).context(format!(
64                        "failed to create virtual device, device_id {device_id} = {} | {}",
65                        instance_id.data2,
66                        instance_id.data3 as u64 & 0xfff8
67                    ))?;
68
69                msi_conn.connect(msi_controller);
70
71                let bus = vpci::bus::VpciBus::new(
72                    driver_source,
73                    instance_id,
74                    device,
75                    &mut services.register_mmio(),
76                    vmbus,
77                    interrupt_mapper,
78                    vtom,
79                )
80                .await?;
81
82                anyhow::Ok(bus)
83            })
84            .await?;
85    }
86
87    Ok(())
88}
89
90/// Resolves a PCI device resource, builds the corresponding device, and attaches
91/// the device at the specified PCIe port.
92pub async fn build_pcie_device(
93    chipset_builder: &mut ChipsetBuilder<'_>,
94    port_name: Arc<str>,
95    driver_source: &VmTaskDriverSource,
96    resolver: &ResourceResolver,
97    guest_memory: &GuestMemory,
98    resource: Resource<PciDeviceHandleKind>,
99    doorbell_registration: Option<Arc<dyn DoorbellRegistration>>,
100    mapper: Option<&dyn guestmem::MemoryMapper>,
101    interrupt_target: Option<Arc<dyn SignalMsi>>,
102) -> anyhow::Result<()> {
103    let dev_name = format!("pcie:{}-{}", port_name, resource.id());
104    let device_builder = chipset_builder
105        .arc_mutex_device(dev_name)
106        .on_pcie_port(vmotherboard::BusId::new(&port_name));
107
108    let (_, msi_conn) = resolve_and_add_pci_device(
109        device_builder,
110        driver_source,
111        resolver,
112        guest_memory,
113        resource,
114        doorbell_registration,
115        mapper,
116    )
117    .await?;
118
119    if let Some(target) = interrupt_target {
120        msi_conn.connect(target);
121    }
122
123    Ok(())
124}
125
126/// Resolves a PCI device resource and adds it to the specified chipset device builder.
127pub async fn resolve_and_add_pci_device(
128    device_builder: ArcMutexChipsetDeviceBuilder<'_, '_, ErasedChipsetDevice>,
129    driver_source: &VmTaskDriverSource,
130    resolver: &ResourceResolver,
131    guest_memory: &GuestMemory,
132    resource: Resource<PciDeviceHandleKind>,
133    doorbell_registration: Option<Arc<dyn DoorbellRegistration>>,
134    mapper: Option<&dyn guestmem::MemoryMapper>,
135) -> anyhow::Result<(Arc<CloseableMutex<ErasedChipsetDevice>>, MsiConnection)> {
136    let msi_conn = MsiConnection::new();
137
138    let device = {
139        device_builder
140            .try_add_async(async |services| {
141                resolver
142                    .resolve(
143                        resource,
144                        pci_resources::ResolvePciDeviceHandleParams {
145                            msi_target: msi_conn.target(),
146                            register_mmio: &mut services.register_mmio(),
147                            driver_source,
148                            guest_memory,
149                            doorbell_registration,
150                            shared_mem_mapper: mapper,
151                        },
152                    )
153                    .await
154                    .map(|r| r.0)
155            })
156            .await?
157    };
158
159    Ok((device, msi_conn))
160}