Skip to content

cpu_profile: support AMD CPUs, pin amd.milan.v1, and serve AMD's configuration MSRs - #115

Merged
ppenna merged 4 commits into
mainfrom
fix/amd-cpu-profiles-396
Oct 6, 2026
Merged

ppenna merged 4 commits into
mainfrom
fix/amd-cpu-profiles-396

Conversation

@ppenna

@ppenna ppenna commented Oct 6, 2026 •

Copy link
Copy Markdown

Support AMD CPUs in the time ABI's CPU profiles, for microsoft/nvx#396. This accepts AMD CPUs in profiles and host profiles, gives AMD profiles their own derivation rules and VM-owned topology fields, maps the Hyper-V feature banks to AMD's CPUID bits, serves AMD's HWCR and DE_CFG MSRs, and pins amd.milan.v1. The four Intel profiles stay byte-identical.

Why

Every microVM cold boot selects a CPU profile, and only Intel profiles existed. On an AMD host, --cpu-profile auto and --cpu-profile host both failed with E_PROFILE_HOST_UNKNOWN. A profile derived from an AMD fingerprint failed validation. Had it been accepted, every cold boot would have failed with E_CPU_SURFACE, because OpenVMM sets AMD topology fields that the profile pinned. microsoft/nvx#396 lists the eight blockers.

Commits

Commit microsoft#396 rows Change
d290df675 cpu_profile, virt: accept AMD CPUs in CPU profiles and host profiles 1-4, 7 Adds CpuVendor and amd.host.v1. AMD's topology fields (0x80000008 ECX, 0x8000001D sharing counts, 0x8000001E) are VM-owned in AMD profiles only. The derivation policy gains an AMD section: data leaves 0x80000005, 0x8000001D, 0x8000001E, and 0x80000021; SVM, IBS, LWP, MONITORX, RDPRU, PPIN, CPPC, SME/SEV, and BTC_NO cleared; PSFD kept only with a SPEC_CTRL control, by the condition that KVM's supported surface now shares. 0x8000001D subleaves are handled like leaf 4's, with one L3 per socket. auto's E_PROFILE_HOST_UNKNOWN and E_PROFILE_UNSUPPORTED cold-boot errors name --cpu-profile host on Intel and AMD hosts
905993c36 cpu_profile: map the Hyper-V feature banks to AMD's CPUID bits 5 Under AMD profiles, IBRS, IBPB, STIBP, SSBD, PSFD, and nested virtualization map to AMD's CPUID bits, and the bank table gains the AMD-only bits
933c1de0c virt: serve AMD's HWCR and DE_CFG and per-VP 0x8000001E under AMD profiles 6, 8 Under AMD profiles only, on WHP, KVM, and MSHV: HWCR reads TscFreqSel, DE_CFG reads LFENCE serialization, and any write of another value raises #GP. MSHV presents 0x8000001E per VP. TOPOEXT stays
07850d27a cpu_profile: pin an AMD Milan CPU profile, amd.milan.v1 plan 5-6 amd.milan.v1 covers family 25, model 1, every stepping. It is derived from one WHP fingerprint of an Azure EPYC 7763 VM, with digest sha256:463ee0368dc01bf56a3ab1157d62b21b51d11b3dcccb46c804864748ef696543

Each commit message details its rules, tests, and validation.

Intel invariance

  • The four Intel profile files and their golden digests are unchanged. pinned_data.rs changes only the array length and appends Milan's block.
  • Every AMD rule is gated on the profile's vendor, and tests derive Intel profiles with values in AMD's leaves to show the Intel policy alone applies. Under Intel profiles, the KVM MSR filter, the MSHV intercepts, and WHP's MSR handling are unchanged. strip_psfd_leaf reads the same bits as before, through a function it now shares with the policy.

Pre-publication review

An independent review of the first revision found that it pinned BTC_NO and PSFD in amd.milan.v1:

  • KVM never offers BTC_NO: kvm_cpu_cap_init(CPUID_8000_0008_EBX, ...) omits it.
  • OpenVMM's KVM surface strips PSFD without a SPEC_CTRL control.

So every KVM Milan host, GitHub's EPYC 7763 runners included, would have failed with E_PROFILE_UNSUPPORTED. The AMD policy now clears BTC_NO, which Linux never needs on family 0x19 or later because it marks no such CPU as Retbleed-affected. It also clears PSFD, which is controlled through SPEC_CTRL, unless a SPEC_CTRL control stays. The profile was re-derived: 0x80000008 EBX changed from 0x30000005 to 0x00000005.

Scope and exclusions

  • Hygon CPUs get neither a profile nor a host profile.
  • No AMD KVM or MSHV host was available. The AMD paths of those backends (the MSR filter and intercepts, and MSHV's per-VP 0x8000001E) are unit-tested only, and Milan hosts on KVM and MSHV are unverified.
  • amd.milan.v1 has no speculation controls, because the source WHP offers none. Its Linux guests therefore use retpolines and report SSB, SRSO, and TSA as vulnerable on every host.
  • Unchanged here: virt_whp does not build for aarch64-pc-windows-msvc (lib.rs:1390), as at the base.

Validation

The series is based on main at a41cf7351, which includes #113. The two changes don't overlap in code, and both sides' Guide edits are kept.

On an Azure Standard_D16as_v5 VM (AMD EPYC 7763, 25/1/1) with Windows 11 build 26200 and nested WHP, under the CI-pinned Rust 1.95.0, at each of the four commits:

  • clippy --all-targets and rustdoc with warnings denied, and xtask fmt, on Windows. For virt_kvm and virt_mshv, clippy also runs for x86_64-unknown-linux-gnu.
  • Unit tests, by commit:
    • cpu_profile 104, 108, 108, and 111;
    • virt 64, 64, 65, and 65;
    • virt_whp 35, 35, 36, and 36;
    • openvmm_core 98, openvmm_helpers 98, and openvmm_entry 179;
    • at the head, virt_kvm 41 and virt_mshv 77 in a rust:1.95.0 Linux container, where cpu_profile 111 and virt 65 also pass.
  • All 16 virt_whp hardware tests on the EPYC 7763, at every commit. At the head they run on amd.milan.v1.

With a release build of the head, --cpu-fingerprint passes with amd.milan.v1. NVX's runtime validation on the same host is described in microsoft/nvx#404.

Dependents

ppenna and others added 4 commits October 6, 2026 01:21
Only Intel CPUs could have a CPU profile (microsoft/nvx#396, rows 1 to
4 and 7). Identity validation accepted only GenuineIntel, host profiles
served only Intel CPUs, and the VM-owned bits, the derivation policy,
and OpenVMM's topology leaves covered only Intel's CPUID layout. On an
AMD host, auto and --cpu-profile host both failed with
E_PROFILE_HOST_UNKNOWN; a profile derived from an AMD fingerprint failed
validation, and, once accepted, every cold boot would have failed with
E_CPU_SURFACE, because OpenVMM sets AMD topology fields that the profile
pinned.

A profile's vendor is now a CpuVendor: Intel, or AMD, which profile IDs
spell amd. Hygon CPUs, which follow AMD's layout, still have neither a
profile nor a host profile. derive_host_profile derives amd.host.v1 on
an AMD host, with the brand string "AMD Processor (host profile)", and
supports_host_profiles accepts AMD CPUs, so OpenVMM's auto error also
names --cpu-profile host there. KnownGeneration takes the vendor too.

auto's errors name --cpu-profile host in one more case: a cold boot
whose built-in profile the backend does not support now fails with
E_PROFILE_UNSUPPORTED and the hint, on an Intel or AMD host, because a
host profile derives from what the backend supports and can boot where
the built-in one cannot. The VM worker adds the hint as a context of the
error, which keeps the backend's chain and code, wherever the code
arises: the WHP and MSHV feature banks at partition creation, or the
support check of every backend. Explicit profile IDs, host, restores,
and other vendors' CPUs get no hint, as for E_PROFILE_HOST_UNKNOWN.

vm_owned_bits and pinned_mask take the profile's vendor. AMD profiles
leave AMD's topology fields to the VM: the core count and APIC ID size
of 0x80000008 ECX, each cache's sharing count in 0x8000001D EAX[25:14],
and the processor topology of 0x8000001E (EAX, EBX[15:0], and
ECX[10:0]), which leaf 1 and the extended topology leaves complete as on
Intel. Leaf 4's fields stay VM-owned on Intel only. The Intel profiles
keep pinning AMD's fields, which a vendor-neutral rule would have
unpinned, so their encodings and digests are unchanged, as the
catalog's golden digests check.

The derivation policy gains an AMD section, which only AMD profiles
take, so it derives every Intel profile as before and keeps its
version. 0x80000005, 0x8000001D, 0x8000001E, and 0x80000021 keep data
(AMD_DATA_LEAVES). 0x8000001D lists its subleaves up to the null cache
type, as leaf 4 does, in profiles and in derivation, and combines by
majority, like the other cache descriptors. The policy clears SVM, IBS,
SKINIT, the watchdog timer, LWP, the node ID MSR, the performance
counter extensions, the performance TSC, the data breakpoint and
address mask extensions, and MONITORX in 0x80000001 ECX; the
instructions-retired counter, INVLPGB, RDPRU, memory bandwidth
enforcement, PPIN, CPPC, and branch sampling in 0x80000008 EBX; and
CPUID faulting, which writes HWCR, and workload classification in
0x80000021 EAX, with its EBX and EDX. It keeps the speculation controls
and immunities that KVM can present: IBPB, IBRS, STIBP, and SSBD in
0x80000008 EBX, and AutoIBRS, LFENCE serialization, SBPB, SRSO_NO, and
the TSA bits in 0x80000021. BTC_NO goes: KVM never enumerates it in
KVM_GET_SUPPORTED_CPUID, so every KVM host would fail a profile that set
it, and a guest loses nothing without it, because Linux marks no CPU of
family 0x19 or later as affected by RETBLEED (cpu_vuln_blacklist), which
is all that BTC_NO decides there. PSFD stays only beside a SPEC_CTRL
control, Intel's IBRS or AMD's IBRS, STIBP, or SSBD
(has_spec_ctrl_control): PSFD is a bit of SPEC_CTRL, which KVM lets a
guest access only with such a control, and OpenVMM's KVM backend
withholds PSFD from KVM's supported surface otherwise. strip_psfd_leaf
now calls the same function, so the policy and the KVM surface cannot
disagree. The rule reads the derived table, which is the guest's CPUID,
not each fingerprint: backends that each offer PSFD beside another
control would otherwise keep PSFD while the intersection drops both
controls. The policy also clears 0x80000001 EBX, the brand ID and
package type, and 3DNowPrefetch: on the Azure AMD EPYC 7763 host that
this work used, WHP presents 3DNowPrefetch to guests but not to the
Hyper-V root, whose CPUID the cold-boot support check reads, so a
profile that pinned it would fail there with E_PROFILE_UNSUPPORTED,
while Linux infers PREFETCHW from long mode whatever the bit says.
0x80000001 EAX, where AMD repeats the CPU's signature, now describes
the CPU as leaf 1's does, so a host of another stepping of a
generation, such as Milan-X's stepping 2, supports its profile; Intel
CPUs leave it zero.

TOPOEXT stays: Linux reads the cache and processor topology through it,
and without it finds no shared cache. topology_cpuid therefore writes
AMD's cache sharing in 0x8000001D, as it writes leaf 4's on Intel: each
VP has its own L1 and L2 caches, shared only with an SMT sibling, and
the socket shares the L3, which Linux's cacheinfo_amd_init_llc_id reads
from the last cache's count. Before, an AMD guest saw the host's L3
grouping. Every AMD partition gets it, not only the time ABI's, as
every Intel partition gets leaf 4's.

Unit tests derive host profiles and a Milan profile from the CPUID that
a WHP probe partition presented on an AMD EPYC 7763 (family 0x19, model
1, stepping 1), and from that CPUID with what KVM can offer on bare
metal added: the identity, the VM-owned fields and their masks, the
cache subleaves, the policy's clears and keeps, majority votes on
0x8000001D, and the support of another stepping and of a root that
lacks 3DNowPrefetch, and that PSFD stays only beside a SPEC_CTRL
control, also when two backends offer different ones, and BTC_NO never.
Others check that Intel profiles keep pinning
AMD's fields, that an Intel fingerprint with values in AMD's leaves
derives what the Intel policy alone gives, PSFD and BTC_NO included,
that the effective CPUID of
an AMD profile takes OpenVMM's AMD topology, that topology_cpuid shares
the L3 across the socket in 0x8000001D at 1 to 8 VPs with and without
SMT, and that auto names --cpu-profile host on an AMD host that no
pinned profile serves, and not on a Hygon one, and where the backend
lacks the profile on Intel and AMD hosts only, not for explicit
profiles, host, restores, or other codes. The Guide's snapshot and CLI
pages name amd.host.v1 and the hint.

Under the CI-pinned Rust 1.95.0, cpu_profile (104 tests), virt (64),
openvmm_core (98), and openvmm_helpers (98) pass, as do clippy
--all-targets and rustdoc with warnings denied for those packages,
clippy for x86_64-unknown-linux-gnu for cpu_profile, virt, virt_kvm, and
virt_mshv, and xtask fmt. In a Linux container (rust:1.95.0),
cpu_profile, virt_kvm (40 tests), and virt_mshv (76) pass. virt_whp's unit tests and its
16 hardware tests pass on the EPYC 7763 with WHP; no pinned profile
serves that host yet, so the profile tests skip.

Part of microsoft/nvx#396.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 01c35bf1-81da-4f98-8380-8a902ac0e5f9
MSHV and WHP derive a partition's processor feature banks from its CPU
profile through HV_FEATURES, which mapped each bank bit to Intel's CPUID
bit only (microsoft/nvx#396, row 5). AMD enumerates IBRS, IBPB, STIBP,
SSBD, and PSFD in 0x80000008 EBX, and SVM in 0x80000001 ECX, so under an
AMD profile profile_features read Intel's bits, which AMD profiles leave
clear, and cleared those bank bits. The partition would have lost
SPEC_CTRL and PRED_CMD while the profile's CPUID still advertised them,
and a guest's unchecked SPEC_CTRL write would have raised #GP
(G_UNCHECKED_MSR). The AMD-only bits that the banks define were
unmapped, so they followed the host instead of the profile.

An HvFeature now says where AMD CPUs enumerate its feature (AmdCpuid,
read through cpuid_for): IBRS, IBPB, STIBP, and SSBD (mdd) in 0x80000008
EBX bits 14, 12, 15, and 24, PSFD in bit 28, and nested virtualization in
SVM, 0x80000001 ECX[2]. The table gains the AMD-only bits: VIRT_SSBD
(virt_spec_ctrl), the always-on STIBP preference, IBPB_RET
(ibpb_rsb_flush), and BTC_NO in 0x80000008 EBX, and SBPB, IBPB_BRTYPE,
SRSO_NO, SRSO_USER_KERNEL_NO, VERW_CLEAR (vrew_clear), and the
TSA_L1_NO and TSA_SQ_NO immunities in 0x80000021. Intel profiles pin
those bits clear, and the fleet's Intel hosts offer none of their bank
bits, so the Intel profiles' banks are unchanged, as the tests of the
fleet's hosts check. profile_features takes the bits of the profile's
vendor, and restrict_cpuid_to_features, which builds the cheap
supported surface from a host's CPUID, those of the vendor that the
CPUID reports. The IA32_ARCH_CAPABILITIES bank bits keep Intel's
meaning: AMD CPUs enumerate their immunities in CPUID, and SSB_NO's AMD
bit, 0x80000008 EBX[26], stays unmapped.

One feature controls no CPUID bit on AMD CPUs: intel_prefetch_support,
Intel's PREFETCHW, whose bit, 0x80000001 ECX[8], AMD CPUs enumerate as
3DNowPrefetch. WHP presents that bit to AMD guests whatever the feature,
which the EPYC 7763 host below does not even offer, so cpuid_for returns
None for it on AMD (AmdCpuid::Uncontrolled): an AMD profile leaves the
feature as the host offers it, and an AMD host's cheap surface keeps the
bit. Mapping it cleared the feature under an AMD profile, which clears
the bit, without hiding the bit, which only the profile's CPUID results
do.

The WHP and MSHV hardware tests that check the table, and the WHP and
MSHV tests that check a host's profile against its features, read the
CPUID bits of the vendor.

On the Azure AMD EPYC 7763 host (Milan; family 0x19, model 1, stepping
1), WHP offers PSFD, BTC_NO, and nested virtualization but no
speculation controls (bank 0 0x06020fcb67f79fbf, bank 1 0x200001ed),
and features_control_the_mapped_cpuid_bits passes with the AMD bits:
clearing psfd_support removes only 0x80000008 EBX[28], and
btc_no_support only EBX[29]. Clearing nested_virt_support removes
nothing there, because the probe partition presents no SVM. The bits
that the host does not offer follow the AMD APM and the TLFS's names,
unverified on hardware, and so does every MSHV mapping. Unit tests
check that an AMD profile follows its own CPUID on that host's banks,
keeping CLZERO and dropping SVM, RDPRU, APERF/MPERF, PSFD, and BTC_NO,
which the derivation policy clears there; takes the speculation
controls, PSFD from AMD's bit beside them, and the immunities from
AMD's bits on a host that offers them, while BTC_NO goes; fails with
E_PROFILE_UNSUPPORTED naming each one a host lacks; that a host's cheap
surface loses AMD's bits, not Intel's, for the features it lacks; and
that intel_prefetch_support controls 0x80000001 ECX[8] on Intel CPUs
only.

Under the CI-pinned Rust 1.95.0, cpu_profile's 108 unit tests and
virt_whp's 35 pass, as do the 16 virt_whp hardware tests on the EPYC
7763; no pinned profile serves that host yet, so the profile tests
skip. clippy --all-targets and rustdoc with warnings denied pass for
cpu_profile and virt_whp, and for virt_kvm and virt_mshv on
x86_64-unknown-linux-gnu, and xtask fmt makes no change. In a Linux
container (rust:1.95.0), virt_kvm's 40 and virt_mshv's 76 unit tests
pass.

Part of microsoft/nvx#396.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 01c35bf1-81da-4f98-8380-8a902ac0e5f9
…files

A Linux guest of an AMD CPU profile reads two AMD configuration MSRs that
no backend presented as the time ABI needs them:

- HWCR (0xc0010015): bsp_init_amd reads it with an unchecked rdmsr on every
  AMD CPU whose CPUID has the invariant TSC, which the time ABI's CPUID
  always sets. A fault fails the guest's G_UNCHECKED_MSR check, and a clear
  TscFreqSel (bit 24) makes Linux log "[Firmware Bug]: TSC doesn't count
  with P0 frequency!". WHP's legacy stub reads it as 0, and so does KVM.
- DE_CFG (0xc0011029): init_amd sets its LFENCE serialization bit on every
  CPU whose CPUID does not enumerate it, with safe accessors. WHP does not
  implement it, so the read raises #GP and Linux silently loses the
  serializing LFENCE that its RDTSC ordering relies on.

virt::time_abi::msr now defines the contract, AMD_CONFIG_MSRS: under an AMD
CPU profile, HWCR reads as TscFreqSel only and DE_CFG as LFENCE
serialization only, on every VP and backend; a write of the value that a
read returns succeeds and changes nothing, and any other write raises #GP.
Linux writes neither on a profile's CPU: it sets IRPERF_EN only when CPUID
enumerates the instructions-retired counter and CPUID faulting only on
request, and the derivation policy clears both. Each backend routes the two
MSRs to OpenVMM under an AMD profile only, so Intel partitions keep their
handling exactly:

- WHP: UnhandledMsrs exits already deliver them; serve_msr answers them
  ahead of the legacy stubs.
- KVM: the MSR filter of an AMD profile also denies them, so the accesses
  exit to OpenVMM (9 filter ranges, within KVM's 16).
- MSHV: an AMD profile also installs MSR intercepts for them.

The Guide's Time and CPU compatibility section states the contract.

MSHV also presents AMD's processor topology leaf 0x8000001E per VP. Like the
extended topology leaves, the hypervisor does not provide its per-VP fields
for these partitions, so create_vp registers each VP's own result: the
extended APIC ID, the compute unit ID within the socket, and the socket's
node ID, computed as virt::x86::topology computes the BSP's and as the WHP
and KVM backends already present them. This completes row 6 of the issue,
and with the AMD cache sharing of 0x8000001D and the core count of
0x80000008 from the first commit, the guest's AMD topology is consistent on
every backend, so profiles keep TOPOEXT instead of hiding it.

Tests cover the contract's values and writes, the KVM filter ranges of both
vendors, the MSR serving of each backend under both vendors (Intel
partitions leave HWCR and DE_CFG to their existing handling), and the MSHV
per-VP 0x8000001E values with and without SMT across sockets.

Validation, with Rust 1.95.0:

- cargo test -p virt -p virt_whp: 65 and 36 passed.
- cargo test -p virt_whp -- --ignored --test-threads=1 on an AMD EPYC 7763
  (Milan) Azure VM with nested WHP: all 16 hardware tests passed.
- cargo test -p virt_kvm -p virt_mshv in a Linux container (rust:1.95.0):
  41 and 77 passed; their hardware tests need /dev/kvm and /dev/mshv.
- cargo clippy --all-targets -D warnings for virt, virt_whp, and
  openvmm_core, and for virt_kvm and virt_mshv with
  --target x86_64-unknown-linux-gnu; rustdoc with -D warnings for all four;
  cargo xtask fmt --fix.
- On the same WHP host, a release build booted the NVX Alpine guest
  (Linux 6.18.38) on 4 VPs with --cpu-profile host (amd.host.v1): the BSP
  read HWCR once (0x1000000) and every VP read DE_CFG once (0x2), with no
  write; dmesg had no Firmware Bug or unchecked MSR access line; nvx-time
  reported phase=boot status=ok; each CPU's APIC ID matched its initial
  APIC ID; and the L3 cache was shared across all 4 CPUs.

The KVM and MSHV paths are hardware-unverified: no AMD KVM or MSHV host was
available.

Part of microsoft/nvx#396.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 01c35bf1-81da-4f98-8380-8a902ac0e5f9
No pinned profile served an AMD CPU, so --cpu-profile auto, the default,
failed every AMD host with E_PROFILE_HOST_UNKNOWN (microsoft/nvx#396, plan
item 5). The catalog now pins amd.milan.v1, AMD EPYC, third generation
(Milan): family 25 (0x19), model 1, steppings 0 to 15. No other generation
uses family 25 model 1, so the generation takes every stepping of the Milan
die, Milan-X's 2 included, whose CPUID differs in its signature and L3
size: the profile's checks treat 0x80000001 EAX, 0x80000006, and 0x8000001D
as descriptive, as the derivation tests check for stepping 2. Genoa
(model 17) and the Zen 3 client parts stay without a profile.

As intel.alderlake.v1 was, the profile is derived from one fingerprint:
openvmm --cpu-fingerprint on WHP on an Azure Standard_D16as_v5 VM with an
AMD EPYC 7763 (stepping 1) and nested Hyper-V, surface digest
sha256:35a6cf971fd37b3f28176b1164b37efeec4bcb5094b5d8222d581ee1dd3e7307.
The derivation policy, unchanged since the first commit of this series
(openvmm-cpu-profile-policy/v1 with its AMD rules), derives it with

    cargo run -p cpu_profile --example derive_profile -- milan 1 <fingerprint>

ID amd.milan.v1, digest
sha256:463ee0368dc01bf56a3ab1157d62b21b51d11b3dcccb46c804864748ef696543.
It presents what that host's WHP offers its guests, under the policy:
AVX2, SHA, VAES, VPCLMULQDQ, and the x87, SSE, and AVX XSAVE states;
TOPOEXT with the cache leaves 0x80000005 and 0x8000001D, whose sharing
counts OpenVMM sets per VM, and 0x8000001E, whose IDs it sets per VP;
CLZERO; a 48-bit physical address width; the brand string "AMD EPYC
Processor (Milan)"; and no pinned MSR, since AMD CPUs have no
IA32_ARCH_CAPABILITIES. It has no SVM, RDPRU, SME or SEV (0x8000001F is
zero), and, because that WHP offers none, no IBRS, IBPB, STIBP, or SSBD
and nothing in 0x80000021. That WHP offers PSFD and BTC_NO, but the policy
drops both: PSFD without a SPEC_CTRL control, and BTC_NO, which KVM never
enumerates. So no KVM host of the generation fails the profile on either
bit, which a KVM surface of the same CPU now checks, and 0x80000008 EBX
holds CLZERO and the XSAVE error pointer bit only. Linux guests of the
profile mitigate Spectre v2 with retpolines and report SSB, SRSO, and TSA
as vulnerable on every host. Milan hosts on KVM and MSHV, whose bare-metal
view offers more, should support the profile, but none was available to
verify it, nor the AMD paths of those backends; a later revision derived
from such hosts could add the speculation controls, and PSFD beside them.

pinned_data.rs, regenerated with generate_pinned from the five files in
catalog order, gains the new profile's block and changes nothing else but
the array's length: the four Intel profiles' constants are byte-identical,
and their JSON files and golden digests are unchanged, which the catalog
test checks.

Tests:

- catalog: Milan and Milan-X select amd.milan.v1; Genoa, an Intel CPU, and
  a Hygon CPU with Milan's signature select nothing; selecting the other
  vendor's profile fails with E_CPU_GENERATION; the new file has its
  golden digest.
- derive: the pinned profile is the policy's derivation of the EPYC 7763's
  recorded CPUID (test_support::MILAN_WHP_CPUID), re-deriving every pinned
  profile, Milan's included, reproduces it, and a KVM host of that CPU,
  which offers neither PSFD nor BTC_NO, supports it.
- check: the --cpu-fingerprint check of that host passes with a partition
  configured from the profile, though its probe partition presents CET's
  XSAVE components outside the profile.
- The tests that assumed Intel profiles take each profile's vendor: the L3
  sharing test reads 0x8000001D on AMD, openvmm_helpers' effective CPUID
  record test builds AMD's topology fields, and virt_whp's unit tests run
  amd.milan.v1 too: its CPUID exits add 0x8000001E (9 exits), its brand is
  its generation's, and the comparison with the profile programming of
  de39f6a, which predates AMD profiles, ignores AMD's topology fields.

Validation, with Rust 1.95.0:

- cargo test: cpu_profile 111, virt 65, virt_whp 36, openvmm_core 98, and
  openvmm_helpers 98 passed; in a Linux container (rust:1.95.0),
  cpu_profile, virt, virt_kvm (41), and virt_mshv (77) passed.
- On the EPYC 7763 WHP host, all 16 virt_whp hardware tests passed, now
  with amd.milan.v1 rather than skipping: the profile's feature bank 1 is
  0x24, VP 0 presents the profile programming's CPUID at all 390 probes
  but for AMD's topology fields, and the host's two entries outside the
  profile (0xd.11 and 0xd.12, CET) read zero on a partition of the
  profile.
- A release build there prints NVX-CPU-PROFILE: status=pass backend=whp
  generation=milan profile=amd.milan.v1
  profile_digest=sha256:463ee036... for --cpu-fingerprint.
- clippy --all-targets and rustdoc with warnings denied for cpu_profile,
  virt, virt_whp, openvmm_core, and openvmm_helpers, and for virt_kvm and
  virt_mshv on x86_64-unknown-linux-gnu; cargo xtask fmt --fix.

Part of microsoft/nvx#396.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 01c35bf1-81da-4f98-8380-8a902ac0e5f9
Copilot AI balanced review requested due to automatic review settings October 6, 2026 08:57
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

⚠️ Unsafe Code Detected

This PR modifies files containing unsafe Rust code. Extra scrutiny is required during review.

For more on why we check whole files, instead of just diffs, check out the Rustonomicon

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

It changes the cross-backend CPU ABI and includes AMD KVM and MSHV paths without runtime validation.

Review effort: Balanced
Findings: None

What changed in this PR

Adds AMD CPU-profile support to the time ABI, including the pinned Milan profile and backend-specific topology, feature, and MSR handling.

Changes:

  • Adds AMD-aware profile derivation, validation, topology, and Hyper-V feature mappings.
  • Pins amd.milan.v1 and enables AMD host profiles.
  • Serves AMD HWCR and DE_CFG consistently across WHP, KVM, and MSHV.
File Description
Guide/​src/​reference/​openvmm/​management/​cli.md Documents AMD host-profile selection.
Guide/​src/​user_guide/​openvmm/​snapshots.md Documents Milan and AMD snapshot behavior.
openvmm/​openvmm_core/​src/​worker/​dispatch.rs Propagates AMD profile guidance in errors.
openvmm/​openvmm_core/​src/​worker/​dispatch/​time_abi.rs Expands host-profile hints to AMD.
openvmm/​openvmm_helpers/​src/​snapshot/​time.rs Covers AMD profiles in snapshot handling.
vmm_core/​cpu_profile/​profiles/​amd.milan.v1.json Defines the pinned Milan profile.
vmm_core/​cpu_profile/​src/​catalog.rs Adds Milan to the profile catalog.
vmm_core/​cpu_profile/​src/​check.rs Tests Milan fingerprint compatibility.
vmm_core/​cpu_profile/​src/​derive.rs Implements vendor-specific AMD derivation.
vmm_core/​cpu_profile/​src/​effective.rs Handles AMD VM-owned topology fields.
vmm_core/​cpu_profile/​src/​hv_banks.rs Maps Hyper-V banks to AMD CPUID bits.
vmm_core/​cpu_profile/​src/​lib.rs Exposes AMD profile APIs.
vmm_core/​cpu_profile/​src/​pinned.rs Registers the Milan profile.
vmm_core/​cpu_profile/​src/​pinned_data.rs Adds generated Milan profile data.
vmm_core/​cpu_profile/​src/​profile.rs Adds vendor-aware profile validation and masks.
vmm_core/​cpu_profile/​src/​surface.rs Classifies AMD surface fields.
vmm_core/​cpu_profile/​src/​test_support.rs Provides AMD fingerprint fixtures.
vmm_core/​cpu_profile/​src/​vendor.rs Defines supported CPU vendors.
vmm_core/​virt/​src/​time_abi/​cpuid.rs Tests AMD cache topology presentation.
vmm_core/​virt/​src/​time_abi/​msr.rs Defines fixed AMD configuration MSRs.
vmm_core/​virt/​src/​x86/​topology.rs Generates AMD cache-sharing topology.
vmm_core/​virt_kvm/​src/​arch/​x86_64/​mod.rs Propagates profile vendor into KVM setup.
vmm_core/​virt_kvm/​src/​arch/​x86_64/​time_abi.rs Routes AMD MSRs through KVM userspace handling.
vmm_core/​virt_mshv/​src/​x86_64/​mod.rs Adds AMD per-VP identity setup.
vmm_core/​virt_mshv/​src/​x86_64/​profile_features.rs Makes feature tests vendor-aware.
vmm_core/​virt_mshv/​src/​x86_64/​time_abi.rs Adds AMD MSR and topology handling.
vmm_core/​virt_whp/​src/​profile_features.rs Updates WHP feature mapping tests.
vmm_core/​virt_whp/​src/​time_abi.rs Serves AMD MSRs and CPUID through WHP.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@ppenna
ppenna merged commit 07850d2 into main Oct 6, 2026
77 checks passed
@ppenna
ppenna deleted the fix/amd-cpu-profiles-396 branch October 6, 2026 17:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants