Skip to main content

vmotherboard/
base_chipset.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! A flexible chipset builder that pre-populates a [`Chipset`](super::Chipset)
5//! with a customizable configuration of semi-standardized device.
6
7use crate::ChipsetDeviceHandle;
8use crate::LegacyPciChipsetDeviceHandle;
9use crate::PowerEvent;
10use crate::chipset::ChipsetBuilder;
11use crate::chipset::backing::arc_mutex::device::AddDeviceError;
12use crate::chipset::backing::arc_mutex::services::ArcMutexChipsetServices;
13use chipset::*;
14use chipset_device::ChipsetDevice;
15#[cfg(any(
16    feature = "dev_generic_isa_floppy",
17    feature = "dev_winbond_super_io_and_floppy_full",
18    feature = "dev_winbond_super_io_and_floppy_stub"
19))]
20use chipset_device::isa_dma::IsaDmaController;
21use chipset_device_resources::ConfigureChipsetDevice;
22use chipset_device_resources::GPE0_LINE_SET;
23use chipset_device_resources::IRQ_LINE_SET;
24use chipset_device_resources::ResolveChipsetDeviceHandleParams;
25use closeable_mutex::CloseableMutex;
26use framebuffer::Framebuffer;
27use framebuffer::FramebufferDevice;
28use framebuffer::FramebufferLocalControl;
29use guestmem::DoorbellRegistration;
30use guestmem::GuestMemory;
31use mesh::MeshPayload;
32use state_unit::StateUnits;
33use std::fmt::Debug;
34use std::sync::Arc;
35use thiserror::Error;
36use vm_resource::Resource;
37use vm_resource::ResourceResolver;
38use vm_resource::kind::IsaDmaControllerHandleKind;
39use vmcore::vm_task::VmTaskDriverSource;
40
41/// Errors which may occur during base chipset construction
42#[expect(missing_docs)] // error enum with self-describing variants
43#[derive(Error, Debug)]
44pub enum BaseChipsetBuilderError {
45    // transparent + from here is fine, since `AddDeviceError`
46    // includes enough context to uniquely identify the source of the error
47    #[error(transparent)]
48    AddDevice(#[from] AddDeviceError),
49    #[error("no valid interrupt controller")]
50    MissingInterruptController,
51    #[error("attempted to add feature-gated device (requires {0})")]
52    FeatureGatedDevice(&'static str),
53    #[error("no valid ISA DMA controller for floppy")]
54    NoDmaForFloppy,
55    #[error("failed to resolve ISA DMA controller")]
56    ResolveIsaDma(#[source] vm_resource::ResolveError),
57    #[error("failed to resolve resource")]
58    ResolveResource(#[source] vm_resource::ResolveError),
59}
60
61/// A grab-bag of device-specific interfaces that may need to be wired up into
62/// upper-layer VMM specific code.
63///
64/// Fields may or may not be present, depending on what devices were
65/// instantiated by the [`BaseChipsetBuilder`]
66#[expect(missing_docs)] // self explanatory field names
67pub struct BaseChipsetDeviceInterfaces {
68    pub framebuffer_local_control: Option<FramebufferLocalControl>,
69}
70
71/// A bundle of goodies the base chipset builder returns.
72pub struct BaseChipsetBuilderOutput<'a> {
73    /// A chipset builder that can be extended with additional devices.
74    pub chipset_builder: ChipsetBuilder<'a>,
75    /// A collection of device-specific interfaces that may need to be wired up
76    /// into upper-layer VMM specific code.
77    pub device_interfaces: BaseChipsetDeviceInterfaces,
78}
79
80/// A builder that kick-starts Chipset construction by instantiating a bunch of
81/// semi-standardized devices.
82///
83/// i.e: we'd rather not maintain two nearly-identical codepaths to instantiate
84/// these devices in both OpenVMM and Underhill.
85pub struct BaseChipsetBuilder<'a> {
86    foundation: options::BaseChipsetFoundation<'a>,
87    devices: options::BaseChipsetDevices,
88    device_handles: Vec<ChipsetDeviceHandle>,
89    pci_device_handles: Vec<LegacyPciChipsetDeviceHandle>,
90    isa_dma_handle: Option<Resource<IsaDmaControllerHandleKind>>,
91    expected_manifest: Option<options::BaseChipsetManifest>,
92    fallback_mmio_device: Option<Arc<CloseableMutex<dyn ChipsetDevice>>>,
93    flags: BaseChipsetBuilderFlags,
94}
95
96struct BaseChipsetBuilderFlags {
97    trace_unknown_pio: bool,
98    trace_unknown_mmio: bool,
99}
100
101impl<'a> BaseChipsetBuilder<'a> {
102    /// Create a new [`BaseChipsetBuilder`]
103    pub fn new(
104        foundation: options::BaseChipsetFoundation<'a>,
105        devices: options::BaseChipsetDevices,
106    ) -> Self {
107        BaseChipsetBuilder {
108            foundation,
109            devices,
110            device_handles: Vec::new(),
111            pci_device_handles: Vec::new(),
112            isa_dma_handle: None,
113            expected_manifest: None,
114            fallback_mmio_device: None,
115            flags: BaseChipsetBuilderFlags {
116                // Legacy OSes have a propensity to blindly access large numbers
117                // of unknown IO ports during boot (e.g: as part of ISA OnP
118                // device probing). As such, VMM implementations that wish to
119                // support Legacy OSes may wish to silence missing pio access
120                // warnings.
121                //
122                // The same is _not_ true for unexpected MMIO intercepts, as a
123                // well-behaved OS shouldn't try to read from unclaimed MMIO.
124                // Such accesses almost certainly indicate that there's a bug
125                // somewhere - be it in our code, or somewhere within the guest.
126                // Certain configurations of the VMM may need to support
127                // emulating on arbitrary MMIO addresses that back assigned
128                // devices, where the address is not known apriori. In such
129                // configurations, provide the option to disable mmio tracing.
130                trace_unknown_pio: false,
131                trace_unknown_mmio: true,
132            },
133        }
134    }
135
136    /// Double-check that the provided [`options::BaseChipsetDevices`] has the
137    /// same devices as specified by `expected_manifest`
138    pub fn with_expected_manifest(
139        mut self,
140        expected_manifest: options::BaseChipsetManifest,
141    ) -> Self {
142        self.expected_manifest = Some(expected_manifest);
143        self
144    }
145
146    /// Adds device handles to be resolved and instantiated.
147    pub fn with_device_handles(mut self, mut device_handles: Vec<ChipsetDeviceHandle>) -> Self {
148        self.device_handles.append(&mut device_handles);
149        self
150    }
151
152    /// Adds legacy PCI device handles to be resolved and instantiated.
153    pub fn with_pci_device_handles(
154        mut self,
155        mut pci_device_handles: Vec<LegacyPciChipsetDeviceHandle>,
156    ) -> Self {
157        self.pci_device_handles.append(&mut pci_device_handles);
158        self
159    }
160
161    /// Sets the ISA DMA controller handle to be resolved and instantiated.
162    pub fn with_isa_dma_handle(
163        mut self,
164        handle: Option<Resource<IsaDmaControllerHandleKind>>,
165    ) -> Self {
166        self.isa_dma_handle = handle;
167        self
168    }
169
170    /// Emit "missing device" traces when accessing unknown port IO addresses.
171    ///
172    /// Disabled by default.
173    pub fn with_trace_unknown_pio(mut self, active: bool) -> Self {
174        self.flags.trace_unknown_pio = active;
175        self
176    }
177
178    /// Emit "missing device" traces when accessing unknown port MMIO addresses.
179    ///
180    /// Enabled by default.
181    pub fn with_trace_unknown_mmio(mut self, active: bool) -> Self {
182        self.flags.trace_unknown_mmio = active;
183        self
184    }
185
186    /// Set a fallback MMIO device to be used when no other device claims an
187    /// address range.
188    pub fn with_fallback_mmio_device(
189        mut self,
190        fallback_mmio_device: Option<Arc<CloseableMutex<dyn ChipsetDevice>>>,
191    ) -> Self {
192        self.fallback_mmio_device = fallback_mmio_device;
193        self
194    }
195
196    /// Create a new base chipset. Returns a [`ChipsetBuilder`] which can be
197    /// extended with additional devices, alongside a collection of
198    /// [`BaseChipsetDeviceInterfaces`] that will need to be wired up by the
199    /// caller.
200    pub async fn build(
201        self,
202        driver_source: &'a VmTaskDriverSource,
203        units: &'a StateUnits,
204        resolver: &ResourceResolver,
205    ) -> Result<BaseChipsetBuilderOutput<'a>, BaseChipsetBuilderError> {
206        let Self {
207            foundation,
208            devices,
209            device_handles,
210            pci_device_handles,
211            isa_dma_handle,
212            expected_manifest,
213            fallback_mmio_device,
214            flags,
215        } = self;
216
217        let manifest = devices.to_manifest();
218        if let Some(expected_manifest) = expected_manifest {
219            assert_eq!(expected_manifest, manifest, "manifests do not match");
220        }
221
222        let mut device_interfaces = BaseChipsetDeviceInterfaces {
223            framebuffer_local_control: None,
224        };
225
226        let builder = ChipsetBuilder::new(
227            driver_source,
228            units,
229            foundation.debug_event_handler.clone(),
230            foundation.vmtime,
231            foundation.vmtime_unit,
232            flags.trace_unknown_pio,
233            flags.trace_unknown_mmio,
234            fallback_mmio_device,
235        );
236
237        // oh boy, time to build all the devices!
238        let options::BaseChipsetDevices {
239            deps_generic_cmos_rtc,
240            deps_generic_isa_floppy,
241            deps_generic_pci_bus,
242            deps_generic_psp: _, // not actually a device... yet
243            deps_hyperv_firmware_pcat,
244            deps_hyperv_framebuffer,
245            deps_hyperv_ide,
246            deps_hyperv_vga,
247            deps_piix4_cmos_rtc,
248            deps_piix4_pci_bus,
249            deps_underhill_vga_proxy,
250            deps_winbond_super_io_and_floppy_stub,
251            deps_winbond_super_io_and_floppy_full,
252        } = devices;
253
254        if let Some(options::dev::GenericPciBusDeps {
255            bus_id,
256            pio_addr,
257            pio_data,
258        }) = deps_generic_pci_bus
259        {
260            let pci = builder.arc_mutex_device("pci_bus").add(|services| {
261                pci_bus::GenericPciBus::new(&mut services.register_pio(), pio_addr, pio_data)
262            })?;
263
264            builder.register_weak_mutex_pci_bus(bus_id, Box::new(pci));
265        }
266
267        if let Some(options::dev::Piix4PciBusDeps { bus_id }) = deps_piix4_pci_bus {
268            // TODO: use PowerRequestHandleKind
269            let reset = {
270                let power = foundation.power_event_handler.clone();
271                Box::new(move || power.on_power_event(PowerEvent::Reset))
272            };
273
274            let pci = builder.arc_mutex_device("piix4-pci-bus").add(|services| {
275                chipset_legacy::piix4_pci_bus::Piix4PciBus::new(
276                    &mut services.register_pio(),
277                    reset.clone(),
278                )
279            })?;
280            builder.register_weak_mutex_pci_bus(bus_id, Box::new(pci));
281        }
282
283        let dma = if let Some(dma_handle) = isa_dma_handle {
284            let resolved = resolver
285                .resolve(dma_handle, ())
286                .await
287                .map_err(BaseChipsetBuilderError::ResolveIsaDma)?;
288            let dev = builder
289                .arc_mutex_device::<dma::DmaController>("dma")
290                .add(|_services| resolved.0)?;
291            Some(dev)
292        } else {
293            None
294        };
295
296        for device in device_handles {
297            let ChipsetDeviceHandle { name, resource } = device;
298            builder
299                .arc_mutex_device(name.as_ref())
300                .try_add_async(async |services| {
301                    resolver
302                        .resolve(
303                            resource,
304                            ResolveChipsetDeviceHandleParams {
305                                device_name: name.as_ref(),
306                                guest_memory: &foundation.untrusted_dma_memory,
307                                encrypted_guest_memory: &foundation.trusted_vtl0_dma_memory,
308                                vmtime: foundation.vmtime,
309                                is_restoring: foundation.is_restoring,
310                                task_driver_source: driver_source,
311                                register_mmio: &mut services.register_mmio(),
312                                register_pio: &mut services.register_pio(),
313                                configure: services,
314                            },
315                        )
316                        .await
317                        .map(|device| device.0)
318                })
319                .await?;
320        }
321
322        let _ = dma;
323        #[cfg(feature = "dev_generic_isa_floppy")]
324        if let Some(options::dev::GenericIsaFloppyDeps {
325            irq,
326            dma_channel: dma_chan,
327            pio_base,
328            drives,
329        }) = deps_generic_isa_floppy
330        {
331            if let Some(dma) = &dma {
332                let dma_channel = ArcMutexIsaDmaChannel::new(dma.clone(), dma_chan);
333
334                builder.arc_mutex_device("floppy").try_add(|services| {
335                    let interrupt = services.new_line(IRQ_LINE_SET, "interrupt", irq);
336                    floppy::FloppyDiskController::new(
337                        foundation.untrusted_dma_memory.clone(),
338                        interrupt,
339                        &mut services.register_pio(),
340                        pio_base,
341                        drives,
342                        Box::new(dma_channel),
343                    )
344                })?;
345            } else {
346                return Err(BaseChipsetBuilderError::NoDmaForFloppy);
347            }
348        }
349
350        #[cfg(feature = "dev_winbond_super_io_and_floppy_full")]
351        if let Some(options::dev::WinbondSuperIoAndFloppyFullDeps {
352            primary_disk_drive,
353            secondary_disk_drive,
354        }) = deps_winbond_super_io_and_floppy_full
355        {
356            if let Some(dma) = &dma {
357                // IRQ and DMA channel assignment MUST match the values reported
358                // by the PCAT BIOS ACPI tables, and the Super IO emulator.
359                let primary_dma = Box::new(ArcMutexIsaDmaChannel::new(dma.clone(), 2));
360                let secondary_dma = Box::new(vmcore::isa_dma_channel::FloatingDmaChannel);
361
362                builder.arc_mutex_device("floppy-sio").try_add(|services| {
363                    let interrupt = services.new_line(IRQ_LINE_SET, "interrupt", 6);
364                    chipset_legacy::winbond83977_sio::Winbond83977FloppySioDevice::<
365                        floppy::FloppyDiskController,
366                    >::new(
367                        foundation.untrusted_dma_memory.clone(),
368                        interrupt,
369                        &mut services.register_pio(),
370                        primary_disk_drive,
371                        secondary_disk_drive,
372                        primary_dma,
373                        secondary_dma,
374                    )
375                })?;
376            } else {
377                return Err(BaseChipsetBuilderError::NoDmaForFloppy);
378            }
379        }
380
381        #[cfg(feature = "dev_winbond_super_io_and_floppy_stub")]
382        if let Some(options::dev::WinbondSuperIoAndFloppyStubDeps) =
383            deps_winbond_super_io_and_floppy_stub
384        {
385            if let Some(dma) = &dma {
386                // IRQ and DMA channel assignment MUST match the values reported
387                // by the PCAT BIOS ACPI tables, and the Super IO emulator.
388                let primary_dma = Box::new(ArcMutexIsaDmaChannel::new(dma.clone(), 2));
389                let secondary_dma = Box::new(vmcore::isa_dma_channel::FloatingDmaChannel);
390
391                builder.arc_mutex_device("floppy-sio").try_add(|services| {
392                    let interrupt = services.new_line(IRQ_LINE_SET, "interrupt", 6);
393                    chipset_legacy::winbond83977_sio::Winbond83977FloppySioDevice::<
394                        floppy_pcat_stub::StubFloppyDiskController,
395                    >::new(
396                        foundation.untrusted_dma_memory.clone(),
397                        interrupt,
398                        &mut services.register_pio(),
399                        floppy::DriveRibbon::None,
400                        floppy::DriveRibbon::None,
401                        primary_dma,
402                        secondary_dma,
403                    )
404                })?;
405            } else {
406                return Err(BaseChipsetBuilderError::NoDmaForFloppy);
407            }
408        }
409
410        if let Some(options::dev::HyperVIdeDeps {
411            attached_to,
412            primary_channel_drives,
413            secondary_channel_drives,
414        }) = deps_hyperv_ide
415        {
416            builder
417                .arc_mutex_device("ide")
418                .on_pci_bus(attached_to)
419                .try_add(|services| {
420                    // hard-coded to iRQ lines 14 and 15, as per PIIX4 spec
421                    let primary_channel_line_interrupt =
422                        services.new_line(IRQ_LINE_SET, "ide1", 14);
423                    let secondary_channel_line_interrupt =
424                        services.new_line(IRQ_LINE_SET, "ide2", 15);
425                    ide::IdeDevice::new(
426                        foundation.untrusted_dma_memory.clone(),
427                        &mut services.register_pio(),
428                        primary_channel_drives,
429                        secondary_channel_drives,
430                        primary_channel_line_interrupt,
431                        secondary_channel_line_interrupt,
432                    )
433                })?;
434        }
435
436        if let Some(options::dev::GenericCmosRtcDeps {
437            irq,
438            time_source,
439            century_reg_idx,
440            initial_cmos,
441        }) = deps_generic_cmos_rtc
442        {
443            let resolved = resolver
444                .resolve(time_source, ())
445                .await
446                .map_err(BaseChipsetBuilderError::ResolveResource)?;
447            builder.arc_mutex_device("rtc").add(|services| {
448                cmos_rtc::Rtc::new(
449                    resolved.0,
450                    services.new_line(IRQ_LINE_SET, "interrupt", irq),
451                    services.register_vmtime(),
452                    century_reg_idx,
453                    initial_cmos,
454                    false,
455                )
456            })?;
457        }
458
459        if let Some(options::dev::Piix4CmosRtcDeps {
460            time_source,
461            initial_cmos,
462            enlightened_interrupts,
463        }) = deps_piix4_cmos_rtc
464        {
465            let resolved = resolver
466                .resolve(time_source, ())
467                .await
468                .map_err(BaseChipsetBuilderError::ResolveResource)?;
469            builder.arc_mutex_device("piix4-rtc").add(|services| {
470                // hard-coded to IRQ line 8, as per PIIX4 spec
471                let rtc_interrupt = services.new_line(IRQ_LINE_SET, "interrupt", 8);
472                chipset_legacy::piix4_cmos_rtc::Piix4CmosRtc::new(
473                    resolved.0,
474                    rtc_interrupt,
475                    services.register_vmtime(),
476                    initial_cmos,
477                    enlightened_interrupts,
478                )
479            })?;
480        }
481
482        // The ACPI GPE0 line to use for generation ID. This must match the
483        // value in the DSDT.
484        const GPE0_LINE_GENERATION_ID: u32 = 0;
485
486        if let Some(options::dev::HyperVFirmwarePcat {
487            config,
488            logger,
489            generation_id_recv,
490            rom,
491            replay_mtrrs,
492        }) = deps_hyperv_firmware_pcat
493        {
494            builder.arc_mutex_device("pcat").try_add(|services| {
495                let notify_interrupt =
496                    services.new_line(GPE0_LINE_SET, "genid", GPE0_LINE_GENERATION_ID);
497                firmware_pcat::PcatBiosDevice::new(
498                    firmware_pcat::PcatBiosRuntimeDeps {
499                        gm: foundation.trusted_vtl0_dma_memory.clone(),
500                        logger,
501                        generation_id_deps: generation_id::GenerationIdRuntimeDeps {
502                            generation_id_recv,
503                            gm: foundation.trusted_vtl0_dma_memory.clone(),
504                            notify_interrupt,
505                        },
506                        vmtime: services.register_vmtime(),
507                        rom,
508                        register_pio: &mut services.register_pio(),
509                        replay_mtrrs,
510                    },
511                    config,
512                )
513            })?;
514        }
515
516        if let Some(options::dev::HyperVFramebufferDeps {
517            fb_mapper,
518            fb,
519            vtl2_framebuffer_gpa_base,
520        }) = deps_hyperv_framebuffer
521        {
522            let fb = FramebufferDevice::new(fb_mapper, fb, vtl2_framebuffer_gpa_base);
523            let control = fb.as_ref().ok().map(|fb| fb.control());
524            builder.arc_mutex_device("fb").try_add(|_| fb)?;
525            device_interfaces.framebuffer_local_control = Some(control.unwrap());
526        }
527
528        #[cfg(feature = "dev_hyperv_vga")]
529        if let Some(options::dev::HyperVVgaDeps { attached_to, rom }) = deps_hyperv_vga {
530            builder
531                .arc_mutex_device("vga")
532                .on_pci_bus(attached_to)
533                .try_add(|services| {
534                    vga::VgaDevice::new(
535                        &driver_source.simple(),
536                        services.register_vmtime(),
537                        device_interfaces.framebuffer_local_control.clone().unwrap(),
538                        rom,
539                    )
540                })?;
541        }
542
543        #[cfg(feature = "dev_underhill_vga_proxy")]
544        if let Some(options::dev::UnderhillVgaProxyDeps {
545            attached_to,
546            pci_cfg_proxy,
547            register_host_io_fastpath,
548        }) = deps_underhill_vga_proxy
549        {
550            builder
551                .arc_mutex_device("vga_proxy")
552                .on_pci_bus(attached_to)
553                .add(|_services| {
554                    vga_proxy::VgaProxyDevice::new(pci_cfg_proxy, &*register_host_io_fastpath)
555                })?;
556        }
557
558        macro_rules! feature_gate_check {
559            ($feature:literal, $dep:ident) => {
560                #[cfg(not(feature = $feature))]
561                let None::<()> = $dep else {
562                    return Err(BaseChipsetBuilderError::FeatureGatedDevice($feature));
563                };
564            };
565        }
566
567        feature_gate_check!("dev_hyperv_vga", deps_hyperv_vga);
568        feature_gate_check!("dev_underhill_vga_proxy", deps_underhill_vga_proxy);
569        feature_gate_check!("dev_generic_isa_floppy", deps_generic_isa_floppy);
570        feature_gate_check!(
571            "dev_winbond_super_io_and_floppy_full",
572            deps_winbond_super_io_and_floppy_full
573        );
574        feature_gate_check!(
575            "dev_winbond_super_io_and_floppy_stub",
576            deps_winbond_super_io_and_floppy_stub
577        );
578
579        for device in pci_device_handles {
580            let LegacyPciChipsetDeviceHandle {
581                name,
582                resource,
583                pci_bus_name,
584                bdf,
585            } = device;
586
587            let (bus, slot, function) = bdf;
588
589            builder
590                .arc_mutex_device(name.as_ref())
591                .on_pci_bus(crate::BusId::new(pci_bus_name.as_str()))
592                .with_pci_addr(bus, slot, function)
593                .try_add_async(async |services| {
594                    resolver
595                        .resolve(
596                            resource,
597                            ResolveChipsetDeviceHandleParams {
598                                device_name: name.as_ref(),
599                                guest_memory: &foundation.untrusted_dma_memory,
600                                encrypted_guest_memory: &foundation.trusted_vtl0_dma_memory,
601                                vmtime: foundation.vmtime,
602                                is_restoring: foundation.is_restoring,
603                                task_driver_source: driver_source,
604                                register_mmio: &mut services.register_mmio(),
605                                register_pio: &mut services.register_pio(),
606                                configure: services,
607                            },
608                        )
609                        .await
610                        .map(|dev| dev.0)
611                })
612                .await?;
613        }
614
615        Ok(BaseChipsetBuilderOutput {
616            chipset_builder: builder,
617            device_interfaces,
618        })
619    }
620}
621
622impl ConfigureChipsetDevice for ArcMutexChipsetServices<'_, '_> {
623    fn new_line(
624        &mut self,
625        id: chipset_device_resources::LineSetId,
626        name: &str,
627        vector: u32,
628    ) -> vmcore::line_interrupt::LineInterrupt {
629        self.new_line(id, name, vector)
630    }
631
632    fn add_line_target(
633        &mut self,
634        id: chipset_device_resources::LineSetId,
635        source_range: std::ops::RangeInclusive<u32>,
636        target_start: u32,
637    ) {
638        self.add_line_target(id, source_range, target_start)
639    }
640
641    fn omit_saved_state(&mut self) {
642        self.omit_saved_state();
643    }
644}
645
646mod weak_mutex_pci {
647    use crate::chipset::PciConflict;
648    use crate::chipset::PciConflictReason;
649    use crate::chipset::PcieConflict;
650    use crate::chipset::PcieConflictReason;
651    use crate::chipset::backing::arc_mutex::pci::RegisterWeakMutexPci;
652    use crate::chipset::backing::arc_mutex::pci::RegisterWeakMutexPcie;
653    use chipset_device::ChipsetDevice;
654    use chipset_device::io::IoResult;
655    use chipset_device::pci::ByteEnabledDwordRead;
656    use chipset_device::pci::ByteEnabledDwordWrite;
657    use chipset_device::pci::PciConfigAccessType;
658    use chipset_device::pci::PciConfigAddress;
659    use closeable_mutex::CloseableMutex;
660    use pci_bus::GenericPciBusDevice;
661    use std::sync::Arc;
662    use std::sync::Weak;
663
664    /// Wrapper around `Weak<CloseableMutex<dyn ChipsetDevice>>` that implements
665    /// [`GenericPciBusDevice`]
666    pub struct WeakMutexPciDeviceWrapper(Weak<CloseableMutex<dyn ChipsetDevice>>);
667
668    impl GenericPciBusDevice for WeakMutexPciDeviceWrapper {
669        fn pci_cfg_read(
670            &mut self,
671            offset: u16,
672            value: ByteEnabledDwordRead<'_>,
673        ) -> Option<IoResult> {
674            Some(
675                self.0
676                    .upgrade()?
677                    .lock()
678                    .supports_pci()
679                    .expect("builder code ensures supports_pci.is_some()")
680                    .pci_cfg_read(offset, value),
681            )
682        }
683
684        fn pci_cfg_write(&mut self, offset: u16, value: ByteEnabledDwordWrite) -> Option<IoResult> {
685            Some(
686                self.0
687                    .upgrade()?
688                    .lock()
689                    .supports_pci()
690                    .expect("builder code ensures supports_pci.is_some()")
691                    .pci_cfg_write(offset, value),
692            )
693        }
694
695        fn pci_cfg_read_with_routing(
696            &mut self,
697            access_type: PciConfigAccessType,
698            address: PciConfigAddress,
699            value: ByteEnabledDwordRead<'_>,
700        ) -> Option<IoResult> {
701            Some(
702                self.0
703                    .upgrade()?
704                    .lock()
705                    .supports_pci()
706                    .expect("builder code ensures supports_pci.is_some()")
707                    .pci_cfg_read_with_routing(access_type, address, value),
708            )
709        }
710
711        fn pci_cfg_write_with_routing(
712            &mut self,
713            access_type: PciConfigAccessType,
714            address: PciConfigAddress,
715            value: ByteEnabledDwordWrite,
716        ) -> Option<IoResult> {
717            Some(
718                self.0
719                    .upgrade()?
720                    .lock()
721                    .supports_pci()
722                    .expect("builder code ensures supports_pci.is_some()")
723                    .pci_cfg_write_with_routing(access_type, address, value),
724            )
725        }
726    }
727
728    // wiring to enable using the generic PCI bus alongside the Arc+CloseableMutex device infra
729    impl RegisterWeakMutexPci for Arc<CloseableMutex<pci_bus::GenericPciBus>> {
730        fn add_pci_device(
731            &mut self,
732            bus: u8,
733            device: u8,
734            function: u8,
735            name: Arc<str>,
736            dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
737        ) -> Result<(), PciConflict> {
738            self.lock()
739                .add_pci_device(
740                    bus,
741                    device,
742                    function,
743                    name.clone(),
744                    WeakMutexPciDeviceWrapper(dev),
745                )
746                .map_err(|occ_err| PciConflict {
747                    bdf: (bus, device, function),
748                    reason: PciConflictReason::ExistingDev(occ_err.existing_device_name),
749                    conflict_dev: name,
750                })
751        }
752    }
753
754    // wiring to enable using the PIIX4 PCI bus alongside the Arc+CloseableMutex device infra
755    impl RegisterWeakMutexPci for Arc<CloseableMutex<chipset_legacy::piix4_pci_bus::Piix4PciBus>> {
756        fn add_pci_device(
757            &mut self,
758            bus: u8,
759            device: u8,
760            function: u8,
761            name: Arc<str>,
762            dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
763        ) -> Result<(), PciConflict> {
764            self.lock()
765                .as_pci_bus()
766                .add_pci_device(
767                    bus,
768                    device,
769                    function,
770                    name.clone(),
771                    WeakMutexPciDeviceWrapper(dev),
772                )
773                .map_err(|occ_err| PciConflict {
774                    bdf: (bus, device, function),
775                    reason: PciConflictReason::ExistingDev(occ_err.existing_device_name),
776                    conflict_dev: name,
777                })
778        }
779    }
780
781    // wiring to enable using the generic PCIe root port alongside the Arc+CloseableMutex device infra
782    impl RegisterWeakMutexPcie for Arc<CloseableMutex<pcie::root::GenericPcieRootComplex>> {
783        fn add_pcie_device(
784            &mut self,
785            port_devfn: u8,
786            name: Arc<str>,
787            dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
788        ) -> Result<(), PcieConflict> {
789            self.lock()
790                .add_pcie_device(
791                    port_devfn,
792                    name.clone(),
793                    Box::new(WeakMutexPciDeviceWrapper(dev)),
794                )
795                .map_err(|existing_dev_name| PcieConflict {
796                    reason: PcieConflictReason::ExistingDev(existing_dev_name),
797                    conflict_dev: name,
798                })
799        }
800
801        fn downstream_ports(&self) -> Vec<pcie::root::DownstreamPortInfo> {
802            self.lock().downstream_ports()
803        }
804
805        fn add_rciep(
806            &mut self,
807            devfn: u8,
808            name: Arc<str>,
809            dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
810        ) -> Result<(), PcieConflict> {
811            self.lock()
812                .add_rciep(
813                    devfn,
814                    name.clone(),
815                    Box::new(WeakMutexPciDeviceWrapper(dev)),
816                )
817                .map_err(|existing_dev_name| PcieConflict {
818                    reason: PcieConflictReason::ExistingDev(existing_dev_name),
819                    conflict_dev: name,
820                })
821        }
822    }
823
824    // wiring to enable using the PCIe switch alongside the Arc+CloseableMutex device infra
825    impl RegisterWeakMutexPcie for Arc<CloseableMutex<pcie::switch::GenericPcieSwitch>> {
826        fn add_pcie_device(
827            &mut self,
828            port_devfn: u8,
829            name: Arc<str>,
830            dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
831        ) -> Result<(), PcieConflict> {
832            self.lock()
833                .add_pcie_device(port_devfn, &name, Box::new(WeakMutexPciDeviceWrapper(dev)))
834                .map_err(|err| PcieConflict {
835                    reason: PcieConflictReason::ExistingDev(err.to_string().into()),
836                    conflict_dev: name,
837                })
838        }
839
840        fn downstream_ports(&self) -> Vec<pcie::root::DownstreamPortInfo> {
841            self.lock().downstream_ports()
842        }
843    }
844}
845
846#[cfg(any(
847    feature = "dev_generic_isa_floppy",
848    feature = "dev_winbond_super_io_and_floppy_full",
849    feature = "dev_winbond_super_io_and_floppy_stub"
850))]
851pub struct ArcMutexIsaDmaChannel {
852    channel_num: u8,
853    dma: Arc<CloseableMutex<dma::DmaController>>,
854}
855
856#[cfg(any(
857    feature = "dev_generic_isa_floppy",
858    feature = "dev_winbond_super_io_and_floppy_full",
859    feature = "dev_winbond_super_io_and_floppy_stub"
860))]
861impl ArcMutexIsaDmaChannel {
862    pub fn new(dma: Arc<CloseableMutex<dma::DmaController>>, channel_num: u8) -> Self {
863        Self { dma, channel_num }
864    }
865}
866
867#[cfg(any(
868    feature = "dev_generic_isa_floppy",
869    feature = "dev_winbond_super_io_and_floppy_full",
870    feature = "dev_winbond_super_io_and_floppy_stub"
871))]
872impl vmcore::isa_dma_channel::IsaDmaChannel for ArcMutexIsaDmaChannel {
873    fn check_transfer_size(&mut self) -> u16 {
874        self.dma.lock().check_transfer_size(self.channel_num.into())
875    }
876
877    fn request(
878        &mut self,
879        direction: vmcore::isa_dma_channel::IsaDmaDirection,
880    ) -> Option<vmcore::isa_dma_channel::IsaDmaBuffer> {
881        self.dma.lock().request(self.channel_num.into(), direction)
882    }
883
884    fn complete(&mut self) {
885        self.dma.lock().complete(self.channel_num.into())
886    }
887}
888
889/// [`BaseChipsetBuilder`] options and configuration
890pub mod options {
891    use super::*;
892    use state_unit::UnitHandle;
893    use vmcore::vmtime::VmTimeSource;
894
895    /// Foundational `BaseChipset` dependencies (read: not device-specific)
896    #[expect(missing_docs)] // self explanatory field names
897    pub struct BaseChipsetFoundation<'a> {
898        pub is_restoring: bool,
899        /// Guest memory access for untrusted devices.
900        ///
901        /// This should provide access only to memory that is also accessible by
902        /// the host. This applies to most devices, where the guest does not
903        /// expect that they are implemented by a paravisor.
904        ///
905        /// If a device incorrectly uses this instead of
906        /// `trusted_vtl0_dma_memory`, then it will likely see failures when
907        /// accessing guest memory in confidential VM configurations. A
908        /// malicious host could additionally use this conspire to observe
909        /// trusted device interactions.
910        pub untrusted_dma_memory: GuestMemory,
911        /// Guest memory access for trusted devices.
912        ///
913        /// This should provide access to all of VTL0 memory (but not VTL1
914        /// memory). This applies to devices that the guest expects to be
915        /// implemented by a paravisor, such as security and firmware devices.
916        ///
917        /// If a device incorrectly uses this instead of `untrusted_dma_memory`,
918        /// then it will likely see failures when accessing guest memory in
919        /// confidential VM configurations. If the device is under control of a
920        /// malicious host in some way, this could also lead to the host
921        /// observing encrypted memory.
922        pub trusted_vtl0_dma_memory: GuestMemory,
923        pub power_event_handler: Arc<dyn crate::PowerEventHandler>,
924        pub debug_event_handler: Arc<dyn crate::DebugEventHandler>,
925        pub vmtime: &'a VmTimeSource,
926        pub vmtime_unit: &'a UnitHandle,
927        pub doorbell_registration: Option<Arc<dyn DoorbellRegistration>>,
928    }
929
930    macro_rules! base_chipset_devices_and_manifest {
931        (
932            // doing this kind of "pseudo-syntax" isn't strictly necessary, but
933            // it serves as a nice bit of visual ✨flair✨ that makes it easier
934            // to grok what the macro is actually emitting
935            impls {
936                $(#[$m:meta])*
937                pub struct $base_chipset_devices:ident {
938                    ...
939                }
940
941                $(#[$m2:meta])*
942                pub struct $base_chipset_manifest:ident {
943                    ...
944                }
945            }
946
947            devices {
948                $($name:ident: $ty:ty,)*
949            }
950        ) => {paste::paste!{
951            $(#[$m])*
952            pub struct $base_chipset_devices {
953                $(pub [<deps_ $name>]: Option<$ty>,)*
954            }
955
956            $(#[$m2])*
957            pub struct $base_chipset_manifest {
958                $(pub [<with_ $name>]: bool,)*
959            }
960
961            impl $base_chipset_manifest {
962                /// Return a [`BaseChipsetManifest`] with all fields set to
963                /// `false`
964                pub const fn empty() -> Self {
965                    Self {
966                        $([<with_ $name>]: false,)*
967                    }
968                }
969            }
970
971            impl $base_chipset_devices {
972                /// Return a [`BaseChipsetDevices`] with all fields set to
973                /// `None`
974                pub fn empty() -> Self {
975                    Self {
976                        $([<deps_ $name>]: None,)*
977                    }
978                }
979
980                /// Return the corresponding [`BaseChipsetManifest`].
981                pub fn to_manifest(&self) -> $base_chipset_manifest {
982                    let Self {
983                        $([<deps_ $name>],)*
984                    } = self;
985
986                    $base_chipset_manifest {
987                        $([<with_ $name>]: [<deps_ $name>].is_some(),)*
988                    }
989                }
990            }
991        }};
992    }
993
994    base_chipset_devices_and_manifest! {
995        impls {
996            /// Device-specific `BaseChipset` dependencies
997            #[expect(missing_docs)] // self explanatory field names
998            pub struct BaseChipsetDevices {
999                // generated struct has fields that look like this:
1000                //
1001                // deps_<device>: Option<dev::<Deps>>,
1002                ...
1003            }
1004
1005            /// A manifest of devices specified by [`BaseChipsetDevices`].
1006            #[expect(missing_docs)] // self explanatory field names
1007            #[derive(Debug, Clone, MeshPayload, PartialEq, Eq)]
1008            pub struct BaseChipsetManifest {
1009                // generated struct has fields that look like this:
1010                //
1011                // with_<device>: bool,
1012                ...
1013            }
1014        }
1015
1016        devices {
1017            generic_cmos_rtc:            dev::GenericCmosRtcDeps,
1018            generic_isa_floppy:          dev::GenericIsaFloppyDeps,
1019            generic_pci_bus:             dev::GenericPciBusDeps,
1020            generic_psp:                 dev::GenericPspDeps,
1021
1022            hyperv_firmware_pcat:        dev::HyperVFirmwarePcat,
1023            hyperv_framebuffer:          dev::HyperVFramebufferDeps,
1024            hyperv_ide:                  dev::HyperVIdeDeps,
1025            hyperv_vga:                  dev::HyperVVgaDeps,
1026
1027            piix4_cmos_rtc:              dev::Piix4CmosRtcDeps,
1028            piix4_pci_bus:               dev::Piix4PciBusDeps,
1029
1030            underhill_vga_proxy:         dev::UnderhillVgaProxyDeps,
1031
1032            winbond_super_io_and_floppy_stub: dev::WinbondSuperIoAndFloppyStubDeps,
1033            winbond_super_io_and_floppy_full: dev::WinbondSuperIoAndFloppyFullDeps,
1034        }
1035    }
1036
1037    /// Derived capabilities for the configured chipset devices.
1038    #[derive(MeshPayload, Debug, Copy, Clone)]
1039    pub struct VmChipsetCapabilities {
1040        /// Whether the VM exposes an IOAPIC.
1041        pub with_ioapic: bool,
1042        /// Whether the VM exposes a legacy PIC.
1043        pub with_pic: bool,
1044        /// Whether the VM exposes a PIT.
1045        pub with_pit: bool,
1046        /// Whether the VM exposes a generic ISA DMA controller.
1047        pub with_generic_isa_dma: bool,
1048        /// Whether the VM exposes a PSP.
1049        pub with_psp: bool,
1050        /// Whether the VM exposes the Hyper-V guest watchdog device.
1051        pub with_guest_watchdog: bool,
1052        /// Whether the VM exposes an i440BX Host-PCI Bridge (Gen1 legacy PCI bus).
1053        pub with_i440bx_host_pci_bridge: bool,
1054    }
1055
1056    /// Device specific dependencies
1057    pub mod dev {
1058        use super::*;
1059        use crate::BusIdPci;
1060
1061        macro_rules! feature_gated {
1062            (
1063                feature = $feat:literal;
1064
1065                $(#[$m:meta])*
1066                pub struct $root_deps:ident $($rest:tt)*
1067            ) => {
1068                #[cfg(not(feature = $feat))]
1069                #[doc(hidden)]
1070                pub type $root_deps = ();
1071
1072                #[cfg(feature = $feat)]
1073                $(#[$m])*
1074                pub struct $root_deps $($rest)*
1075            };
1076        }
1077
1078        /// Hyper-V IDE controller (fixed pci address: 0:7.1)
1079        // TODO: this device needs to be broken down further, into a PIIX4 IDE
1080        // device (without the Hyper-V enlightenments), and then a Generic IDE
1081        // device (without any of the PIIX4 bus mastering stuff).
1082        pub struct HyperVIdeDeps {
1083            /// `vmotherboard` bus identifier
1084            pub attached_to: BusIdPci,
1085            /// Drives attached to the primary IDE channel
1086            pub primary_channel_drives: [Option<ide::DriveMedia>; 2],
1087            /// Drives attached to the secondary IDE channel
1088            pub secondary_channel_drives: [Option<ide::DriveMedia>; 2],
1089        }
1090
1091        /// AMD Platform Security Processor (PSP)
1092        pub struct GenericPspDeps;
1093
1094        feature_gated! {
1095            feature = "dev_generic_isa_floppy";
1096
1097            /// Generic ISA floppy controller
1098            pub struct GenericIsaFloppyDeps {
1099                /// IRQ line shared by both floppy controllers
1100                pub irq: u32,
1101                /// DMA channel to use for floppy DMA transfers
1102                pub dma_channel: u8,
1103                /// Base port io address of the primary devices's register region
1104                pub pio_base: u16,
1105                /// Floppy Drives attached to the controller
1106                pub drives: floppy::DriveRibbon,
1107            }
1108        }
1109
1110        feature_gated! {
1111            feature = "dev_winbond_super_io_and_floppy_stub";
1112
1113            /// Stub Winbond83977 "Super I/O" chip + dual-floppy controllers
1114            ///
1115            /// Unconditionally reports no connected floppy drives. Useful for
1116            /// VMMs that wish to support BIOS boot via the Microsoft PCAT
1117            /// firmware, without paying the binary size + complexity cost of a
1118            /// full floppy disk controller implementation.
1119            ///
1120            /// IRQ and DMA channel assignment MUST match the values reported by
1121            /// the PCAT BIOS ACPI tables, and the Super IO emulator, and cannot
1122            /// be tweaked by top-level VMM code.
1123            pub struct WinbondSuperIoAndFloppyStubDeps;
1124        }
1125
1126        feature_gated! {
1127            feature = "dev_winbond_super_io_and_floppy_full";
1128
1129            /// Winbond83977 "Super I/O" chip + dual-floppy controllers
1130            ///
1131            /// IRQ and DMA channel assignment MUST match the values reported by the
1132            /// PCAT BIOS ACPI tables, and the Super IO emulator, and cannot be
1133            /// tweaked by top-level VMM code.
1134            pub struct WinbondSuperIoAndFloppyFullDeps {
1135                /// Floppy Drive attached to the primary controller
1136                pub primary_disk_drive: floppy::DriveRibbon,
1137                /// Floppy Drive attached to the secondary controller
1138                pub secondary_disk_drive: floppy::DriveRibbon,
1139            }
1140        }
1141
1142        /// Generic PCI bus
1143        pub struct GenericPciBusDeps {
1144            /// `vmotherboard` bus identifier
1145            pub bus_id: BusIdPci,
1146            /// Port io address of the 32-bit PCI ADDR register
1147            pub pio_addr: u16,
1148            /// Port io address of the 32-bit PCI DATA register
1149            pub pio_data: u16,
1150        }
1151
1152        /// PIIX4 PCI Bus
1153        pub struct Piix4PciBusDeps {
1154            /// `vmotherboard` bus identifier
1155            pub bus_id: BusIdPci,
1156        }
1157
1158        feature_gated! {
1159            feature = "dev_hyperv_vga";
1160
1161            /// Hyper-V specific VGA graphics card
1162            pub struct HyperVVgaDeps {
1163                /// `vmotherboard` bus identifier
1164                pub attached_to: BusIdPci,
1165                /// Interface to map SVGABIOS.bin into memory (or None, if that's
1166                /// handled externally, by the platform itself)
1167                pub rom: Option<Box<dyn guestmem::MapRom>>,
1168            }
1169        }
1170
1171        /// Generic MC146818A compatible RTC + CMOS device
1172        pub struct GenericCmosRtcDeps {
1173            /// IRQ line to signal RTC device events
1174            pub irq: u32,
1175            /// A time source resource, resolved at device build time.
1176            pub time_source: Resource<chipset_resources::CmosRtcTimeSourceHandleKind>,
1177            /// Which CMOS RAM register contains the century register
1178            pub century_reg_idx: u8,
1179            /// Initial state of CMOS RAM
1180            pub initial_cmos: Option<[u8; 256]>,
1181        }
1182
1183        /// PIIX4 "flavored" MC146818A compatible RTC + CMOS device
1184        pub struct Piix4CmosRtcDeps {
1185            /// A time source resource, resolved at device build time.
1186            pub time_source: Resource<chipset_resources::CmosRtcTimeSourceHandleKind>,
1187            /// Initial state of CMOS RAM
1188            pub initial_cmos: Option<[u8; 256]>,
1189            /// Whether enlightened interrupts are enabled. Needed when
1190            /// advertised by ACPI WAET table.
1191            pub enlightened_interrupts: bool,
1192        }
1193
1194        /// Hyper-V specific UEFI Helper Device
1195        pub struct HyperVFirmwarePcat {
1196            /// Bundle of static configuration required by the PCAT BIOS
1197            /// helper device
1198            pub config: firmware_pcat::config::PcatBiosConfig,
1199            /// Interface to log PCAT BIOS events
1200            pub logger: Box<dyn firmware_pcat::PcatLogger>,
1201            /// Channel to receive updated generation ID values
1202            pub generation_id_recv: mesh::Receiver<[u8; 16]>,
1203            /// Interface to map VMBIOS.bin into memory (or None, if that's
1204            /// handled externally, by the platform itself)
1205            pub rom: Option<Box<dyn guestmem::MapRom>>,
1206            /// Trigger the partition to replay the initially-set MTRRs across
1207            /// all VPs.
1208            pub replay_mtrrs: Box<dyn Send + FnMut()>,
1209        }
1210
1211        /// Hyper-V specific framebuffer device
1212        // TODO: this doesn't really belong in base_chipset... it's less-so a
1213        // device, and more a bit of "infrastructure" that supports other
1214        // video devices.
1215        #[expect(missing_docs)] // see TODO above
1216        pub struct HyperVFramebufferDeps {
1217            pub fb_mapper: Box<dyn guestmem::MemoryMapper>,
1218            pub fb: Framebuffer,
1219            pub vtl2_framebuffer_gpa_base: Option<u64>,
1220        }
1221
1222        feature_gated! {
1223            feature = "dev_underhill_vga_proxy";
1224
1225            /// Underhill specific VGA proxy device
1226            pub struct UnderhillVgaProxyDeps {
1227                /// `vmotherboard` bus identifier
1228                pub attached_to: BusIdPci,
1229                /// PCI proxy callbacks
1230                pub pci_cfg_proxy: Arc<dyn vga_proxy::ProxyVgaPciCfgAccess>,
1231                /// Host IO hotpath registration object
1232                pub register_host_io_fastpath: Box<dyn vga_proxy::RegisterHostIoPortFastPath>,
1233            }
1234        }
1235    }
1236}