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