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::PcieConflict;
801    use crate::chipset::PcieConflictReason;
802    use crate::chipset::backing::arc_mutex::pci::RegisterWeakMutexPci;
803    use crate::chipset::backing::arc_mutex::pci::RegisterWeakMutexPcie;
804    use chipset_device::ChipsetDevice;
805    use chipset_device::io::IoResult;
806    use closeable_mutex::CloseableMutex;
807    use pci_bus::GenericPciBusDevice;
808    use std::sync::Arc;
809    use std::sync::Weak;
810
811    /// Wrapper around `Weak<CloseableMutex<dyn ChipsetDevice>>` that implements
812    /// [`GenericPciBusDevice`]
813    pub struct WeakMutexPciDeviceWrapper(Weak<CloseableMutex<dyn ChipsetDevice>>);
814
815    impl GenericPciBusDevice for WeakMutexPciDeviceWrapper {
816        fn pci_cfg_read(&mut self, offset: u16, value: &mut u32) -> Option<IoResult> {
817            Some(
818                self.0
819                    .upgrade()?
820                    .lock()
821                    .supports_pci()
822                    .expect("builder code ensures supports_pci.is_some()")
823                    .pci_cfg_read(offset, value),
824            )
825        }
826
827        fn pci_cfg_write(&mut self, offset: u16, value: u32) -> Option<IoResult> {
828            Some(
829                self.0
830                    .upgrade()?
831                    .lock()
832                    .supports_pci()
833                    .expect("builder code ensures supports_pci.is_some()")
834                    .pci_cfg_write(offset, value),
835            )
836        }
837
838        fn pci_cfg_read_forward(
839            &mut self,
840            bus: u8,
841            device_function: u8,
842            offset: u16,
843            value: &mut u32,
844        ) -> Option<IoResult> {
845            self.0
846                .upgrade()?
847                .lock()
848                .supports_pci()
849                .expect("builder code ensures supports_pci.is_some()")
850                .pci_cfg_read_forward(bus, device_function, offset, value)
851        }
852
853        fn pci_cfg_write_forward(
854            &mut self,
855            bus: u8,
856            device_function: u8,
857            offset: u16,
858            value: u32,
859        ) -> Option<IoResult> {
860            self.0
861                .upgrade()?
862                .lock()
863                .supports_pci()
864                .expect("builder code ensures supports_pci.is_some()")
865                .pci_cfg_write_forward(bus, device_function, offset, value)
866        }
867    }
868
869    // wiring to enable using the generic PCI bus alongside the Arc+CloseableMutex device infra
870    impl RegisterWeakMutexPci for Arc<CloseableMutex<pci_bus::GenericPciBus>> {
871        fn add_pci_device(
872            &mut self,
873            bus: u8,
874            device: u8,
875            function: u8,
876            name: Arc<str>,
877            dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
878        ) -> Result<(), PciConflict> {
879            self.lock()
880                .add_pci_device(
881                    bus,
882                    device,
883                    function,
884                    name.clone(),
885                    WeakMutexPciDeviceWrapper(dev),
886                )
887                .map_err(|occ_err| PciConflict {
888                    bdf: (bus, device, function),
889                    reason: PciConflictReason::ExistingDev(occ_err.existing_device_name),
890                    conflict_dev: name,
891                })
892        }
893    }
894
895    // wiring to enable using the PIIX4 PCI bus alongside the Arc+CloseableMutex device infra
896    impl RegisterWeakMutexPci for Arc<CloseableMutex<chipset_legacy::piix4_pci_bus::Piix4PciBus>> {
897        fn add_pci_device(
898            &mut self,
899            bus: u8,
900            device: u8,
901            function: u8,
902            name: Arc<str>,
903            dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
904        ) -> Result<(), PciConflict> {
905            self.lock()
906                .as_pci_bus()
907                .add_pci_device(
908                    bus,
909                    device,
910                    function,
911                    name.clone(),
912                    WeakMutexPciDeviceWrapper(dev),
913                )
914                .map_err(|occ_err| PciConflict {
915                    bdf: (bus, device, function),
916                    reason: PciConflictReason::ExistingDev(occ_err.existing_device_name),
917                    conflict_dev: name,
918                })
919        }
920    }
921
922    // wiring to enable using the generic PCIe root port alongside the Arc+CloseableMutex device infra
923    impl RegisterWeakMutexPcie for Arc<CloseableMutex<pcie::root::GenericPcieRootComplex>> {
924        fn add_pcie_device(
925            &mut self,
926            port: u8,
927            name: Arc<str>,
928            dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
929        ) -> Result<(), PcieConflict> {
930            self.lock()
931                .add_pcie_device(port, name.clone(), Box::new(WeakMutexPciDeviceWrapper(dev)))
932                .map_err(|existing_dev_name| PcieConflict {
933                    reason: PcieConflictReason::ExistingDev(existing_dev_name),
934                    conflict_dev: name,
935                })
936        }
937
938        fn downstream_ports(&self) -> Vec<(u8, Arc<str>)> {
939            self.lock().downstream_ports()
940        }
941    }
942
943    // wiring to enable using the PCIe switch alongside the Arc+CloseableMutex device infra
944    impl RegisterWeakMutexPcie for Arc<CloseableMutex<pcie::switch::GenericPcieSwitch>> {
945        fn add_pcie_device(
946            &mut self,
947            port: u8,
948            name: Arc<str>,
949            dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
950        ) -> Result<(), PcieConflict> {
951            self.lock()
952                .add_pcie_device(port, &name, Box::new(WeakMutexPciDeviceWrapper(dev)))
953                .map_err(|err| PcieConflict {
954                    reason: PcieConflictReason::ExistingDev(err.to_string().into()),
955                    conflict_dev: name,
956                })
957        }
958
959        fn downstream_ports(&self) -> Vec<(u8, Arc<str>)> {
960            self.lock().downstream_ports()
961        }
962    }
963}
964
965pub struct ArcMutexIsaDmaChannel {
966    channel_num: u8,
967    dma: Arc<CloseableMutex<dma::DmaController>>,
968}
969
970impl ArcMutexIsaDmaChannel {
971    #[allow(dead_code)] // use is feature dependent
972    pub fn new(dma: Arc<CloseableMutex<dma::DmaController>>, channel_num: u8) -> Self {
973        Self { dma, channel_num }
974    }
975}
976
977impl vmcore::isa_dma_channel::IsaDmaChannel for ArcMutexIsaDmaChannel {
978    fn check_transfer_size(&mut self) -> u16 {
979        self.dma.lock().check_transfer_size(self.channel_num.into())
980    }
981
982    fn request(
983        &mut self,
984        direction: vmcore::isa_dma_channel::IsaDmaDirection,
985    ) -> Option<vmcore::isa_dma_channel::IsaDmaBuffer> {
986        self.dma.lock().request(self.channel_num.into(), direction)
987    }
988
989    fn complete(&mut self) {
990        self.dma.lock().complete(self.channel_num.into())
991    }
992}
993
994/// [`BaseChipsetBuilder`] options and configuration
995pub mod options {
996    use super::*;
997    use state_unit::UnitHandle;
998    use vmcore::vmtime::VmTimeSource;
999
1000    /// Foundational `BaseChipset` dependencies (read: not device-specific)
1001    #[expect(missing_docs)] // self explanatory field names
1002    pub struct BaseChipsetFoundation<'a> {
1003        pub is_restoring: bool,
1004        /// Guest memory access for untrusted devices.
1005        ///
1006        /// This should provide access only to memory that is also accessible by
1007        /// the host. This applies to most devices, where the guest does not
1008        /// expect that they are implemented by a paravisor.
1009        ///
1010        /// If a device incorrectly uses this instead of
1011        /// `trusted_vtl0_dma_memory`, then it will likely see failures when
1012        /// accessing guest memory in confidential VM configurations. A
1013        /// malicious host could additionally use this conspire to observe
1014        /// trusted device interactions.
1015        pub untrusted_dma_memory: GuestMemory,
1016        /// Guest memory access for trusted devices.
1017        ///
1018        /// This should provide access to all of VTL0 memory (but not VTL1
1019        /// memory). This applies to devices that the guest expects to be
1020        /// implemented by a paravisor, such as security and firmware devices.
1021        ///
1022        /// If a device incorrectly uses this instead of `untrusted_dma_memory`,
1023        /// then it will likely see failures when accessing guest memory in
1024        /// confidential VM configurations. If the device is under control of a
1025        /// malicious host in some way, this could also lead to the host
1026        /// observing encrypted memory.
1027        pub trusted_vtl0_dma_memory: GuestMemory,
1028        pub power_event_handler: Arc<dyn crate::PowerEventHandler>,
1029        pub debug_event_handler: Arc<dyn crate::DebugEventHandler>,
1030        pub vmtime: &'a VmTimeSource,
1031        pub vmtime_unit: &'a UnitHandle,
1032        pub doorbell_registration: Option<Arc<dyn DoorbellRegistration>>,
1033    }
1034
1035    macro_rules! base_chipset_devices_and_manifest {
1036        (
1037            // doing this kind of "pseudo-syntax" isn't strictly necessary, but
1038            // it serves as a nice bit of visual ✨flair✨ that makes it easier
1039            // to grok what the macro is actually emitting
1040            impls {
1041                $(#[$m:meta])*
1042                pub struct $base_chipset_devices:ident {
1043                    ...
1044                }
1045
1046                $(#[$m2:meta])*
1047                pub struct $base_chipset_manifest:ident {
1048                    ...
1049                }
1050            }
1051
1052            devices {
1053                $($name:ident: $ty:ty,)*
1054            }
1055        ) => {paste::paste!{
1056            $(#[$m])*
1057            pub struct $base_chipset_devices {
1058                $(pub [<deps_ $name>]: Option<$ty>,)*
1059            }
1060
1061            $(#[$m2])*
1062            pub struct $base_chipset_manifest {
1063                $(pub [<with_ $name>]: bool,)*
1064            }
1065
1066            impl $base_chipset_manifest {
1067                /// Return a [`BaseChipsetManifest`] with all fields set to
1068                /// `false`
1069                pub const fn empty() -> Self {
1070                    Self {
1071                        $([<with_ $name>]: false,)*
1072                    }
1073                }
1074            }
1075
1076            impl $base_chipset_devices {
1077                /// Return a [`BaseChipsetDevices`] with all fields set to
1078                /// `None`
1079                pub fn empty() -> Self {
1080                    Self {
1081                        $([<deps_ $name>]: None,)*
1082                    }
1083                }
1084
1085                /// Return the corresponding [`BaseChipsetManifest`].
1086                pub fn to_manifest(&self) -> $base_chipset_manifest {
1087                    let Self {
1088                        $([<deps_ $name>],)*
1089                    } = self;
1090
1091                    $base_chipset_manifest {
1092                        $([<with_ $name>]: [<deps_ $name>].is_some(),)*
1093                    }
1094                }
1095            }
1096        }};
1097    }
1098
1099    base_chipset_devices_and_manifest! {
1100        impls {
1101            /// Device-specific `BaseChipset` dependencies
1102            #[expect(missing_docs)] // self explanatory field names
1103            pub struct BaseChipsetDevices {
1104                // generated struct has fields that look like this:
1105                //
1106                // deps_<device>: Option<dev::<Deps>>,
1107                ...
1108            }
1109
1110            /// A manifest of devices specified by [`BaseChipsetDevices`].
1111            #[expect(missing_docs)] // self explanatory field names
1112            #[derive(Debug, Clone, MeshPayload, PartialEq, Eq)]
1113            pub struct BaseChipsetManifest {
1114                // generated struct has fields that look like this:
1115                //
1116                // with_<device>: bool,
1117                ...
1118            }
1119        }
1120
1121        devices {
1122            generic_cmos_rtc:            dev::GenericCmosRtcDeps,
1123            generic_ioapic:              dev::GenericIoApicDeps,
1124            generic_isa_dma:             dev::GenericIsaDmaDeps,
1125            generic_isa_floppy:          dev::GenericIsaFloppyDeps,
1126            generic_pci_bus:             dev::GenericPciBusDeps,
1127            generic_pic:                 dev::GenericPicDeps,
1128            generic_pit:                 dev::GenericPitDeps,
1129            generic_psp:                 dev::GenericPspDeps,
1130
1131            hyperv_firmware_pcat:        dev::HyperVFirmwarePcat,
1132            hyperv_firmware_uefi:        dev::HyperVFirmwareUefi,
1133            hyperv_framebuffer:          dev::HyperVFramebufferDeps,
1134            hyperv_guest_watchdog:       dev::HyperVGuestWatchdogDeps,
1135            hyperv_ide:                  dev::HyperVIdeDeps,
1136            hyperv_power_management:     dev::HyperVPowerManagementDeps,
1137            hyperv_vga:                  dev::HyperVVgaDeps,
1138
1139            i440bx_host_pci_bridge:      dev::I440BxHostPciBridgeDeps,
1140
1141            piix4_cmos_rtc:              dev::Piix4CmosRtcDeps,
1142            piix4_pci_bus:               dev::Piix4PciBusDeps,
1143            piix4_pci_isa_bridge:        dev::Piix4PciIsaBridgeDeps,
1144            piix4_pci_usb_uhci_stub:     dev::Piix4PciUsbUhciStubDeps,
1145            piix4_power_management:      dev::Piix4PowerManagementDeps,
1146
1147            underhill_vga_proxy:         dev::UnderhillVgaProxyDeps,
1148
1149            winbond_super_io_and_floppy_stub: dev::WinbondSuperIoAndFloppyStubDeps,
1150            winbond_super_io_and_floppy_full: dev::WinbondSuperIoAndFloppyFullDeps,
1151        }
1152    }
1153
1154    /// Device specific dependencies
1155    pub mod dev {
1156        use super::*;
1157        use crate::BusIdPci;
1158        use chipset_resources::battery::HostBatteryUpdate;
1159        use local_clock::InspectableLocalClock;
1160
1161        macro_rules! feature_gated {
1162            (
1163                feature = $feat:literal;
1164
1165                $(#[$m:meta])*
1166                pub struct $root_deps:ident $($rest:tt)*
1167            ) => {
1168                #[cfg(not(feature = $feat))]
1169                #[doc(hidden)]
1170                pub type $root_deps = ();
1171
1172                #[cfg(feature = $feat)]
1173                $(#[$m])*
1174                pub struct $root_deps $($rest)*
1175            };
1176        }
1177
1178        /// PIIX4 PCI-ISA bridge (fixed pci address: 0:7.0)
1179        pub struct Piix4PciIsaBridgeDeps {
1180            /// `vmotherboard` bus identifier
1181            pub attached_to: BusIdPci,
1182        }
1183
1184        /// Hyper-V IDE controller (fixed pci address: 0:7.1)
1185        // TODO: this device needs to be broken down further, into a PIIX4 IDE
1186        // device (without the Hyper-V enlightenments), and then a Generic IDE
1187        // device (without any of the PIIX4 bus mastering stuff).
1188        pub struct HyperVIdeDeps {
1189            /// `vmotherboard` bus identifier
1190            pub attached_to: BusIdPci,
1191            /// Drives attached to the primary IDE channel
1192            pub primary_channel_drives: [Option<ide::DriveMedia>; 2],
1193            /// Drives attached to the secondary IDE channel
1194            pub secondary_channel_drives: [Option<ide::DriveMedia>; 2],
1195        }
1196
1197        /// PIIX4 USB UHCI controller (fixed pci address: 0:7.2)
1198        ///
1199        /// NOTE: current implementation is a minimal stub, implementing just
1200        /// enough to keep the PCAT BIOS happy.
1201        pub struct Piix4PciUsbUhciStubDeps {
1202            /// `vmotherboard` bus identifier
1203            pub attached_to: BusIdPci,
1204        }
1205
1206        /// PIIX4 power management device (fixed pci address: 0:7.3)
1207        pub struct Piix4PowerManagementDeps {
1208            /// `vmotherboard` bus identifier
1209            pub attached_to: BusIdPci,
1210            /// Interface to enable/disable PM timer assist
1211            pub pm_timer_assist: Option<Box<dyn pm::PmTimerAssist>>,
1212        }
1213
1214        /// Generic dual 8237A ISA DMA controllers
1215        pub struct GenericIsaDmaDeps;
1216
1217        /// Hyper-V specific ACPI-compatible power management device
1218        pub struct HyperVPowerManagementDeps {
1219            /// IRQ line triggered on ACPI power event
1220            pub acpi_irq: u32,
1221            /// Base port io address of the device's register region
1222            pub pio_base: u16,
1223            /// Interface to enable/disable PM timer assist
1224            pub pm_timer_assist: Option<Box<dyn pm::PmTimerAssist>>,
1225        }
1226
1227        /// AMD Platform Security Processor (PSP)
1228        pub struct GenericPspDeps;
1229
1230        feature_gated! {
1231            feature = "dev_generic_isa_floppy";
1232
1233            /// Generic ISA floppy controller
1234            pub struct GenericIsaFloppyDeps {
1235                /// IRQ line shared by both floppy controllers
1236                pub irq: u32,
1237                /// DMA channel to use for floppy DMA transfers
1238                pub dma_channel: u8,
1239                /// Base port io address of the primary devices's register region
1240                pub pio_base: u16,
1241                /// Floppy Drives attached to the controller
1242                pub drives: floppy::DriveRibbon,
1243            }
1244        }
1245
1246        feature_gated! {
1247            feature = "dev_winbond_super_io_and_floppy_stub";
1248
1249            /// Stub Winbond83977 "Super I/O" chip + dual-floppy controllers
1250            ///
1251            /// Unconditionally reports no connected floppy drives. Useful for
1252            /// VMMs that wish to support BIOS boot via the Microsoft PCAT
1253            /// firmware, without paying the binary size + complexity cost of a
1254            /// full floppy disk controller implementation.
1255            ///
1256            /// IRQ and DMA channel assignment MUST match the values reported by
1257            /// the PCAT BIOS ACPI tables, and the Super IO emulator, and cannot
1258            /// be tweaked by top-level VMM code.
1259            pub struct WinbondSuperIoAndFloppyStubDeps;
1260        }
1261
1262        feature_gated! {
1263            feature = "dev_winbond_super_io_and_floppy_full";
1264
1265            /// Winbond83977 "Super I/O" chip + dual-floppy controllers
1266            ///
1267            /// IRQ and DMA channel assignment MUST match the values reported by the
1268            /// PCAT BIOS ACPI tables, and the Super IO emulator, and cannot be
1269            /// tweaked by top-level VMM code.
1270            pub struct WinbondSuperIoAndFloppyFullDeps {
1271                /// Floppy Drive attached to the primary controller
1272                pub primary_disk_drive: floppy::DriveRibbon,
1273                /// Floppy Drive attached to the secondary controller
1274                pub secondary_disk_drive: floppy::DriveRibbon,
1275            }
1276        }
1277
1278        /// Generic PCI bus
1279        pub struct GenericPciBusDeps {
1280            /// `vmotherboard` bus identifier
1281            pub bus_id: BusIdPci,
1282            /// Port io address of the 32-bit PCI ADDR register
1283            pub pio_addr: u16,
1284            /// Port io address of the 32-bit PCI DATA register
1285            pub pio_data: u16,
1286        }
1287
1288        /// PIIX4 PCI Bus
1289        pub struct Piix4PciBusDeps {
1290            /// `vmotherboard` bus identifier
1291            pub bus_id: BusIdPci,
1292        }
1293
1294        /// i440BX Host-PCI bridge (fixed pci address: 0:0.0)
1295        pub struct I440BxHostPciBridgeDeps {
1296            /// `vmotherboard` bus identifier
1297            pub attached_to: BusIdPci,
1298            /// Interface to create GPA alias ranges.
1299            pub adjust_gpa_range: Box<dyn chipset_legacy::i440bx_host_pci_bridge::AdjustGpaRange>,
1300        }
1301
1302        /// Generic Intel 8253/8254 Programmable Interval Timer (PIT)
1303        pub struct GenericPitDeps;
1304
1305        feature_gated! {
1306            feature = "dev_hyperv_vga";
1307
1308            /// Hyper-V specific VGA graphics card
1309            pub struct HyperVVgaDeps {
1310                /// `vmotherboard` bus identifier
1311                pub attached_to: BusIdPci,
1312                /// Interface to map SVGABIOS.bin into memory (or None, if that's
1313                /// handled externally, by the platform itself)
1314                pub rom: Option<Box<dyn guestmem::MapRom>>,
1315            }
1316        }
1317
1318        /// Generic Dual 8259 Programmable Interrupt Controllers  (PIC)
1319        pub struct GenericPicDeps {}
1320
1321        /// Generic IO Advanced Programmable Interrupt Controller (IOAPIC)
1322        pub struct GenericIoApicDeps {
1323            /// Number of IO-APIC entries
1324            pub num_entries: u8,
1325            /// Trait allowing the IO-APIC device to assert VM interrupts.
1326            pub routing: Box<dyn ioapic::IoApicRouting>,
1327        }
1328
1329        /// Generic MC146818A compatible RTC + CMOS device
1330        pub struct GenericCmosRtcDeps {
1331            /// IRQ line to signal RTC device events
1332            pub irq: u32,
1333            /// A source of "real time"
1334            pub time_source: Box<dyn InspectableLocalClock>,
1335            /// Which CMOS RAM register contains the century register
1336            pub century_reg_idx: u8,
1337            /// Initial state of CMOS RAM
1338            pub initial_cmos: Option<[u8; 256]>,
1339        }
1340
1341        /// PIIX4 "flavored" MC146818A compatible RTC + CMOS device
1342        pub struct Piix4CmosRtcDeps {
1343            /// A source of "real time"
1344            pub time_source: Box<dyn InspectableLocalClock>,
1345            /// Initial state of CMOS RAM
1346            pub initial_cmos: Option<[u8; 256]>,
1347            /// Whether enlightened interrupts are enabled. Needed when
1348            /// advertised by ACPI WAET table.
1349            pub enlightened_interrupts: bool,
1350        }
1351
1352        /// Hyper-V specific ACPI-compatible battery device
1353        pub struct HyperVBatteryDeps {
1354            /// Base MMIO address for the battery device
1355            pub base_addr: u64,
1356            /// Whether to use gpe0 for battery status updates
1357            pub use_gpe0: bool,
1358            /// The line interrupt number to use for battery status updates
1359            pub line_interrupt_no: u32,
1360            /// Channel to receive updated battery state
1361            pub battery_status_recv: mesh::Receiver<HostBatteryUpdate>,
1362        }
1363
1364        /// Hyper-V specific Guest Watchdog device
1365        pub struct HyperVGuestWatchdogDeps {
1366            /// Port io address of the device's register region
1367            pub port_base: u16,
1368            /// Device-specific functions the platform must provide in order to
1369            /// use this device.
1370            pub watchdog_platform: Box<dyn watchdog_core::platform::WatchdogPlatform>,
1371        }
1372
1373        /// Hyper-V specific UEFI Helper Device
1374        pub struct HyperVFirmwarePcat {
1375            /// Bundle of static configuration required by the PCAT BIOS
1376            /// helper device
1377            pub config: firmware_pcat::config::PcatBiosConfig,
1378            /// Interface to log PCAT BIOS events
1379            pub logger: Box<dyn firmware_pcat::PcatLogger>,
1380            /// Channel to receive updated generation ID values
1381            pub generation_id_recv: mesh::Receiver<[u8; 16]>,
1382            /// Interface to map VMBIOS.bin into memory (or None, if that's
1383            /// handled externally, by the platform itself)
1384            pub rom: Option<Box<dyn guestmem::MapRom>>,
1385            /// Trigger the partition to replay the initially-set MTRRs across
1386            /// all VPs.
1387            pub replay_mtrrs: Box<dyn Send + FnMut()>,
1388        }
1389
1390        /// Hyper-V specific UEFI Helper Device
1391        pub struct HyperVFirmwareUefi {
1392            /// Bundle of static configuration required by the Hyper-V UEFI
1393            /// helper device
1394            pub config: firmware_uefi::UefiConfig,
1395            /// Interface to log UEFI BIOS events
1396            pub logger: Box<dyn firmware_uefi::platform::logger::UefiLogger>,
1397            /// Interface for storing/retrieving UEFI NVRAM variables
1398            pub nvram_storage: Box<dyn uefi_nvram_storage::VmmNvramStorage>,
1399            /// Channel to receive updated generation ID values
1400            pub generation_id_recv: mesh::Receiver<[u8; 16]>,
1401            /// Device-specific functions the platform must provide in order
1402            /// to use the UEFI watchdog device.
1403            pub watchdog_platform: Box<dyn watchdog_core::platform::WatchdogPlatform>,
1404            /// Channel receiver for watchdog timeout notifications.
1405            pub watchdog_recv: mesh::Receiver<()>,
1406            /// Interface to revoke VSM on `ExitBootServices()` if requested
1407            /// by the guest.
1408            pub vsm_config: Option<Box<dyn firmware_uefi::platform::nvram::VsmConfig>>,
1409            /// Time source
1410            pub time_source: Box<dyn InspectableLocalClock>,
1411        }
1412
1413        /// Hyper-V specific framebuffer device
1414        // TODO: this doesn't really belong in base_chipset... it's less-so a
1415        // device, and more a bit of "infrastructure" that supports other
1416        // video devices.
1417        #[expect(missing_docs)] // see TODO above
1418        pub struct HyperVFramebufferDeps {
1419            pub fb_mapper: Box<dyn guestmem::MemoryMapper>,
1420            pub fb: Framebuffer,
1421            pub vtl2_framebuffer_gpa_base: Option<u64>,
1422        }
1423
1424        feature_gated! {
1425            feature = "dev_underhill_vga_proxy";
1426
1427            /// Underhill specific VGA proxy device
1428            pub struct UnderhillVgaProxyDeps {
1429                /// `vmotherboard` bus identifier
1430                pub attached_to: BusIdPci,
1431                /// PCI proxy callbacks
1432                pub pci_cfg_proxy: Arc<dyn vga_proxy::ProxyVgaPciCfgAccess>,
1433                /// Host IO hotpath registration object
1434                pub register_host_io_fastpath: Box<dyn vga_proxy::RegisterHostIoPortFastPath>,
1435            }
1436        }
1437    }
1438}