diff --git a/Cargo.lock b/Cargo.lock index d6f50d429e..7547f97ee2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -681,6 +681,7 @@ name = "chipset_resources" version = "0.0.0" dependencies = [ "arbitrary", + "ide_resources", "inspect", "mesh", "vm_resource", @@ -3466,8 +3467,11 @@ dependencies = [ name = "ide" version = "0.0.0" dependencies = [ + "async-trait", "bitfield-struct 0.11.0", "chipset_device", + "chipset_device_resources", + "chipset_resources", "disk_backend", "disk_file", "guestmem", @@ -3489,6 +3493,7 @@ dependencies = [ "tracelimit", "tracing", "tracing_helpers", + "vm_resource", "vmcore", "zerocopy", ] @@ -5253,8 +5258,6 @@ dependencies = [ "hcl_compat_uefi_nvram_storage", "hvdef", "hypervisor_resources", - "ide", - "ide_resources", "igvm", "igvm_defs", "input_core", @@ -5275,8 +5278,6 @@ dependencies = [ "pci_resources", "pcie", "range_map_vec", - "scsi_core", - "scsidisk", "serial_16550_resources", "sparse_mmap", "state_unit", @@ -5450,6 +5451,7 @@ dependencies = [ "debug_worker", "disk_striped", "hyperv_ic", + "ide", "mesh_worker", "missing_dev", "nvme", @@ -5539,6 +5541,7 @@ dependencies = [ "guest_emulation_device", "guest_emulation_log", "hyperv_ic", + "ide", "mesh_worker", "missing_dev", "net_backend", @@ -8303,6 +8306,7 @@ dependencies = [ "chipset_device_worker", "chipset_device_worker_defs", "chipset_legacy", + "chipset_resources", "closeable_mutex", "crypto", "cvm_tracing", @@ -8336,7 +8340,6 @@ dependencies = [ "hyperv_ic_resources", "hyperv_secure_boot_templates", "hyperv_uefi_custom_vars_json", - "ide", "ide_resources", "igvm", "igvm_defs", @@ -8375,8 +8378,6 @@ dependencies = [ "profiler_worker", "safe_intrinsics", "scsi_buffers", - "scsi_core", - "scsidisk", "scsidisk_resources", "serde", "serde_helpers", @@ -8386,7 +8387,6 @@ dependencies = [ "sparse_mmap", "state_unit", "storage_string", - "storvsp", "storvsp_resources", "string_page_buf", "tee_call", @@ -10076,7 +10076,6 @@ dependencies = [ "generation_id", "guest_watchdog", "guestmem", - "ide", "inspect", "inspect_counters", "local_clock", diff --git a/openhcl/openvmm_hcl_resources/Cargo.toml b/openhcl/openvmm_hcl_resources/Cargo.toml index 85c86c2042..77fdb12daf 100644 --- a/openhcl/openvmm_hcl_resources/Cargo.toml +++ b/openhcl/openvmm_hcl_resources/Cargo.toml @@ -30,6 +30,7 @@ uidevices = { workspace = true, optional = true } chipset.workspace = true chipset_legacy.workspace = true +ide.workspace = true hyperv_ic.workspace = true missing_dev.workspace = true serial_16550.workspace = true diff --git a/openhcl/openvmm_hcl_resources/src/lib.rs b/openhcl/openvmm_hcl_resources/src/lib.rs index 87c5a2d3ad..c5a629bbee 100644 --- a/openhcl/openvmm_hcl_resources/src/lib.rs +++ b/openhcl/openvmm_hcl_resources/src/lib.rs @@ -19,6 +19,8 @@ vm_resource::register_static_resolvers! { #[cfg(guest_arch = "x86_64")] chipset_legacy::piix4_pci_isa_bridge::resolver::Piix4PciIsaBridgeResolver, #[cfg(guest_arch = "x86_64")] + ide::resolver::HyperVIdeResolver, + #[cfg(guest_arch = "x86_64")] chipset::pit::resolver::PitResolver, #[cfg(guest_arch = "x86_64")] chipset::pic::resolver::PicResolver, @@ -51,6 +53,7 @@ vm_resource::register_static_resolvers! { // Vmbus devices hyperv_ic::resolver::ShutdownIcResolver, storvsp::resolver::StorvspResolver, + storvsp::resolver::StorvspIdeResolver, #[cfg(feature = "uidevices")] uidevices::resolver::VmbusUiResolver, diff --git a/openhcl/underhill_core/Cargo.toml b/openhcl/underhill_core/Cargo.toml index 70b334518b..e880b81e26 100644 --- a/openhcl/underhill_core/Cargo.toml +++ b/openhcl/underhill_core/Cargo.toml @@ -26,6 +26,7 @@ vmotherboard = { workspace = true, features = [ "dev_underhill_vga_proxy", "dev_winbond_super_io_and_floppy_stub", ] } +chipset_resources.workspace = true build_info.workspace = true cvm_tracing.workspace = true mem_profile_tracing = { workspace = true, optional = true } @@ -63,7 +64,6 @@ hcl_compat_uefi_nvram_storage = { workspace = true, features = ["inspect", "save get_helpers.workspace = true get_protocol.workspace = true guest_emulation_transport.workspace = true -ide.workspace = true ide_resources.workspace = true input_core.workspace = true kmsg_defs.workspace = true @@ -78,12 +78,9 @@ netvsp.workspace = true nvme_driver.workspace = true nvme_resources.workspace = true openhcl_dma_manager.workspace = true -scsi_core.workspace = true -scsidisk.workspace = true scsidisk_resources.workspace = true serial_16550_resources.workspace = true storage_string.workspace = true -storvsp.workspace = true storvsp_resources.workspace = true tpm_protocol.workspace = true tpm_resources.workspace = true diff --git a/openhcl/underhill_core/src/dispatch/mod.rs b/openhcl/underhill_core/src/dispatch/mod.rs index 511639253f..af3c6ede03 100644 --- a/openhcl/underhill_core/src/dispatch/mod.rs +++ b/openhcl/underhill_core/src/dispatch/mod.rs @@ -169,7 +169,6 @@ pub(crate) struct LoadedVm { pub host_vmbus_relay: Option, // channels are revoked when dropped, so make sure to keep them alive pub _vmbus_devices: Vec>>, - pub _ide_accel_devices: Vec>>, pub network_settings: Option>, pub shutdown_relay: Option<( mesh::Receiver>, diff --git a/openhcl/underhill_core/src/worker.rs b/openhcl/underhill_core/src/worker.rs index df511c89e5..2b29789e09 100644 --- a/openhcl/underhill_core/src/worker.rs +++ b/openhcl/underhill_core/src/worker.rs @@ -19,7 +19,6 @@ use crate::ControlRequest; use crate::dispatch::LoadedVm; use crate::dispatch::LoadedVmNetworkSettings; use crate::dispatch::vtl2_settings_worker::InitialControllers; -use crate::dispatch::vtl2_settings_worker::disk_from_disk_type; use crate::dispatch::vtl2_settings_worker::wait_for_mana; use crate::emuplat::EmuplatServicing; use crate::emuplat::firmware::UnderhillLogger; @@ -89,7 +88,6 @@ use hvdef::Vtl; use hvdef::hypercall::HvGuestOsId; use hyperv_ic_guest::ShutdownGuestIc; use ide_resources::GuestMedia; -use ide_resources::IdePath; use igvm_defs::MemoryMapEntryType; use input_core::InputData; use input_core::MultiplexedInputHandle; @@ -115,8 +113,6 @@ use pal_async::DefaultPool; use pal_async::local::LocalDriver; use pal_async::task::Spawn; use parking_lot::Mutex; -use scsi_core::ResolveScsiDeviceHandleParams; -use scsidisk::atapi_scsi::AtapiScsiDisk; use socket2::Socket; use state_unit::SpawnedUnit; use state_unit::StateUnits; @@ -126,7 +122,6 @@ use std::future; use std::sync::Arc; use std::thread::JoinHandle; use std::time::Duration; -use storvsp::ScsiControllerDisk; use thiserror::Error; use tpm_resources::TpmAkCertTypeResource; use tpm_resources::TpmDeviceHandle; @@ -2649,16 +2644,16 @@ async fn new_underhill_vm( }) .unwrap(); - let mut ide_drives = [[None, None], [None, None]]; - let mut storvsp_ide_disks = Vec::new(); - let mut ide_io_queue_depth = None; + let mut storvsp_ide_handles: Vec> = + Vec::new(); + let mut ide_handle_disks: Vec = Vec::new(); if let Some(ide_config) = controllers.ide_controller { if firmware_type != FirmwareType::Pcat { anyhow::bail!("ide requires generation 1, VM is configured for generation 2"); } - ide_io_queue_depth = ide_config.io_queue_depth; + let ide_io_queue_depth = ide_config.io_queue_depth; for (channel, disks) in [ (0, ide_config.primary_channel_disks), @@ -2666,48 +2661,55 @@ async fn new_underhill_vm( ] { for disk_cfg in disks.into_iter() { let drive = disk_cfg.path.drive; - let media = match disk_cfg.guest_media { - GuestMedia::Dvd(device) => { - let scsi_dvd = resolver - .resolve( - device, - ResolveScsiDeviceHandleParams { - driver_source: &driver_source, - }, - ) - .await?; - ide::DriveMedia::optical_disk(Arc::new(AtapiScsiDisk::new(scsi_dvd.0))) - } - GuestMedia::Disk { + + // Duplicate the disk config: one copy for the IDE device + // handle (HyperVIdeDeviceHandle resolver) and one for the + // storvsp IDE accelerator handle. VTL2 settings are pure + // protobuf data (no OS handles), so mesh serialization is + // safe for cloning. + let is_hard_disk = matches!(disk_cfg.guest_media, GuestMedia::Disk { .. }); + let serialized = mesh::resource::SerializedMessage::from_message(disk_cfg); + anyhow::ensure!( + serialized.resources.is_empty(), + "IDE disk configs from VTL2 settings must not contain OS resources" + ); + + // IDE device handle copy (always). + let handle_cfg: ide_resources::IdeDeviceConfig = + (mesh::resource::SerializedMessage { + data: serialized.data.clone(), + resources: Vec::new(), + }) + .into_message() + .context("failed to duplicate IDE disk config for device handle")?; + ide_handle_disks.push(handle_cfg); + + // Storvsp IDE accelerator handle (hard disks only). + if is_hard_disk { + let storvsp_cfg: ide_resources::IdeDeviceConfig = serialized + .into_message() + .context("failed to decode IDE disk config for storvsp")?; + if let GuestMedia::Disk { disk_type, read_only, disk_parameters, - } => { - let disk = - disk_from_disk_type(disk_type, read_only, &resolver, &driver_source) - .await?; - let scsi_disk = Arc::new(scsidisk::SimpleScsiDisk::new( - disk.clone(), - disk_parameters.unwrap_or_default(), - )); - - // Only disks, not DVD drives, get IDE accelerator channels. - storvsp_ide_disks.push(( - IdePath { channel, drive }, - ScsiControllerDisk::new(scsi_disk), - )); - - ide::DriveMedia::hard_disk(disk) + } = storvsp_cfg.guest_media + { + storvsp_ide_handles.push( + storvsp_resources::StorvspIdeDeviceHandle { + channel_id: channel, + device_id: drive, + disk: scsidisk_resources::SimpleScsiDiskHandle { + disk: disk_type, + read_only, + parameters: disk_parameters.unwrap_or_default(), + } + .into_resource(), + io_queue_depth: ide_io_queue_depth, + } + .into_resource(), + ); } - }; - - let old_media = ide_drives[channel as usize] - .get_mut(drive as usize) - .context("invalid ide device")? - .replace(media); - - if old_media.is_some() { - anyhow::bail!("duplicate ide device at {}/{}", channel, drive); } } } @@ -2801,18 +2803,24 @@ async fn new_underhill_vm( enlightened_interrupts: true, // As advertised by the PCAT BIOS. }); - let deps_hyperv_ide = if chipset.with_hyperv_ide { - let [primary_channel_drives, secondary_channel_drives] = ide_drives; - Some(dev::HyperVIdeDeps { - attached_to: pci_bus_id_piix4.clone(), - primary_channel_drives, - secondary_channel_drives, - }) - } else { - // Ensured above. - assert!(ide_drives.iter().flatten().all(|d| d.is_none())); - None - }; + // Push the IDE device handle for the chipset builder resolver. + let mut pci_chipset_devices = pci_chipset_devices; + if !ide_handle_disks.is_empty() { + use chipset_resources::LEGACY_CHIPSET_PCI_BUS_NAME; + use chipset_resources::ide::HYPERV_IDE_BDF; + use chipset_resources::ide::HyperVIdeDeviceHandle; + use vmotherboard::LegacyPciChipsetDeviceHandle; + + pci_chipset_devices.push(LegacyPciChipsetDeviceHandle { + name: "hyperv-ide".to_string(), + resource: HyperVIdeDeviceHandle { + disks: ide_handle_disks, + } + .into_resource(), + pci_bus_name: LEGACY_CHIPSET_PCI_BUS_NAME.to_string(), + bdf: HYPERV_IDE_BDF, + }); + } let deps_underhill_vga_proxy = chipset @@ -2970,7 +2978,6 @@ async fn new_underhill_vm( deps_generic_pci_bus: None, deps_hyperv_firmware_pcat, deps_hyperv_framebuffer: None, - deps_hyperv_ide, deps_hyperv_vga: None, deps_i440bx_host_pci_bridge, deps_piix4_cmos_rtc, @@ -3300,31 +3307,12 @@ async fn new_underhill_vm( let mut vmbus_device_handles = controllers.vmbus_devices; + // Push storvsp IDE accelerator handles into the VMBus device list. + vmbus_device_handles.append(&mut storvsp_ide_handles); + // Storage - let mut ide_accel_devices = Vec::new(); { let _span = tracing::info_span!("scsi_controller_map", CVM_ALLOWED).entered(); - - for (path, scsi_disk) in storvsp_ide_disks { - let io_queue_depth = ide_io_queue_depth.unwrap_or(default_io_queue_depth); - ide_accel_devices.push( - offer_channel_unit( - &tp, - &state_units, - vmbus_server - .as_ref() - .context("ide requires vmbus redirection to be configured")?, - storvsp::StorageDevice::build_ide( - &driver_source, - path.channel, - path.drive, - scsi_disk, - io_queue_depth, - ), - ) - .await?, - ); - } } // VPCI @@ -3618,7 +3606,6 @@ async fn new_underhill_vm( vmbus_server, host_vmbus_relay, _vmbus_devices: vmbus_devices, - _ide_accel_devices: ide_accel_devices, network_settings, shutdown_relay, diff --git a/openvmm/openvmm_core/Cargo.toml b/openvmm/openvmm_core/Cargo.toml index 2adf4460a3..f95f7db319 100644 --- a/openvmm/openvmm_core/Cargo.toml +++ b/openvmm/openvmm_core/Cargo.toml @@ -31,7 +31,6 @@ aarch64defs.workspace = true acpi.workspace = true floppy_resources.workspace = true hvdef.workspace = true -ide_resources.workspace = true igvm.workspace = true igvm_defs.workspace = true loader.workspace = true @@ -62,7 +61,6 @@ uefi_nvram_storage = { workspace = true, features = ["save_restore"] } framebuffer.workspace = true get_resources.workspace = true hcl_compat_uefi_nvram_storage = { workspace = true, features = ["inspect", "save_restore"] } -ide.workspace = true floppy.workspace = true input_core.workspace = true missing_dev.workspace = true @@ -70,8 +68,6 @@ pci_bus.workspace = true pci_core.workspace = true pci_resources.workspace = true pcie.workspace = true -scsi_core.workspace = true -scsidisk.workspace = true serial_16550_resources.workspace = true storvsp.workspace = true virtio.workspace = true diff --git a/openvmm/openvmm_core/src/worker/dispatch.rs b/openvmm/openvmm_core/src/worker/dispatch.rs index 0535edb655..8fde4ccb48 100644 --- a/openvmm/openvmm_core/src/worker/dispatch.rs +++ b/openvmm/openvmm_core/src/worker/dispatch.rs @@ -26,8 +26,6 @@ use guestmem::GuestMemory; use hvdef::HV_PAGE_SIZE; use hvdef::Vtl; use hypervisor_resources::HypervisorKind; -use ide_resources::GuestMedia; -use ide_resources::IdeDeviceConfig; use igvm::IgvmFile; use input_core::InputData; use input_core::MultiplexedInputHandle; @@ -80,9 +78,6 @@ use pci_core::PciInterruptPin; use pcie::root::GenericPcieRootComplex; use pcie::root::GenericPcieRootPortDefinition; use pcie::switch::GenericPcieSwitch; -use scsi_core::ResolveScsiDeviceHandleParams; -use scsidisk::SimpleScsiDisk; -use scsidisk::atapi_scsi::AtapiScsiDisk; use serial_16550_resources::ComPort; use state_unit::SavedStateUnit; use state_unit::SpawnedUnit; @@ -91,7 +86,6 @@ use std::fs::File; use std::sync::Arc; use std::thread; use std::thread::JoinHandle; -use storvsp::ScsiControllerDisk; use virt::ProtoPartition; use virt::VpIndex; use virtio::PciInterruptModel; @@ -134,7 +128,6 @@ use vmm_core::partition_unit::PartitionUnitParams; use vmm_core::partition_unit::block_on_vp; use vmm_core::vmbus_unit::ChannelUnit; use vmm_core::vmbus_unit::VmbusServerHandle; -use vmm_core::vmbus_unit::offer_channel_unit; use vmm_core::vmbus_unit::offer_vmbus_device_handle_unit; use vmm_core_defs::HaltReason; use vmotherboard::BaseChipsetBuilder; @@ -167,7 +160,6 @@ impl Manifest { Self { load_mode: config.load_mode, floppy_disks: config.floppy_disks, - ide_disks: config.ide_disks, pcie_root_complexes: config.pcie_root_complexes, pcie_devices: config.pcie_devices, pcie_switches: config.pcie_switches, @@ -216,7 +208,6 @@ impl Manifest { pub struct Manifest { load_mode: LoadMode, floppy_disks: Vec, - ide_disks: Vec, pcie_root_complexes: Vec, pcie_devices: Vec, pcie_switches: Vec, @@ -1317,64 +1308,9 @@ impl InitializedVm { let mut pci_legacy_interrupts = Vec::new(); - let mut ide_drives = [[None, None], [None, None]]; - let mut storvsp_ide_disks = Vec::new(); - if cfg.chipset.with_hyperv_ide { + if cfg.chipset_capabilities.with_ide { pci_legacy_interrupts.push(((7, None), 14)); pci_legacy_interrupts.push(((7, None), 15)); - - for disk_cfg in cfg.ide_disks { - let path = disk_cfg.path; - let media = match disk_cfg.guest_media { - GuestMedia::Dvd(disk_type) => { - let dvd = resolver - .resolve( - disk_type, - ResolveScsiDeviceHandleParams { - driver_source: &driver_source, - }, - ) - .await - .context("failed to open IDE DVD")?; - - let scsi_disk = Arc::new(AtapiScsiDisk::new(dvd.0)); - ide::DriveMedia::optical_disk(scsi_disk.clone()) - } - GuestMedia::Disk { - disk_type, - read_only, - disk_parameters, - } => { - let disk = - open_simple_disk(&resolver, disk_type, read_only, &driver_source) - .await - .context("failed to open IDE disk")?; - - // Only disks get accelerator channels. DVDs dont. - let scsi_disk = ScsiControllerDisk::new(Arc::new(SimpleScsiDisk::new( - disk.clone(), - disk_parameters.unwrap_or_default(), - ))); - storvsp_ide_disks.push((path, scsi_disk)); - ide::DriveMedia::hard_disk(disk.clone()) - } - }; - - let old_media = ide_drives - .get_mut(path.channel as usize) - .context("invalid ide channel")? - .get_mut(path.drive as usize) - .context("invalid ide device")? - .replace(media); - - if old_media.is_some() { - anyhow::bail!( - "ide drive {}:{} is already in use", - path.channel, - path.drive - ); - } - } } let deps_hyperv_guest_watchdog = if cfg.chipset.with_hyperv_guest_watchdog { @@ -1579,6 +1515,11 @@ impl InitializedVm { } }); + let deps_piix4_pci_isa_bridge = + (cfg.chipset.with_piix4_pci_isa_bridge).then_some(dev::Piix4PciIsaBridgeDeps { + attached_to: pci_bus_id_piix4.clone(), + }); + let [primary_channel_drives, secondary_channel_drives] = ide_drives; let deps_hyperv_ide = (cfg.chipset.with_hyperv_ide).then_some(dev::HyperVIdeDeps { attached_to: pci_bus_id_piix4.clone(), @@ -1604,7 +1545,6 @@ impl InitializedVm { deps_hyperv_firmware_uefi, deps_hyperv_framebuffer, deps_hyperv_guest_watchdog, - deps_hyperv_ide, deps_hyperv_power_management, deps_hyperv_vga, deps_i440bx_host_pci_bridge, @@ -1708,7 +1648,7 @@ impl InitializedVm { } }; - let mut scsi_devices = Vec::new(); + let scsi_devices = Vec::new(); let mut vtl0_hvsock_relay = None; #[cfg(windows)] let mut vmbus_proxy = None; @@ -1955,28 +1895,6 @@ impl InitializedVm { // Synthetic devices { - // Arbitrary default - const DEFAULT_IO_QUEUE_DEPTH: u32 = 256; - if let Some(vmbus) = &vmbus_server { - for (path, scsi_disk) in storvsp_ide_disks { - scsi_devices.push( - offer_channel_unit( - &driver_source.simple(), - &state_units, - vmbus, - storvsp::StorageDevice::build_ide( - &driver_source, - path.channel, - path.drive, - scsi_disk, - DEFAULT_IO_QUEUE_DEPTH, - ), - ) - .await?, - ); - } - } - #[cfg(windows)] for nic_config in cfg.kernel_vmnics { let mut nic = vmswitch::kernel::KernelVmNic::new( @@ -3070,7 +2988,6 @@ impl LoadedVm { let manifest = Manifest { load_mode: self.inner.load_mode, floppy_disks: vec![], // TODO - ide_disks: vec![], // TODO pcie_root_complexes: vec![], // TODO pcie_devices: vec![], // TODO pcie_switches: vec![], // TODO diff --git a/openvmm/openvmm_entry/src/storage_builder.rs b/openvmm/openvmm_entry/src/storage_builder.rs index 35bc22f13f..27b12f93eb 100644 --- a/openvmm/openvmm_entry/src/storage_builder.rs +++ b/openvmm/openvmm_entry/src/storage_builder.rs @@ -25,17 +25,19 @@ use std::collections::BTreeMap; use storvsp_resources::ScsiControllerHandle; use storvsp_resources::ScsiDeviceAndPath; use storvsp_resources::ScsiPath; +use storvsp_resources::StorvspIdeDeviceHandle; use virtio_resources::VirtioPciDeviceHandle; use virtio_resources::blk::VirtioBlkHandle; use vm_resource::IntoResource; use vm_resource::Resource; use vm_resource::kind::DiskHandleKind; +use vm_resource::kind::ScsiDeviceHandleKind; use vtl2_settings_proto::Lun; use vtl2_settings_proto::StorageController; use vtl2_settings_proto::storage_controller; pub(super) struct StorageBuilder { - vtl0_ide_disks: Vec, + vtl0_ide_entries: Vec, vtl0_scsi_devices: Vec, vtl2_scsi_devices: Vec, vtl0_nvme_namespaces: Vec, @@ -53,6 +55,13 @@ struct VirtioBlkDisk { read_only: bool, } +struct IdeEntry { + config: IdeDeviceConfig, + /// For hard disks, the SCSI device resource for the storvsp IDE + /// accelerator channel. `None` for DVDs. + storvsp_scsi_disk: Option>, +} + #[derive(Clone)] pub enum DiskLocation { Ide(Option, Option), @@ -113,7 +122,7 @@ const VIRTIO_BLK_INSTANCE_ID_TEMPLATE: Guid = guid::guid!("00000000-a4e7-4b53-b7 impl StorageBuilder { pub fn new(openhcl_vtl: Option) -> Self { Self { - vtl0_ide_disks: Vec::new(), + vtl0_ide_entries: Vec::new(), vtl0_scsi_devices: Vec::new(), vtl2_scsi_devices: Vec::new(), vtl0_nvme_namespaces: Vec::new(), @@ -185,9 +194,9 @@ impl StorageBuilder { channel.unwrap_or(c) == c && device.unwrap_or(d) == d && !self - .vtl0_ide_disks + .vtl0_ide_entries .iter() - .any(|cfg| cfg.path.channel == c && cfg.path.drive == d) + .any(|e| e.config.path.channel == c && e.config.path.drive == d) }; let (channel, device) = (0..=1) @@ -198,12 +207,30 @@ impl StorageBuilder { if vtl != DeviceVtl::Vtl0 { anyhow::bail!("ide only supported for VTL0"); } - self.vtl0_ide_disks.push(IdeDeviceConfig { - path: IdePath { - channel, - drive: device, + + let storvsp_scsi_disk = if !is_dvd { + let storvsp_disk = disk_open(kind, read_only).await?; + Some( + SimpleScsiDiskHandle { + disk: storvsp_disk, + read_only, + parameters: Default::default(), + } + .into_resource(), + ) + } else { + None + }; + + self.vtl0_ide_entries.push(IdeEntry { + config: IdeDeviceConfig { + path: IdePath { + channel, + drive: device, + }, + guest_media, }, - guest_media, + storvsp_scsi_disk, }); None } @@ -372,7 +399,39 @@ impl StorageBuilder { resources: &mut VmResources, scsi_sub_channels: u16, ) -> anyhow::Result<()> { - config.ide_disks.append(&mut self.vtl0_ide_disks); + let ide_entries = std::mem::take(&mut self.vtl0_ide_entries); + let mut ide_disks = Vec::new(); + for entry in ide_entries { + if let Some(storvsp_scsi_disk) = entry.storvsp_scsi_disk { + config.vmbus_devices.push(( + DeviceVtl::Vtl0, + StorvspIdeDeviceHandle { + channel_id: entry.config.path.channel, + device_id: entry.config.path.drive, + disk: storvsp_scsi_disk, + io_queue_depth: None, + } + .into_resource(), + )); + } + ide_disks.push(entry.config); + } + + if !ide_disks.is_empty() { + use chipset_resources::LEGACY_CHIPSET_PCI_BUS_NAME; + use chipset_resources::ide::HYPERV_IDE_BDF; + use chipset_resources::ide::HyperVIdeDeviceHandle; + use vmotherboard::LegacyPciChipsetDeviceHandle; + + config + .pci_chipset_devices + .push(LegacyPciChipsetDeviceHandle { + name: "hyperv-ide".to_string(), + resource: HyperVIdeDeviceHandle { disks: ide_disks }.into_resource(), + pci_bus_name: LEGACY_CHIPSET_PCI_BUS_NAME.to_string(), + bdf: HYPERV_IDE_BDF, + }); + } // Add an empty VTL0 SCSI controller even if there are no configured disks. if !self.vtl0_scsi_devices.is_empty() || config.vmbus.is_some() { diff --git a/openvmm/openvmm_resources/Cargo.toml b/openvmm/openvmm_resources/Cargo.toml index 33aea95551..af17277c3e 100644 --- a/openvmm/openvmm_resources/Cargo.toml +++ b/openvmm/openvmm_resources/Cargo.toml @@ -50,6 +50,7 @@ disklayer_sqlite = { workspace = true, optional = true } # Chipset devices chipset.workspace = true chipset_legacy.workspace = true +ide.workspace = true missing_dev.workspace = true tpm_device = { workspace = true, optional = true, features = ["tpm"] } diff --git a/openvmm/openvmm_resources/src/lib.rs b/openvmm/openvmm_resources/src/lib.rs index 71a2a8aad7..567bfb2483 100644 --- a/openvmm/openvmm_resources/src/lib.rs +++ b/openvmm/openvmm_resources/src/lib.rs @@ -16,6 +16,8 @@ vm_resource::register_static_resolvers! { #[cfg(guest_arch = "x86_64")] chipset_legacy::piix4_pci_isa_bridge::resolver::Piix4PciIsaBridgeResolver, #[cfg(guest_arch = "x86_64")] + ide::resolver::HyperVIdeResolver, + #[cfg(guest_arch = "x86_64")] chipset::pit::resolver::PitResolver, #[cfg(guest_arch = "x86_64")] chipset::pic::resolver::PicResolver, @@ -101,6 +103,7 @@ vm_resource::register_static_resolvers! { hyperv_ic::resolver::TimesyncIcResolver, netvsp::resolver::NetvspResolver, storvsp::resolver::StorvspResolver, + storvsp::resolver::StorvspIdeResolver, uidevices::resolver::VmbusUiResolver, vmbfs::resolver::VmbfsResolver, vmbus_serial_host::resolver::VmbusSerialDeviceResolver, diff --git a/petri/src/vm/openvmm/construct.rs b/petri/src/vm/openvmm/construct.rs index 2f33efca6c..54f0cf37bf 100644 --- a/petri/src/vm/openvmm/construct.rs +++ b/petri/src/vm/openvmm/construct.rs @@ -205,7 +205,8 @@ impl PetriVmConfigOpenVmm { None => (None, None, None), }; - let ide_disks = ide_controllers_to_openvmm(firmware.ide_controllers()).await?; + let (ide_disks, storvsp_ide_handles) = + ide_controllers_to_openvmm(firmware.ide_controllers()).await?; let (mut vmbus_devices, vpci_devices) = vmbus_storage_controllers_to_openvmm(&vmbus_storage_controllers).await?; @@ -239,6 +240,8 @@ impl PetriVmConfigOpenVmm { }); } + vmbus_devices.extend(storvsp_ide_handles); + let (firmware_event_send, firmware_event_recv) = mesh::mpsc_channel(); let make_vsock_listener = || -> anyhow::Result<(UnixListener, TempPath)> { @@ -464,7 +467,7 @@ impl PetriVmConfigOpenVmm { let VmChipsetResult { chipset, mut chipset_devices, - pci_chipset_devices, + mut pci_chipset_devices, capabilities, } = chipset; @@ -473,6 +476,21 @@ impl PetriVmConfigOpenVmm { chipset_devices.push(tpm); } + // Add the IDE device handle if there are any IDE disks. + if !ide_disks.is_empty() { + use chipset_resources::LEGACY_CHIPSET_PCI_BUS_NAME; + use chipset_resources::ide::HYPERV_IDE_BDF; + use chipset_resources::ide::HyperVIdeDeviceHandle; + use vmotherboard::LegacyPciChipsetDeviceHandle; + + pci_chipset_devices.push(LegacyPciChipsetDeviceHandle { + name: "hyperv-ide".to_string(), + resource: HyperVIdeDeviceHandle { disks: ide_disks }.into_resource(), + pci_bus_name: LEGACY_CHIPSET_PCI_BUS_NAME.to_string(), + bdf: HYPERV_IDE_BDF, + }); + } + let config = Config { // Firmware load_mode, @@ -510,7 +528,7 @@ impl PetriVmConfigOpenVmm { // Devices floppy_disks: vec![], - ide_disks, + ide_disks: vec![], pcie_root_complexes: vec![], pcie_devices, pcie_switches: vec![], @@ -1111,17 +1129,38 @@ fn spawn_dump_handler(driver: &DefaultDriver, logger: &PetriLogSource) -> GuestC handle } -/// Convert the generic IDE configuration to OpenVMM IDE disks. +/// Convert the generic IDE configuration to OpenVMM IDE device disks and +/// storvsp IDE accelerator handles. +/// +/// Returns the IDE device disk configs (for HyperVIdeDeviceHandle) and +/// storvsp IDE accelerator VMBus device handles (hard disks only). async fn ide_controllers_to_openvmm( ide_controllers: Option<&[[Option; 2]; 2]>, -) -> anyhow::Result> { +) -> anyhow::Result<( + Vec, + Vec<(DeviceVtl, Resource)>, +)> { let mut ide_disks = Vec::new(); + let mut storvsp_ide_handles = Vec::new(); if let Some(ide_controllers) = ide_controllers { for (controller_number, controller) in ide_controllers.iter().enumerate() { for (controller_location, drive) in controller.iter().enumerate() { if let Some(drive) = drive { if let Some(disk) = &drive.disk { + let path = ide_resources::IdePath { + channel: controller_number as u8, + drive: controller_location as u8, + }; + + // Create storvsp resource before shadowing the disk + // binding with the resolved resource below. + let storvsp_disk = if !drive.is_dvd { + Some(petri_disk_to_openvmm(disk).await?) + } else { + None + }; + let disk = petri_disk_to_openvmm(disk).await?; let guest_media = if drive.is_dvd { GuestMedia::Dvd( @@ -1139,20 +1178,33 @@ async fn ide_controllers_to_openvmm( } }; - ide_disks.push(IdeDeviceConfig { - path: ide_resources::IdePath { - channel: controller_number as u8, - drive: controller_location as u8, - }, - guest_media, - }); + ide_disks.push(IdeDeviceConfig { path, guest_media }); + + // Hard disks also get a storvsp IDE accelerator channel. + if let Some(storvsp_disk) = storvsp_disk { + storvsp_ide_handles.push(( + DeviceVtl::Vtl0, + storvsp_resources::StorvspIdeDeviceHandle { + channel_id: path.channel, + device_id: path.drive, + disk: SimpleScsiDiskHandle { + disk: storvsp_disk, + read_only: false, + parameters: Default::default(), + } + .into_resource(), + io_queue_depth: None, + } + .into_resource(), + )); + } } } } } } - Ok(ide_disks) + Ok((ide_disks, storvsp_ide_handles)) } /// Convert the generic VMBUS storage configuration to OpenVMM VMBUS and VPCI devices. diff --git a/vm/devices/chipset_resources/Cargo.toml b/vm/devices/chipset_resources/Cargo.toml index a6f9c2e597..748ad7dcfc 100644 --- a/vm/devices/chipset_resources/Cargo.toml +++ b/vm/devices/chipset_resources/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true rust-version.workspace = true [dependencies] +ide_resources.workspace = true vm_resource.workspace = true inspect.workspace = true diff --git a/vm/devices/chipset_resources/src/lib.rs b/vm/devices/chipset_resources/src/lib.rs index 3c27ab2d72..734024a970 100644 --- a/vm/devices/chipset_resources/src/lib.rs +++ b/vm/devices/chipset_resources/src/lib.rs @@ -147,6 +147,29 @@ pub mod piix4_pci_isa_bridge { } } +pub mod ide { + //! Resource definitions for the Hyper-V IDE controller device. + + use ide_resources::IdeDeviceConfig; + use mesh::MeshPayload; + use vm_resource::ResourceId; + use vm_resource::kind::ChipsetDeviceHandleKind; + + /// A handle to a Hyper-V IDE controller device. + #[derive(MeshPayload)] + pub struct HyperVIdeDeviceHandle { + /// IDE device configurations for attached drives. + pub disks: Vec, + } + + /// The fixed BDF used by the Hyper-V IDE controller in the Gen1 chipset. + pub const HYPERV_IDE_BDF: (u8, u8, u8) = (0, 7, 1); + + impl ResourceId for HyperVIdeDeviceHandle { + const ID: &'static str = "hyperv-ide"; + } +} + pub mod piix4_uhci { //! Resource definitions for the PIIX4 USB UHCI stub device. diff --git a/vm/devices/storage/ide/Cargo.toml b/vm/devices/storage/ide/Cargo.toml index 21564b021f..325b4520fb 100644 --- a/vm/devices/storage/ide/Cargo.toml +++ b/vm/devices/storage/ide/Cargo.toml @@ -8,6 +8,8 @@ rust-version.workspace = true [dependencies] chipset_device.workspace = true +chipset_device_resources.workspace = true +chipset_resources.workspace = true disk_backend.workspace = true ide_resources.workspace = true inspect.workspace = true @@ -20,8 +22,10 @@ scsi_core.workspace = true scsi_defs.workspace = true tracing_helpers.workspace = true guestmem.workspace = true +vm_resource.workspace = true vmcore.workspace = true +async-trait.workspace = true bitfield-struct.workspace = true static_assertions.workspace = true thiserror.workspace = true diff --git a/vm/devices/storage/ide/src/lib.rs b/vm/devices/storage/ide/src/lib.rs index 8af8efcac9..55cfdf2adf 100644 --- a/vm/devices/storage/ide/src/lib.rs +++ b/vm/devices/storage/ide/src/lib.rs @@ -30,6 +30,8 @@ #![expect(missing_docs)] #![forbid(unsafe_code)] +pub mod resolver; + mod drive; mod protocol; diff --git a/vm/devices/storage/ide/src/resolver.rs b/vm/devices/storage/ide/src/resolver.rs new file mode 100644 index 0000000000..d489d8e522 --- /dev/null +++ b/vm/devices/storage/ide/src/resolver.rs @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Resolver for the Hyper-V IDE controller device. + +use super::DriveMedia; +use super::IdeDevice; +use async_trait::async_trait; +use chipset_device_resources::IRQ_LINE_SET; +use chipset_device_resources::ResolveChipsetDeviceHandleParams; +use chipset_device_resources::ResolvedChipsetDevice; +use chipset_resources::ide::HyperVIdeDeviceHandle; +use disk_backend::resolve::ResolveDiskParameters; +use ide_resources::GuestMedia; +use scsi_core::ResolveScsiDeviceHandleParams; +use thiserror::Error; +use vm_resource::AsyncResolveResource; +use vm_resource::ResolveError; +use vm_resource::ResourceResolver; +use vm_resource::declare_static_async_resolver; +use vm_resource::kind::ChipsetDeviceHandleKind; +use vm_resource::kind::DiskHandleKind; +use vm_resource::kind::ScsiDeviceHandleKind; + +/// A resolver for the Hyper-V IDE controller device. +pub struct HyperVIdeResolver; + +declare_static_async_resolver! { + HyperVIdeResolver, + (ChipsetDeviceHandleKind, HyperVIdeDeviceHandle), +} + +/// Errors that can occur when resolving the IDE controller. +#[derive(Debug, Error)] +#[expect(missing_docs)] +pub enum ResolveIdeError { + #[error("invalid IDE channel {0}")] + InvalidChannel(u8), + #[error("invalid IDE drive {0}")] + InvalidDrive(u8), + #[error("IDE drive {0}:{1} is already in use")] + DriveInUse(u8, u8), + #[error("failed to open IDE disk at {0}/{1}")] + OpenDisk(u8, u8, #[source] ResolveError), + #[error("failed to open IDE DVD at {0}/{1}")] + OpenDvd(u8, u8, #[source] ResolveError), + #[error("failed to create IDE device")] + NewDevice(#[source] super::NewDeviceError), +} + +#[async_trait] +impl AsyncResolveResource for HyperVIdeResolver { + type Output = ResolvedChipsetDevice; + type Error = ResolveIdeError; + + async fn resolve( + &self, + resolver: &ResourceResolver, + resource: HyperVIdeDeviceHandle, + input: ResolveChipsetDeviceHandleParams<'_>, + ) -> Result { + let primary_interrupt = input.configure.new_line(IRQ_LINE_SET, "primary", 14); + let secondary_interrupt = input.configure.new_line(IRQ_LINE_SET, "secondary", 15); + + let mut drives = [[None, None], [None, None]]; + + for disk_cfg in resource.disks { + let path = disk_cfg.path; + + let channel = drives + .get_mut(path.channel as usize) + .ok_or(ResolveIdeError::InvalidChannel(path.channel))?; + let slot = channel + .get_mut(path.drive as usize) + .ok_or(ResolveIdeError::InvalidDrive(path.drive))?; + + if slot.is_some() { + return Err(ResolveIdeError::DriveInUse(path.channel, path.drive)); + } + + let media = match disk_cfg.guest_media { + GuestMedia::Dvd(scsi_resource) => { + let scsi_device = resolver + .resolve::( + scsi_resource, + ResolveScsiDeviceHandleParams { + driver_source: input.task_driver_source, + }, + ) + .await + .map_err(|e| ResolveIdeError::OpenDvd(path.channel, path.drive, e))?; + + DriveMedia::optical_disk(scsi_device.0) + } + GuestMedia::Disk { + disk_type, + read_only, + disk_parameters: _, + } => { + let disk = resolver + .resolve::( + disk_type, + ResolveDiskParameters { + read_only, + driver_source: input.task_driver_source, + }, + ) + .await + .map_err(|e| ResolveIdeError::OpenDisk(path.channel, path.drive, e))?; + + DriveMedia::hard_disk(disk.0) + } + }; + + *slot = Some(media); + } + + let [primary_drives, secondary_drives] = drives; + + let device = IdeDevice::new( + input.guest_memory.clone(), + input.register_pio, + primary_drives, + secondary_drives, + primary_interrupt, + secondary_interrupt, + ) + .map_err(ResolveIdeError::NewDevice)?; + + Ok(device.into()) + } +} diff --git a/vm/devices/storage/storvsp/src/resolver.rs b/vm/devices/storage/storvsp/src/resolver.rs index 5aec4cb883..82d73e4b47 100644 --- a/vm/devices/storage/storvsp/src/resolver.rs +++ b/vm/devices/storage/storvsp/src/resolver.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! Resolver for a SCSI controller. +//! Resolvers for storvsp SCSI and IDE accelerator devices. use super::StorageDevice; use crate::ScsiController; @@ -19,6 +19,7 @@ use storvsp_resources::ScsiControllerHandle; use storvsp_resources::ScsiControllerRequest; use storvsp_resources::ScsiDeviceAndPath; use storvsp_resources::ScsiPath; +use storvsp_resources::StorvspIdeDeviceHandle; use thiserror::Error; use vm_resource::AsyncResolveResource; use vm_resource::ResolveError; @@ -146,3 +147,62 @@ async fn handle_requests( } } } + +/// The resolver for [`StorvspIdeDeviceHandle`]. +pub struct StorvspIdeResolver; + +declare_static_async_resolver! { + StorvspIdeResolver, + (VmbusDeviceHandleKind, StorvspIdeDeviceHandle), +} + +/// An error returned by [`StorvspIdeResolver`]. +#[derive(Debug, Error)] +pub enum IdeError { + #[error("invalid IDE channel {0} (must be 0 or 1)")] + InvalidChannel(u8), + #[error("invalid IDE device {0} (must be 0 or 1)")] + InvalidDevice(u8), + #[error("failed to resolve IDE disk at channel {0} device {1}")] + ResolveDisk(u8, u8, #[source] ResolveError), +} + +#[async_trait] +impl AsyncResolveResource for StorvspIdeResolver { + type Output = ResolvedVmbusDevice; + type Error = IdeError; + + async fn resolve( + &self, + resolver: &ResourceResolver, + resource: StorvspIdeDeviceHandle, + input: ResolveVmbusDeviceHandleParams<'_>, + ) -> Result { + if resource.channel_id > 1 { + return Err(IdeError::InvalidChannel(resource.channel_id)); + } + if resource.device_id > 1 { + return Err(IdeError::InvalidDevice(resource.device_id)); + } + + let disk = resolver + .resolve( + resource.disk, + ResolveScsiDeviceHandleParams { + driver_source: input.driver_source, + }, + ) + .await + .map_err(|e| IdeError::ResolveDisk(resource.channel_id, resource.device_id, e))?; + + let device = StorageDevice::build_ide( + input.driver_source, + resource.channel_id, + resource.device_id, + ScsiControllerDisk::new(disk.0), + resource.io_queue_depth.unwrap_or(256), + ); + + Ok(device.into()) + } +} diff --git a/vm/devices/storage/storvsp_resources/src/lib.rs b/vm/devices/storage/storvsp_resources/src/lib.rs index c8687ab174..2d1777ac7d 100644 --- a/vm/devices/storage/storvsp_resources/src/lib.rs +++ b/vm/devices/storage/storvsp_resources/src/lib.rs @@ -81,3 +81,24 @@ pub enum ScsiControllerRequest { /// Remove a device. RemoveDevice(FailableRpc), } + +/// Handle for a storvsp IDE accelerator device. +/// +/// Each handle represents a single IDE disk offered as a VMBus accelerator +/// channel, mapping IDE addressing (channel/device) to SCSI addressing +/// (path/target/LUN 0). +#[derive(MeshPayload)] +pub struct StorvspIdeDeviceHandle { + /// The IDE channel (maps to SCSI path). + pub channel_id: u8, + /// The IDE device number (maps to SCSI target). + pub device_id: u8, + /// The SCSI device backing the IDE disk. + pub disk: Resource, + /// The I/O queue depth per channel. + pub io_queue_depth: Option, +} + +impl ResourceId for StorvspIdeDeviceHandle { + const ID: &'static str = "storvsp-ide"; +} diff --git a/vmm_core/vm_manifest_builder/src/lib.rs b/vmm_core/vm_manifest_builder/src/lib.rs index 890a3ebee8..11da4489fc 100644 --- a/vmm_core/vm_manifest_builder/src/lib.rs +++ b/vmm_core/vm_manifest_builder/src/lib.rs @@ -226,6 +226,7 @@ impl VmManifestBuilder { pci_chipset_devices: Vec::new(), chipset: BaseChipsetManifest::empty(), capabilities: VmChipsetCapabilities { + with_ide: false, with_ioapic: false, with_pic: false, with_pit: false, @@ -265,7 +266,6 @@ impl VmManifestBuilder { with_hyperv_firmware_uefi: false, with_hyperv_framebuffer: !self.proxy_vga, with_hyperv_guest_watchdog: false, - with_hyperv_ide: true, with_hyperv_power_management: false, with_hyperv_vga: !self.proxy_vga, with_i440bx_host_pci_bridge: true, @@ -278,6 +278,7 @@ impl VmManifestBuilder { }; result.capabilities.with_ioapic = true; result.attach_pic(); + result.capabilities.with_ide = true; result.attach_pit(); result.attach_missing_arch_ports(self.arch, false); if let Some(recv) = self.battery_status_recv { @@ -297,7 +298,6 @@ impl VmManifestBuilder { with_hyperv_firmware_uefi: false, with_hyperv_framebuffer: self.framebuffer, with_hyperv_guest_watchdog: self.guest_watchdog, - with_hyperv_ide: false, with_hyperv_power_management: is_x86, with_hyperv_vga: false, with_i440bx_host_pci_bridge: false, @@ -339,7 +339,6 @@ impl VmManifestBuilder { with_hyperv_firmware_uefi: matches!(self.ty, BaseChipsetType::HypervGen2Uefi), with_hyperv_framebuffer: self.framebuffer, with_hyperv_guest_watchdog: self.guest_watchdog, - with_hyperv_ide: false, with_hyperv_power_management: is_x86, with_hyperv_vga: false, with_i440bx_host_pci_bridge: false, diff --git a/vmm_core/vmotherboard/Cargo.toml b/vmm_core/vmotherboard/Cargo.toml index 1e1d4207fc..b441a99d6b 100644 --- a/vmm_core/vmotherboard/Cargo.toml +++ b/vmm_core/vmotherboard/Cargo.toml @@ -34,7 +34,6 @@ floppy = { optional = true, workspace = true } floppy_pcat_stub = { optional = true, workspace = true } generation_id.workspace = true guest_watchdog.workspace = true -ide.workspace = true missing_dev.workspace = true pci_bus.workspace = true pcie.workspace = true diff --git a/vmm_core/vmotherboard/src/base_chipset.rs b/vmm_core/vmotherboard/src/base_chipset.rs index 4c2d602870..92247fe1c8 100644 --- a/vmm_core/vmotherboard/src/base_chipset.rs +++ b/vmm_core/vmotherboard/src/base_chipset.rs @@ -224,7 +224,6 @@ impl<'a> BaseChipsetBuilder<'a> { deps_hyperv_firmware_uefi, deps_hyperv_framebuffer, deps_hyperv_guest_watchdog, - deps_hyperv_ide, deps_hyperv_power_management, deps_hyperv_vga, deps_i440bx_host_pci_bridge, @@ -391,32 +390,6 @@ impl<'a> BaseChipsetBuilder<'a> { } } - if let Some(options::dev::HyperVIdeDeps { - attached_to, - primary_channel_drives, - secondary_channel_drives, - }) = deps_hyperv_ide - { - builder - .arc_mutex_device("ide") - .on_pci_bus(attached_to) - .try_add(|services| { - // hard-coded to iRQ lines 14 and 15, as per PIIX4 spec - let primary_channel_line_interrupt = - services.new_line(IRQ_LINE_SET, "ide1", 14); - let secondary_channel_line_interrupt = - services.new_line(IRQ_LINE_SET, "ide2", 15); - ide::IdeDevice::new( - foundation.untrusted_dma_memory.clone(), - &mut services.register_pio(), - primary_channel_drives, - secondary_channel_drives, - primary_channel_line_interrupt, - secondary_channel_line_interrupt, - ) - })?; - } - if let Some(options::dev::GenericCmosRtcDeps { irq, time_source, @@ -1121,7 +1094,6 @@ pub mod options { hyperv_firmware_uefi: dev::HyperVFirmwareUefi, hyperv_framebuffer: dev::HyperVFramebufferDeps, hyperv_guest_watchdog: dev::HyperVGuestWatchdogDeps, - hyperv_ide: dev::HyperVIdeDeps, hyperv_power_management: dev::HyperVPowerManagementDeps, hyperv_vga: dev::HyperVVgaDeps, @@ -1149,6 +1121,8 @@ pub mod options { pub with_pit: bool, /// Whether the VM exposes a PSP. pub with_psp: bool, + /// Whether the VM exposes a Hyper-V IDE controller. + pub with_ide: bool, } /// Device specific dependencies @@ -1175,6 +1149,12 @@ pub mod options { }; } + /// PIIX4 PCI-ISA bridge (fixed pci address: 0:7.0) + pub struct Piix4PciIsaBridgeDeps { + /// `vmotherboard` bus identifier + pub attached_to: BusIdPci, + } + /// Hyper-V IDE controller (fixed pci address: 0:7.1) // TODO: this device needs to be broken down further, into a PIIX4 IDE // device (without the Hyper-V enlightenments), and then a Generic IDE