@@ -10,7 +10,7 @@ use crate::logger::warn;
1010const MAX_CACHE_LEVEL : u8 = 7 ;
1111
1212#[ derive( Debug , thiserror:: Error , displaydoc:: Display ) ]
13- pub ( crate ) enum CacheInfoError {
13+ pub enum CacheInfoError {
1414 /// Failed to read cache information: {0}
1515 FailedToReadCacheInfo ( #[ from] io:: Error ) ,
1616 /// Invalid cache configuration found for {0}: {1}
@@ -32,7 +32,7 @@ trait CacheStore: std::fmt::Debug {
3232}
3333
3434#[ derive( Debug ) ]
35- pub ( crate ) struct CacheEntry {
35+ pub struct CacheEntry {
3636 // Cache Level: 1, 2, 3..
3737 pub level : u8 ,
3838 // Type of cache: Unified, Data, Instruction.
@@ -154,7 +154,7 @@ impl Default for CacheEntry {
154154
155155#[ derive( Debug ) ]
156156// Based on https://elixir.free-electrons.com/linux/v4.9.62/source/include/linux/cacheinfo.h#L11.
157- pub ( crate ) enum CacheType {
157+ pub enum CacheType {
158158 Instruction ,
159159 Data ,
160160 Unified ,
@@ -314,6 +314,105 @@ pub(crate) fn read_cache_config(
314314 Ok ( ( ) )
315315}
316316
317+ // CLIDR_EL1 field positions
318+ // https://developer.arm.com/documentation/ddi0595/2021-12/AArch64-Registers/CLIDR-EL1--Cache-Level-ID-Register
319+ const CLIDR_CTYPE_SHIFT : u8 = 3 ; // Each Ctype field is 3 bits
320+ const CLIDR_LOC_SHIFT : u8 = 24 ;
321+
322+ // CLIDR_EL1 Ctype field values
323+ const CLIDR_CTYPE_NO_CACHE : u64 = 0 ;
324+ const CLIDR_CTYPE_INSTRUCTION : u64 = 1 ;
325+ const CLIDR_CTYPE_DATA : u64 = 2 ;
326+ const CLIDR_CTYPE_SEPARATE : u64 = 3 ;
327+ const CLIDR_CTYPE_UNIFIED : u64 = 4 ;
328+
329+ /// Classify a set of cache entries at the same level into a CLIDR Ctype value.
330+ fn ctype_for_entries < ' a > ( entries : impl Iterator < Item = & ' a CacheEntry > ) -> u64 {
331+ let ( mut has_data, mut has_inst, mut has_unified) = ( false , false , false ) ;
332+ let mut any = false ;
333+ for c in entries {
334+ any = true ;
335+ match c. type_ {
336+ CacheType :: Data => has_data = true ,
337+ CacheType :: Instruction => has_inst = true ,
338+ CacheType :: Unified => has_unified = true ,
339+ }
340+ }
341+ if !any {
342+ return CLIDR_CTYPE_NO_CACHE ;
343+ }
344+ if has_unified {
345+ CLIDR_CTYPE_UNIFIED
346+ } else if has_data && has_inst {
347+ CLIDR_CTYPE_SEPARATE
348+ } else if has_data {
349+ CLIDR_CTYPE_DATA
350+ } else if has_inst {
351+ CLIDR_CTYPE_INSTRUCTION
352+ } else {
353+ CLIDR_CTYPE_NO_CACHE
354+ }
355+ }
356+
357+ /// Build a CLIDR_EL1 value from the host's cache topology read from sysfs.
358+ ///
359+ /// Since host kernel 6.3 (commit 7af0c2534f4c), KVM fabricates CLIDR_EL1 to
360+ /// expose a different cache topology than the host. Guest kernels >= 6.1.156
361+ /// backported `init_of_cache_level()` which counts cache leaves from the DT,
362+ /// while `populate_cache_leaves()` uses CLIDR_EL1. If the DT (built from
363+ /// sysfs) describes different cache entries than CLIDR_EL1, the mismatch
364+ /// causes cache sysfs entries to not be created in the guest.
365+ ///
366+ /// This function builds a CLIDR_EL1 value that matches the host's real cache
367+ /// topology so it can be written to each vCPU, making CLIDR_EL1 consistent
368+ /// with the FDT.
369+ pub ( crate ) fn build_clidr_from_caches (
370+ l1_caches : & [ CacheEntry ] ,
371+ non_l1_caches : & [ CacheEntry ] ,
372+ ) -> u64 {
373+ let mut clidr: u64 = 0 ;
374+ let mut max_level: u8 = 0 ;
375+
376+ let l1_ctype = ctype_for_entries ( l1_caches. iter ( ) ) ;
377+ if l1_ctype != CLIDR_CTYPE_NO_CACHE {
378+ clidr |= l1_ctype;
379+ max_level = 1 ;
380+ }
381+
382+ for level in 2 ..=MAX_CACHE_LEVEL {
383+ let ctype = ctype_for_entries ( non_l1_caches. iter ( ) . filter ( |c| c. level == level) ) ;
384+ if ctype == CLIDR_CTYPE_NO_CACHE {
385+ break ;
386+ }
387+
388+ let shift = CLIDR_CTYPE_SHIFT * ( level - 1 ) ;
389+ clidr |= ctype << shift;
390+ max_level = level;
391+ }
392+
393+ // Set LoC (Level of Coherence) to the highest cache level
394+ clidr |= u64:: from ( max_level) << CLIDR_LOC_SHIFT ;
395+
396+ clidr
397+ }
398+
399+ /// Merge sysfs-derived ctype/LoC fields into an existing CLIDR_EL1 value,
400+ /// preserving LoUU, LoUIS, ICB, and Ttype fields from the original.
401+ ///
402+ /// This ensures that on pre-6.3 kernels (where CLIDR already matches sysfs),
403+ /// the write is effectively a no-op, and fields we can't derive from sysfs
404+ /// (like LoUU, LoUIS, ICB) are never clobbered.
405+ pub ( crate ) fn merge_clidr ( current : u64 , sysfs : u64 ) -> u64 {
406+ // Ctype fields: bits [20:0] (7 levels × 3 bits each = 21 bits)
407+ // LoC field: bits [26:24]
408+ // We replace only these fields from sysfs, preserving LoUIS [23:21],
409+ // LoUU [29:27], ICB [32:30], and Ttype [46:33] from the original.
410+ const CTYPE_MASK : u64 = 0x001F_FFFF ; // bits [20:0]
411+ const LOC_MASK : u64 = 0x0700_0000 ; // bits [26:24]
412+ const REPLACE_MASK : u64 = CTYPE_MASK | LOC_MASK ;
413+ ( current & !REPLACE_MASK ) | ( sysfs & REPLACE_MASK )
414+ }
415+
317416#[ cfg( test) ]
318417mod tests {
319418 use std:: collections:: HashMap ;
@@ -576,4 +675,101 @@ mod tests {
576675 assert_eq ! ( l1_caches. len( ) , 2 ) ;
577676 assert_eq ! ( l1_caches. len( ) , 2 ) ;
578677 }
678+
679+ #[ test]
680+ fn test_build_clidr_from_caches ( ) {
681+ // L1 Separate (Data + Instruction) + L2 Unified + L3 Unified
682+ let l1 = vec ! [
683+ CacheEntry {
684+ level: 1 ,
685+ type_: CacheType :: Data ,
686+ ..CacheEntry :: default ( )
687+ } ,
688+ CacheEntry {
689+ level: 1 ,
690+ type_: CacheType :: Instruction ,
691+ ..CacheEntry :: default ( )
692+ } ,
693+ ] ;
694+ let non_l1 = vec ! [
695+ CacheEntry {
696+ level: 2 ,
697+ type_: CacheType :: Unified ,
698+ ..CacheEntry :: default ( )
699+ } ,
700+ CacheEntry {
701+ level: 3 ,
702+ type_: CacheType :: Unified ,
703+ ..CacheEntry :: default ( )
704+ } ,
705+ ] ;
706+ let clidr = build_clidr_from_caches ( & l1, & non_l1) ;
707+ // ctype1=3 (Separate), ctype2=4 (Unified), ctype3=4 (Unified), LoC=3
708+ assert_eq ! ( clidr & 0x7 , 3 , "L1 should be Separate" ) ;
709+ assert_eq ! ( ( clidr >> 3 ) & 0x7 , 4 , "L2 should be Unified" ) ;
710+ assert_eq ! ( ( clidr >> 6 ) & 0x7 , 4 , "L3 should be Unified" ) ;
711+ assert_eq ! ( ( clidr >> 24 ) & 0x7 , 3 , "LoC should be 3" ) ;
712+
713+ // L1 Unified only (no higher levels)
714+ let l1_unified = vec ! [ CacheEntry {
715+ level: 1 ,
716+ type_: CacheType :: Unified ,
717+ ..CacheEntry :: default ( )
718+ } ] ;
719+ let clidr = build_clidr_from_caches ( & l1_unified, & [ ] ) ;
720+ assert_eq ! ( clidr & 0x7 , 4 , "L1 should be Unified" ) ;
721+ assert_eq ! ( ( clidr >> 3 ) & 0x7 , 0 , "L2 should be NoCache" ) ;
722+ assert_eq ! ( ( clidr >> 24 ) & 0x7 , 1 , "LoC should be 1" ) ;
723+
724+ // No caches at all
725+ let clidr = build_clidr_from_caches ( & [ ] , & [ ] ) ;
726+ assert_eq ! ( clidr, 0 , "Empty caches should produce CLIDR=0" ) ;
727+
728+ // Mock store default: L1 Data + L1 Instruction + L2 Unified
729+ let mut l1_mock: Vec < CacheEntry > = Vec :: new ( ) ;
730+ let mut non_l1_mock: Vec < CacheEntry > = Vec :: new ( ) ;
731+ read_cache_config ( & mut l1_mock, & mut non_l1_mock) . unwrap ( ) ;
732+ let clidr = build_clidr_from_caches ( & l1_mock, & non_l1_mock) ;
733+ assert_eq ! ( clidr & 0x7 , 3 , "Mock L1 should be Separate" ) ;
734+ assert_eq ! ( ( clidr >> 3 ) & 0x7 , 4 , "Mock L2 should be Unified" ) ;
735+ assert_eq ! ( ( clidr >> 24 ) & 0x7 , 2 , "Mock LoC should be 2" ) ;
736+ }
737+
738+ #[ test]
739+ fn test_merge_clidr ( ) {
740+ // CLIDR_EL1 layout:
741+ // [20:0] Ctype1..Ctype7 (7 × 3 bits)
742+ // [23:21] LoUIS
743+ // [26:24] LoC
744+ // [29:27] LoUU
745+ // [32:30] ICB
746+ // [46:33] Ttype1..Ttype7
747+ //
748+ // merge_clidr replaces only Ctype [20:0] and LoC [26:24] from sysfs,
749+ // preserving LoUIS, LoUU, ICB, and Ttype from current.
750+
751+ // current: LoUU=2 [29:27], LoUIS=1 [23:21], ICB=1 [32:30]
752+ // Ctype1=Unified(4) [2:0], LoC=1 [26:24]
753+ let current: u64 = ( 1 << 30 ) // ICB=1
754+ | ( 2 << 27 ) // LoUU=2
755+ | ( 1 << 24 ) // LoC=1
756+ | ( 1 << 21 ) // LoUIS=1
757+ | 4 ; // Ctype1=Unified
758+ // sysfs: Ctype1=Separate(3), Ctype2=Unified(4), Ctype3=Unified(4), LoC=3
759+ let sysfs: u64 = ( 3 << 24 ) | ( 4 << 6 ) | ( 4 << 3 ) | 3 ;
760+ let merged = merge_clidr ( current, sysfs) ;
761+
762+ // Ctype and LoC should come from sysfs
763+ assert_eq ! ( merged & 0x001F_FFFF , sysfs & 0x001F_FFFF , "Ctype mismatch" ) ;
764+ assert_eq ! ( ( merged >> 24 ) & 0x7 , 3 , "LoC should be 3 from sysfs" ) ;
765+ // LoUIS, LoUU, ICB should be preserved from current
766+ assert_eq ! ( ( merged >> 21 ) & 0x7 , 1 , "LoUIS should be preserved" ) ;
767+ assert_eq ! ( ( merged >> 27 ) & 0x7 , 2 , "LoUU should be preserved" ) ;
768+ assert_eq ! ( ( merged >> 30 ) & 0x7 , 1 , "ICB should be preserved" ) ;
769+
770+ // When current == sysfs in the replaced region, merge is identity
771+ let current = 0x0000_0000_0300_0123_u64 ;
772+ let sysfs = 0x0000_0000_0300_0123_u64 ;
773+ assert_eq ! ( merge_clidr( current, sysfs) , current) ;
774+ }
579775}
0 commit comments