1use 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
28pub struct PciDeviceResolveContext<'a> {
31 pub driver_source: &'a VmTaskDriverSource,
33 pub resolver: &'a ResourceResolver,
35 pub resource: Resource<PciDeviceHandleKind>,
37 pub doorbell_registration: Option<Arc<dyn DoorbellRegistration>>,
39 pub shared_mem_mapper: Option<&'a dyn guestmem::MemoryMapper>,
41}
42
43pub 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 pub async fn eject(&self) -> anyhow::Result<()> {
54 self.eject.eject().await
55 }
56
57 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
65pub 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
166pub 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
232pub 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
250pub 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}