Get L3 cache associativity using cpuid

137 Views Asked by At

I'm very new to assembly and I didn't fully understand what was wrong. I need to write has_cpuid(), has_l3_chache(), get_l3_cache associativity() functions that will work on both Intel and AMD using __asm. has_cpuid works fine, but the others don't work either. What is the problem?

/* Function to check CPUID support */
bool has_cpuid() {
     int id_bit;
     __asm {
         pushfd             // Push EFLAGS on the stack
         pop eax            // Load EFLAGS flags into EAX
         mov ecx, eax       // Save a copy
         xor eax, 200000h   // Toggle the ID bit in EAX
         push eax           // Push new EFLAGS on the stack
         popfd              // Restore EFLAGS from the stack
         pushfd             // Save EFLAGS back on the stack
         pop eax            // Load EFLAGS flags into EAX again
         xor eax, ecx       // If the ID bit has changed, then the CPUID is supported
         shr eax, 21        // Move result to least significant bit
         mov id_bit,eax
     }
     return id_bit;
}

/* Function to check for L3 cache */
bool has_l3_cache() {
     int cacheLevel;
     __asm {
         mov eax, 0x80000006
         cpuid
         mov cacheLevel, ecx
     }
     return cacheLevel & (1 << 16);
}

/* Function to get L3 cache associativity */
int get_l3_cache_associativity() {
     int level = 3;         // L3 cache
     int result;
     __asm {
         mov eax, 4         // CPUID function for cache information
         mov ecx, level     // Cache level to check
         cpuid              // Call CPUID
         mov eax, ebx       // Associativity data is in EBX[31:22]
         shr eax, 22        // Move Associativity Data To LSB
         inc eax            // CPUID returns n-1, so increase by 1
         mov result, eax
     }
     return result;
}

int main() {
     if (!has_cpuid()) {
         printf("CPUID is not supported.\n");
         return 1;
     }

     if (has_l3_cache()) {
         printf("L3 Cache is present.\n");
         printf("L3 Cache associativity: %d\n", get_l3_cache_associativity());
     }
     else {
         printf("L3 Cache is not present.\n");
     }

     return 0;
}

has_l3_cache() outputs false despite I have L3 cache. get_l3_cache_associativity() also gives incorrect associativity.

1

There are 1 best solutions below

0
Brendan On

In general:

  • extremely old CPUs didn't support CPUID

  • very old CPUs didn't have L3 caches

  • older Intel CPUs used "CPUID leaf 0x00000002", which provides a bunch of 8-bit identifiers that you have to decode via. a lookup table (e.g. the byte 0xD2 means "Unified L3 cache, 2048 KiB, 4 way associativity, 64 byte cache lines"), and where some are ambiguous and depend on the CPU model (e.g. the byte 0x49 means "4 MiB L3 cache" if it's a Pentium 4 or "4 MiB L2 cache" if it's a Core 2).

  • newer Intel CPUs use "CPUID leaf 0x00000004", where the input value in ECX selects an entry to return information for. Note: "ECX = 3" does not necessarily mean "get information for L3 cache" and only means "get whatever entry #3 is which could be any kind of cache", and you have to check the returned value in EAX bits 0 to 4 to determine what kind of cache the information is for; which means to get a specific type of cache you need a loop to try each supported value of ECX until you find what you wanted. This also gives information about the number of CPUs sharing each cache. If you also care about extra cache characteristics (quality of service, class of service) you might also want info from "CPUID leaf 0x0000001B" (if available); and if you also want TLB info you might also need to care about "CPUID leaf 0x0000001B" (if available). Also, it's possible (for Intel CPUs) that this CPUID leaf (and all higher CPUID leaves) are disabled by an MSR to work around an ancient bug in Windows NT; so it'd be a potentially good idea to check for and correct for that (in Intel's "MISC_ENABLE" MSR, in newer CPUs but not ancient CPUs that won't support it).

  • older AMD CPUs used "CPUID leaf 0x80000005" (for L1 caches and TLBs) and maybe also "CPUID leaf 0x80000006" (for L2 and L3 caches and TLBs).

  • newer AMD CPUs use "CPUID leaf 0x0000001D", and is mostly similar to Intel's "CPUID leaf 0x00000004".

  • other vendors (Cyrix, VIA) mostly used Intel's old methods (and mostly didn't have L3 caches).

  • for all of the above you should worry about CPU errata and quirks. Just because CPUID says something doesn't mean that CPUID is correct. This mostly takes the form of code that uses the CPU's signature ("vendor:family:model:stepping") to determine if there's any corrections and then (if necessary) call CPU specific code to correct information after potentially wrong information was gathered. There are also potentially cases where a CPU supports something but CPUID doesn't, where the same errata/quirk handling code can also fill in any missing information.

Mostly; if you want code that detects cache information reliably (on all 80x86 CPUs) then you can expect to spend months writing code, checking errata lists and trying to test different code for different CPUs.

For your specific code; you're using a "newer AMD only" method to determine if L3 information exists (your has_l3_cache()) and then using a "newer Intel only" method to obtain information for something that might not be an L3 cache; so you can't expect it to work because its impossible for a CPU to be an AMD CPU and an Intel CPU at the same time. There are also other problems (e.g. assuming that CPUID leaves exist just because the CPUID instruction exists, without checking "max. supported level" from "CPUID leaf 0x00000000" or checking "max. supported extended level" from "CPUID leaf 0x80000000").