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            watchdog_recv,
609            vsm_config,
610            time_source,
611        }) = deps_hyperv_firmware_uefi
612        {
613            builder
614                .arc_mutex_device("uefi")
615                .try_add_async(async |services| {
616                    let notify_interrupt = match config.command_set {
617                        UefiCommandSet::X64 => {
618                            services.new_line(GPE0_LINE_SET, "genid", GPE0_LINE_GENERATION_ID)
619                        }
620                        UefiCommandSet::Aarch64 => {
621                            services.new_line(IRQ_LINE_SET, "genid", GENERATION_ID_IRQ)
622                        }
623                    };
624                    let vmtime = services.register_vmtime();
625                    let gm = foundation.trusted_vtl0_dma_memory.clone();
626                    let runtime_deps = firmware_uefi::UefiRuntimeDeps {
627                        gm: gm.clone(),
628                        nvram_storage,
629                        logger,
630                        vmtime,
631                        watchdog_platform,
632                        watchdog_recv,
633                        generation_id_deps: generation_id::GenerationIdRuntimeDeps {
634                            generation_id_recv,
635                            gm,
636                            notify_interrupt,
637                        },
638                        vsm_config,
639                        time_source,
640                    };
641
642                    firmware_uefi::UefiDevice::new(runtime_deps, config, foundation.is_restoring)
643                        .await
644                })
645                .await?;
646        }
647
648        if let Some(options::dev::HyperVFirmwarePcat {
649            config,
650            logger,
651            generation_id_recv,
652            rom,
653            replay_mtrrs,
654        }) = deps_hyperv_firmware_pcat
655        {
656            builder.arc_mutex_device("pcat").try_add(|services| {
657                let notify_interrupt =
658                    services.new_line(GPE0_LINE_SET, "genid", GPE0_LINE_GENERATION_ID);
659                firmware_pcat::PcatBiosDevice::new(
660                    firmware_pcat::PcatBiosRuntimeDeps {
661                        gm: foundation.trusted_vtl0_dma_memory.clone(),
662                        logger,
663                        generation_id_deps: generation_id::GenerationIdRuntimeDeps {
664                            generation_id_recv,
665                            gm: foundation.trusted_vtl0_dma_memory.clone(),
666                            notify_interrupt,
667                        },
668                        vmtime: services.register_vmtime(),
669                        rom,
670                        register_pio: &mut services.register_pio(),
671                        replay_mtrrs,
672                    },
673                    config,
674                )
675            })?;
676        }
677
678        if let Some(options::dev::HyperVFramebufferDeps {
679            fb_mapper,
680            fb,
681            vtl2_framebuffer_gpa_base,
682        }) = deps_hyperv_framebuffer
683        {
684            let fb = FramebufferDevice::new(fb_mapper, fb, vtl2_framebuffer_gpa_base);
685            let control = fb.as_ref().ok().map(|fb| fb.control());
686            builder.arc_mutex_device("fb").try_add(|_| fb)?;
687            device_interfaces.framebuffer_local_control = Some(control.unwrap());
688        }
689
690        #[cfg(feature = "dev_hyperv_vga")]
691        if let Some(options::dev::HyperVVgaDeps { attached_to, rom }) = deps_hyperv_vga {
692            builder
693                .arc_mutex_device("vga")
694                .on_pci_bus(attached_to)
695                .try_add(|services| {
696                    vga::VgaDevice::new(
697                        &driver_source.simple(),
698                        services.register_vmtime(),
699                        device_interfaces.framebuffer_local_control.clone().unwrap(),
700                        rom,
701                    )
702                })?;
703        }
704
705        #[cfg(feature = "dev_underhill_vga_proxy")]
706        if let Some(options::dev::UnderhillVgaProxyDeps {
707            attached_to,
708            pci_cfg_proxy,
709            register_host_io_fastpath,
710        }) = deps_underhill_vga_proxy
711        {
712            builder
713                .arc_mutex_device("vga_proxy")
714                .on_pci_bus(attached_to)
715                .add(|_services| {
716                    vga_proxy::VgaProxyDevice::new(pci_cfg_proxy, &*register_host_io_fastpath)
717                })?;
718        }
719
720        macro_rules! feature_gate_check {
721            ($feature:literal, $dep:ident) => {
722                #[cfg(not(feature = $feature))]
723                let None::<()> = $dep else {
724                    return Err(BaseChipsetBuilderError::FeatureGatedDevice($feature));
725                };
726            };
727        }
728
729        feature_gate_check!("dev_hyperv_vga", deps_hyperv_vga);
730        feature_gate_check!("dev_underhill_vga_proxy", deps_underhill_vga_proxy);
731        feature_gate_check!("dev_generic_isa_floppy", deps_generic_isa_floppy);
732        feature_gate_check!(
733            "dev_winbond_super_io_and_floppy_full",
734            deps_winbond_super_io_and_floppy_full
735        );
736        feature_gate_check!(
737            "dev_winbond_super_io_and_floppy_stub",
738            deps_winbond_super_io_and_floppy_stub
739        );
740
741        for device in device_handles {
742            builder
743                .arc_mutex_device(device.name.as_ref())
744                .try_add_async(async |services| {
745                    resolver
746                        .resolve(
747                            device.resource,
748                            ResolveChipsetDeviceHandleParams {
749                                device_name: device.name.as_ref(),
750                                guest_memory: &foundation.untrusted_dma_memory,
751                                encrypted_guest_memory: &foundation.trusted_vtl0_dma_memory,
752                                vmtime: foundation.vmtime,
753                                is_restoring: foundation.is_restoring,
754                                task_driver_source: driver_source,
755                                register_mmio: &mut services.register_mmio(),
756                                register_pio: &mut services.register_pio(),
757                                configure: services,
758                            },
759                        )
760                        .await
761                        .map(|dev| dev.0)
762                })
763                .await?;
764        }
765
766        Ok(BaseChipsetBuilderOutput {
767            chipset_builder: builder,
768            device_interfaces,
769        })
770    }
771}
772
773impl ConfigureChipsetDevice for ArcMutexChipsetServices<'_, '_> {
774    fn new_line(
775        &mut self,
776        id: chipset_device_resources::LineSetId,
777        name: &str,
778        vector: u32,
779    ) -> vmcore::line_interrupt::LineInterrupt {
780        self.new_line(id, name, vector)
781    }
782
783    fn add_line_target(
784        &mut self,
785        id: chipset_device_resources::LineSetId,
786        source_range: std::ops::RangeInclusive<u32>,
787        target_start: u32,
788    ) {
789        self.add_line_target(id, source_range, target_start)
790    }
791
792    fn omit_saved_state(&mut self) {
793        self.omit_saved_state();
794    }
795}
796
797mod weak_mutex_pci {
798    use crate::chipset::PciConflict;
799    use crate::chipset::PciConflictReason;
800    use crate::chipset::backing::arc_mutex::pci::RegisterWeakMutexPci;
801    use chipset_device::ChipsetDevice;
802    use chipset_device::io::IoResult;
803    use closeable_mutex::CloseableMutex;
804    use pci_bus::GenericPciBusDevice;
805    use std::sync::Arc;
806    use std::sync::Weak;
807
808    /// Wrapper around `Weak<CloseableMutex<dyn ChipsetDevice>>` that implements
809    /// [`GenericPciBusDevice`]
810    pub struct WeakMutexPciDeviceWrapper(Weak<CloseableMutex<dyn ChipsetDevice>>);
811
812    impl GenericPciBusDevice for WeakMutexPciDeviceWrapper {
813        fn pci_cfg_read(&mut self, offset: u16, value: &mut u32) -> Option<IoResult> {
814            Some(
815                self.0
816                    .upgrade()?
817                    .lock()
818                    .supports_pci()
819                    .expect("builder code ensures supports_pci.is_some()")
820                    .pci_cfg_read(offset, value),
821            )
822        }
823
824        fn pci_cfg_write(&mut self, offset: u16, value: u32) -> Option<IoResult> {
825            Some(
826                self.0
827                    .upgrade()?
828                    .lock()
829                    .supports_pci()
830                    .expect("builder code ensures supports_pci.is_some()")
831                    .pci_cfg_write(offset, value),
832            )
833        }
834    }
835
836    // wiring to enable using the generic PCI bus alongside the Arc+CloseableMutex device infra
837    impl RegisterWeakMutexPci for Arc<CloseableMutex<pci_bus::GenericPciBus>> {
838        fn add_pci_device(
839            &mut self,
840            bus: u8,
841            device: u8,
842            function: u8,
843            name: Arc<str>,
844            dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
845        ) -> Result<(), PciConflict> {
846            self.lock()
847                .add_pci_device(
848                    bus,
849                    device,
850                    function,
851                    name.clone(),
852                    WeakMutexPciDeviceWrapper(dev),
853                )
854                .map_err(|(_, existing_dev)| PciConflict {
855                    bdf: (bus, device, function),
856                    reason: PciConflictReason::ExistingDev(existing_dev),
857                    conflict_dev: name,
858                })
859        }
860    }
861
862    // wiring to enable using the PIIX4 PCI bus alongside the Arc+CloseableMutex device infra
863    impl RegisterWeakMutexPci for Arc<CloseableMutex<chipset_legacy::piix4_pci_bus::Piix4PciBus>> {
864        fn add_pci_device(
865            &mut self,
866            bus: u8,
867            device: u8,
868            function: u8,
869            name: Arc<str>,
870            dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
871        ) -> Result<(), PciConflict> {
872            self.lock()
873                .as_pci_bus()
874                .add_pci_device(
875                    bus,
876                    device,
877                    function,
878                    name.clone(),
879                    WeakMutexPciDeviceWrapper(dev),
880                )
881                .map_err(|(_, existing_dev)| PciConflict {
882                    bdf: (bus, device, function),
883                    reason: PciConflictReason::ExistingDev(existing_dev),
884                    conflict_dev: name,
885                })
886        }
887    }
888}
889
890pub struct ArcMutexIsaDmaChannel {
891    channel_num: u8,
892    dma: Arc<CloseableMutex<dma::DmaController>>,
893}
894
895impl ArcMutexIsaDmaChannel {
896    #[allow(dead_code)] // use is feature dependent
897    pub fn new(dma: Arc<CloseableMutex<dma::DmaController>>, channel_num: u8) -> Self {
898        Self { dma, channel_num }
899    }
900}
901
902impl vmcore::isa_dma_channel::IsaDmaChannel for ArcMutexIsaDmaChannel {
903    fn check_transfer_size(&mut self) -> u16 {
904        self.dma.lock().check_transfer_size(self.channel_num.into())
905    }
906
907    fn request(
908        &mut self,
909        direction: vmcore::isa_dma_channel::IsaDmaDirection,
910    ) -> Option<vmcore::isa_dma_channel::IsaDmaBuffer> {
911        self.dma.lock().request(self.channel_num.into(), direction)
912    }
913
914    fn complete(&mut self) {
915        self.dma.lock().complete(self.channel_num.into())
916    }
917}
918
919/// [`BaseChipsetBuilder`] options and configuration
920pub mod options {
921    use super::*;
922    use state_unit::UnitHandle;
923    use vmcore::vmtime::VmTimeSource;
924
925    /// Foundational `BaseChipset` dependencies (read: not device-specific)
926    #[expect(missing_docs)] // self explanatory field names
927    pub struct BaseChipsetFoundation<'a> {
928        pub is_restoring: bool,
929        /// Guest memory access for untrusted devices.
930        ///
931        /// This should provide access only to memory that is also accessible by
932        /// the host. This applies to most devices, where the guest does not
933        /// expect that they are implemented by a paravisor.
934        ///
935        /// If a device incorrectly uses this instead of
936        /// `trusted_vtl0_dma_memory`, then it will likely see failures when
937        /// accessing guest memory in confidential VM configurations. A
938        /// malicious host could additionally use this conspire to observe
939        /// trusted device interactions.
940        pub untrusted_dma_memory: GuestMemory,
941        /// Guest memory access for trusted devices.
942        ///
943        /// This should provide access to all of VTL0 memory (but not VTL1
944        /// memory). This applies to devices that the guest expects to be
945        /// implemented by a paravisor, such as security and firmware devices.
946        ///
947        /// If a device incorrectly uses this instead of `untrusted_dma_memory`,
948        /// then it will likely see failures when accessing guest memory in
949        /// confidential VM configurations. If the device is under control of a
950        /// malicious host in some way, this could also lead to the host
951        /// observing encrypted memory.
952        pub trusted_vtl0_dma_memory: GuestMemory,
953        pub power_event_handler: Arc<dyn crate::PowerEventHandler>,
954        pub debug_event_handler: Arc<dyn crate::DebugEventHandler>,
955        pub vmtime: &'a VmTimeSource,
956        pub vmtime_unit: &'a UnitHandle,
957        pub doorbell_registration: Option<Arc<dyn DoorbellRegistration>>,
958    }
959
960    macro_rules! base_chipset_devices_and_manifest {
961        (
962            // doing this kind of "pseudo-syntax" isn't strictly necessary, but
963            // it serves as a nice bit of visual ✨flair✨ that makes it easier
964            // to grok what the macro is actually emitting
965            impls {
966                $(#[$m:meta])*
967                pub struct $base_chipset_devices:ident {
968                    ...
969                }
970
971                $(#[$m2:meta])*
972                pub struct $base_chipset_manifest:ident {
973                    ...
974                }
975            }
976
977            devices {
978                $($name:ident: $ty:ty,)*
979            }
980        ) => {paste::paste!{
981            $(#[$m])*
982            pub struct $base_chipset_devices {
983                $(pub [<deps_ $name>]: Option<$ty>,)*
984            }
985
986            $(#[$m2])*
987            pub struct $base_chipset_manifest {
988                $(pub [<with_ $name>]: bool,)*
989            }
990
991            impl $base_chipset_manifest {
992                /// Return a [`BaseChipsetManifest`] with all fields set to
993                /// `false`
994                pub const fn empty() -> Self {
995                    Self {
996                        $([<with_ $name>]: false,)*
997                    }
998                }
999            }
1000
1001            impl $base_chipset_devices {
1002                /// Return a [`BaseChipsetDevices`] with all fields set to
1003                /// `None`
1004                pub fn empty() -> Self {
1005                    Self {
1006                        $([<deps_ $name>]: None,)*
1007                    }
1008                }
1009
1010                /// Return the corresponding [`BaseChipsetManifest`].
1011                pub fn to_manifest(&self) -> $base_chipset_manifest {
1012                    let Self {
1013                        $([<deps_ $name>],)*
1014                    } = self;
1015
1016                    $base_chipset_manifest {
1017                        $([<with_ $name>]: [<deps_ $name>].is_some(),)*
1018                    }
1019                }
1020            }
1021        }};
1022    }
1023
1024    base_chipset_devices_and_manifest! {
1025        impls {
1026            /// Device-specific `BaseChipset` dependencies
1027            #[expect(missing_docs)] // self explanatory field names
1028            pub struct BaseChipsetDevices {
1029                // generated struct has fields that look like this:
1030                //
1031                // deps_<device>: Option<dev::<Deps>>,
1032                ...
1033            }
1034
1035            /// A manifest of devices specified by [`BaseChipsetDevices`].
1036            #[expect(missing_docs)] // self explanatory field names
1037            #[derive(Debug, Clone, MeshPayload, PartialEq, Eq)]
1038            pub struct BaseChipsetManifest {
1039                // generated struct has fields that look like this:
1040                //
1041                // with_<device>: bool,
1042                ...
1043            }
1044        }
1045
1046        devices {
1047            generic_cmos_rtc:            dev::GenericCmosRtcDeps,
1048            generic_ioapic:              dev::GenericIoApicDeps,
1049            generic_isa_dma:             dev::GenericIsaDmaDeps,
1050            generic_isa_floppy:          dev::GenericIsaFloppyDeps,
1051            generic_pci_bus:             dev::GenericPciBusDeps,
1052            generic_pic:                 dev::GenericPicDeps,
1053            generic_pit:                 dev::GenericPitDeps,
1054            generic_psp:                 dev::GenericPspDeps,
1055
1056            hyperv_firmware_pcat:        dev::HyperVFirmwarePcat,
1057            hyperv_firmware_uefi:        dev::HyperVFirmwareUefi,
1058            hyperv_framebuffer:          dev::HyperVFramebufferDeps,
1059            hyperv_guest_watchdog:       dev::HyperVGuestWatchdogDeps,
1060            hyperv_ide:                  dev::HyperVIdeDeps,
1061            hyperv_power_management:     dev::HyperVPowerManagementDeps,
1062            hyperv_vga:                  dev::HyperVVgaDeps,
1063
1064            i440bx_host_pci_bridge:      dev::I440BxHostPciBridgeDeps,
1065
1066            piix4_cmos_rtc:              dev::Piix4CmosRtcDeps,
1067            piix4_pci_bus:               dev::Piix4PciBusDeps,
1068            piix4_pci_isa_bridge:        dev::Piix4PciIsaBridgeDeps,
1069            piix4_pci_usb_uhci_stub:     dev::Piix4PciUsbUhciStubDeps,
1070            piix4_power_management:      dev::Piix4PowerManagementDeps,
1071
1072            underhill_vga_proxy:         dev::UnderhillVgaProxyDeps,
1073
1074            winbond_super_io_and_floppy_stub: dev::WinbondSuperIoAndFloppyStubDeps,
1075            winbond_super_io_and_floppy_full: dev::WinbondSuperIoAndFloppyFullDeps,
1076        }
1077    }
1078
1079    /// Device specific dependencies
1080    pub mod dev {
1081        use super::*;
1082        use crate::BusIdPci;
1083        use chipset_resources::battery::HostBatteryUpdate;
1084        use local_clock::InspectableLocalClock;
1085
1086        macro_rules! feature_gated {
1087            (
1088                feature = $feat:literal;
1089
1090                $(#[$m:meta])*
1091                pub struct $root_deps:ident $($rest:tt)*
1092            ) => {
1093                #[cfg(not(feature = $feat))]
1094                #[doc(hidden)]
1095                pub type $root_deps = ();
1096
1097                #[cfg(feature = $feat)]
1098                $(#[$m])*
1099                pub struct $root_deps $($rest)*
1100            };
1101        }
1102
1103        /// PIIX4 PCI-ISA bridge (fixed pci address: 0:7.0)
1104        pub struct Piix4PciIsaBridgeDeps {
1105            /// `vmotherboard` bus identifier
1106            pub attached_to: BusIdPci,
1107        }
1108
1109        /// Hyper-V IDE controller (fixed pci address: 0:7.1)
1110        // TODO: this device needs to be broken down further, into a PIIX4 IDE
1111        // device (without the Hyper-V enlightenments), and then a Generic IDE
1112        // device (without any of the PIIX4 bus mastering stuff).
1113        pub struct HyperVIdeDeps {
1114            /// `vmotherboard` bus identifier
1115            pub attached_to: BusIdPci,
1116            /// Drives attached to the primary IDE channel
1117            pub primary_channel_drives: [Option<ide::DriveMedia>; 2],
1118            /// Drives attached to the secondary IDE channel
1119            pub secondary_channel_drives: [Option<ide::DriveMedia>; 2],
1120        }
1121
1122        /// PIIX4 USB UHCI controller (fixed pci address: 0:7.2)
1123        ///
1124        /// NOTE: current implementation is a minimal stub, implementing just
1125        /// enough to keep the PCAT BIOS happy.
1126        pub struct Piix4PciUsbUhciStubDeps {
1127            /// `vmotherboard` bus identifier
1128            pub attached_to: BusIdPci,
1129        }
1130
1131        /// PIIX4 power management device (fixed pci address: 0:7.3)
1132        pub struct Piix4PowerManagementDeps {
1133            /// `vmotherboard` bus identifier
1134            pub attached_to: BusIdPci,
1135            /// Interface to enable/disable PM timer assist
1136            pub pm_timer_assist: Option<Box<dyn pm::PmTimerAssist>>,
1137        }
1138
1139        /// Generic dual 8237A ISA DMA controllers
1140        pub struct GenericIsaDmaDeps;
1141
1142        /// Hyper-V specific ACPI-compatible power management device
1143        pub struct HyperVPowerManagementDeps {
1144            /// IRQ line triggered on ACPI power event
1145            pub acpi_irq: u32,
1146            /// Base port io address of the device's register region
1147            pub pio_base: u16,
1148            /// Interface to enable/disable PM timer assist
1149            pub pm_timer_assist: Option<Box<dyn pm::PmTimerAssist>>,
1150        }
1151
1152        /// AMD Platform Security Processor (PSP)
1153        pub struct GenericPspDeps;
1154
1155        feature_gated! {
1156            feature = "dev_generic_isa_floppy";
1157
1158            /// Generic ISA floppy controller
1159            pub struct GenericIsaFloppyDeps {
1160                /// IRQ line shared by both floppy controllers
1161                pub irq: u32,
1162                /// DMA channel to use for floppy DMA transfers
1163                pub dma_channel: u8,
1164                /// Base port io address of the primary devices's register region
1165                pub pio_base: u16,
1166                /// Floppy Drives attached to the controller
1167                pub drives: floppy::DriveRibbon,
1168            }
1169        }
1170
1171        feature_gated! {
1172            feature = "dev_winbond_super_io_and_floppy_stub";
1173
1174            /// Stub Winbond83977 "Super I/O" chip + dual-floppy controllers
1175            ///
1176            /// Unconditionally reports no connected floppy drives. Useful for
1177            /// VMMs that wish to support BIOS boot via the Microsoft PCAT
1178            /// firmware, without paying the binary size + complexity cost of a
1179            /// full floppy disk controller implementation.
1180            ///
1181            /// IRQ and DMA channel assignment MUST match the values reported by
1182            /// the PCAT BIOS ACPI tables, and the Super IO emulator, and cannot
1183            /// be tweaked by top-level VMM code.
1184            pub struct WinbondSuperIoAndFloppyStubDeps;
1185        }
1186
1187        feature_gated! {
1188            feature = "dev_winbond_super_io_and_floppy_full";
1189
1190            /// Winbond83977 "Super I/O" chip + dual-floppy controllers
1191            ///
1192            /// IRQ and DMA channel assignment MUST match the values reported by the
1193            /// PCAT BIOS ACPI tables, and the Super IO emulator, and cannot be
1194            /// tweaked by top-level VMM code.
1195            pub struct WinbondSuperIoAndFloppyFullDeps {
1196                /// Floppy Drive attached to the primary controller
1197                pub primary_disk_drive: floppy::DriveRibbon,
1198                /// Floppy Drive attached to the secondary controller
1199                pub secondary_disk_drive: floppy::DriveRibbon,
1200            }
1201        }
1202
1203        /// Generic PCI bus
1204        pub struct GenericPciBusDeps {
1205            /// `vmotherboard` bus identifier
1206            pub bus_id: BusIdPci,
1207            /// Port io address of the 32-bit PCI ADDR register
1208            pub pio_addr: u16,
1209            /// Port io address of the 32-bit PCI DATA register
1210            pub pio_data: u16,
1211        }
1212
1213        /// PIIX4 PCI Bus
1214        pub struct Piix4PciBusDeps {
1215            /// `vmotherboard` bus identifier
1216            pub bus_id: BusIdPci,
1217        }
1218
1219        /// i440BX Host-PCI bridge (fixed pci address: 0:0.0)
1220        pub struct I440BxHostPciBridgeDeps {
1221            /// `vmotherboard` bus identifier
1222            pub attached_to: BusIdPci,
1223            /// Interface to create GPA alias ranges.
1224            pub adjust_gpa_range: Box<dyn chipset_legacy::i440bx_host_pci_bridge::AdjustGpaRange>,
1225        }
1226
1227        /// Generic Intel 8253/8254 Programmable Interval Timer (PIT)
1228        pub struct GenericPitDeps;
1229
1230        feature_gated! {
1231            feature = "dev_hyperv_vga";
1232
1233            /// Hyper-V specific VGA graphics card
1234            pub struct HyperVVgaDeps {
1235                /// `vmotherboard` bus identifier
1236                pub attached_to: BusIdPci,
1237                /// Interface to map SVGABIOS.bin into memory (or None, if that's
1238                /// handled externally, by the platform itself)
1239                pub rom: Option<Box<dyn guestmem::MapRom>>,
1240            }
1241        }
1242
1243        /// Generic Dual 8259 Programmable Interrupt Controllers  (PIC)
1244        pub struct GenericPicDeps {}
1245
1246        /// Generic IO Advanced Programmable Interrupt Controller (IOAPIC)
1247        pub struct GenericIoApicDeps {
1248            /// Number of IO-APIC entries
1249            pub num_entries: u8,
1250            /// Trait allowing the IO-APIC device to assert VM interrupts.
1251            pub routing: Box<dyn ioapic::IoApicRouting>,
1252        }
1253
1254        /// Generic MC146818A compatible RTC + CMOS device
1255        pub struct GenericCmosRtcDeps {
1256            /// IRQ line to signal RTC device events
1257            pub irq: u32,
1258            /// A source of "real time"
1259            pub time_source: Box<dyn InspectableLocalClock>,
1260            /// Which CMOS RAM register contains the century register
1261            pub century_reg_idx: u8,
1262            /// Initial state of CMOS RAM
1263            pub initial_cmos: Option<[u8; 256]>,
1264        }
1265
1266        /// PIIX4 "flavored" MC146818A compatible RTC + CMOS device
1267        pub struct Piix4CmosRtcDeps {
1268            /// A source of "real time"
1269            pub time_source: Box<dyn InspectableLocalClock>,
1270            /// Initial state of CMOS RAM
1271            pub initial_cmos: Option<[u8; 256]>,
1272            /// Whether enlightened interrupts are enabled. Needed when
1273            /// advertised by ACPI WAET table.
1274            pub enlightened_interrupts: bool,
1275        }
1276
1277        /// Hyper-V specific ACPI-compatible battery device
1278        pub struct HyperVBatteryDeps {
1279            /// Base MMIO address for the battery device
1280            pub base_addr: u64,
1281            /// Whether to use gpe0 for battery status updates
1282            pub use_gpe0: bool,
1283            /// The line interrupt number to use for battery status updates
1284            pub line_interrupt_no: u32,
1285            /// Channel to receive updated battery state
1286            pub battery_status_recv: mesh::Receiver<HostBatteryUpdate>,
1287        }
1288
1289        /// Hyper-V specific Guest Watchdog device
1290        pub struct HyperVGuestWatchdogDeps {
1291            /// Port io address of the device's register region
1292            pub port_base: u16,
1293            /// Device-specific functions the platform must provide in order to
1294            /// use this device.
1295            pub watchdog_platform: Box<dyn watchdog_core::platform::WatchdogPlatform>,
1296        }
1297
1298        /// Hyper-V specific UEFI Helper Device
1299        pub struct HyperVFirmwarePcat {
1300            /// Bundle of static configuration required by the PCAT BIOS
1301            /// helper device
1302            pub config: firmware_pcat::config::PcatBiosConfig,
1303            /// Interface to log PCAT BIOS events
1304            pub logger: Box<dyn firmware_pcat::PcatLogger>,
1305            /// Channel to receive updated generation ID values
1306            pub generation_id_recv: mesh::Receiver<[u8; 16]>,
1307            /// Interface to map VMBIOS.bin into memory (or None, if that's
1308            /// handled externally, by the platform itself)
1309            pub rom: Option<Box<dyn guestmem::MapRom>>,
1310            /// Trigger the partition to replay the initially-set MTRRs across
1311            /// all VPs.
1312            pub replay_mtrrs: Box<dyn Send + FnMut()>,
1313        }
1314
1315        /// Hyper-V specific UEFI Helper Device
1316        pub struct HyperVFirmwareUefi {
1317            /// Bundle of static configuration required by the Hyper-V UEFI
1318            /// helper device
1319            pub config: firmware_uefi::UefiConfig,
1320            /// Interface to log UEFI BIOS events
1321            pub logger: Box<dyn firmware_uefi::platform::logger::UefiLogger>,
1322            /// Interface for storing/retrieving UEFI NVRAM variables
1323            pub nvram_storage: Box<dyn uefi_nvram_storage::VmmNvramStorage>,
1324            /// Channel to receive updated generation ID values
1325            pub generation_id_recv: mesh::Receiver<[u8; 16]>,
1326            /// Device-specific functions the platform must provide in order
1327            /// to use the UEFI watchdog device.
1328            pub watchdog_platform: Box<dyn watchdog_core::platform::WatchdogPlatform>,
1329            /// Channel receiver for watchdog timeout notifications.
1330            pub watchdog_recv: mesh::Receiver<()>,
1331            /// Interface to revoke VSM on `ExitBootServices()` if requested
1332            /// by the guest.
1333            pub vsm_config: Option<Box<dyn firmware_uefi::platform::nvram::VsmConfig>>,
1334            /// Time source
1335            pub time_source: Box<dyn InspectableLocalClock>,
1336        }
1337
1338        /// Hyper-V specific framebuffer device
1339        // TODO: this doesn't really belong in base_chipset... it's less-so a
1340        // device, and more a bit of "infrastructure" that supports other
1341        // video devices.
1342        #[expect(missing_docs)] // see TODO above
1343        pub struct HyperVFramebufferDeps {
1344            pub fb_mapper: Box<dyn guestmem::MemoryMapper>,
1345            pub fb: Framebuffer,
1346            pub vtl2_framebuffer_gpa_base: Option<u64>,
1347        }
1348
1349        feature_gated! {
1350            feature = "dev_underhill_vga_proxy";
1351
1352            /// Underhill specific VGA proxy device
1353            pub struct UnderhillVgaProxyDeps {
1354                /// `vmotherboard` bus identifier
1355                pub attached_to: BusIdPci,
1356                /// PCI proxy callbacks
1357                pub pci_cfg_proxy: Arc<dyn vga_proxy::ProxyVgaPciCfgAccess>,
1358                /// Host IO hotpath registration object
1359                pub register_host_io_fastpath: Box<dyn vga_proxy::RegisterHostIoPortFastPath>,
1360            }
1361        }
1362    }
1363}