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