diff --git a/bin/propolis-cli/src/main.rs b/bin/propolis-cli/src/main.rs index e380d469c..963dc3e45 100644 --- a/bin/propolis-cli/src/main.rs +++ b/bin/propolis-cli/src/main.rs @@ -27,6 +27,7 @@ use propolis_client::types::{ InstanceEnsureRequest, InstanceInitializationMethod, InstanceMetadata, InstanceSpecGetResponse, }; +use propolis_config_toml::spec::toml_cpuid_to_spec_cpuid; use propolis_config_toml::spec::SpecConfig; use serde::{Deserialize, Serialize}; use slog::{o, Drain, Level, Logger}; @@ -66,6 +67,9 @@ struct Opt { cmd: Command, } +// `New`, via `VmConfig`, is large enough to trip this lint. This enum is +// created exactly once, so we don't need to be picky about the layout.. +#[allow(clippy::large_enum_variant)] #[derive(Debug, Subcommand)] enum Command { /// Create a new propolis instance @@ -180,6 +184,12 @@ struct VmConfig { #[clap(short, default_value = "1024", action, requires = "config_toml")] memory: u64, + /// CPUID profile to use. + /// + /// The named profile must be defined in `config_toml`. + #[clap(long, requires = "config_toml")] + cpuid_profile: Option, + /// A path to a file containing a config TOML #[clap(short = 't', long, action, group = "config_group", requires_all = ["vcpus", "memory"])] config_toml: Option, @@ -288,22 +298,39 @@ impl VmConfig { return parse_json_file(path); } - let from_toml = &self + let parsed_toml = self .config_toml .as_ref() .map(propolis_config_toml::parse) - .transpose()? - .as_ref() - .map(SpecConfig::try_from) .transpose()?; + let from_toml = + parsed_toml.as_ref().map(SpecConfig::try_from).transpose()?; + let enable_pcie = from_toml.as_ref().map(|cfg| cfg.enable_pcie).unwrap_or(false); + let cpuid_profile = parsed_toml + .as_ref() + .and_then(|cfg| { + self.cpuid_profile.as_ref().map(|profile| { + cfg.cpuid_profiles + .get(profile) + .ok_or_else(|| { + anyhow!("CPUID profile not defined: {}", profile) + }) + .and_then(|profile| { + toml_cpuid_to_spec_cpuid(profile) + .map_err(Into::into) + }) + }) + }) + .transpose()?; + let mut spec = InstanceSpecV0 { board: Board { chipset: Chipset::I440Fx(I440Fx { enable_pcie }), - cpuid: None, + cpuid: cpuid_profile, cpus: self.vcpus, memory_mb: self.memory, guest_hv_interface: if self.hyperv { diff --git a/crates/propolis-config-toml/src/lib.rs b/crates/propolis-config-toml/src/lib.rs index 31f50ba36..583fb6591 100644 --- a/crates/propolis-config-toml/src/lib.rs +++ b/crates/propolis-config-toml/src/lib.rs @@ -10,7 +10,9 @@ use std::str::FromStr; use serde_derive::{Deserialize, Serialize}; use thiserror::Error; -pub use cpuid_profile_config::CpuidProfile; +pub use cpuid_profile_config::{ + CpuVendor, CpuidEntry, CpuidParseError, CpuidProfile, +}; pub mod spec; diff --git a/crates/propolis-config-toml/src/spec.rs b/crates/propolis-config-toml/src/spec.rs index fce06b2a9..ab86fb719 100644 --- a/crates/propolis-config-toml/src/spec.rs +++ b/crates/propolis-config-toml/src/spec.rs @@ -11,10 +11,10 @@ use std::{ use propolis_client::{ instance_spec::{ - ComponentV0, DlpiNetworkBackend, FileStorageBackend, - MigrationFailureInjector, NvmeDisk, P9fs, PciPath, PciPciBridge, - SoftNpuP9, SoftNpuPciPort, SoftNpuPort, SpecKey, VirtioDisk, - VirtioNetworkBackend, VirtioNic, + ComponentV0, Cpuid, CpuidVendor, DlpiNetworkBackend, + FileStorageBackend, MigrationFailureInjector, NvmeDisk, P9fs, PciPath, + PciPciBridge, SoftNpuP9, SoftNpuPciPort, SoftNpuPort, SpecKey, + VirtioDisk, VirtioNetworkBackend, VirtioNic, }, support::nvme_serial_from_str, }; @@ -430,3 +430,40 @@ fn parse_p9fs_from_config( pci_path, }) } + +/// Translate a parsed TOML-provided `CpuidEntry` into a `propolis-server` +/// API-style `CpuidEntry`. +/// +/// The transformation here is trivial. Using the API-style `CpuidEntry` for the +/// TOML definition would make for clumsier text, though, so they're defined +/// slightly differently for the different use cases. +fn translate_cpuid_entry( + toml_entry: super::CpuidEntry, +) -> propolis_client::instance_spec::CpuidEntry { + let super::CpuidEntry { func, idx, values: [eax, ebx, ecx, edx] } = + toml_entry; + + propolis_client::instance_spec::CpuidEntry { + leaf: func, + subleaf: idx, + eax, + ebx, + ecx, + edx, + } +} + +/// Not a `TryFrom` or `TryInto` because we're re-exporting types from +/// `cpuid-profile-config`, so they're actually defined in a foreign crate. +pub fn toml_cpuid_to_spec_cpuid( + profile: &super::CpuidProfile, +) -> Result { + let entries = Vec::::try_from(profile)?; + let entries = entries.into_iter().map(translate_cpuid_entry).collect(); + + let vendor = match profile.vendor { + super::CpuVendor::Amd => CpuidVendor::Amd, + super::CpuVendor::Intel => CpuidVendor::Intel, + }; + Ok(Cpuid { entries, vendor }) +} diff --git a/lib/propolis-client/src/lib.rs b/lib/propolis-client/src/lib.rs index 04778f4ff..86ab2599d 100644 --- a/lib/propolis-client/src/lib.rs +++ b/lib/propolis-client/src/lib.rs @@ -43,6 +43,7 @@ progenitor::generate_api!( ReplacementComponent = crate::instance_spec::ReplacementComponent, InstanceSpecV0 = crate::instance_spec::InstanceSpecV0, VersionedInstanceSpec = crate::instance_spec::VersionedInstanceSpec, + CpuidEntry = crate::instance_spec::CpuidEntry, }, // Automatically derive JsonSchema for instance spec-related types so that // they can be reused in sled-agent's API. This can't be done with a