save fpu state properly-ish, add a guard to doom_malloc_wrapper, fix QueryPerformance* and GetSystemTimeAsFileTime
This commit is contained in:
1 parent
def649b995
commit
ddb5d5f5d8
8 files changed
+77
-55
No files matched your search
Binary file not shown.
+4
-3
@@ -27,11 +27,12 @@ static void doom_print_wrapper(const char* msg)
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// doom_malloc_fn / doom_free_fn
|
// doom_malloc_fn / doom_free_fn
|
||||||
// Heap handle 0, same convention as video.c.
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
static void* doom_malloc_wrapper(int size)
|
static void* doom_malloc_wrapper(int size)
|
||||||
{
|
{
|
||||||
return HeapAlloc(0, HEAP_ZERO_MEMORY, size);
|
void* p = HeapAlloc(0, HEAP_ZERO_MEMORY, size);
|
||||||
|
if (!p) ExitProcess(GetLastError());
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void doom_free_wrapper(void* ptr)
|
static void doom_free_wrapper(void* ptr)
|
||||||
@@ -126,7 +127,7 @@ static void doom_exit_wrapper(int code)
|
|||||||
static char* doom_getenv_wrapper(const char* name)
|
static char* doom_getenv_wrapper(const char* name)
|
||||||
{
|
{
|
||||||
if (strcmp(name, "HOME") == 0) {
|
if (strcmp(name, "HOME") == 0) {
|
||||||
static char home[] = "fs0:\\";
|
static char home[] = "c:";
|
||||||
doom_print_wrapper("getenv: Returning HOME="); doom_print_wrapper(home); doom_print_wrapper("\n");
|
doom_print_wrapper("getenv: Returning HOME="); doom_print_wrapper(home); doom_print_wrapper("\n");
|
||||||
return home;
|
return home;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ void KeInitializeScheduler(void) {
|
|||||||
__stosq((unsigned __int64 *)ctx, 0, sizeof(KCONTEXT) / 8);
|
__stosq((unsigned __int64 *)ctx, 0, sizeof(KCONTEXT) / 8);
|
||||||
ctx->Rip = (uint64_t)IdleLoop;
|
ctx->Rip = (uint64_t)IdleLoop;
|
||||||
idle->SavedContext = ctx;
|
idle->SavedContext = ctx;
|
||||||
|
KeInitThreadFpuContext(idle);
|
||||||
|
|
||||||
gIdleThread = idle;
|
gIdleThread = idle;
|
||||||
gCurrentThread = idle;
|
gCurrentThread = idle;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
THREAD_SavedContext EQU 88
|
THREAD_SavedContext EQU 88
|
||||||
THREAD_TssRsp0 EQU 104
|
THREAD_TssRsp0 EQU 104
|
||||||
THREAD_Teb EQU 112
|
THREAD_Teb EQU 112
|
||||||
|
THREAD_FpuContext EQU 160
|
||||||
TSS_RSP0_OFFSET EQU 4
|
TSS_RSP0_OFFSET EQU 4
|
||||||
|
|
||||||
EXTERN gTss:QWORD ; address of the single TSS, exported from ke_idt.c
|
EXTERN gTss:QWORD ; address of the single TSS, exported from ke_idt.c
|
||||||
@@ -20,6 +21,9 @@ EXTERN gTss:QWORD ; address of the single TSS, exported from ke_idt.c
|
|||||||
; RCX = from, RDX = to (Microsoft x64)
|
; RCX = from, RDX = to (Microsoft x64)
|
||||||
; -------------------------------------------------------------------------
|
; -------------------------------------------------------------------------
|
||||||
KeSwitchContext PROC
|
KeSwitchContext PROC
|
||||||
|
mov rax, [rcx + THREAD_FpuContext]
|
||||||
|
fxsave64 [rax]
|
||||||
|
|
||||||
; Save non-volatile registers onto the current (outgoing) kernel stack.
|
; Save non-volatile registers onto the current (outgoing) kernel stack.
|
||||||
push rbx
|
push rbx
|
||||||
push rbp
|
push rbp
|
||||||
@@ -50,6 +54,9 @@ KeSwitchContext PROC
|
|||||||
mov r8, gTss
|
mov r8, gTss
|
||||||
mov qword ptr [r8 + TSS_RSP0_OFFSET], rax
|
mov qword ptr [r8 + TSS_RSP0_OFFSET], rax
|
||||||
|
|
||||||
|
mov rax, [rdx + THREAD_FpuContext]
|
||||||
|
fxrstor64 [rax]
|
||||||
|
|
||||||
; Restore non-volatile registers of the incoming thread.
|
; Restore non-volatile registers of the incoming thread.
|
||||||
pop r15
|
pop r15
|
||||||
pop r14
|
pop r14
|
||||||
|
|||||||
+23
-2
@@ -7,6 +7,9 @@
|
|||||||
#include <mm_virt.h>
|
#include <mm_virt.h>
|
||||||
#include <ob_object.h>
|
#include <ob_object.h>
|
||||||
|
|
||||||
|
#define FPU_FCW_DEFAULT 0x037Fu
|
||||||
|
#define FPU_MXCSR_DEFAULT 0x1F80u
|
||||||
|
|
||||||
static uint32_t gNextThreadId = 1;
|
static uint32_t gNextThreadId = 1;
|
||||||
extern void KeThreadEntry(void);
|
extern void KeThreadEntry(void);
|
||||||
|
|
||||||
@@ -80,8 +83,8 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui
|
|||||||
// offset 0x10 is StackLimit, which __chkstk checks.
|
// offset 0x10 is StackLimit, which __chkstk checks.
|
||||||
uint64_t* teb = (uint64_t*)ustack;
|
uint64_t* teb = (uint64_t*)ustack;
|
||||||
teb[0] = 0; // ExceptionList (GS:0x00)
|
teb[0] = 0; // ExceptionList (GS:0x00)
|
||||||
teb[1] = 0; // StackBase (GS:0x08)
|
teb[1] = userStackTop; // StackBase
|
||||||
teb[2] = 0; // StackLimit (GS:0x10) - __chkstk will see 0 and safely exit
|
teb[2] = (uintptr_t)ustack; // StackLimit
|
||||||
|
|
||||||
thread->Teb = teb;
|
thread->Teb = teb;
|
||||||
|
|
||||||
@@ -124,6 +127,7 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui
|
|||||||
owner->ThreadCount++;
|
owner->ThreadCount++;
|
||||||
KeReleaseSpinlock(&owner->HandleLock, flags);
|
KeReleaseSpinlock(&owner->HandleLock, flags);
|
||||||
|
|
||||||
|
KeInitThreadFpuContext(thread);
|
||||||
KeReadyThread(thread);
|
KeReadyThread(thread);
|
||||||
*out = thread;
|
*out = thread;
|
||||||
return STATUS_SUCCESS;
|
return STATUS_SUCCESS;
|
||||||
@@ -211,6 +215,7 @@ __declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg,
|
|||||||
|
|
||||||
if (out) *out = thread;
|
if (out) *out = thread;
|
||||||
|
|
||||||
|
KeInitThreadFpuContext(thread);
|
||||||
KeReadyThread(thread);
|
KeReadyThread(thread);
|
||||||
return STATUS_SUCCESS;
|
return STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
@@ -233,3 +238,19 @@ void KeUnblockThread(THREAD* thread) {
|
|||||||
|
|
||||||
KeReadyThread(thread);
|
KeReadyThread(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void KeInitThreadFpuContext(THREAD* thread)
|
||||||
|
{
|
||||||
|
uintptr_t raw =
|
||||||
|
(uintptr_t)thread->FpuStorage;
|
||||||
|
|
||||||
|
uintptr_t aligned =
|
||||||
|
(raw + 15) & ~(uintptr_t)15;
|
||||||
|
|
||||||
|
thread->FpuContext = (void*)aligned;
|
||||||
|
|
||||||
|
memset(thread->FpuContext, 0, 512);
|
||||||
|
|
||||||
|
*(uint16_t*)((uint8_t*)thread->FpuContext + 0x00) = 0x037F;
|
||||||
|
*(uint32_t*)((uint8_t*)thread->FpuContext + 0x18) = 0x1F80;
|
||||||
|
}
|
||||||
@@ -73,6 +73,9 @@ typedef struct THREAD {
|
|||||||
// Allocated in KeCreateThread, signaled in KeExitThread before teardown.
|
// Allocated in KeCreateThread, signaled in KeExitThread before teardown.
|
||||||
// Waiters block on this via SYS_WAIT_THREAD -> KeWaitForEvent.
|
// Waiters block on this via SYS_WAIT_THREAD -> KeWaitForEvent.
|
||||||
EVENT* ExitEvent;
|
EVENT* ExitEvent;
|
||||||
|
|
||||||
|
void* FpuContext;
|
||||||
|
uint8_t FpuStorage[528];
|
||||||
} THREAD;
|
} THREAD;
|
||||||
|
|
||||||
__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg,
|
__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg,
|
||||||
@@ -94,3 +97,5 @@ void KeUnblockThread(THREAD* thread);
|
|||||||
|
|
||||||
// Assembly stub - implemented in ke_switch.asm.
|
// Assembly stub - implemented in ke_switch.asm.
|
||||||
void KeSwitchContext(THREAD* from, THREAD* to);
|
void KeSwitchContext(THREAD* from, THREAD* to);
|
||||||
|
|
||||||
|
void KeInitThreadFpuContext(THREAD* thread);
|
||||||
@@ -131,9 +131,11 @@ static int runCommand(const char* cmd) {
|
|||||||
if (strncmp(cmd, "type ", 5) == 0) { cmd_type(cmd + 5); return 0; }
|
if (strncmp(cmd, "type ", 5) == 0) { cmd_type(cmd + 5); return 0; }
|
||||||
if (strncmp(cmd, "write ", 6) == 0) { cmd_write(cmd + 6); return 0; }
|
if (strncmp(cmd, "write ", 6) == 0) { cmd_write(cmd + 6); return 0; }
|
||||||
|
|
||||||
STARTUPINFOA si = { sizeof(si) }; PROCESS_INFORMATION pi;
|
PROCESS_INFORMATION pi;
|
||||||
char cmdBuf[MAX_CMD]; strncpy(cmdBuf, cmd, MAX_CMD-1);
|
char cmdBuf[MAX_CMD];
|
||||||
if (CreateProcessA(NULL, cmdBuf, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
|
strncpy(cmdBuf, cmd, MAX_CMD - 1);
|
||||||
|
cmdBuf[MAX_CMD - 1] = '\0';
|
||||||
|
if (CreateProcessA(NULL, cmdBuf, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi)) {
|
||||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||||
CloseHandle(pi.hProcess); CloseHandle(pi.hThread);
|
CloseHandle(pi.hProcess); CloseHandle(pi.hThread);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+32
-47
@@ -1139,69 +1139,51 @@ __declspec(dllexport) void WakeAllConditionVariable(CONDITION_VARIABLE* cv) { (v
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Timing & Performance Counters
|
// Timing & Performance Counters
|
||||||
//
|
|
||||||
// We use the CPU's Time Stamp Counter (TSC) for QueryPerformanceCounter so
|
|
||||||
// that callers like Bink get a truly high-resolution, continuously-advancing
|
|
||||||
// clock. The TSC frequency is measured once on first use.
|
|
||||||
//
|
|
||||||
// Calibration strategy:
|
|
||||||
// 1. Try CPUID leaf 0x15 (TSC / Crystal-clock ratio). If the crystal
|
|
||||||
// frequency (ECX) is non-zero, compute TSC Hz = crystal_hz * EBX / EAX
|
|
||||||
// with no blocking at all.
|
|
||||||
// 2. Otherwise fall back to measuring TSC ticks over a known wall-clock
|
|
||||||
// interval supplied by SysGetTick(). SysGetTick() returns raw LAPIC
|
|
||||||
// interrupt counts; each unit represents QUANTUM_MS = 10 ms. We wait
|
|
||||||
// for two consecutive tick-boundary crossings and divide.
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
#define QUANTUM_MS 10 // must match hal_timer.c QUANTUM_MS
|
|
||||||
|
|
||||||
typedef union {
|
typedef union {
|
||||||
struct { DWORD LowPart; long HighPart; } u;
|
struct { DWORD LowPart; long HighPart; } u;
|
||||||
long long QuadPart;
|
long long QuadPart;
|
||||||
} LARGE_INTEGER;
|
} LARGE_INTEGER;
|
||||||
|
|
||||||
static volatile long long gTscFreq = 0; // TSC ticks per second; 0 = uninitialised
|
static volatile long long gTscFreq = 0;
|
||||||
|
|
||||||
// Attempt to read TSC frequency from CPUID leaf 0x15.
|
// Attempt to read TSC frequency from CPUID leaf 0x15 (Modern Intel/AMD).
|
||||||
// Returns 0 if the leaf is absent or the crystal frequency is not enumerated.
|
|
||||||
static long long TscFreqFromCpuid(void) {
|
static long long TscFreqFromCpuid(void) {
|
||||||
int info[4];
|
int info[4];
|
||||||
__cpuid(info, 0);
|
__cpuid(info, 0);
|
||||||
if ((unsigned)info[0] < 0x15) return 0;
|
if ((unsigned)info[0] < 0x15) return 0;
|
||||||
|
|
||||||
__cpuid(info, 0x15);
|
__cpuid(info, 0x15);
|
||||||
unsigned denom = (unsigned)info[0]; // EAX – TSC/crystal denominator
|
unsigned denom = (unsigned)info[0];
|
||||||
unsigned numer = (unsigned)info[1]; // EBX – TSC/crystal numerator
|
unsigned numer = (unsigned)info[1];
|
||||||
unsigned crystal_hz = (unsigned)info[2]; // ECX – crystal clock in Hz
|
unsigned crystal_hz = (unsigned)info[2];
|
||||||
|
|
||||||
if (!denom || !numer || !crystal_hz) return 0;
|
if (!denom || !numer || !crystal_hz) return 0;
|
||||||
return (long long)crystal_hz * numer / denom;
|
return (long long)crystal_hz * numer / denom;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calibrate TSC frequency by timing over two LAPIC-tick boundaries.
|
// Calibrate TSC against SysGetTick (1 unit = 1 microsecond).
|
||||||
// Each SysGetTick() unit = QUANTUM_MS ms; we measure across N_TICKS units
|
|
||||||
// for a total gate time of N_TICKS * QUANTUM_MS ms.
|
|
||||||
static long long TscFreqCalibrate(void) {
|
static long long TscFreqCalibrate(void) {
|
||||||
const uint64_t N_TICKS = 10; // 10 * 10 ms = 100 ms gate time
|
// We'll wait for 100ms (100,000 microseconds) to get a stable sample.
|
||||||
|
const uint64_t CALIBRATION_US = 100000;
|
||||||
|
|
||||||
// Synchronise to the leading edge of a tick.
|
|
||||||
uint64_t t = SysGetTick();
|
|
||||||
while (SysGetTick() == t) { _mm_pause(); }
|
|
||||||
|
|
||||||
uint64_t tsc0 = __rdtsc();
|
|
||||||
uint64_t t0 = SysGetTick();
|
uint64_t t0 = SysGetTick();
|
||||||
|
uint64_t tsc0 = __rdtsc();
|
||||||
|
|
||||||
// Wait for N_TICKS more ticks.
|
// Simple busy wait for the duration
|
||||||
while ((SysGetTick() - t0) < N_TICKS) { _mm_pause(); }
|
while ((SysGetTick() - t0) < CALIBRATION_US) {
|
||||||
|
_mm_pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t t1 = SysGetTick();
|
||||||
uint64_t tsc1 = __rdtsc();
|
uint64_t tsc1 = __rdtsc();
|
||||||
uint64_t ticks_got = SysGetTick() - t0; // actual ticks elapsed (>= N_TICKS)
|
|
||||||
|
|
||||||
// ticks_got LAPIC ticks = ticks_got * QUANTUM_MS ms = ticks_got * QUANTUM_MS / 1000 s
|
uint64_t delta_tsc = tsc1 - tsc0;
|
||||||
// TSC Hz = (tsc1-tsc0) / (ticks_got * QUANTUM_MS / 1000)
|
uint64_t delta_us = t1 - t0;
|
||||||
// = (tsc1-tsc0) * 1000 / (ticks_got * QUANTUM_MS)
|
|
||||||
return (long long)((tsc1 - tsc0) * 1000ULL / (ticks_got * (uint64_t)QUANTUM_MS));
|
// Hz = (Delta TSC / Delta Microseconds) * 1,000,000
|
||||||
|
return (long long)(delta_tsc * 1000000ULL / delta_us);
|
||||||
}
|
}
|
||||||
|
|
||||||
static long long TscFreqGet(void) {
|
static long long TscFreqGet(void) {
|
||||||
@@ -1220,8 +1202,7 @@ __declspec(dllexport) BOOL QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency)
|
|||||||
}
|
}
|
||||||
|
|
||||||
__declspec(dllexport) BOOL QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount) {
|
__declspec(dllexport) BOOL QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount) {
|
||||||
// Ensure frequency is measured before the first counter read so that the
|
// Ensure calibration happens before the first read.
|
||||||
// baseline TSC and the frequency come from the same calibration window.
|
|
||||||
TscFreqGet();
|
TscFreqGet();
|
||||||
if (lpPerformanceCount)
|
if (lpPerformanceCount)
|
||||||
lpPerformanceCount->QuadPart = (long long)__rdtsc();
|
lpPerformanceCount->QuadPart = (long long)__rdtsc();
|
||||||
@@ -1229,15 +1210,19 @@ __declspec(dllexport) BOOL QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceC
|
|||||||
}
|
}
|
||||||
|
|
||||||
__declspec(dllexport) void GetSystemTimeAsFileTime(void* lpSystemTimeAsFileTime) {
|
__declspec(dllexport) void GetSystemTimeAsFileTime(void* lpSystemTimeAsFileTime) {
|
||||||
// Return a fixed FILETIME representing 2024-01-01 00:00:00 UTC.
|
|
||||||
// FILETIME = 100-nanosecond intervals since 1601-01-01.
|
|
||||||
// 2024-01-01 offset: 133186080000000000 * 100 ns
|
|
||||||
if (!lpSystemTimeAsFileTime) return;
|
if (!lpSystemTimeAsFileTime) return;
|
||||||
// SysGetTick() units = QUANTUM_MS ms = QUANTUM_MS * 10000 * 100-ns intervals.
|
|
||||||
unsigned long long ft = 133186080000000000ULL
|
// Baseline: 2024-01-01 00:00:00 UTC in 100-ns intervals
|
||||||
+ SysGetTick() * ((unsigned long long)QUANTUM_MS * 10000ULL);
|
const unsigned long long BASELINE_FT = 133485408000000000ULL;
|
||||||
unsigned char* p = (unsigned char*)lpSystemTimeAsFileTime;
|
|
||||||
for (int i = 0; i < 8; i++) p[i] = (unsigned char)(ft >> (i * 8));
|
// SysGetTick() is in microseconds.
|
||||||
|
// 1 microsecond = 10 units of 100-nanoseconds.
|
||||||
|
unsigned long long now_us = SysGetTick();
|
||||||
|
unsigned long long ft = BASELINE_FT + (now_us * 10ULL);
|
||||||
|
|
||||||
|
// Copy the 64-bit value into the output buffer.
|
||||||
|
// We use memcpy to safely handle potential alignment issues with the void pointer.
|
||||||
|
memcpy(lpSystemTimeAsFileTime, &ft, sizeof(ft));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in new issue
Block a user