@@ -1116,6 +1116,7 @@ mod debug {
11161116
11171117#[ cfg( test) ]
11181118#[ cfg( feature = "init-paging" ) ]
1119+ #[ allow( clippy:: needless_range_loop) ]
11191120mod tests {
11201121 use std:: sync:: { Arc , Mutex } ;
11211122
@@ -1292,24 +1293,108 @@ mod tests {
12921293 }
12931294 }
12941295
1295- /// Build a dirty XSAVE area for testing reset_vcpu.
1296- /// Can't use all 1s because XSAVE has reserved fields that must be zero
1297- /// (header at 512-575, MXCSR reserved bits, etc.)
1298- ///
1299- /// Takes the current xsave state to preserve hypervisor-specific header fields
1300- /// like XCOMP_BV which MSHV/WHP require to be set correctly.
1296+ /// Query CPUID.0DH.n for XSAVE component info.
1297+ /// Returns (size, offset, align_64) for the given component:
1298+ /// - size: CPUID.0DH.n:EAX - size in bytes
1299+ /// - offset: CPUID.0DH.n:EBX - offset from XSAVE base (standard format only)
1300+ /// - align_64: CPUID.0DH.n:ECX bit 1 - true if 64-byte aligned (compacted format)
1301+ fn xsave_component_info ( comp_id : u32 ) -> ( usize , usize , bool ) {
1302+ let result = unsafe { std:: arch:: x86_64:: __cpuid_count ( 0xD , comp_id) } ;
1303+ let size = result. eax as usize ;
1304+ let offset = result. ebx as usize ;
1305+ let align_64 = ( result. ecx & 0b10 ) != 0 ;
1306+ ( size, offset, align_64)
1307+ }
1308+
1309+ /// Query CPUID.0DH.00H for the bitmap of supported user state components.
1310+ /// EDX:EAX forms a 64-bit bitmap where bit i indicates support for component i.
1311+ fn xsave_supported_components ( ) -> u64 {
1312+ let result = unsafe { std:: arch:: x86_64:: __cpuid_count ( 0xD , 0 ) } ;
1313+ ( result. edx as u64 ) << 32 | ( result. eax as u64 )
1314+ }
1315+
1316+ /// Dirty extended state components using compacted XSAVE format (MSHV/WHP).
1317+ /// Components are stored contiguously starting at byte 576, with alignment
1318+ /// requirements from CPUID.0DH.n:ECX[1].
1319+ /// Returns a bitmask of components that were actually dirtied.
1320+ fn dirty_xsave_extended_compacted (
1321+ xsave : & mut [ u32 ] ,
1322+ xcomp_bv : u64 ,
1323+ supported_components : u64 ,
1324+ ) -> u64 {
1325+ let mut dirtied_mask = 0u64 ;
1326+ let mut offset = 576usize ;
1327+
1328+ for comp_id in 2 ..63u32 {
1329+ // Skip if component not supported by CPU or not enabled in XCOMP_BV
1330+ if ( supported_components & ( 1u64 << comp_id) ) == 0 {
1331+ continue ;
1332+ }
1333+ if ( xcomp_bv & ( 1u64 << comp_id) ) == 0 {
1334+ continue ;
1335+ }
1336+
1337+ let ( size, _, align_64) = xsave_component_info ( comp_id) ;
1338+
1339+ // ECX[1]=1 means 64-byte aligned; ECX[1]=0 means immediately after previous
1340+ if align_64 {
1341+ offset = offset. next_multiple_of ( 64 ) ;
1342+ }
1343+
1344+ // Dirty this component's data area (only if it fits in the buffer)
1345+ let start_idx = offset / 4 ;
1346+ let end_idx = ( offset + size) / 4 ;
1347+ if end_idx <= xsave. len ( ) {
1348+ for i in start_idx..end_idx {
1349+ xsave[ i] = 0x12345678 ^ comp_id. wrapping_mul ( 0x11111111 ) ;
1350+ }
1351+ dirtied_mask |= 1u64 << comp_id;
1352+ }
1353+
1354+ offset += size;
1355+ }
1356+
1357+ dirtied_mask
1358+ }
1359+
1360+ /// Dirty extended state components using standard XSAVE format (KVM).
1361+ /// Components are at fixed offsets from CPUID.0DH.n:EBX.
1362+ /// Returns a bitmask of components that were actually dirtied.
1363+ fn dirty_xsave_extended_standard ( xsave : & mut [ u32 ] , supported_components : u64 ) -> u64 {
1364+ let mut dirtied_mask = 0u64 ;
1365+
1366+ for comp_id in 2 ..63u32 {
1367+ // Skip if component not supported by CPU
1368+ if ( supported_components & ( 1u64 << comp_id) ) == 0 {
1369+ continue ;
1370+ }
1371+
1372+ let ( size, fixed_offset, _) = xsave_component_info ( comp_id) ;
1373+
1374+ let start_idx = fixed_offset / 4 ;
1375+ let end_idx = ( fixed_offset + size) / 4 ;
1376+ if end_idx <= xsave. len ( ) {
1377+ for i in start_idx..end_idx {
1378+ xsave[ i] = 0x12345678 ^ comp_id. wrapping_mul ( 0x11111111 ) ;
1379+ }
1380+ dirtied_mask |= 1u64 << comp_id;
1381+ }
1382+ }
1383+
1384+ dirtied_mask
1385+ }
1386+
1387+ /// Dirty the legacy XSAVE region (bytes 0-511) for testing reset_vcpu.
1388+ /// This includes FPU/x87 state, SSE state, and reserved areas.
13011389 ///
13021390 /// Layout (from Intel SDM Table 13-1):
13031391 /// Bytes 0-1: FCW, 2-3: FSW, 4: FTW, 5: reserved, 6-7: FOP
13041392 /// Bytes 8-15: FIP, 16-23: FDP
1305- /// Bytes 24-27: MXCSR, 28-31: MXCSR_MASK (don't touch )
1306- /// Bytes 32-159: ST0-ST7/MM0-MM7 (8 regs × 16 bytes, only 10 bytes used per reg )
1393+ /// Bytes 24-27: MXCSR, 28-31: MXCSR_MASK (preserve - hardware defined )
1394+ /// Bytes 32-159: ST0-ST7/MM0-MM7 (8 regs × 16 bytes)
13071395 /// Bytes 160-415: XMM0-XMM15 (16 regs × 16 bytes)
13081396 /// Bytes 416-511: Reserved
1309- /// Bytes 512-575: XSAVE header (preserve XCOMP_BV at 520-527)
1310- fn dirty_xsave ( current_xsave : & [ u8 ] ) -> [ u32 ; 1024 ] {
1311- let mut xsave = [ 0u32 ; 1024 ] ;
1312-
1397+ fn dirty_xsave_legacy ( xsave : & mut [ u32 ] , current_xsave : & [ u8 ] ) {
13131398 // FCW (bytes 0-1) + FSW (bytes 2-3) - pack into xsave[0]
13141399 // FCW = 0x0F7F (different from default 0x037F), FSW = 0x1234
13151400 xsave[ 0 ] = 0x0F7F | ( 0x1234 << 16 ) ;
@@ -1324,35 +1409,66 @@ mod tests {
13241409 xsave[ 5 ] = 0xBABE0004 ;
13251410 // MXCSR (bytes 24-27) - xsave[6], use valid value different from default
13261411 xsave[ 6 ] = 0x3F80 ;
1327- // xsave[7] is MXCSR_MASK - preserve from current
1412+ // xsave[7] is MXCSR_MASK - preserve from current (hardware defined, read-only)
13281413 if current_xsave. len ( ) >= 32 {
13291414 xsave[ 7 ] = u32:: from_le_bytes ( current_xsave[ 28 ..32 ] . try_into ( ) . unwrap ( ) ) ;
13301415 }
13311416
13321417 // ST0-ST7/MM0-MM7 (bytes 32-159, indices 8-39)
1333- for item in xsave . iter_mut ( ) . take ( 40 ) . skip ( 8 ) {
1334- * item = 0xCAFEBABE ;
1418+ for i in 8 .. 40 {
1419+ xsave [ i ] = 0xCAFEBABE ;
13351420 }
13361421 // XMM0-XMM15 (bytes 160-415, indices 40-103)
1337- for item in xsave . iter_mut ( ) . take ( 104 ) . skip ( 40 ) {
1338- * item = 0xDEADBEEF ;
1422+ for i in 40 .. 104 {
1423+ xsave [ i ] = 0xDEADBEEF ;
13391424 }
13401425
1341- // Preserve entire XSAVE header (bytes 512-575, indices 128-143) from current state
1342- // This includes XSTATE_BV (512-519) and XCOMP_BV (520-527) which
1343- // MSHV/WHP require to be set correctly for compacted format.
1344- // Failure to do this will cause set_xsave to fail on MSHV.
1345- if current_xsave. len ( ) >= 576 {
1346- #[ allow( clippy:: needless_range_loop) ]
1347- for i in 128 ..144 {
1348- let byte_offset = i * 4 ;
1349- xsave[ i] = u32:: from_le_bytes (
1350- current_xsave[ byte_offset..byte_offset + 4 ]
1351- . try_into ( )
1352- . unwrap ( ) ,
1353- ) ;
1354- }
1426+ // Reserved area (bytes 416-511, indices 104-127)
1427+ for i in 104 ..128 {
1428+ xsave[ i] = 0xABCDEF12 ;
1429+ }
1430+ }
1431+
1432+ /// Preserve XSAVE header (bytes 512-575) from current state.
1433+ /// This includes XSTATE_BV and XCOMP_BV which hypervisors require.
1434+ fn preserve_xsave_header ( xsave : & mut [ u32 ] , current_xsave : & [ u8 ] ) {
1435+ for i in 128 ..144 {
1436+ let byte_offset = i * 4 ;
1437+ xsave[ i] = u32:: from_le_bytes (
1438+ current_xsave[ byte_offset..byte_offset + 4 ]
1439+ . try_into ( )
1440+ . unwrap ( ) ,
1441+ ) ;
13551442 }
1443+ }
1444+
1445+ fn dirty_xsave ( current_xsave : & [ u8 ] ) -> Vec < u32 > {
1446+ let mut xsave = vec ! [ 0u32 ; current_xsave. len( ) / 4 ] ;
1447+
1448+ dirty_xsave_legacy ( & mut xsave, current_xsave) ;
1449+ preserve_xsave_header ( & mut xsave, current_xsave) ;
1450+
1451+ let xcomp_bv = u64:: from_le_bytes ( current_xsave[ 520 ..528 ] . try_into ( ) . unwrap ( ) ) ;
1452+ let supported_components = xsave_supported_components ( ) ;
1453+
1454+ // Dirty extended components and get mask of what was actually dirtied
1455+ let extended_mask = if ( xcomp_bv & ( 1u64 << 63 ) ) != 0 {
1456+ // Compacted format (MSHV/WHP)
1457+ dirty_xsave_extended_compacted ( & mut xsave, xcomp_bv, supported_components)
1458+ } else {
1459+ // Standard format (KVM)
1460+ dirty_xsave_extended_standard ( & mut xsave, supported_components)
1461+ } ;
1462+
1463+ // UPDATE XSTATE_BV to indicate dirtied components have valid data.
1464+ // WHP validates consistency between XSTATE_BV and actual data in the buffer.
1465+ // Bits 0,1 = legacy x87/SSE (always set after dirty_xsave_legacy)
1466+ // Bits 2+ = extended components that we actually dirtied
1467+ let xstate_bv = 0x3 | extended_mask;
1468+
1469+ // Write XSTATE_BV to bytes 512-519 (u32 indices 128-129)
1470+ xsave[ 128 ] = ( xstate_bv & 0xFFFFFFFF ) as u32 ;
1471+ xsave[ 129 ] = ( xstate_bv >> 32 ) as u32 ;
13561472
13571473 xsave
13581474 }
0 commit comments