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