From 689577604f7132ab538f209cec0cae0c19a29458 Mon Sep 17 00:00:00 2001 From: Enrique Saurez Date: Wed, 7 Oct 2026 16:19:26 -0700 Subject: [PATCH 1/2] cpu_profile: pin amd.genoa.v2 for Genoa hosts without the TSA immunities amd.genoa.v1 pins what Azure presents to the KVM of a GitHub-hosted runner with an EPYC 9V74, CPUID 0x80000021 included: LFENCE serialization and NoSmmCtlMsr in EAX (0x204), and the TSA immunities, TSA_SQ_NO and TSA_L1_NO, in ECX (0x6). Not every Genoa host's hypervisor presents them. On an Azure VM with the same CPU (family 25 model 17 stepping 1) whose root runs Linux with MSHV (Microsoft Hv 10.0.26100.9444), the host partition offers neither TSA immunity (ProcessorFeatures1 0x50e00001ed), the root's 0x80000021 reads zero, and its kernel reports TSA as "Vulnerable: Clear CPU buffers attempted, no microcode". Every cold boot with --cpu-profile auto there fails before the guest runs: [E_PROFILE_UNSUPPORTED] the hypervisor's processor features cannot present CPU profile amd.genoa.v1: they lack tsa_l1_no_support, tsa_sq_no_support and --cpu-fingerprint also finds CPUID 0x0 EAX at 0x1c, above the supported 0xd, and 0x80000021 EAX bits 2 and 9 unsupported. As the time ABI design anticipates for such a host, a later revision intersects the fingerprints. amd.genoa.v2 derives from two fingerprints: the runner's KVM fingerprint that amd.genoa.v1 derives from (surface digest sha256:68c11e9ed42a2e64267876eca0d21ba1083d24f36d5b4f9d891a53523646c2aa), and that MSHV host's (surface digest sha256:0d4f63812ed2feae52196f88a16733f54b3822389ef062c8a0c31cdb31083585), whose CPUID and processor features test_support::GENOA_MSHV_CPUID and GENOA_MSHV_HOST record: cargo run -p cpu_profile --example derive_profile -- genoa 2 That gives amd.genoa.v2, digest sha256:8ee00e9ee8fd5defe3eebdc343733195d477f5f3449defa403f616d530a84839, which is amd.genoa.v1 but for two differences: - 0x80000021 reads zero: no LFENCE serialization, NoSmmCtlMsr, or TSA immunities, as under amd.milan.v1. Linux guests still serialize LFENCE through DE_CFG, whose LFENCE bit reads set under every AMD profile, and report TSA as vulnerable. - The maximum basic leaf is 0xd, that MSHV's, instead of 0x1c. The leaves 0xe to 0x1c, which amd.genoa.v1 lists as zero, are out of range. Everything else is identical: XCR0 0x7, the features, the cache and TLB descriptors, the address widths, and no pinned MSR. Deriving it needed one fix to the registers' classes. The two hosts disagree on CPUID 0x80000008 EDX, KVM 0 and MSHV 0x10000, and the policy took that register to be descriptive, so the backends' single votes tied ("no majority for CPUID 0x80000008 EDX among the backends"). It holds AMD's INVLPGB page count (bits 15:0) and RDPRU's largest register index (bits 25:16), Intel reserves it, and both are limits: the class is now Limits, so derivation takes their minimum, and a support check requires a profile's to be within the host's. Every profile clears the register, so neither a derivation that succeeded before nor a support check changes: every pinned profile rederives identically, and the policy stays openvmm-cpu-profile-policy/v1. auto selects a generation's latest revision, so it now selects amd.genoa.v2 on every Genoa host, GitHub's runners included. amd.genoa.v1 stays pinned: --cpu-profile amd.genoa.v1 still selects it, and its snapshots still restore where it is supported. pinned_data.rs, regenerated with generate_pinned from the eight files in catalog order, gains amd.genoa.v2's block after amd.genoa.v1's and changes nothing else but the array's length. Tests: - derive: amd.genoa.v2 is the policy's derivation of the recorded KVM and MSHV hosts, both of which support it, and the MSHV host's surface fails amd.genoa.v1 with exactly the three violations above. Rederiving a pinned profile uses its own revision. - hv_banks: the MSHV host's processor features cannot present amd.genoa.v1, lacking tsa_l1_no_support and tsa_sq_no_support, and present amd.genoa.v2. - catalog: auto selects amd.genoa.v2 on Genoa hosts, and amd.genoa.v1 stays selectable by its ID. - virt_whp: the time ABI tests' sample of the pinned profiles gains amd.genoa.v2. Validation: - cargo test with Rust 1.95.0 on Windows: cpu_profile 115, virt 65, virt_whp 36, openvmm_core 98, and openvmm_helpers 99 passed. clippy for cpu_profile and virt_whp and rustdoc for cpu_profile are clean with warnings denied, and xtask fmt passes. - On the MSHV host above, with a musl release build of this change on top of the streamed block snapshot stack (2b38c407c), built through NVX's platform build: - --cpu-fingerprint passes on amd.genoa.v2, which auto selects (NVX-CPU-PROFILE: status=pass), checking the entries outside the profile on a partition configured from it. - The --x-time-abi-verify preflight passes on auto with 1 and 4 VPs, and still fails --cpu-profile amd.genoa.v1 on the TSA immunities. - A 2-VP microVM boots on auto: its time ABI boot checks pass, its clock synchronizes, and its guest reports TSA as "Vulnerable: No microcode". - 15 guest-triggered snapshot captures and restores through NVX's broker pass on auto, without a --cpu-profile argument: clone and continuation snapshots, local, portable, and Blob-storage transfers, and two runs that drain running commands first. - Not run on GitHub-hosted runners: amd.genoa.v2 takes nothing from amd.genoa.v1 that their KVM lacks, and the recorded runner's surface supports it. Part of microsoft/nvx#409. Ported onto feature/microvm/dev (57d0a18b3) for microsoft/nvx#928: the Guide conflict keeps this commit's sentences on Genoa's revisions and the base's on auto's fallback to a host profile, and pinned_data.rs regenerates identically. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> (cherry picked from commit d946e7a89acf19c0f1aab285e922d5fd5190714b) --- Guide/src/user_guide/openvmm/snapshots.md | 12 +- .../cpu_profile/profiles/amd.genoa.v2.json | 902 ++++++++++++++++++ vmm_core/cpu_profile/src/catalog.rs | 18 +- vmm_core/cpu_profile/src/derive.rs | 83 +- vmm_core/cpu_profile/src/hv_banks.rs | 24 + vmm_core/cpu_profile/src/pinned_data.rs | 347 ++++++- vmm_core/cpu_profile/src/surface.rs | 6 +- vmm_core/cpu_profile/src/test_support.rs | 134 ++- vmm_core/virt_whp/src/time_abi.rs | 3 +- 9 files changed, 1512 insertions(+), 17 deletions(-) create mode 100644 vmm_core/cpu_profile/profiles/amd.genoa.v2.json diff --git a/Guide/src/user_guide/openvmm/snapshots.md b/Guide/src/user_guide/openvmm/snapshots.md index 96880f5e7ec..ef5678a2736 100644 --- a/Guide/src/user_guide/openvmm/snapshots.md +++ b/Guide/src/user_guide/openvmm/snapshots.md @@ -320,10 +320,14 @@ generation: family 6, model 85, steppings 0 to 4), `intel.icelake-sp.v1` (Xeon Scalable, third generation: 6/106), `intel.emeraldrapids.v1` (Xeon Scalable, fifth generation: 6/207), `intel.alderlake.v1` (Core, twelfth generation: 6/151 and 6/154), `amd.milan.v1` (AMD EPYC, third generation: -25/1), `amd.genoa.v1` (AMD EPYC, fourth generation: 25/17), and -`amd.turin.v1` (AMD EPYC, fifth generation: 26/2). `--cpu-profile ` -selects one; the default, `auto`, selects the profile of the host's -generation. An Intel or AMD host without one boots on a host profile +25/1), `amd.genoa.v1` and `amd.genoa.v2` (AMD EPYC, fourth generation: +25/17), and `amd.turin.v1` (AMD EPYC, fifth generation: 26/2). +`amd.genoa.v2` drops what `amd.genoa.v1` presents in `0x80000021`, `LFENCE` +serialization and the TSA immunities, which not every Genoa host's +hypervisor presents. `--cpu-profile ` selects one; the default, +`auto`, selects the latest revision of the host's generation's profile. An +earlier revision stays pinned, so its snapshots still restore. An Intel or +AMD host that no pinned profile serves boots on a host profile instead: `auto` falls back to the profile that `--cpu-profile host` selects, for which OpenVMM fingerprints the backend on this host, as `--cpu-fingerprint` does, and applies the pinned profiles' derivation policy diff --git a/vmm_core/cpu_profile/profiles/amd.genoa.v2.json b/vmm_core/cpu_profile/profiles/amd.genoa.v2.json new file mode 100644 index 00000000000..af1a37d47e7 --- /dev/null +++ b/vmm_core/cpu_profile/profiles/amd.genoa.v2.json @@ -0,0 +1,902 @@ +{ + "cpuid": [ + { + "leaf": "0x00000000", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x0000000d", + "0x68747541", + "0x444d4163", + "0x69746e65" + ] + }, + { + "leaf": "0x00000001", + "mask": [ + "0xffffffff", + "0x0000ffff", + "0xf7dfffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00a10f11", + "0x00000800", + "0xf6da3203", + "0x078bfbff" + ] + }, + { + "leaf": "0x00000002", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x00000003", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x00000004", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": "0x00000000", + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x00000005", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x00000006", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000004", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x00000007", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffef", + "0xffffffff" + ], + "subleaf": "0x00000000", + "value": [ + "0x00000000", + "0x219c07a9", + "0x00400604", + "0x00000010" + ] + }, + { + "leaf": "0x00000008", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x00000009", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x0000000a", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x0000000c", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x0000000d", + "mask": [ + "0xffffffff", + "0x00000000", + "0xffffffff", + "0xffffffff" + ], + "subleaf": "0x00000000", + "value": [ + "0x00000007", + "0x00000000", + "0x00000340", + "0x00000000" + ] + }, + { + "leaf": "0x0000000d", + "mask": [ + "0xffffffff", + "0x00000000", + "0xffffffff", + "0xffffffff" + ], + "subleaf": "0x00000001", + "value": [ + "0x0000000f", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x0000000d", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": "0x00000002", + "value": [ + "0x00000100", + "0x00000240", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000000", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x80000021", + "0x68747541", + "0x444d4163", + "0x69746e65" + ] + }, + { + "leaf": "0x80000001", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00a10f11", + "0x00000000", + "0x004002f3", + "0x2fd3fbff" + ] + }, + { + "leaf": "0x80000002", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x20444d41", + "0x43595045", + "0x6f725020", + "0x73736563" + ] + }, + { + "leaf": "0x80000003", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x2820726f", + "0x6f6e6547", + "0x00002961", + "0x00000000" + ] + }, + { + "leaf": "0x80000004", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000005", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0xff48ff40", + "0xff48ff40", + "0x20080140", + "0x20080140" + ] + }, + { + "leaf": "0x80000006", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x5c002200", + "0x6c004200", + "0x04006140", + "0x0a009140" + ] + }, + { + "leaf": "0x80000007", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000100" + ] + }, + { + "leaf": "0x80000008", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffff0f00", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00003030", + "0x00000005", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000009", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x8000000a", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x8000000b", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x8000000c", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x8000000d", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x8000000e", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x8000000f", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000010", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000011", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000012", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000013", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000014", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000015", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000016", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000017", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000018", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000019", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x8000001a", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x8000001b", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x8000001c", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x8000001d", + "mask": [ + "0xfc003fff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": "0x00000000", + "value": [ + "0x00000121", + "0x01c0003f", + "0x0000003f", + "0x00000000" + ] + }, + { + "leaf": "0x8000001d", + "mask": [ + "0xfc003fff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": "0x00000001", + "value": [ + "0x00000122", + "0x01c0003f", + "0x0000003f", + "0x00000000" + ] + }, + { + "leaf": "0x8000001d", + "mask": [ + "0xfc003fff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": "0x00000002", + "value": [ + "0x00000143", + "0x01c0003f", + "0x000007ff", + "0x00000002" + ] + }, + { + "leaf": "0x8000001d", + "mask": [ + "0xfc003fff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": "0x00000003", + "value": [ + "0x00000163", + "0x03c0003f", + "0x00007fff", + "0x00000001" + ] + }, + { + "leaf": "0x8000001d", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": "0x00000004", + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x8000001e", + "mask": [ + "0x00000000", + "0xffff0000", + "0xfffff800", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x8000001f", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000020", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + }, + { + "leaf": "0x80000021", + "mask": [ + "0xffffffff", + "0xffffffff", + "0xffffffff", + "0xffffffff" + ], + "subleaf": null, + "value": [ + "0x00000000", + "0x00000000", + "0x00000000", + "0x00000000" + ] + } + ], + "description": "AMD EPYC, fourth generation (Genoa), on every backend; NVX time ABI v1", + "generation": { + "cpus": [ + { + "family": 25, + "model": 17, + "steppings": [ + 0, + 15 + ] + } + ], + "name": "genoa" + }, + "id": "amd.genoa.v2", + "msrs": [], + "physical_address_width": 48, + "provenance": { + "method": "derived by cpu_profile::derive under openvmm-cpu-profile-policy/v1: per-register consensus within each backend, then across backends, of the hosts' supported CPU surfaces", + "sources": [ + { + "backend": "kvm", + "hosts": 1, + "surface_digests": [ + "sha256:68c11e9ed42a2e64267876eca0d21ba1083d24f36d5b4f9d891a53523646c2aa" + ] + }, + { + "backend": "mshv", + "hosts": 1, + "surface_digests": [ + "sha256:0d4f63812ed2feae52196f88a16733f54b3822389ef062c8a0c31cdb31083585" + ] + } + ] + }, + "schema": "openvmm-cpu-profile/v1", + "vendor": "AuthenticAMD", + "xcr0": "0x0000000000000007", + "xsave_components": [ + { + "align64": false, + "index": 2, + "offset": 576, + "size": 256, + "supervisor": false, + "xfd": false + } + ], + "xss": "0x0000000000000000" +} diff --git a/vmm_core/cpu_profile/src/catalog.rs b/vmm_core/cpu_profile/src/catalog.rs index aec93046179..1284600ca5a 100644 --- a/vmm_core/cpu_profile/src/catalog.rs +++ b/vmm_core/cpu_profile/src/catalog.rs @@ -437,7 +437,7 @@ mod tests { /// The pinned JSON files, the source of the static data, and their golden /// digests. Released profiles are immutable: changing a golden digest is /// a deliberate act, here. - const FILES: [(&str, &str, &str); 7] = [ + const FILES: [(&str, &str, &str); 8] = [ ( "intel.skylake-sp.v1", include_str!("../profiles/intel.skylake-sp.v1.json"), @@ -468,6 +468,11 @@ mod tests { include_str!("../profiles/amd.genoa.v1.json"), "sha256:894ac647399039242eb0f0e6582e91691234446ebc9362576d7c5613f34b966b", ), + ( + "amd.genoa.v2", + include_str!("../profiles/amd.genoa.v2.json"), + "sha256:8ee00e9ee8fd5defe3eebdc343733195d477f5f3449defa403f616d530a84839", + ), ( "amd.turin.v1", include_str!("../profiles/amd.turin.v1.json"), @@ -565,8 +570,8 @@ mod tests { (intel(ALDER_LAKE_S), "intel.alderlake.v1", "alderlake"), (amd(MILAN), "amd.milan.v1", "milan"), (amd(MILAN_X), "amd.milan.v1", "milan"), - (amd(GENOA), "amd.genoa.v1", "genoa"), - (amd(GENOA_STEPPING_2), "amd.genoa.v1", "genoa"), + (amd(GENOA), "amd.genoa.v2", "genoa"), + (amd(GENOA_STEPPING_2), "amd.genoa.v2", "genoa"), (amd(TURIN), "amd.turin.v1", "turin"), ] { assert_eq!(select_auto(&host).unwrap().id(), id); @@ -726,6 +731,13 @@ mod tests { code(select("amd.milan.v1", &amd(GENOA))), ProfileErrorCode::CpuGeneration ); + + // An earlier revision of the host's generation, which auto no longer + // selects. + assert_eq!( + select("amd.genoa.v1", &amd(GENOA)).unwrap().id(), + "amd.genoa.v1" + ); } #[test] diff --git a/vmm_core/cpu_profile/src/derive.rs b/vmm_core/cpu_profile/src/derive.rs index fa8a50be069..457a251de15 100644 --- a/vmm_core/cpu_profile/src/derive.rs +++ b/vmm_core/cpu_profile/src/derive.rs @@ -994,6 +994,7 @@ mod tests { use crate::test_support::fingerprint; use crate::test_support::fingerprint_with; use crate::test_support::genoa_kvm_entries; + use crate::test_support::genoa_mshv_fingerprint; use crate::test_support::host_fingerprint; use crate::test_support::milan_whp_entries; use crate::test_support::profile; @@ -1040,9 +1041,14 @@ mod tests { fn rederiving_a_pinned_profile_reproduces_it() { for pinned in crate::pinned_profiles() { let known = generation(&pinned.generation().name); + let revision = pinned + .id() + .rsplit_once(".v") + .and_then(|(_, revision)| revision.parse().ok()) + .unwrap(); let derived = derive_profile( known, - 1, + revision, &[fingerprint(pinned, "kvm"), fingerprint(pinned, "whp")], ) .unwrap(); @@ -1419,6 +1425,81 @@ mod tests { } } + /// `amd.genoa.v2` is the policy's profile of the KVM host that + /// `amd.genoa.v1` derives from and of an Azure VM with the same CPU whose + /// MSHV enumerates the basic leaves only up to 0xD and presents nothing + /// in `0x80000021` (`test_support::GENOA_MSHV_CPUID`): v1 without + /// `LFENCE` serialization, the TSA immunities, and the zero basic leaves + /// above 0xD. Both hosts support it, and the MSHV host does not support + /// v1. + #[test] + fn the_second_genoa_profile_serves_the_kvm_and_mshv_hosts() { + let pinned = profile("amd.genoa.v2"); + let first = profile("amd.genoa.v1"); + let kvm = host_fingerprint("kvm", genoa_kvm_entries()); + let mshv = genoa_mshv_fingerprint(); + let derived = derive_profile(generation("genoa"), 2, &[kvm.clone(), mshv.clone()]).unwrap(); + assert_eq!(derived.id(), pinned.id()); + assert_eq!(derived.description(), pinned.description()); + assert_eq!(derived.generation(), pinned.generation()); + assert_eq!(derived.cpuid(), pinned.cpuid()); + assert_eq!(derived.xcr0(), pinned.xcr0()); + assert_eq!(derived.xss(), pinned.xss()); + assert_eq!(derived.xsave_components(), pinned.xsave_components()); + assert_eq!( + derived.physical_address_width(), + pinned.physical_address_width() + ); + assert_eq!(derived.msrs(), pinned.msrs()); + + // v1 but for the maximum basic leaf and 0x80000021. + assert_eq!((first.lookup(0, 0)[0], pinned.lookup(0, 0)[0]), (0x1c, 0xd)); + assert_eq!(first.lookup(0x8000_0021, 0), [0x204, 0, 0x6, 0]); + assert_eq!(pinned.lookup(0x8000_0021, 0), [0; 4]); + for entry in first.cpuid() { + let (leaf, subleaf) = entry.key(); + match pinned + .cpuid() + .iter() + .find(|pinned| pinned.key() == (leaf, subleaf)) + { + Some(_) if leaf == 0 || leaf == 0x8000_0021 => {} + Some(pinned) => assert_eq!(pinned, entry), + None => { + assert!((0xe..=0x1c).contains(&leaf), "{leaf:#x}"); + assert_eq!(entry.values(), [0; 4], "{leaf:#x}"); + } + } + } + + // The hosts disagree on AMD's INVLPGB and RDPRU limits, which take + // their minimum, as other limits do, rather than tie. + let limits = |fingerprint: &CpuFingerprint| { + cpuid::lookup(&fingerprint.backend.cpuid, 0x8000_0008, 0).unwrap()[3] + }; + assert_eq!((limits(&kvm), limits(&mshv)), (0, 0x1_0000)); + assert_eq!(pinned.lookup(0x8000_0008, 0)[3], 0); + + for host in [&kvm, &mshv] { + let surface = crate::HostCpuSurface::from_fingerprint(&host.backend); + assert_eq!( + crate::support_violations(pinned, &surface), + Vec::::new(), + "{}", + host.backend.name + ); + } + let surface = crate::HostCpuSurface::from_fingerprint(&mshv.backend); + assert_eq!( + crate::support_violations(first, &surface), + [ + "CPUID 0x0 EAX[31:0] is 0x1c, above the supported 0xd", + "CPUID 0x80000021 EAX bits 2, 9 are not supported", + "CPUID 0x80000021 ECX bits 1, 2 are not supported", + ] + ); + } + /// KVM enumerates Intel's speculation controls and /// `IA32_ARCH_CAPABILITIES` on AMD hosts too, as the Genoa and Turin /// hosts' KVM does, and emulates the MSR. An AMD profile has none of diff --git a/vmm_core/cpu_profile/src/hv_banks.rs b/vmm_core/cpu_profile/src/hv_banks.rs index 1726c1eaa58..d85f7245079 100644 --- a/vmm_core/cpu_profile/src/hv_banks.rs +++ b/vmm_core/cpu_profile/src/hv_banks.rs @@ -1280,4 +1280,28 @@ mod tests { "intel_prefetch_support" )); } + + /// The MSHV host of `test_support::GENOA_MSHV_CPUID` offers neither TSA + /// immunity, so it cannot present `amd.genoa.v1`, which pins both, and + /// presents `amd.genoa.v2`, which clears them. + #[test] + fn the_genoa_mshv_host_presents_the_second_genoa_profile() { + let host = crate::test_support::GENOA_MSHV_HOST; + let error = profile_features(profile("amd.genoa.v1"), host).unwrap_err(); + assert_eq!(error.code, ProfileErrorCode::ProfileUnsupported); + assert!( + error + .message + .ends_with("they lack tsa_l1_no_support, tsa_sq_no_support"), + "{error}" + ); + + let features = profile_features(profile("amd.genoa.v2"), host).unwrap(); + for word in HvFeatureWord::ALL { + assert_eq!(features.word(word) & !host.word(word), 0, "{}", word.name()); + } + for name in ["tsa_l1_no_support", "tsa_sq_no_support"] { + assert!(!has(&features, HvFeatureWord::Bank1, name), "{name}"); + } + } } diff --git a/vmm_core/cpu_profile/src/pinned_data.rs b/vmm_core/cpu_profile/src/pinned_data.rs index d2687e175e6..289b6d55972 100644 --- a/vmm_core/cpu_profile/src/pinned_data.rs +++ b/vmm_core/cpu_profile/src/pinned_data.rs @@ -15,7 +15,7 @@ use crate::pinned::xsave; /// The pinned profiles, in catalog order. Released profiles are immutable: a /// change is a new revision with a new ID. -pub(crate) const PINNED: [PinnedProfile; 7] = [ +pub(crate) const PINNED: [PinnedProfile; 8] = [ PinnedProfile { id: "intel.skylake-sp.v1", digest: digest("b36ef861ffec67350a28faff8c446d5a19f78d6d58862378fa9d2282dad87492"), @@ -1990,6 +1990,351 @@ pub(crate) const PINNED: [PinnedProfile; 7] = [ &["sha256:68c11e9ed42a2e64267876eca0d21ba1083d24f36d5b4f9d891a53523646c2aa"], )], }, + PinnedProfile { + id: "amd.genoa.v2", + digest: digest("8ee00e9ee8fd5defe3eebdc343733195d477f5f3449defa403f616d530a84839"), + encoding: r#"{"cpuid":[{"leaf":"0x00000000","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x0000000d","0x68747541","0x444d4163","0x69746e65"]},{"leaf":"0x00000001","mask":["0xffffffff","0x0000ffff","0xf7dfffff","0xffffffff"],"subleaf":null,"value":["0x00a10f11","0x00000800","0xf6da3203","0x078bfbff"]},{"leaf":"0x00000002","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x00000003","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x00000004","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":"0x00000000","value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x00000005","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x00000006","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000004","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x00000007","mask":["0xffffffff","0xffffffff","0xffffffef","0xffffffff"],"subleaf":"0x00000000","value":["0x00000000","0x219c07a9","0x00400604","0x00000010"]},{"leaf":"0x00000008","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x00000009","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x0000000a","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x0000000c","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x0000000d","mask":["0xffffffff","0x00000000","0xffffffff","0xffffffff"],"subleaf":"0x00000000","value":["0x00000007","0x00000000","0x00000340","0x00000000"]},{"leaf":"0x0000000d","mask":["0xffffffff","0x00000000","0xffffffff","0xffffffff"],"subleaf":"0x00000001","value":["0x0000000f","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x0000000d","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":"0x00000002","value":["0x00000100","0x00000240","0x00000000","0x00000000"]},{"leaf":"0x80000000","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x80000021","0x68747541","0x444d4163","0x69746e65"]},{"leaf":"0x80000001","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00a10f11","0x00000000","0x004002f3","0x2fd3fbff"]},{"leaf":"0x80000002","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x20444d41","0x43595045","0x6f725020","0x73736563"]},{"leaf":"0x80000003","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x2820726f","0x6f6e6547","0x00002961","0x00000000"]},{"leaf":"0x80000004","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x80000005","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0xff48ff40","0xff48ff40","0x20080140","0x20080140"]},{"leaf":"0x80000006","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x5c002200","0x6c004200","0x04006140","0x0a009140"]},{"leaf":"0x80000007","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000100"]},{"leaf":"0x80000008","mask":["0xffffffff","0xffffffff","0xffff0f00","0xffffffff"],"subleaf":null,"value":["0x00003030","0x00000005","0x00000000","0x00000000"]},{"leaf":"0x80000009","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x8000000a","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x8000000b","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x8000000c","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x8000000d","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x8000000e","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x8000000f","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x80000010","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x80000011","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x80000012","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x80000013","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x80000014","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x80000015","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x80000016","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x80000017","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x80000018","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x80000019","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x8000001a","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x8000001b","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x8000001c","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x8000001d","mask":["0xfc003fff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":"0x00000000","value":["0x00000121","0x01c0003f","0x0000003f","0x00000000"]},{"leaf":"0x8000001d","mask":["0xfc003fff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":"0x00000001","value":["0x00000122","0x01c0003f","0x0000003f","0x00000000"]},{"leaf":"0x8000001d","mask":["0xfc003fff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":"0x00000002","value":["0x00000143","0x01c0003f","0x000007ff","0x00000002"]},{"leaf":"0x8000001d","mask":["0xfc003fff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":"0x00000003","value":["0x00000163","0x03c0003f","0x00007fff","0x00000001"]},{"leaf":"0x8000001d","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":"0x00000004","value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x8000001e","mask":["0x00000000","0xffff0000","0xfffff800","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x8000001f","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x80000020","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]},{"leaf":"0x80000021","mask":["0xffffffff","0xffffffff","0xffffffff","0xffffffff"],"subleaf":null,"value":["0x00000000","0x00000000","0x00000000","0x00000000"]}],"description":"AMD EPYC, fourth generation (Genoa), on every backend; NVX time ABI v1","generation":{"cpus":[{"family":25,"model":17,"steppings":[0,15]}],"name":"genoa"},"id":"amd.genoa.v2","msrs":[],"physical_address_width":48,"provenance":{"method":"derived by cpu_profile::derive under openvmm-cpu-profile-policy/v1: per-register consensus within each backend, then across backends, of the hosts' supported CPU surfaces","sources":[{"backend":"kvm","hosts":1,"surface_digests":["sha256:68c11e9ed42a2e64267876eca0d21ba1083d24f36d5b4f9d891a53523646c2aa"]},{"backend":"mshv","hosts":1,"surface_digests":["sha256:0d4f63812ed2feae52196f88a16733f54b3822389ef062c8a0c31cdb31083585"]}]},"schema":"openvmm-cpu-profile/v1","vendor":"AuthenticAMD","xcr0":"0x0000000000000007","xsave_components":[{"align64":false,"index":2,"offset":576,"size":256,"supervisor":false,"xfd":false}],"xss":"0x0000000000000000"}"#, + description: "AMD EPYC, fourth generation (Genoa), on every backend; NVX time ABI v1", + generation: &KNOWN_GENERATIONS[5], + cpuid: &[ + leaf( + 0x0, + None, + [0xd, 0x6874_7541, 0x444d_4163, 0x6974_6e65], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x1, + None, + [0xa1_0f11, 0x800, 0xf6da_3203, 0x78b_fbff], + [0xffff_ffff, 0xffff, 0xf7df_ffff, 0xffff_ffff], + ), + leaf( + 0x2, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x3, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x4, + Some(0x0), + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x5, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x6, + None, + [0x4, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x7, + Some(0x0), + [0x0, 0x219c_07a9, 0x40_0604, 0x10], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffef, 0xffff_ffff], + ), + leaf( + 0x8, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x9, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0xa, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0xc, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0xd, + Some(0x0), + [0x7, 0x0, 0x340, 0x0], + [0xffff_ffff, 0x0, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0xd, + Some(0x1), + [0xf, 0x0, 0x0, 0x0], + [0xffff_ffff, 0x0, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0xd, + Some(0x2), + [0x100, 0x240, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0000, + None, + [0x8000_0021, 0x6874_7541, 0x444d_4163, 0x6974_6e65], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0001, + None, + [0xa1_0f11, 0x0, 0x40_02f3, 0x2fd3_fbff], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0002, + None, + [0x2044_4d41, 0x4359_5045, 0x6f72_5020, 0x7373_6563], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0003, + None, + [0x2820_726f, 0x6f6e_6547, 0x2961, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0004, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0005, + None, + [0xff48_ff40, 0xff48_ff40, 0x2008_0140, 0x2008_0140], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0006, + None, + [0x5c00_2200, 0x6c00_4200, 0x400_6140, 0xa00_9140], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0007, + None, + [0x0, 0x0, 0x0, 0x100], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0008, + None, + [0x3030, 0x5, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_0f00, 0xffff_ffff], + ), + leaf( + 0x8000_0009, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_000a, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_000b, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_000c, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_000d, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_000e, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_000f, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0010, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0011, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0012, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0013, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0014, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0015, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0016, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0017, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0018, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0019, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_001a, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_001b, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_001c, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_001d, + Some(0x0), + [0x121, 0x1c0_003f, 0x3f, 0x0], + [0xfc00_3fff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_001d, + Some(0x1), + [0x122, 0x1c0_003f, 0x3f, 0x0], + [0xfc00_3fff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_001d, + Some(0x2), + [0x143, 0x1c0_003f, 0x7ff, 0x2], + [0xfc00_3fff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_001d, + Some(0x3), + [0x163, 0x3c0_003f, 0x7fff, 0x1], + [0xfc00_3fff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_001d, + Some(0x4), + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_001e, + None, + [0x0, 0x0, 0x0, 0x0], + [0x0, 0xffff_0000, 0xffff_f800, 0xffff_ffff], + ), + leaf( + 0x8000_001f, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0020, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + leaf( + 0x8000_0021, + None, + [0x0, 0x0, 0x0, 0x0], + [0xffff_ffff, 0xffff_ffff, 0xffff_ffff, 0xffff_ffff], + ), + ], + xcr0: 0x7, + xss: 0x0, + xsave_components: &[xsave(2, 256, 576, false, false, false)], + physical_address_width: 48, + msrs: &[], + provenance_method: "derived by cpu_profile::derive under openvmm-cpu-profile-policy/v1: per-register consensus within each backend, then across backends, of the hosts' supported CPU surfaces", + provenance_sources: &[ + source( + "kvm", + 1, + &["sha256:68c11e9ed42a2e64267876eca0d21ba1083d24f36d5b4f9d891a53523646c2aa"], + ), + source( + "mshv", + 1, + &["sha256:0d4f63812ed2feae52196f88a16733f54b3822389ef062c8a0c31cdb31083585"], + ), + ], + }, PinnedProfile { id: "amd.turin.v1", digest: digest("0d9d044922eae3f27442178249f0b60feb0db07efe446e6641a1fcbfbb3a04ff"), diff --git a/vmm_core/cpu_profile/src/surface.rs b/vmm_core/cpu_profile/src/surface.rs index fe8e186d011..246a6b27ba8 100644 --- a/vmm_core/cpu_profile/src/surface.rs +++ b/vmm_core/cpu_profile/src/surface.rs @@ -155,14 +155,18 @@ pub(crate) enum RegisterClass { pub(crate) fn register_class(leaf: u32, subleaf: u32, register: usize) -> RegisterClass { const ALL: &[(u32, u32)] = &[(0, 32)]; const ADDRESS_WIDTHS: &[(u32, u32)] = &[(0, 8), (8, 8)]; + // AMD's INVLPGB page count and RDPRU's largest register index; Intel + // reserves the register. + const AMD_INSTRUCTION_LIMITS: &[(u32, u32)] = &[(0, 16), (16, 10)]; match (leaf, subleaf, register) { (0x0, _, 0) | (0x8000_0000, _, 0) | (0x7, 0, 0) => RegisterClass::Limits(ALL), (0x8000_0008, _, 0) => RegisterClass::Limits(ADDRESS_WIDTHS), + (0x8000_0008, _, 3) => RegisterClass::Limits(AMD_INSTRUCTION_LIMITS), (0x0 | 0x2 | 0x3 | 0x4 | 0xd | 0x18 | 0x8000_001d, _, _) | (0x1 | 0x8000_0001, _, 0) | (0x1, _, 1) | (0x8000_0002..=0x8000_0006, _, _) - | (0x8000_0008, _, 2 | 3) => RegisterClass::Informational, + | (0x8000_0008, _, 2) => RegisterClass::Informational, _ => RegisterClass::Features, } } diff --git a/vmm_core/cpu_profile/src/test_support.rs b/vmm_core/cpu_profile/src/test_support.rs index 217593e5332..9e8905f37e1 100644 --- a/vmm_core/cpu_profile/src/test_support.rs +++ b/vmm_core/cpu_profile/src/test_support.rs @@ -14,6 +14,7 @@ use crate::fingerprint::ToolIdentity; use crate::host::HostCpu; use crate::host::HostIdentity; use crate::host::HostOs; +use crate::hv_banks::HvFeatures; use crate::signature::decode_signature; /// Returns the pinned profile `id`. @@ -55,14 +56,16 @@ pub(crate) fn fingerprint_with( /// Returns the fingerprint of a host whose `backend` reports `cpuid`, of the /// CPU whose vendor, signature, and brand `cpuid` reports. pub(crate) fn host_fingerprint(backend: &str, cpuid: Vec) -> CpuFingerprint { - let leaf = |leaf| crate::cpuid::lookup(&cpuid, leaf, 0).unwrap_or_default(); + backend_host_fingerprint(BackendFingerprint::new(backend, "test", cpuid)) +} + +/// Returns the fingerprint of a host whose backend reports `backend`, of the +/// CPU whose vendor, signature, and brand the backend's CPUID reports. +fn backend_host_fingerprint(backend: BackendFingerprint) -> CpuFingerprint { + let leaf = |leaf| crate::cpuid::lookup(&backend.cpuid, leaf, 0).unwrap_or_default(); let [_, ebx, ecx, edx] = leaf(0); let host = host_identity(&crate::signature::vendor_bytes(ebx, edx, ecx), &leaf); - CpuFingerprint::new( - tool(), - host, - BackendFingerprint::new(backend, "test", cpuid), - ) + CpuFingerprint::new(tool(), host, backend) } fn tool() -> ToolIdentity { @@ -320,6 +323,125 @@ pub(crate) fn genoa_kvm_entries() -> Vec { entries(&GENOA_KVM_CPUID) } +/// The CPUID of an AMD EPYC 9V74 (Genoa: family 0x19, model 0x11, +/// stepping 1) as MSHV presents it in an Azure VM with nested +/// virtualization, which `--cpu-fingerprint` recorded: each leaf, subleaf, +/// and the registers of a probe partition that enables every processor and +/// XSAVE feature of the host partition ([`GENOA_MSHV_HOST`]). That MSHV +/// offers AVX-512 and CET's XSAVE states, enumerates the basic leaves only up +/// to 0xD, and presents nothing in `0x80000021`: neither `LFENCE` +/// serialization nor the TSA immunities that the Genoa runner's Azure host +/// presents to KVM ([`GENOA_KVM_CPUID`]). +pub(crate) const GENOA_MSHV_CPUID: [(u32, Option, [u32; 4]); 61] = [ + (0x0, None, [0xd, 0x6874_7541, 0x444d_4163, 0x6974_6e65]), + (0x1, None, [0x00a1_0f11, 0x800, 0x76fa_3203, 0x078b_fbff]), + (0x2, None, [0; 4]), + (0x3, None, [0; 4]), + (0x4, Some(0), [0; 4]), + (0x5, None, [0; 4]), + (0x6, None, [0, 0, 0x1, 0]), + (0x7, Some(0), [0x1, 0xf1bf_07a9, 0x0040_5fc6, 0x10]), + (0x7, Some(1), [0x20, 0, 0, 0]), + (0x8, None, [0; 4]), + (0x9, None, [0; 4]), + (0xa, None, [0; 4]), + (0xb, Some(0), [0; 4]), + (0xc, None, [0; 4]), + (0xd, Some(0), [0xe7, 0x980, 0x980, 0]), + (0xd, Some(1), [0xf, 0x9a8, 0x1800, 0]), + (0xd, Some(2), [0x100, 0x240, 0, 0]), + (0xd, Some(5), [0x40, 0x340, 0, 0]), + (0xd, Some(6), [0x200, 0x380, 0, 0]), + (0xd, Some(7), [0x400, 0x580, 0, 0]), + (0xd, Some(0xb), [0x10, 0, 0x1, 0]), + (0xd, Some(0xc), [0x18, 0, 0x1, 0]), + (0x4000_0000, None, [0; 4]), + ( + 0x8000_0000, + None, + [0x8000_0021, 0x6874_7541, 0x444d_4163, 0x6974_6e65], + ), + ( + 0x8000_0001, + None, + [0x00a1_0f11, 0x4000_0000, 0x0040_03f3, 0x2fd3_fbff], + ), + ( + 0x8000_0002, + None, + [0x2044_4d41, 0x4359_5045, 0x3756_3920, 0x3038_2034], + ), + ( + 0x8000_0003, + None, + [0x726f_432d, 0x7250_2065, 0x7365_636f, 0x2072_6f73], + ), + ( + 0x8000_0004, + None, + [0x2020_2020, 0x2020_2020, 0x2020_2020, 0x0020_2020], + ), + ( + 0x8000_0005, + None, + [0xff48_ff40, 0xff48_ff40, 0x2008_0140, 0x2008_0140], + ), + ( + 0x8000_0006, + None, + [0x5c00_2200, 0x6c00_4200, 0x0400_6140, 0x0a00_9140], + ), + (0x8000_0007, None, [0; 4]), + (0x8000_0008, None, [0x3030, 0x3000_0015, 0, 0x0001_0000]), + (0x8000_0009, None, [0; 4]), + (0x8000_000a, None, [0; 4]), + (0x8000_000b, None, [0; 4]), + (0x8000_000c, None, [0; 4]), + (0x8000_000d, None, [0; 4]), + (0x8000_000e, None, [0; 4]), + (0x8000_000f, None, [0; 4]), + (0x8000_0010, None, [0; 4]), + (0x8000_0011, None, [0; 4]), + (0x8000_0012, None, [0; 4]), + (0x8000_0013, None, [0; 4]), + (0x8000_0014, None, [0; 4]), + (0x8000_0015, None, [0; 4]), + (0x8000_0016, None, [0; 4]), + (0x8000_0017, None, [0; 4]), + (0x8000_0018, None, [0; 4]), + (0x8000_0019, None, [0; 4]), + (0x8000_001a, None, [0x2, 0, 0, 0]), + (0x8000_001b, None, [0; 4]), + (0x8000_001c, None, [0; 4]), + (0x8000_001d, Some(0), [0x121, 0x01c0_003f, 0x3f, 0]), + (0x8000_001d, Some(1), [0x122, 0x01c0_003f, 0x3f, 0]), + (0x8000_001d, Some(2), [0x143, 0x01c0_003f, 0x7ff, 0x2]), + (0x8000_001d, Some(3), [0x163, 0x03c0_003f, 0x7fff, 0x1]), + (0x8000_001d, Some(4), [0; 4]), + (0x8000_001e, None, [0; 4]), + (0x8000_001f, None, [0; 4]), + (0x8000_0020, None, [0; 4]), + (0x8000_0021, None, [0; 4]), +]; + +/// The processor features that the host partition of [`GENOA_MSHV_CPUID`]'s +/// host offers its child partitions: none of the TSA immunities. +pub(crate) const GENOA_MSHV_HOST: HvFeatures = HvFeatures { + banks: [0x0602_0fcb_67f7_9fbf, 0x0000_0050_e000_01ed], + xsave: 0xff_ffdf, +}; + +/// Returns the fingerprint of the MSHV host of [`GENOA_MSHV_CPUID`], with +/// its processor features. +pub(crate) fn genoa_mshv_fingerprint() -> CpuFingerprint { + let mut backend = BackendFingerprint::new("mshv", "test", entries(&GENOA_MSHV_CPUID)); + let [bank0, bank1] = GENOA_MSHV_HOST.banks; + backend.set_feature_bank("mshv.host.ProcessorFeatures0", bank0); + backend.set_feature_bank("mshv.host.ProcessorFeatures1", bank1); + backend.set_feature_bank("mshv.host.ProcessorXsaveFeatures", GENOA_MSHV_HOST.xsave); + backend_host_fingerprint(backend) +} + /// The CPUID of an AMD EPYC 9V45 (Turin: family 0x1a, model 2, stepping 1) /// as KVM reports it in an Azure VM with nested virtualization, a /// GitHub-hosted Actions runner, which `--cpu-fingerprint` recorded. That diff --git a/vmm_core/virt_whp/src/time_abi.rs b/vmm_core/virt_whp/src/time_abi.rs index 56066e274aa..b9d9e6e4fef 100644 --- a/vmm_core/virt_whp/src/time_abi.rs +++ b/vmm_core/virt_whp/src/time_abi.rs @@ -1279,12 +1279,13 @@ pub(crate) mod test_cpuid { use vm_topology::processor::x86::X2ApicState; /// Pinned CPU profiles of each vendor. - pub const PROFILES: [&str; 6] = [ + pub const PROFILES: [&str; 7] = [ "intel.skylake-sp.v1", "intel.icelake-sp.v1", "intel.emeraldrapids.v1", "amd.milan.v1", "amd.genoa.v1", + "amd.genoa.v2", "amd.turin.v1", ]; From d2338ea22c36135b468e7f3ae168066b6c9efde9 Mon Sep 17 00:00:00 2001 From: Pedro Henrique Penna Date: Tue, 27 Oct 2026 10:45:15 -0700 Subject: [PATCH 2/2] cpu_profile: record a Genoa WHP host that amd.genoa.v2 serves An Azure Standard_D8ads_v5 VM with an AMD EPYC 9V74 (family 25 model 17 stepping 1), whose WHP is Microsoft Hv 10.0.26100.32522 on Windows Server 2025, cannot cold-boot on amd.genoa.v1 (microsoft/nvx#928): [E_PROFILE_UNSUPPORTED] the hypervisor's processor features cannot present CPU profile amd.genoa.v1: they lack tsa_l1_no_support, tsa_sq_no_support and its --cpu-fingerprint (surface digest sha256:16be9bd6dcff0e889003df7c287ae9acf34d3a801edc2e4b31d6f8b2a3ebfd23) fails amd.genoa.v1 on the maximum basic leaf and 0x80000021, as the MSHV host that amd.genoa.v2 derives from does. test_support::GENOA_WHP_CPUID and GENOA_WHP_HOST record its CPUID and processor features. They are the Milan WHP host's but for the CPU's identity and its cache and TLB descriptors. amd.genoa.v2 serves that host as pinned: - hv_banks: its processor features cannot present amd.genoa.v1, lacking both TSA immunities, and present amd.genoa.v2. - check: its fingerprint fails amd.genoa.v1 with the violations that the host reported, and passes amd.genoa.v2, which auto selects, once a partition configured from the profile reads zero at CET's XSAVE components, as on the Milan WHP host. - derive: deriving revision 2 from the KVM, MSHV, and WHP hosts gives amd.genoa.v2's CPUID, XSAVE state, and MSRs, so the WHP host constrains nothing that the others do not, and amd.genoa.v2 keeps its digest and its provenance. Not run on that host: a cold boot, the time ABI preflight, and NVX's doctor and test-microvm. Part of microsoft/nvx#928 and microsoft/nvx#409. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- vmm_core/cpu_profile/src/check.rs | 51 ++++++++++ vmm_core/cpu_profile/src/derive.rs | 33 +++++++ vmm_core/cpu_profile/src/hv_banks.rs | 57 +++++++---- vmm_core/cpu_profile/src/test_support.rs | 120 +++++++++++++++++++++++ 4 files changed, 242 insertions(+), 19 deletions(-) diff --git a/vmm_core/cpu_profile/src/check.rs b/vmm_core/cpu_profile/src/check.rs index fa31222e80f..898b18b9027 100644 --- a/vmm_core/cpu_profile/src/check.rs +++ b/vmm_core/cpu_profile/src/check.rs @@ -141,6 +141,7 @@ mod tests { use crate::cpuid::CpuidEntry; use crate::test_support::fingerprint; use crate::test_support::fingerprint_with; + use crate::test_support::genoa_whp_fingerprint; use crate::test_support::host_fingerprint; use crate::test_support::milan_whp_entries; use crate::test_support::profile; @@ -372,6 +373,56 @@ mod tests { ); } + /// The Genoa host of microsoft/nvx#928, an AMD EPYC 9V74 that WHP serves + /// on an Azure VM, does not support `amd.genoa.v1` but supports + /// `amd.genoa.v2`, which `auto` selects. Its probe partition presents + /// CET's XSAVE components, which a partition configured from the profile + /// does not, as on the Milan host. + #[test] + fn the_genoa_whp_host_supports_the_second_genoa_profile() { + let fingerprint = genoa_whp_fingerprint(); + // The check that the host reported on amd.genoa.v1, which auto + // selected before amd.genoa.v2. + let surface = HostCpuSurface::from_fingerprint(&fingerprint.backend); + let error = verify_support(profile("amd.genoa.v1"), &surface).unwrap_err(); + assert_eq!(error.code, ProfileErrorCode::ProfileUnsupported); + assert_eq!( + error.message, + "the backend does not support CPU profile amd.genoa.v1: CPUID 0x0 EAX[31:0] \ + is 0x1c, above the supported 0xd; CPUID 0x80000021 EAX bits 2, 9 are not \ + supported; CPUID 0x80000021 ECX bits 1, 2 are not supported; CPUID 0xd.11 \ + is outside the profile and reads EAX 0x10, EBX 0x0, ECX 0x1, EDX 0x0; \ + CPUID 0xd.12 is outside the profile and reads EAX 0x18, EBX 0x0, ECX 0x1, \ + EDX 0x0" + ); + + let error = check_fingerprint(&fingerprint).result.unwrap_err(); + assert_eq!(error.code, ProfileErrorCode::CpuUnlisted); + assert!( + error.message.starts_with( + "the backend presents CPUID entries outside CPU profile amd.genoa.v2: \ + CPUID 0xd.11 is outside the profile" + ), + "{error}" + ); + let check = check_fingerprint_with(&fingerprint, |profile| { + assert_eq!(profile.id(), "amd.genoa.v2"); + Ok(Some(vec![ + CpuidEntry::new(0xd, Some(11), [0; 4]), + CpuidEntry::new(0xd, Some(12), [0; 4]), + ])) + }); + check.result.as_ref().unwrap(); + assert!( + check.summary_line(&fingerprint).starts_with( + "NVX-CPU-PROFILE: status=pass backend=whp generation=genoa \ + profile=amd.genoa.v2 profile_digest=sha256:8ee00e9e" + ), + "{}", + check.summary_line(&fingerprint) + ); + } + #[test] fn configures_a_partition_only_when_it_decides_the_check() { let unused = |_: &CpuProfile| -> Result>, ProfileError> { diff --git a/vmm_core/cpu_profile/src/derive.rs b/vmm_core/cpu_profile/src/derive.rs index 457a251de15..9d6ff1e9de7 100644 --- a/vmm_core/cpu_profile/src/derive.rs +++ b/vmm_core/cpu_profile/src/derive.rs @@ -995,6 +995,7 @@ mod tests { use crate::test_support::fingerprint_with; use crate::test_support::genoa_kvm_entries; use crate::test_support::genoa_mshv_fingerprint; + use crate::test_support::genoa_whp_fingerprint; use crate::test_support::host_fingerprint; use crate::test_support::milan_whp_entries; use crate::test_support::profile; @@ -1500,6 +1501,38 @@ mod tests { ); } + /// The WHP host of `test_support::GENOA_WHP_CPUID` supports + /// `amd.genoa.v2`, and adding it to the KVM and MSHV hosts that v2 + /// derives from derives v2's CPUID, XSAVE state, and MSRs again: it lacks + /// nothing that v2 takes from them. + #[test] + fn the_whp_host_adds_nothing_to_the_second_genoa_profile() { + let pinned = profile("amd.genoa.v2"); + let whp = genoa_whp_fingerprint(); + let surface = crate::HostCpuSurface::from_fingerprint(&whp.backend); + assert_eq!( + crate::support_violations(pinned, &surface), + Vec::::new() + ); + + let hosts = [ + host_fingerprint("kvm", genoa_kvm_entries()), + genoa_mshv_fingerprint(), + whp, + ]; + let derived = derive_profile(generation("genoa"), 2, &hosts).unwrap(); + assert_eq!(derived.id(), pinned.id()); + assert_eq!(derived.cpuid(), pinned.cpuid()); + assert_eq!(derived.xcr0(), pinned.xcr0()); + assert_eq!(derived.xss(), pinned.xss()); + assert_eq!(derived.xsave_components(), pinned.xsave_components()); + assert_eq!( + derived.physical_address_width(), + pinned.physical_address_width() + ); + assert_eq!(derived.msrs(), pinned.msrs()); + } + /// KVM enumerates Intel's speculation controls and /// `IA32_ARCH_CAPABILITIES` on AMD hosts too, as the Genoa and Turin /// hosts' KVM does, and emulates the MSR. An AMD profile has none of diff --git a/vmm_core/cpu_profile/src/hv_banks.rs b/vmm_core/cpu_profile/src/hv_banks.rs index d85f7245079..e282a5af87e 100644 --- a/vmm_core/cpu_profile/src/hv_banks.rs +++ b/vmm_core/cpu_profile/src/hv_banks.rs @@ -1281,27 +1281,46 @@ mod tests { )); } - /// The MSHV host of `test_support::GENOA_MSHV_CPUID` offers neither TSA - /// immunity, so it cannot present `amd.genoa.v1`, which pins both, and - /// presents `amd.genoa.v2`, which clears them. + /// The Genoa hosts of `test_support::GENOA_MSHV_CPUID` and + /// `test_support::GENOA_WHP_CPUID` offer neither TSA immunity, so they + /// cannot present `amd.genoa.v1`, which pins both, and present + /// `amd.genoa.v2`, which clears them. WHP offers its Genoa host the + /// processor features that it offers the Milan host. #[test] - fn the_genoa_mshv_host_presents_the_second_genoa_profile() { - let host = crate::test_support::GENOA_MSHV_HOST; - let error = profile_features(profile("amd.genoa.v1"), host).unwrap_err(); - assert_eq!(error.code, ProfileErrorCode::ProfileUnsupported); - assert!( - error - .message - .ends_with("they lack tsa_l1_no_support, tsa_sq_no_support"), - "{error}" - ); + fn the_genoa_mshv_and_whp_hosts_present_the_second_genoa_profile() { + assert_eq!(crate::test_support::GENOA_WHP_HOST, MILAN_WHP_HOST); + for (backend, host) in [ + ("mshv", crate::test_support::GENOA_MSHV_HOST), + ("whp", crate::test_support::GENOA_WHP_HOST), + ] { + let error = profile_features(profile("amd.genoa.v1"), host).unwrap_err(); + assert_eq!( + error.code, + ProfileErrorCode::ProfileUnsupported, + "{backend}" + ); + assert!( + error + .message + .ends_with("they lack tsa_l1_no_support, tsa_sq_no_support"), + "{backend}: {error}" + ); - let features = profile_features(profile("amd.genoa.v2"), host).unwrap(); - for word in HvFeatureWord::ALL { - assert_eq!(features.word(word) & !host.word(word), 0, "{}", word.name()); - } - for name in ["tsa_l1_no_support", "tsa_sq_no_support"] { - assert!(!has(&features, HvFeatureWord::Bank1, name), "{name}"); + let features = profile_features(profile("amd.genoa.v2"), host).unwrap(); + for word in HvFeatureWord::ALL { + assert_eq!( + features.word(word) & !host.word(word), + 0, + "{backend}: {}", + word.name() + ); + } + for name in ["tsa_l1_no_support", "tsa_sq_no_support"] { + assert!( + !has(&features, HvFeatureWord::Bank1, name), + "{backend}: {name}" + ); + } } } } diff --git a/vmm_core/cpu_profile/src/test_support.rs b/vmm_core/cpu_profile/src/test_support.rs index 9e8905f37e1..b6d086a9c13 100644 --- a/vmm_core/cpu_profile/src/test_support.rs +++ b/vmm_core/cpu_profile/src/test_support.rs @@ -442,6 +442,126 @@ pub(crate) fn genoa_mshv_fingerprint() -> CpuFingerprint { backend_host_fingerprint(backend) } +/// The CPUID of an AMD EPYC 9V74 (Genoa: family 0x19, model 0x11, +/// stepping 1) as WHP presents it in an Azure VM with nested +/// virtualization, which `--cpu-fingerprint` recorded (microsoft/nvx#928): +/// each leaf, subleaf, and the registers of a probe partition that enables +/// every processor and XSAVE feature WHP offers ([`GENOA_WHP_HOST`]). That +/// WHP offers the x87, SSE, AVX, and CET XSAVE states, enumerates the basic +/// leaves only up to 0xD, and presents nothing in `0x80000021`: neither +/// `LFENCE` serialization nor the TSA immunities that the Genoa runner's +/// Azure host presents to KVM ([`GENOA_KVM_CPUID`]). Its CPUID is the Milan +/// host's ([`MILAN_WHP_CPUID`]) but for the CPU's identity and its cache and +/// TLB descriptors. +pub(crate) const GENOA_WHP_CPUID: [(u32, Option, [u32; 4]); 57] = [ + (0x0, None, [0xd, 0x6874_7541, 0x444d_4163, 0x6974_6e65]), + (0x1, None, [0x00a1_0f11, 0x800, 0x76fa_3203, 0x078b_fbff]), + (0x2, None, [0; 4]), + (0x3, None, [0; 4]), + (0x4, Some(0), [0; 4]), + (0x5, None, [0; 4]), + (0x6, None, [0, 0, 0x1, 0]), + (0x7, Some(0), [0, 0x219c_07a9, 0x0040_0684, 0x10]), + (0x8, None, [0; 4]), + (0x9, None, [0; 4]), + (0xa, None, [0; 4]), + (0xb, Some(0), [0; 4]), + (0xc, None, [0; 4]), + (0xd, Some(0), [0x7, 0x340, 0x340, 0]), + (0xd, Some(1), [0xf, 0x368, 0x1800, 0]), + (0xd, Some(2), [0x100, 0x240, 0, 0]), + (0xd, Some(0xb), [0x10, 0, 0x1, 0]), + (0xd, Some(0xc), [0x18, 0, 0x1, 0]), + (0x4000_0000, None, [0; 4]), + ( + 0x8000_0000, + None, + [0x8000_0021, 0x6874_7541, 0x444d_4163, 0x6974_6e65], + ), + ( + 0x8000_0001, + None, + [0x00a1_0f11, 0x4000_0000, 0x0040_03f3, 0x2fd3_fbff], + ), + ( + 0x8000_0002, + None, + [0x2044_4d41, 0x4359_5045, 0x3756_3920, 0x3038_2034], + ), + ( + 0x8000_0003, + None, + [0x726f_432d, 0x7250_2065, 0x7365_636f, 0x2072_6f73], + ), + ( + 0x8000_0004, + None, + [0x2020_2020, 0x2020_2020, 0x2020_2020, 0x0020_2020], + ), + ( + 0x8000_0005, + None, + [0xff48_ff40, 0xff48_ff40, 0x2008_0140, 0x2008_0140], + ), + ( + 0x8000_0006, + None, + [0x5c00_2200, 0x6c00_4200, 0x0400_6140, 0x0a00_9140], + ), + (0x8000_0007, None, [0; 4]), + (0x8000_0008, None, [0x3030, 0x3000_0015, 0, 0x0001_0000]), + (0x8000_0009, None, [0; 4]), + (0x8000_000a, None, [0; 4]), + (0x8000_000b, None, [0; 4]), + (0x8000_000c, None, [0; 4]), + (0x8000_000d, None, [0; 4]), + (0x8000_000e, None, [0; 4]), + (0x8000_000f, None, [0; 4]), + (0x8000_0010, None, [0; 4]), + (0x8000_0011, None, [0; 4]), + (0x8000_0012, None, [0; 4]), + (0x8000_0013, None, [0; 4]), + (0x8000_0014, None, [0; 4]), + (0x8000_0015, None, [0; 4]), + (0x8000_0016, None, [0; 4]), + (0x8000_0017, None, [0; 4]), + (0x8000_0018, None, [0; 4]), + (0x8000_0019, None, [0; 4]), + (0x8000_001a, None, [0x2, 0, 0, 0]), + (0x8000_001b, None, [0; 4]), + (0x8000_001c, None, [0; 4]), + (0x8000_001d, Some(0), [0x121, 0x01c0_003f, 0x3f, 0]), + (0x8000_001d, Some(1), [0x122, 0x01c0_003f, 0x3f, 0]), + (0x8000_001d, Some(2), [0x143, 0x01c0_003f, 0x7ff, 0x2]), + (0x8000_001d, Some(3), [0x163, 0x03c0_003f, 0x7fff, 0x1]), + (0x8000_001d, Some(4), [0; 4]), + (0x8000_001e, None, [0; 4]), + (0x8000_001f, None, [0; 4]), + (0x8000_0020, None, [0; 4]), + (0x8000_0021, None, [0; 4]), +]; + +/// The processor features that WHP offers the host of [`GENOA_WHP_CPUID`], +/// as it does the Milan host: none of the TSA immunities. +pub(crate) const GENOA_WHP_HOST: HvFeatures = HvFeatures { + banks: [0x0602_0fcb_67f7_9fbf, 0x0000_0000_2000_01ed], + xsave: 0x50_381f, +}; + +/// Returns the fingerprint of the WHP host of [`GENOA_WHP_CPUID`], with its +/// processor features. +pub(crate) fn genoa_whp_fingerprint() -> CpuFingerprint { + let mut backend = BackendFingerprint::new("whp", "test", entries(&GENOA_WHP_CPUID)); + let [bank0, bank1] = GENOA_WHP_HOST.banks; + backend.set_feature_bank("whp.capability.ProcessorFeaturesBanks.bank0", bank0); + backend.set_feature_bank("whp.capability.ProcessorFeaturesBanks.bank1", bank1); + backend.set_feature_bank( + "whp.capability.ProcessorXsaveFeatures", + GENOA_WHP_HOST.xsave, + ); + backend_host_fingerprint(backend) +} + /// The CPUID of an AMD EPYC 9V45 (Turin: family 0x1a, model 2, stepping 1) /// as KVM reports it in an Azure VM with nested virtualization, a /// GitHub-hosted Actions runner, which `--cpu-fingerprint` recorded. That