/*! @file i386/libsaio/freq_detect.c Copyright 2007-2009 David F. Elliott. All rights reserved. */ #include "libsaio.h" #include "freq_detect.h" #include "proc_reg.h" /* Decimal powers: */ #define kilo (1000ULL) #define Mega (kilo * kilo) #define Giga (kilo * Mega) #define Tera (kilo * Giga) #define Peta (kilo * Tera) // DFE: enable_PIT2 and disable_PIT2 come from older xnu /* * Enable or disable timer 2. * Port 0x61 controls timer 2: * bit 0 gates the clock, * bit 1 gates output to speaker. */ inline static void enable_PIT2(void) { /* Enable gate, disable speaker */ __asm__ volatile( " inb $0x61,%%al \n\t" " and $0xFC,%%al \n\t" /* & ~0x03 */ " or $1,%%al \n\t" " outb %%al,$0x61 \n\t" : : : "%al" ); } inline static void disable_PIT2(void) { /* Disable gate and output to speaker */ __asm__ volatile( " inb $0x61,%%al \n\t" " and $0xFC,%%al \n\t" /* & ~0x03 */ " outb %%al,$0x61 \n\t" : : : "%al" ); } // DFE: set_PIT2_mode0, poll_PIT2_gate, and measure_tsc_frequency are // roughly based on Linux code /* Set the 8254 channel 2 to mode 0 with the specified value. In mode 0, the counter will initially set its gate low when the timer expires. For this to be useful, you ought to set it high before calling this function. The enable_PIT2 function does this. */ static inline void set_PIT2_mode0(uint16_t value) { __asm__ volatile( " movb $0xB0,%%al \n\t" " outb %%al,$0x43 \n\t" " movb %%dl,%%al \n\t" " outb %%al,$0x42 \n\t" " movb %%dh,%%al \n\t" " outb %%al,$0x42" : : "d"(value) /*: no clobber */ ); } /* Returns the number of times the loop ran before the PIT2 signaled */ static inline unsigned long poll_PIT2_gate(void) { unsigned long count = 0; unsigned char nmi_sc_val; do { ++count; __asm__ volatile( "inb $0x61,%0" : "=a"(nmi_sc_val) /*:*/ /* no input */ /*:*/ /* no clobber */); } while( (nmi_sc_val & 0x20) == 0); return count; } // DFE: This constant comes from older xnu: #define CLKNUM 1193182 /* formerly 1193167 */ // DFE: These two constants come from Linux except CLOCK_TICK_RATE replaced with CLKNUM #define CALIBRATE_TIME_MSEC 30 /* 30 msecs */ #define CALIBRATE_LATCH \ ((CLKNUM * CALIBRATE_TIME_MSEC + 1000/2)/1000) /* * Measures the TSC frequency in Hz (64-bit) using the ACPI PM timer */ uint64_t measure_tsc_frequency(void); uint64_t measure_tsc_frequency(void) { uint64_t tscStart; uint64_t tscEnd; uint64_t tscDelta = 0xffffffffffffffffULL; unsigned long pollCount; uint64_t retval = 0; int i; /* Time how many TSC ticks elapse in 30 msec using the 8254 PIT * counter 2. We run this loop 3 times to make sure the cache * is hot and we take the minimum delta from all of the runs. * That is to say that we're biased towards measuring the minimum * number of TSC ticks that occur while waiting for the timer to * expire. That theoretically helps avoid inconsistencies when * running under a VM if the TSC is not virtualized and the host * steals time. The TSC is normally virtualized for VMware. */ for(i = 0; i < 3; ++i) { enable_PIT2(); set_PIT2_mode0(CALIBRATE_LATCH); tscStart = rdtsc64(); pollCount = poll_PIT2_gate(); tscEnd = rdtsc64(); /* The poll loop must have run at least a few times for accuracy */ if(pollCount <= 1) continue; /* The TSC must increment at LEAST once every millisecond. We * should have waited exactly 30 msec so the TSC delta should * be >= 30. Anything less and the processor is way too slow. */ if((tscEnd - tscStart) <= CALIBRATE_TIME_MSEC) continue; // tscDelta = min(tscDelta, (tscEnd - tscStart)) if( (tscEnd - tscStart) < tscDelta ) tscDelta = tscEnd - tscStart; } /* tscDelta is now the least number of TSC ticks the processor made in * a timespan of 0.03 s (e.g. 30 milliseconds) * Linux thus divides by 30 which gives the answer in kiloHertz because * 1 / ms = kHz. But we're xnu and most of the rest of the code uses * Hz so we need to convert our milliseconds to seconds. Since we're * dividing by the milliseconds, we simply multiply by 1000. */ /* Unlike linux, we're not limited to 32-bit, but we do need to take care * that we're going to multiply by 1000 first so we do need at least some * arithmetic headroom. For now, 32-bit should be enough. * Also unlike Linux, our compiler can do 64-bit integer arithmetic. */ if(tscDelta > (1ULL<<32)) retval = 0; else { retval = tscDelta * 1000 / 30; } disable_PIT2(); return retval; } #define MSR_FLEX_RATIO 0x194 #define MSR_PLATFORM_INFO 0x0ce #define bit(n) (1ULL << (n)) #define bitmask(h,l) ((bit(h)|(bit(h)-1)) & ~(bit(l)-1)) #define bitfield(x,h,l) (((x) & bitmask(h,l)) >> l) uint64_t fsbFrequency = 0; void determineFsbFrequency() { const char *val; // value int cnt; // length of value if(getValueForKey("fsbmhz", &val, &cnt)) { uint32_t fsbmhz; char valcpy[10]; bool gotvalue = (cnt < 10); if(gotvalue) { // Force NUL-term by making a copy with strlcpy strlcpy(valcpy, val, cnt+1); char *endval; fsbmhz = strtoul(valcpy, &endval, 10); gotvalue = (valcpy != endval && *endval == '\0'); } if(gotvalue) { switch(fsbmhz) { case 33: fsbFrequency = 33333333; break; case 66: fsbFrequency = 66666667; break; case 133: fsbFrequency = 133333333; break; case 166: fsbFrequency = 166666667; break; default: printf("Strange fsbmhz=%d specified.. assuming you mean it literally.\n", fsbmhz); /* Fall through */ case 100: case 200: fsbFrequency = fsbmhz * Mega; } } else fsbFrequency = 0; } else fsbFrequency = 0; if(fsbFrequency != 0) { printf("Using specified FSB Frequency %d.%06dMHz\n", (uint32_t)(fsbFrequency/Mega), (uint32_t)(fsbFrequency % Mega)); sleep(1); return; } uint64_t prfsts = 0; uint32_t tscGranularity; // NOTE: This is a flag for N + 1/2, not a flag for N/2. Xnu's variable name is stupid. uint32_t tscGranularityHalf; if( (cpuIsGenuineIntel(NULL) != 0) && (cpuIntelFamily() == 0x6) && (cpuIntelModel() == 0x1a) ) { // Swiped from xnu... uint32_t flex_ratio = 0; uint32_t flex_ratio_min = 0; uint32_t flex_ratio_max = 0; // uint64_t cpu_mhz; uint64_t msr_flex_ratio; uint64_t msr_platform_info; /* See if FLEX_RATIO is being used */ msr_flex_ratio = rdmsr64(MSR_FLEX_RATIO); msr_platform_info = rdmsr64(MSR_PLATFORM_INFO); flex_ratio_min = (uint32_t)bitfield(msr_platform_info, 47, 40); flex_ratio_max = (uint32_t)bitfield(msr_platform_info, 15, 8); /* No BIOS-programed flex ratio. Use hardware max as default */ tscGranularity = flex_ratio_max; if (msr_flex_ratio & bit(16)) { /* Flex Enabled: Use this MSR if less than max */ flex_ratio = (uint32_t)bitfield(msr_flex_ratio, 15, 8); if (flex_ratio < flex_ratio_max) tscGranularity = flex_ratio; } tscGranularityHalf = 0; printf("Determined Nehalem CPU:FSB multiplier to be %d\n", tscGranularity); } else if(rdmsr_safe(MSR_IA32_PERF_STS, &prfsts) == 0) { tscGranularity = bitfield(prfsts, 44, 40); tscGranularityHalf = (prfsts & bit(46)) != 0; printf("Determined CPU:FSB multiplier to be %d.%c\n", tscGranularity, tscGranularityHalf != 0 ? '5' : '0'); } else { printf("Failed to read CPU:FSB multiplier\n"); tscGranularity = 0; tscGranularityHalf = 0; } if(tscGranularity == 0 && tscGranularityHalf == 0) { printf("CPU says its multiplier is 0 which makes no sense. The kernel as shipped by\n"); printf("Apple will not support this and will cause the machine to reboot immediately.\n"); printf("Press 'y' to continue or use Ctrl+Alt+Delete to reboot\n"); while(getc() != 'y') ; fsbFrequency = 0; return; } printf("Measuring TSC frequency (e.g. CPU speed)\n"); uint64_t measuredTscFrequency = measure_tsc_frequency(); printf("CPU runs at %d.%06d MHz\n", (uint32_t)(measuredTscFrequency / Mega), (uint32_t)(measuredTscFrequency % Mega)); // Algebraic proof for the case when we have a half step of granularity. // Given: // mult = tscGranularity + 0.5 // fsb = tsc / mult // Then: // fsb = tsc / (tscGranularity + 0.5) // fsb = (2 / 2) * tsc / (tscGranularity + 0.5) // fsb = (2 * tsc) / (2 * (tscGranularity + 0.5)) // fsb = (2 * tsc) / (2 * tscGranularity + 1) if(tscGranularityHalf) fsbFrequency = 2 * measuredTscFrequency / (2 * tscGranularity + 1); else fsbFrequency = measuredTscFrequency / tscGranularity; printf("BUS must therefore run at %d.%06d MHz\n", (uint32_t)(fsbFrequency / Mega), (uint32_t)(fsbFrequency % Mega)); sleep(1); } void printFrequencyInfo() { printf("\n"); uint64_t measuredTscFrequency = measure_tsc_frequency(); printf("CPU runs at %d.%06d MHz\n", (uint32_t)(measuredTscFrequency / Mega), (uint32_t)(measuredTscFrequency % Mega)); printf("\n"); printf("(Press a key to continue...)"); getc(); }