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