>From a4189bdcdcaed0df1b17e083dd0a00d681fcaade Mon Sep 17 00:00:00 2001
From: Rudolf Marek <r.marek@assembler.cz>
Date: Wed, 25 Jan 2012 17:42:02 +0100
Subject: [PATCH 2/2] Add TSC emulation layer, which enables to emulate the TSC counter on 386/486 CPUs.

Signed-off-by: Rudolf Marek <r.marek@assembler.cz>
---
 src/biosvar.h |    4 ++++
 src/clock.c   |   40 +++++++++++++++++++++++++++++++++++++++-
 src/util.h    |    9 ++++++++-
 3 files changed, 51 insertions(+), 2 deletions(-)

diff --git a/src/biosvar.h b/src/biosvar.h
index ad791ab..b6f7174 100644
--- a/src/biosvar.h
+++ b/src/biosvar.h
@@ -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;
diff --git a/src/clock.c b/src/clock.c
index fcdc698..eb65e90 100644
--- a/src/clock.c
+++ b/src/clock.c
@@ -57,6 +57,24 @@
 #define CALIBRATE_COUNT 0x800   // Approx 1.7ms
 
 u32 cpu_khz VAR16VISIBLE;
+u8 no_tsc VAR16VISIBLE;
+
+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 | 0x10 | 0x2, 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 void
 calibrate_tsc(void)
@@ -85,6 +103,8 @@ calibrate_tsc(void)
             , (u32)start, (u32)end, (u32)diff);
     u32 hz = diff * PIT_TICK_RATE / CALIBRATE_COUNT;
     SET_GLOBAL(cpu_khz, hz / 1000);
+    SET_EBDA(last_tsc_8254, 0);
+    SET_EBDA(tsc_8254, 0);
 
     dprintf(1, "CPU Mhz=%u\n", hz / 1000000);
 }
@@ -199,8 +219,26 @@ bcd2bin(u8 val)
 void
 timer_setup(void)
 {
+    u32 eax, ebx, ecx, edx, cpuid_features = 0;
+
     dprintf(3, "init timer\n");
-    calibrate_tsc();
+    cpuid(0, &eax, &ebx, &ecx, &edx);
+
+    if (eax > 0)
+        cpuid(1, &eax, &ebx, &ecx, &cpuid_features);
+
+    SET_GLOBAL(no_tsc, 0);
+
+    if (cpuid_features & CPUID_TSC) {
+        calibrate_tsc();
+    } else {
+        SET_GLOBAL(no_tsc, 1);
+        SET_GLOBAL(cpu_khz, PIT_TICK_RATE / 1000);
+        SET_EBDA(last_tsc_8254, 0);
+        SET_EBDA(tsc_8254, 0);
+        dprintf(3, "386/486 class CPU. Using TSC emulation\n");
+    }
+
     pit_setup();
 
     init_rtc();
diff --git a/src/util.h b/src/util.h
index 0c49eab..df428ac 100644
--- a/src/util.h
+++ b/src/util.h
@@ -120,10 +120,17 @@ static inline void wrmsr(u32 index, u64 val)
     asm volatile ("wrmsr" : : "c"(index), "A"(val));
 }
 
+u64 emulate_tsc(void);
+extern u8 no_tsc;
+
 static inline u64 rdtscll(void)
 {
     u64 val;
-    asm volatile("rdtsc" : "=A" (val));
+    if (unlikely(GET_GLOBAL(no_tsc))) {
+        val = emulate_tsc();
+    } else {
+        asm volatile("rdtsc" : "=A" (val));
+    }
     return val;
 }
 
-- 
1.7.1

