Add TSC emulation layer for 386/486 CPUs.
Original patch from Rudolf Marek.
Signed-off-by: Rudolf Marek <r.marek@assembler.cz>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
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 @@
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..e8a48a1 100644
--- a/src/clock.c
+++ b/src/clock.c
@@ -48,6 +48,12 @@
#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)
/****************************************************************
@@ -57,10 +63,23 @@
#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 @@
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 @@
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 @@
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);
}
diff --git a/src/util.h b/src/util.h
index 2c5f7eb..70d3c4c 100644
--- a/src/util.h
+++ b/src/util.h
@@ -50,6 +50,7 @@
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 @@
// 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);