From 21bb272722cd5896d6ea6009e7eaf8b6e69ca515 Mon Sep 17 00:00:00 2001 From: iximeow Date: Fri, 18 Sep 2026 19:45:24 +0000 Subject: [PATCH] standalone: should work without explicit CPUID profiles 93e04e189 added support for selecting specific hypervisor interfaces to propolis-standalone, rather than the prior state of "guest always sees bhyve because of the 0x4000_0000 leaf default in vmm_cpuid.c". unfortunately, all my test VM configs include a `cpuid_profile`, so I missed that this change introduced a bug. given a configuration with the default CPUID leaves, the hypervisor interface leaves would get added and misconfigure guests to have all-zero CPUID leaves for every non-hypervisor leaf. many things go wrong; EDK2 sometimes will do bogus rdmsr and panic propolis on the unimplemented MSR. *if* you get past EDK2, GRUB checks for the TSC's presence via leaf 1 EDX[4], which is also clear. failing to find a TSC, GRUB gives up trying to boot. this change has propolis-standalone collect CPUID information more like propolis-server - do not rely on "default" resulting in an empty CPUID profile getting to bhyve, and instead at the point we decide to use a default profile, collect that from bhyve and explicitly provide it later. --- bin/propolis-standalone/src/main.rs | 50 +++++++++++++++++------------ 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/bin/propolis-standalone/src/main.rs b/bin/propolis-standalone/src/main.rs index 0dc548fa7..57c290331 100644 --- a/bin/propolis-standalone/src/main.rs +++ b/bin/propolis-standalone/src/main.rs @@ -1566,28 +1566,36 @@ fn setup_instance( guard.inventory.register(&fwcfg); guard.inventory.register(&ramfb); + let mut base_profile = match cpuid_profile { + Some(profile) => profile, + None => { + // If the config has provided no CPUID configuration, collect the + // default leaves from a bhyve guest and use that. We must collect + // this now, because we'll add hypervisor interface leaves and + // specialize for each vCPU below. + cpuid_utils::host::query_complete( + cpuid_utils::host::CpuidSource::BhyveDefault, + ) + .context("failed to query host cpuid")? + } + }; + machine + .guest_hv_interface + .add_cpuid(&mut base_profile) + .context("failed to add hypervisor cpuid leaves")?; + for vcpu in machine.vcpus.iter() { - let mut vcpu_profile = if let Some(profile) = cpuid_profile.as_ref() { - propolis::cpuid::Specializer::new() - .with_vcpu_count( - std::num::NonZeroU8::new(config.main.cpus).unwrap(), - true, - ) - .with_vcpuid(vcpu.id) - .with_cache_topo() - .clear_cpu_topo(cpuid::TopoKind::iter()) - .with_cpu_topo(cpuid::TopoKind::supported()) - .execute(profile.clone()) - .context("failed to specialize cpuid profile")? - } else { - // An empty set will instruct the kernel to use the legacy - // fallback behavior - cpuid_utils::CpuidSet::new_host() - }; - machine - .guest_hv_interface - .add_cpuid(&mut vcpu_profile) - .context("failed to add hypervisor cpuid leaves")?; + let vcpu_profile = propolis::cpuid::Specializer::new() + .with_vcpu_count( + std::num::NonZeroU8::new(config.main.cpus).unwrap(), + true, + ) + .with_vcpuid(vcpu.id) + .with_cache_topo() + .clear_cpu_topo(cpuid::TopoKind::iter()) + .with_cpu_topo(cpuid::TopoKind::supported()) + .execute(base_profile.clone()) + .context("failed to specialize cpuid profile")?; vcpu.set_cpuid(vcpu_profile)?; vcpu.set_default_capabs()?;