Add TSC emulation layer for 386/486 CPUs.
authorKevin O'Connor <kevin@koconnor.net>
Sun, 29 Jan 2012 19:15:14 +0000 (14:15 -0500)
committerKevin O'Connor <kevin@koconnor.net>
Thu, 2 Feb 2012 01:42:51 +0000 (20:42 -0500)
Original patch from Rudolf Marek.

Signed-off-by: Rudolf Marek <r.marek@assembler.cz>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
src/biosvar.h
src/clock.c
src/util.h

index ad791ab66474c01e8c2ac6fd948d476dca68e353..b6f7174c59dd7b3ffd739c1ff85f45cd172ce91c 100644 (file)
@@ -239,6 +239,10 @@ struct extended_bios_data_area_s {
 
     u16 boot_sequence;
 
+    /* TSC emulation timekeepers */
+    u64 tsc_8254;
+    int last_tsc_8254;
+
     // Stack space available for code that needs it.
     u8 extra_stack[512] __aligned(8);
 } PACKED;
index fcdc698bb15b8c9a0cad8a950312fa1b3b35e965..e8a48a13c1733bdd20b3f176dc829eeaabc1a6df 100644 (file)
 #define PM_MODE5 (5<<1)
 #define PM_CNT_BINARY (0<<0)
 #define PM_CNT_BCD    (1<<0)
+#define PM_READ_COUNTER0 (1<<1)
+#define PM_READ_COUNTER1 (1<<2)
+#define PM_READ_COUNTER2 (1<<3)
+#define PM_READ_STATUSVALUE (0<<4)
+#define PM_READ_VALUE       (1<<4)
+#define PM_READ_STATUS      (2<<4)
 
 
 /****************************************************************
 #define CALIBRATE_COUNT 0x800   // Approx 1.7ms
 
 u32 cpu_khz VAR16VISIBLE;
+u8 no_tsc VAR16VISIBLE;
 
 static void
 calibrate_tsc(void)
 {
+    u32 eax, ebx, ecx, edx, cpuid_features = 0;
+    cpuid(0, &eax, &ebx, &ecx, &edx);
+    if (eax > 0)
+        cpuid(1, &eax, &ebx, &ecx, &cpuid_features);
+
+    if (!(cpuid_features & CPUID_TSC)) {
+        SET_GLOBAL(no_tsc, 1);
+        SET_GLOBAL(cpu_khz, PIT_TICK_RATE / 1000);
+        dprintf(3, "386/486 class CPU. Using TSC emulation\n");
+        return;
+    }
+
     // Setup "timer2"
     u8 orig = inb(PORT_PS2_CTRLB);
     outb((orig & ~PPCB_SPKR) | PPCB_T2GATE, PORT_PS2_CTRLB);
@@ -89,10 +108,43 @@ calibrate_tsc(void)
     dprintf(1, "CPU Mhz=%u\n", hz / 1000000);
 }
 
+static u64
+emulate_tsc(void)
+{
+    int cnt, d;
+    u16 ebda_seg = get_ebda_seg();
+    u64 ret;
+    /* read timer 0 current count */
+    ret = GET_EBDA2(ebda_seg, tsc_8254);
+    /* readback mode has slightly shifted registers, works on all 8254, readback PIT0 latch */
+    outb(PM_SEL_READBACK | PM_READ_VALUE | PM_READ_COUNTER0, PORT_PIT_MODE);
+    cnt = (inb(PORT_PIT_COUNTER0) | (inb(PORT_PIT_COUNTER0) << 8));
+    d = GET_EBDA2(ebda_seg, last_tsc_8254) - cnt;
+    /* Determine the ticks count from last invocation of this function */
+    ret += (d > 0) ? d : (PIT_TICK_INTERVAL + d);
+    SET_EBDA2(ebda_seg, last_tsc_8254, cnt);
+    SET_EBDA2(ebda_seg, tsc_8254, ret);
+    return ret;
+}
+
+static u64
+get_tsc(void)
+{
+    if (unlikely(GET_GLOBAL(no_tsc)))
+        return emulate_tsc();
+    return rdtscll();
+}
+
+int
+check_tsc(u64 end)
+{
+    return (s64)(get_tsc() - end) > 0;
+}
+
 static void
 tscdelay(u64 diff)
 {
-    u64 start = rdtscll();
+    u64 start = get_tsc();
     u64 end = start + diff;
     while (!check_tsc(end))
         cpu_relax();
@@ -101,7 +153,7 @@ tscdelay(u64 diff)
 static void
 tscsleep(u64 diff)
 {
-    u64 start = rdtscll();
+    u64 start = get_tsc();
     u64 end = start + diff;
     while (!check_tsc(end))
         yield();
@@ -132,13 +184,13 @@ u64
 calc_future_tsc(u32 msecs)
 {
     u32 khz = GET_GLOBAL(cpu_khz);
-    return rdtscll() + ((u64)khz * msecs);
+    return get_tsc() + ((u64)khz * msecs);
 }
 u64
 calc_future_tsc_usec(u32 usecs)
 {
     u32 khz = GET_GLOBAL(cpu_khz);
-    return rdtscll() + ((u64)(khz/1000) * usecs);
+    return get_tsc() + ((u64)(khz/1000) * usecs);
 }
 
 
index 2c5f7ebc37931efe2b4fba97c602019aed1d4cf7..70d3c4c3271ff468925ae515f54c5dfb3ebbdc44 100644 (file)
@@ -50,6 +50,7 @@ static inline void wbinvd(void)
     asm volatile("wbinvd": : :"memory");
 }
 
+#define CPUID_TSC (1 << 4)
 #define CPUID_MSR (1 << 5)
 #define CPUID_APIC (1 << 9)
 #define CPUID_MTRR (1 << 12)
@@ -326,9 +327,7 @@ void lpt_setup(void);
 // clock.c
 #define PIT_TICK_RATE 1193180   // Underlying HZ of PIT
 #define PIT_TICK_INTERVAL 65536 // Default interval for 18.2Hz timer
-static inline int check_tsc(u64 end) {
-    return (s64)(rdtscll() - end) > 0;
-}
+int check_tsc(u64 end);
 void timer_setup(void);
 void ndelay(u32 count);
 void udelay(u32 count);