/*++ Copyright (c) 1998 Microsoft Corporation Module Name: cpu.c Abstract: Read CPU specifics performance counters. Author: Scott Field (sfield) 24-Sep-98 --*/ #ifndef KMODE_RNG #include #include #include #include #else #include #include #endif // KMODE_RNG #include "cpu.h" unsigned int GatherCPUSpecificCountersPrivileged( IN unsigned char *pbCounterState, IN OUT unsigned long *pcbCounterState ); #define X86_CAPS_RDTSC 0x01 #define X86_CAPS_RDMSR 0x02 #define X86_CAPS_RDPMC 0x04 VOID X86_GetCapabilities( IN OUT BYTE *pbCapabilities ); VOID X86_ReadRDTSC( IN PLARGE_INTEGER prdtsc // RDTSC ); VOID X86_ReadRDMSR( IN PLARGE_INTEGER pc0, // counter0 IN PLARGE_INTEGER pc1 // counter1 ); VOID X86_ReadRDPMC( IN PLARGE_INTEGER pc0, // counter0 IN PLARGE_INTEGER pc1 // counter1 ); #ifdef KMODE_RNG #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, GatherCPUSpecificCounters) #pragma alloc_text(PAGE, GatherCPUSpecificCountersPrivileged) #ifdef _X86_ #pragma alloc_text(PAGE, X86_ReadRDTSC) #pragma alloc_text(PAGE, X86_ReadRDMSR) #pragma alloc_text(PAGE, X86_ReadRDPMC) #pragma alloc_text(PAGE, X86_GetCapabilities) #endif // _X86_ #endif // ALLOC_PRAGMA #endif // KMODE_RNG unsigned int GatherCPUSpecificCounters( IN unsigned char *pbCounterState, IN OUT unsigned long *pcbCounterState ) { #ifndef KMODE_RNG // // NT5 doesn't set CR4.PCE by default, so don't bother trying for usermode. // return FALSE; #else PAGED_CODE(); // // kernel mode version of library: just call the privileged routine directly. // user mode version of library: try privileged routine first, if it fails, use // device driver provided interface. // if( pbCounterState == NULL || pcbCounterState == NULL ) return FALSE; return GatherCPUSpecificCountersPrivileged( pbCounterState, pcbCounterState ); #endif } unsigned int GatherCPUSpecificCountersPrivileged( IN unsigned char *pbCounterState, IN OUT unsigned long *pcbCounterState ) /*++ we are at ring0 in kernel mode, so we can issue the privileges CPU instructions directly. Note that this routine also serves as the core code which is executed by the ksecdd.sys device driver for user mode clients. This call can also be made directly in usermode by certain CPUs, or when certain CPUs are configured to allow such calls from ring3. --*/ { #ifdef _X86_ PLARGE_INTEGER prdtsc; PLARGE_INTEGER pc0; PLARGE_INTEGER pc1; DWORD cbCounters; BYTE ProcessorCaps; #endif // _X86_ #ifdef KMODE_RNG PAGED_CODE(); #endif // KMODE_RNG #ifdef _X86_ cbCounters = 3 * sizeof( LARGE_INTEGER ) ; if( *pcbCounterState < cbCounters ) { *pcbCounterState = cbCounters; return FALSE; } cbCounters = 0; prdtsc = (PLARGE_INTEGER)pbCounterState; pc0 = prdtsc + 1; pc1 = pc0 + 1; // // make the initial determination about what the countertype is in this // system. // in theory, this could be cached, but things get a little complicated // in an SMP machine -- we'd have to track caps across all processors // to be correct. Since we don't really care about perf, just check the // caps every time. // __try { X86_GetCapabilities( &ProcessorCaps ); } __except( EXCEPTION_EXECUTE_HANDLER ) { ProcessorCaps = 0; ; // swallow exception } // // wrap the query in a try/except. This is just paranoia. Since we aren't // particularly interested in the values of the perf counters for the normal // (eg: perfmon) reasons, introducing the extra overhead of try/except is // of no relevance to us. // note that in the case of the p6, we could be calling this from usermode, // and CR4.PCE could be toggled which could cause subsequent AV(s). // In theory, the KMODE build could avoid the try/except, but there is a // remote possiblity the code above which makes the initial countertype // determination may not be supported on every installed processor in a // SMP machine. The cost of try/except is well worth avoiding the possibility // of a access violation / bluescreen in usermode vs. kernel mode respectively. // if( ProcessorCaps & X86_CAPS_RDTSC ) { __try { X86_ReadRDTSC( prdtsc ); cbCounters += sizeof( LARGE_INTEGER ); } __except( EXCEPTION_EXECUTE_HANDLER ) { ; // swallow exception. } } if( ProcessorCaps & X86_CAPS_RDPMC ) { __try { X86_ReadRDPMC( pc0, pc1 ); cbCounters += (2*sizeof( LARGE_INTEGER )); } __except( EXCEPTION_EXECUTE_HANDLER ) { ; // swallow exception. } } #ifdef KMODE_RNG else if ( ProcessorCaps & X86_CAPS_RDMSR ) { __try { X86_ReadRDMSR( pc0, pc1 ); cbCounters += (2*sizeof( LARGE_INTEGER )); } __except( EXCEPTION_EXECUTE_HANDLER ) { ; // swallow exception. } } #endif // KMODE_RNG *pcbCounterState = cbCounters; return TRUE; #else // _X86_ // // no non-x86 counter handling code at this time. // return FALSE; #endif } #ifdef _X86_ VOID X86_ReadRDTSC( IN PLARGE_INTEGER prdtsc // RDTSC ) { DWORD prdtscLow ; DWORD prdtscHigh ; #ifdef KMODE_RNG PAGED_CODE(); #endif // KMODE_RNG __asm { push eax push ecx push edx // RDTSC _emit 0fh _emit 31h mov prdtscLow, eax mov prdtscHigh, edx pop edx pop ecx pop eax } prdtsc->LowPart = prdtscLow; prdtsc->HighPart = prdtscHigh; } VOID X86_ReadRDMSR( IN PLARGE_INTEGER pc0, // counter0 IN PLARGE_INTEGER pc1 // counter1 ) { DWORD pc0Low ; DWORD pc0High ; DWORD pc1Low ; DWORD pc1High ; #ifdef KMODE_RNG PAGED_CODE(); #endif // KMODE_RNG __asm { push eax push ecx push edx // RDMSR counter0 mov ecx, 12h _emit 0fh _emit 32h mov pc0Low, eax mov pc0High, edx // RDMSR counter1 mov ecx, 13h _emit 0fh _emit 32h mov pc1Low, eax mov pc1High, edx pop edx pop ecx pop eax } pc0->LowPart = pc0Low; pc0->HighPart = pc0High; pc1->LowPart = pc1Low; pc1->HighPart = pc1High; } VOID X86_ReadRDPMC( IN PLARGE_INTEGER pc0, // counter0 IN PLARGE_INTEGER pc1 // counter1 ) { DWORD pc0Low ; DWORD pc0High ; DWORD pc1Low ; DWORD pc1High ; #ifdef KMODE_RNG PAGED_CODE(); #endif // KMODE_RNG __asm { push eax push ecx push edx // RDPMC executes from ring3 if CR4.PCE is set, otherwise, runs from ring0 only. // RDPMC counter0 xor ecx, ecx _emit 0fh _emit 33h mov pc0Low, eax mov pc0High, edx // RDPMC counter1 mov ecx, 1 _emit 0fh _emit 33h mov pc1Low, eax mov pc1High, edx pop edx pop ecx pop eax } pc0->LowPart = pc0Low; pc0->HighPart = pc0High; pc1->LowPart = pc1Low; pc1->HighPart = pc1High; } #if _MSC_FULL_VER >= 13008827 && defined(_M_IX86) #pragma warning(push) #pragma warning(disable:4731) // EBP modified with inline asm #endif VOID X86_GetCapabilities( IN OUT BYTE *pbCapabilities ) { DWORD dwLevels; DWORD dwStdFeatures; DWORD dwVersionInfo; DWORD Family; DWORD Model; #ifdef KMODE_RNG PAGED_CODE(); #endif // KMODE_RNG *pbCapabilities = 0; __asm { push eax push ecx push ebx push edx push edi push esi push ebp xor eax, eax _emit 0x0f _emit 0xa2 mov dwLevels, eax pop ebp pop esi pop edi pop edx pop ebx pop ecx pop eax } if( dwLevels == 0 ) return; // // try CPUID at level1 to get standard features. // __asm { push eax push ecx push ebx push edx push edi push esi push ebp mov eax, 1 _emit 0x0f _emit 0xa2 mov dwVersionInfo, eax mov dwStdFeatures, edx pop ebp pop esi pop edi pop edx pop ebx pop ecx pop eax } // // determine if RDTSC supported. // if( dwStdFeatures & 0x10 ) { *pbCapabilities |= X86_CAPS_RDTSC; } Model = (dwVersionInfo >> 4) & 0xf; Family = (dwVersionInfo >> 8) & 0xf; // AMD K6-2 model 8 proved buggy and left interrupts disabled during RDMSR #if 0 // // determine if RDMSR supported. // if( dwStdFeatures & 0x20 && (Model == 1 || Model == 2) ) { *pbCapabilities |= X86_CAPS_RDMSR; } // // extract family, > pentium (family5) supports RDPMC // if( Family > 5 ) { *pbCapabilities |= X86_CAPS_RDPMC; } #endif } #if _MSC_FULL_VER >= 13008827 #pragma warning(pop) #endif #endif // _X86_