diff --git a/Cargo.lock b/Cargo.lock index 8a1e1f08c3..26f1b3023f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -680,6 +680,7 @@ version = "0.0.0" dependencies = [ "arbitrary", "inspect", + "local_clock", "mesh", "vm_resource", ] @@ -5445,6 +5446,7 @@ dependencies = [ "build_rs_guest_arch", "chipset", "chipset_legacy", + "chipset_resources", "debug_worker", "disk_striped", "hyperv_ic", @@ -5520,6 +5522,7 @@ dependencies = [ "build_rs_guest_arch", "chipset", "chipset_legacy", + "chipset_resources", "debug_worker", "disk_blob", "disk_blockdevice", @@ -8301,6 +8304,7 @@ dependencies = [ "chipset_device_worker", "chipset_device_worker_defs", "chipset_legacy", + "chipset_resources", "closeable_mutex", "crypto", "cvm_tracing", diff --git a/openhcl/openvmm_hcl_resources/Cargo.toml b/openhcl/openvmm_hcl_resources/Cargo.toml index 85c86c2042..629b0c240a 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 +chipset_resources.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 c633a360c0..7e7b6ff45b 100644 --- a/openhcl/openvmm_hcl_resources/src/lib.rs +++ b/openhcl/openvmm_hcl_resources/src/lib.rs @@ -20,6 +20,7 @@ vm_resource::register_static_resolvers! { chipset::pit::resolver::PitResolver, #[cfg(guest_arch = "x86_64")] chipset::pic::resolver::PicResolver, + chipset_resources::cmos_rtc_time_source::SystemTimeClockResolver, missing_dev::resolver::MissingDevResolver, #[cfg(feature = "tpm")] tpm_device::resolver::TpmDeviceResolver, diff --git a/openhcl/underhill_core/Cargo.toml b/openhcl/underhill_core/Cargo.toml index 70b334518b..8d4a3b773d 100644 --- a/openhcl/underhill_core/Cargo.toml +++ b/openhcl/underhill_core/Cargo.toml @@ -43,6 +43,7 @@ chipset_device.workspace = true chipset_device_resources.workspace = true chipset_device_worker.workspace = true chipset_device_worker_defs.workspace = true +chipset_resources.workspace = true closeable_mutex.workspace = true profiler_worker = { workspace = true, optional = true } chipset_legacy.workspace = true diff --git a/openhcl/underhill_core/src/worker.rs b/openhcl/underhill_core/src/worker.rs index 817c511008..c5d86eb6db 100644 --- a/openhcl/underhill_core/src/worker.rs +++ b/openhcl/underhill_core/src/worker.rs @@ -62,6 +62,8 @@ use anyhow::Context; use async_trait::async_trait; use chipset_device::ChipsetDevice; use chipset_device_worker_defs::RemoteChipsetDeviceHandle; +use chipset_resources::CmosRtcTimeSourceHandleKind; +use chipset_resources::ResolvedCmosRtcTimeSource; use closeable_mutex::CloseableMutex; use cvm_tracing::CVM_ALLOWED; use debug_ptr::DebugPtr; @@ -148,6 +150,8 @@ use virt_mshv_vtl::UhPartitionNewParams; use virt_mshv_vtl::UhProtoPartition; use vm_loader::initial_regs::initial_regs; use vm_resource::IntoResource; +use vm_resource::PlatformResource; +use vm_resource::ResolveResource; use vm_resource::Resource; use vm_resource::ResourceResolver; use vm_resource::kind::DiskHandleKind; @@ -194,6 +198,23 @@ pub(crate) const PM_BASE: u16 = 0x400; pub(crate) const SYSTEM_IRQ_ACPI: u32 = 9; pub(crate) const WDAT_PORT: u16 = 0x30; +struct UnderhillCmosRtcTimeSourceResolver { + time_source: ArcMutexUnderhillLocalClock, +} + +impl ResolveResource + for UnderhillCmosRtcTimeSourceResolver +{ + type Output = ResolvedCmosRtcTimeSource; + type Error = std::convert::Infallible; + + fn resolve(&self, _resource: PlatformResource, (): ()) -> Result { + Ok(ResolvedCmosRtcTimeSource(Box::new( + self.time_source.new_linked_clock(), + ))) + } +} + pub const UNDERHILL_WORKER: WorkerId = WorkerId::new("UnderhillWorker"); const MAX_SUBCHANNELS_PER_VNIC: u16 = 32; @@ -2286,7 +2307,6 @@ async fn new_underhill_vm( Some(n) => n.to_ne_bytes(), }; - // TODO: move to instantiate via a resource. let rtc_time_source = ArcMutexUnderhillLocalClock(Arc::new(Mutex::new( UnderhillLocalClock::new( get_client.clone(), @@ -2303,6 +2323,10 @@ async fn new_underhill_vm( .context("failed to initialize UnderhillLocalClock emuplat")?, ))); + resolver.add_resolver(UnderhillCmosRtcTimeSourceResolver { + time_source: rtc_time_source.new_linked_clock(), + }); + let mut serial_inputs = [None, None, None, None]; if dps.general.com1_vmbus_redirector { @@ -2802,7 +2826,7 @@ async fn new_underhill_vm( #[cfg(guest_arch = "x86_64")] let deps_piix4_cmos_rtc = chipset.with_piix4_cmos_rtc.then(|| dev::Piix4CmosRtcDeps { - time_source: Box::new(rtc_time_source.new_linked_clock()), + time_source: PlatformResource.into_resource(), initial_cmos: Some(firmware_pcat::default_cmos_values(&mem_layout)), enlightened_interrupts: true, // As advertised by the PCAT BIOS. }); @@ -2866,7 +2890,7 @@ async fn new_underhill_vm( .with_generic_cmos_rtc .then(|| dev::GenericCmosRtcDeps { irq: 8, - time_source: Box::new(rtc_time_source.new_linked_clock()), + time_source: PlatformResource.into_resource(), century_reg_idx: 0x32, initial_cmos: None, }); diff --git a/openvmm/openvmm_core/src/worker/dispatch.rs b/openvmm/openvmm_core/src/worker/dispatch.rs index 2179f6ae49..1cf9478fe9 100644 --- a/openvmm/openvmm_core/src/worker/dispatch.rs +++ b/openvmm/openvmm_core/src/worker/dispatch.rs @@ -11,6 +11,7 @@ use anyhow::Context; use cfg_if::cfg_if; use chipset_device_resources::IRQ_LINE_SET; use chipset_resources::LEGACY_CHIPSET_PCI_BUS_NAME; +use chipset_resources::cmos_rtc_time_source::SystemTimeClockHandle; use debug_ptr::DebugPtr; use disk_backend::Disk; use disk_backend::resolve::ResolveDiskParameters; @@ -99,6 +100,7 @@ use virtio::VirtioMmioDevice; use virtio::VirtioPciDevice; use virtio::resolve::VirtioResolveInput; use vm_loader::initial_regs::initial_regs; +use vm_resource::IntoResource; use vm_resource::Resource; use vm_resource::ResourceResolver; use vm_resource::kind::DiskHandleKind; @@ -1419,14 +1421,12 @@ impl InitializedVm { }; let deps_generic_cmos_rtc = (cfg.chipset.with_generic_cmos_rtc).then(|| { - // TODO: persist SystemTimeClock time across reboots. - // TODO: move to instantiate via a resource. - let time_source = Box::new(local_clock::SystemTimeClock::new( - LocalClockDelta::from_millis(cfg.rtc_delta_milliseconds), - )); dev::GenericCmosRtcDeps { irq: 8, - time_source, + time_source: SystemTimeClockHandle { + delta_milliseconds: cfg.rtc_delta_milliseconds, + } + .into_resource(), century_reg_idx: 0x32, // TODO: automatically sync with FADT initial_cmos: initial_rtc_cmos, } @@ -1569,13 +1569,11 @@ impl InitializedVm { }); let deps_piix4_cmos_rtc = (cfg.chipset.with_piix4_cmos_rtc).then(|| { - // TODO: persist SystemTimeClock time across reboots. - // TODO: move to instantiate via a resource. - let time_source = Box::new(local_clock::SystemTimeClock::new( - LocalClockDelta::from_millis(cfg.rtc_delta_milliseconds), - )); dev::Piix4CmosRtcDeps { - time_source, + time_source: SystemTimeClockHandle { + delta_milliseconds: cfg.rtc_delta_milliseconds, + } + .into_resource(), initial_cmos: initial_rtc_cmos, enlightened_interrupts: true, // As advertised by the PCAT BIOS. } diff --git a/openvmm/openvmm_resources/Cargo.toml b/openvmm/openvmm_resources/Cargo.toml index 33aea95551..644bded9bd 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 +chipset_resources.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 81e40e4103..c7aec23a8d 100644 --- a/openvmm/openvmm_resources/src/lib.rs +++ b/openvmm/openvmm_resources/src/lib.rs @@ -17,6 +17,7 @@ vm_resource::register_static_resolvers! { chipset::pit::resolver::PitResolver, #[cfg(guest_arch = "x86_64")] chipset::pic::resolver::PicResolver, + chipset_resources::cmos_rtc_time_source::SystemTimeClockResolver, missing_dev::resolver::MissingDevResolver, #[cfg(feature = "tpm")] tpm_device::resolver::TpmDeviceResolver, diff --git a/vm/devices/chipset_resources/Cargo.toml b/vm/devices/chipset_resources/Cargo.toml index a6f9c2e597..465a64e522 100644 --- a/vm/devices/chipset_resources/Cargo.toml +++ b/vm/devices/chipset_resources/Cargo.toml @@ -8,6 +8,7 @@ rust-version.workspace = true [dependencies] vm_resource.workspace = true +local_clock.workspace = true inspect.workspace = true mesh.workspace = true diff --git a/vm/devices/chipset_resources/src/lib.rs b/vm/devices/chipset_resources/src/lib.rs index c3726e5d57..27ea2c04b8 100644 --- a/vm/devices/chipset_resources/src/lib.rs +++ b/vm/devices/chipset_resources/src/lib.rs @@ -5,9 +5,77 @@ #![forbid(unsafe_code)] +use local_clock::InspectableLocalClock; +use vm_resource::CanResolveTo; +use vm_resource::ResourceKind; + /// The PCI bus name used by the Gen1 (i440BX + PIIX4) chipset. pub const LEGACY_CHIPSET_PCI_BUS_NAME: &str = "i440bx"; +/// Resource kind for CMOS RTC time-source handles. +pub enum CmosRtcTimeSourceHandleKind {} + +impl ResourceKind for CmosRtcTimeSourceHandleKind { + const NAME: &'static str = "cmos_rtc_time_source"; +} + +/// Resolved runtime time source for CMOS RTC devices. +pub struct ResolvedCmosRtcTimeSource(pub Box); + +impl CanResolveTo for CmosRtcTimeSourceHandleKind { + type Input<'a> = (); +} + +pub mod cmos_rtc_time_source { + //! Resource definitions and resolvers for CMOS RTC time sources. + + use super::CmosRtcTimeSourceHandleKind; + use super::ResolvedCmosRtcTimeSource; + use local_clock::LocalClockDelta; + use local_clock::SystemTimeClock; + use mesh::MeshPayload; + use vm_resource::ResolveResource; + use vm_resource::ResourceId; + use vm_resource::declare_static_resolver; + + /// A time source backed by the host system clock with a configurable + /// millisecond delta. + #[derive(MeshPayload)] + pub struct SystemTimeClockHandle { + /// Offset from system time in milliseconds. + pub delta_milliseconds: i64, + } + + impl ResourceId for SystemTimeClockHandle { + const ID: &'static str = "system_time_clock"; + } + + /// Resolver for [`SystemTimeClockHandle`]. + pub struct SystemTimeClockResolver; + + declare_static_resolver! { + SystemTimeClockResolver, + (CmosRtcTimeSourceHandleKind, SystemTimeClockHandle), + } + + impl ResolveResource + for SystemTimeClockResolver + { + type Output = ResolvedCmosRtcTimeSource; + type Error = std::convert::Infallible; + + fn resolve( + &self, + resource: SystemTimeClockHandle, + (): (), + ) -> Result { + Ok(ResolvedCmosRtcTimeSource(Box::new(SystemTimeClock::new( + LocalClockDelta::from_millis(resource.delta_milliseconds), + )))) + } + } +} + pub mod i8042 { //! Resource definitions for the i8042 PS2 keyboard/mouse controller. diff --git a/vmm_core/vmotherboard/src/base_chipset.rs b/vmm_core/vmotherboard/src/base_chipset.rs index b719242a03..a2ca40db58 100644 --- a/vmm_core/vmotherboard/src/base_chipset.rs +++ b/vmm_core/vmotherboard/src/base_chipset.rs @@ -46,6 +46,8 @@ pub enum BaseChipsetBuilderError { FeatureGatedDevice(&'static str), #[error("no valid ISA DMA controller for floppy")] NoDmaForFloppy, + #[error("failed to resolve resource")] + ResolveResource(#[source] vm_resource::ResolveError), } /// A grab-bag of device-specific interfaces that may need to be wired up into @@ -448,9 +450,13 @@ impl<'a> BaseChipsetBuilder<'a> { initial_cmos, }) = deps_generic_cmos_rtc { + let resolved = resolver + .resolve(time_source, ()) + .await + .map_err(BaseChipsetBuilderError::ResolveResource)?; builder.arc_mutex_device("rtc").add(|services| { cmos_rtc::Rtc::new( - time_source, + resolved.0, services.new_line(IRQ_LINE_SET, "interrupt", irq), services.register_vmtime(), century_reg_idx, @@ -466,11 +472,15 @@ impl<'a> BaseChipsetBuilder<'a> { enlightened_interrupts, }) = deps_piix4_cmos_rtc { + let resolved = resolver + .resolve(time_source, ()) + .await + .map_err(BaseChipsetBuilderError::ResolveResource)?; builder.arc_mutex_device("piix4-rtc").add(|services| { // hard-coded to IRQ line 8, as per PIIX4 spec let rtc_interrupt = services.new_line(IRQ_LINE_SET, "interrupt", 8); chipset_legacy::piix4_cmos_rtc::Piix4CmosRtc::new( - time_source, + resolved.0, rtc_interrupt, services.register_vmtime(), initial_cmos, @@ -1340,8 +1350,8 @@ pub mod options { pub struct GenericCmosRtcDeps { /// IRQ line to signal RTC device events pub irq: u32, - /// A source of "real time" - pub time_source: Box, + /// A time source resource, resolved at device build time. + pub time_source: vm_resource::Resource, /// Which CMOS RAM register contains the century register pub century_reg_idx: u8, /// Initial state of CMOS RAM @@ -1350,8 +1360,8 @@ pub mod options { /// PIIX4 "flavored" MC146818A compatible RTC + CMOS device pub struct Piix4CmosRtcDeps { - /// A source of "real time" - pub time_source: Box, + /// A time source resource, resolved at device build time. + pub time_source: vm_resource::Resource, /// Initial state of CMOS RAM pub initial_cmos: Option<[u8; 256]>, /// Whether enlightened interrupts are enabled. Needed when