22// License, v. 2.0. If a copy of the MPL was not distributed with this
33// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44
5- use cpuid_utils:: { CpuidIdent , CpuidValues , CpuidVendor } ;
5+ use cpuid_utils:: { CpuidIdent , CpuidSet , CpuidValues } ;
6+ use phd_framework:: { test_vm:: MigrationTimeout , TestVm } ;
67use phd_testcase:: * ;
78use propolis_client:: types:: {
89 CpuidEntry , InstanceSpecStatus , VersionedInstanceSpec ,
910} ;
1011use tracing:: info;
12+ use uuid:: Uuid ;
1113
1214fn cpuid_entry (
1315 leaf : u32 ,
@@ -47,44 +49,24 @@ async fn cpuid_instance_spec_round_trip_test(ctx: &Framework) {
4749 itertools:: assert_equal ( cpuid. entries , entries) ;
4850}
4951
50- #[ phd_testcase]
51- async fn cpuid_boot_test ( ctx : & Framework ) {
52- use cpuid_utils:: bits:: * ;
53-
54- // This test verifies that plumbing a reasonably sensible-looking set of
55- // CPUID values into the guest produces a bootable guest.
56- //
57- // The definition of "reasonably sensible" is derived from the suggested
58- // virtual-Milan CPUID definitions in RFD 314. Those are in turn derived
59- // from reading AMD's manuals and bhyve's source code to figure out what
60- // host CPU features to hide from guests.
61- //
62- // To try to make this test at least somewhat host-agnostic, read all of the
63- // host's CPUID leaves, then filter to just a handful of leaves that
64- // advertise features to the guest (and that Linux guests will check for
65- // during boot).
66- let mut host_cpuid = cpuid_utils:: host:: query_complete (
67- cpuid_utils:: host:: CpuidSource :: BhyveDefault ,
68- ) ?;
69-
70- info ! ( ?host_cpuid, "read bhyve default CPUID" ) ;
71-
72- // Linux guests expect to see at least a couple of leaves in the extended
73- // CPUID range. These have vendor-specific meanings. This test only encodes
74- // AMD's definitions, so skip the test if leaf 0 reports any other vendor.
75- if host_cpuid. vendor ( ) != CpuidVendor :: Amd {
76- phd_skip ! ( "cpuid_boot_test can only run on AMD hosts" ) ;
77- }
78-
79- // This test works by injecting a fake brand string into extended leaves
80- // 0x8000_0002-0x8000_0004 and seeing if the guest observes that string.
81- //
52+ /// A synthetic brand string that can be injected into guest CPUID leaves
53+ /// 0x8000_0002-0x8000_0004.
54+ const BRAND_STRING : & [ u8 ; 48 ] =
55+ b"Oxide Cloud Computer Company Cloud Computer\0 \0 \0 \0 \0 " ;
56+
57+ /// Injects a fake CPU brand string into CPUID leaves 0x8000_0002-0x8000_0004.
58+ ///
59+ /// # Panics
60+ ///
61+ /// Panics if the input CPUID set does not include the brand string leaves.
62+ fn inject_brand_string ( cpuid : & mut CpuidSet ) {
8263 // The brand string leaves have been defined for long enough that they
8364 // should be present on virtually any host that's modern enough to run
84- // Propolis and PHD. Fail the test (instead of skipping) if they're missing,
85- // since that may indicate a latent bug in the `cpuid_utils` crate.
86- let ext_leaf_0 = host_cpuid
87- . get ( CpuidIdent :: leaf ( EXTENDED_BASE_LEAF ) )
65+ // Propolis and PHD. Assert (instead of returning a "skipped" result) if
66+ // they're missing, since that may indicate a latent bug in the
67+ // `cpuid_utils` crate.
68+ let ext_leaf_0 = cpuid
69+ . get ( CpuidIdent :: leaf ( cpuid_utils:: bits:: EXTENDED_BASE_LEAF ) )
8870 . expect ( "PHD-capable processors should have some extended leaves" ) ;
8971
9072 assert ! (
@@ -94,11 +76,6 @@ async fn cpuid_boot_test(ctx: &Framework) {
9476 ext_leaf_0. eax
9577 ) ;
9678
97- // Reprogram the brand string leaves and see if the new string shows up in
98- // the guest.
99- const BRAND_STRING : & [ u8 ; 48 ] =
100- b"Oxide Cloud Computer Company Cloud Computer\0 \0 \0 \0 \0 " ;
101-
10279 let chunks = BRAND_STRING . chunks_exact ( 4 ) ;
10380 let mut ext_leaf_2 = CpuidValues :: default ( ) ;
10481 let mut ext_leaf_3 = CpuidValues :: default ( ) ;
@@ -112,13 +89,38 @@ async fn cpuid_boot_test(ctx: &Framework) {
11289 * dst = u32:: from_le_bytes ( chunk. try_into ( ) . unwrap ( ) ) ;
11390 }
11491
115- host_cpuid. insert ( CpuidIdent :: leaf ( 0x8000_0002 ) , ext_leaf_2) . unwrap ( ) ;
116- host_cpuid. insert ( CpuidIdent :: leaf ( 0x8000_0003 ) , ext_leaf_3) . unwrap ( ) ;
117- host_cpuid. insert ( CpuidIdent :: leaf ( 0x8000_0004 ) , ext_leaf_4) . unwrap ( ) ;
92+ cpuid. insert ( CpuidIdent :: leaf ( 0x8000_0002 ) , ext_leaf_2) . unwrap ( ) ;
93+ cpuid. insert ( CpuidIdent :: leaf ( 0x8000_0003 ) , ext_leaf_3) . unwrap ( ) ;
94+ cpuid. insert ( CpuidIdent :: leaf ( 0x8000_0004 ) , ext_leaf_4) . unwrap ( ) ;
95+ }
96+
97+ /// Asserts that `/proc/cpuinfo` in the guest returns output that contains
98+ /// [`BRAND_STRING`].
99+ async fn verify_guest_brand_string ( vm : & TestVm ) -> anyhow:: Result < ( ) > {
100+ let cpuinfo = vm. run_shell_command ( "cat /proc/cpuinfo" ) . await ?;
101+ info ! ( cpuinfo, "/proc/cpuinfo output" ) ;
102+ assert ! ( cpuinfo. contains(
103+ std:: str :: from_utf8( BRAND_STRING ) . unwrap( ) . trim_matches( '\0' )
104+ ) ) ;
105+
106+ Ok ( ( ) )
107+ }
118108
119- // Try to boot a guest with the computed CPUID values. The modified brand
120- // string should show up in /proc/cpuinfo.
121- let mut cfg = ctx. vm_config_builder ( "cpuid_boot_test" ) ;
109+ /// Launches a test VM with a synthetic brand string injected into its CPUID
110+ /// leaves.
111+ async fn launch_cpuid_smoke_test_vm (
112+ ctx : & Framework ,
113+ vm_name : & str ,
114+ ) -> anyhow:: Result < TestVm > {
115+ let mut host_cpuid = cpuid_utils:: host:: query_complete (
116+ cpuid_utils:: host:: CpuidSource :: BhyveDefault ,
117+ ) ?;
118+
119+ info ! ( ?host_cpuid, "read bhyve default CPUID" ) ;
120+
121+ inject_brand_string ( & mut host_cpuid) ;
122+
123+ let mut cfg = ctx. vm_config_builder ( vm_name) ;
122124 cfg. cpuid (
123125 host_cpuid
124126 . iter ( )
@@ -136,9 +138,31 @@ async fn cpuid_boot_test(ctx: &Framework) {
136138 vm. launch ( ) . await ?;
137139 vm. wait_to_boot ( ) . await ?;
138140
139- let cpuinfo = vm. run_shell_command ( "cat /proc/cpuinfo" ) . await ?;
140- info ! ( cpuinfo, "/proc/cpuinfo output" ) ;
141- assert ! ( cpuinfo. contains(
142- std:: str :: from_utf8( BRAND_STRING ) . unwrap( ) . trim_matches( '\0' )
143- ) ) ;
141+ Ok ( vm)
142+ }
143+
144+ #[ phd_testcase]
145+ async fn cpuid_boot_test ( ctx : & Framework ) {
146+ let vm = launch_cpuid_smoke_test_vm ( ctx, "cpuid_boot_test" ) . await ?;
147+ verify_guest_brand_string ( & vm) . await ?;
148+ }
149+
150+ #[ phd_testcase]
151+ async fn cpuid_migrate_smoke_test ( ctx : & Framework ) {
152+ let vm = launch_cpuid_smoke_test_vm ( ctx, "cpuid_boot_test" ) . await ?;
153+ verify_guest_brand_string ( & vm) . await ?;
154+
155+ // Migrate the VM and make sure the brand string setting persists.
156+ let mut target = ctx
157+ . spawn_successor_vm ( "cpuid_boot_test_migration_target" , & vm, None )
158+ . await ?;
159+
160+ target
161+ . migrate_from ( & vm, Uuid :: new_v4 ( ) , MigrationTimeout :: default ( ) )
162+ . await ?;
163+
164+ // Reset the target to force it to reread its CPU information.
165+ target. reset ( ) . await ?;
166+ target. wait_to_boot ( ) . await ?;
167+ verify_guest_brand_string ( & target) . await ?;
144168}
0 commit comments