diff options
Diffstat (limited to 'src/cpu/x86/tsc')
-rw-r--r-- | src/cpu/x86/tsc/Config.lb | 5 | ||||
-rw-r--r-- | src/cpu/x86/tsc/delay_tsc.c | 165 |
2 files changed, 170 insertions, 0 deletions
diff --git a/src/cpu/x86/tsc/Config.lb b/src/cpu/x86/tsc/Config.lb new file mode 100644 index 0000000000..07272ad9d7 --- /dev/null +++ b/src/cpu/x86/tsc/Config.lb @@ -0,0 +1,5 @@ +uses CONFIG_UDELAY_TSC +uses CONFIG_TSC_X86RDTSC_CALIBRATE_WITH_TIMER2 + +default CONFIG_TSC_X86RDTSC_CALIBRATE_WITH_TIMER2=0 +if CONFIG_UDELAY_TSC object delay_tsc.o end diff --git a/src/cpu/x86/tsc/delay_tsc.c b/src/cpu/x86/tsc/delay_tsc.c new file mode 100644 index 0000000000..c7c431baac --- /dev/null +++ b/src/cpu/x86/tsc/delay_tsc.c @@ -0,0 +1,165 @@ +#include <console/console.h> +#include <arch/io.h> +#include <cpu/x86/msr.h> +#include <cpu/x86/tsc.h> +#include <smp/spinlock.h> +#include <delay.h> + +static unsigned long clocks_per_usec; + +#if (CONFIG_TSC_X86RDTSC_CALIBRATE_WITH_TIMER2 == 1) +#define CLOCK_TICK_RATE 1193180U /* Underlying HZ */ + +/* ------ Calibrate the TSC ------- + * Too much 64-bit arithmetic here to do this cleanly in C, and for + * accuracy's sake we want to keep the overhead on the CTC speaker (channel 2) + * output busy loop as low as possible. We avoid reading the CTC registers + * directly because of the awkward 8-bit access mechanism of the 82C54 + * device. + */ + +#define CALIBRATE_INTERVAL ((20*CLOCK_TICK_RATE)/1000) /* 20ms */ +#define CALIBRATE_DIVISOR (20*1000) /* 20ms / 20000 == 1usec */ + +static unsigned long long calibrate_tsc(void) +{ + /* Set the Gate high, disable speaker */ + outb((inb(0x61) & ~0x02) | 0x01, 0x61); + + /* + * Now let's take care of CTC channel 2 + * + * Set the Gate high, program CTC channel 2 for mode 0, + * (interrupt on terminal count mode), binary count, + * load 5 * LATCH count, (LSB and MSB) to begin countdown. + */ + outb(0xb0, 0x43); /* binary, mode 0, LSB/MSB, Ch 2 */ + outb(CALIBRATE_INTERVAL & 0xff, 0x42); /* LSB of count */ + outb(CALIBRATE_INTERVAL >> 8, 0x42); /* MSB of count */ + + { + tsc_t start; + tsc_t end; + unsigned long count; + + start = rdtsc(); + count = 0; + do { + count++; + } while ((inb(0x61) & 0x20) == 0); + end = rdtsc(); + + /* Error: ECTCNEVERSET */ + if (count <= 1) + goto bad_ctc; + + /* 64-bit subtract - gcc just messes up with long longs */ + __asm__("subl %2,%0\n\t" + "sbbl %3,%1" + :"=a" (end.lo), "=d" (end.hi) + :"g" (start.lo), "g" (start.hi), + "0" (end.lo), "1" (end.hi)); + + /* Error: ECPUTOOFAST */ + if (end.hi) + goto bad_ctc; + + + /* Error: ECPUTOOSLOW */ + if (end.lo <= CALIBRATE_DIVISOR) + goto bad_ctc; + + return (end.lo + CALIBRATE_DIVISOR -1)/CALIBRATE_DIVISOR; + } + + /* + * The CTC wasn't reliable: we got a hit on the very first read, + * or the CPU was so fast/slow that the quotient wouldn't fit in + * 32 bits.. + */ +bad_ctc: + printk_err("bad_ctc\n"); + return 0; +} + +#else /* CONFIG_TSC_X86RDTSC_CALIBRATE_WITH_TIMER2 */ + +/* + * this is the "no timer2" version. + * to calibrate tsc, we get a TSC reading, then do 1,000,000 outbs to port 0x80 + * then we read TSC again, and divide the difference by 1,000,000 + * we have found on a wide range of machines that this gives us a a + * good microsecond value + * to +- 10%. On a dual AMD 1.6 Ghz box, it gives us .97 microseconds, and on a + * 267 Mhz. p5, it gives us 1.1 microseconds. + * also, since gcc now supports long long, we use that. + * also no unsigned long long / operator, so we play games. + * about the only thing you can do with long longs, it seems, + *is return them and assign them. + * (and do asm on them, yuck) + * so avoid all ops on long longs. + */ +static unsigned long long calibrate_tsc(void) +{ + unsigned long long start, end, delta; + unsigned long allones = (unsigned long) -1, result; + unsigned long count; + + start = rdtscll(); + // no udivdi3, dammit. + // so we count to 1<< 20 and then right shift 20 + for(count = 0; count < (1<<20); count ++) + outb(0x80, 0x80); + end = rdtscll(); + +#if 0 + // make delta be (endhigh - starthigh) + (endlow - startlow) + // but >> 20 + // do it this way to avoid gcc warnings. + start = tsc_start.hi; + start <<= 32; + start |= start.lo; + end = tsc_end.hi; + end <<= 32; + end |= tsc_end.lo; +#endif + delta = end - start; + // at this point we have a delta for 1,000,000 outbs. Now rescale for one microsecond. + delta >>= 20; + // save this for microsecond timing. + result = delta; + printk_spew("end %x:%x, start %x:%x\n", + endhigh, endlow, starthigh, startlow); + printk_spew("32-bit delta %d\n", (unsigned long) delta); + + printk_spew(__FUNCTION__ " 32-bit result is %d\n", result); + return delta; +} + + +#endif /* CONFIG_TSC_X86RDTSC_CALIBRATE_WITH_TIMER2*/ + +void init_timer(void) +{ + if (!clocks_per_usec) { + clocks_per_usec = calibrate_tsc(); + printk_info("clocks_per_usec: %u\n", clocks_per_usec); + } +} + +void udelay(unsigned us) +{ + unsigned long long count; + unsigned long long stop; + unsigned long long clocks; + + init_timer(); + clocks = us; + clocks *= clocks_per_usec; + count = rdtscll(); + stop = clocks + count; + while(stop > count) { + cpu_relax(); + count = rdtscll(); + } +} |