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
+78
-56
No files matched your search
+4
-3
@@ -27,11 +27,12 @@ static void doom_print_wrapper(const char* msg)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// doom_malloc_fn / doom_free_fn
|
||||
// Heap handle 0, same convention as video.c.
|
||||
// ---------------------------------------------------------------------------
|
||||
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)
|
||||
@@ -126,7 +127,7 @@ static void doom_exit_wrapper(int code)
|
||||
static char* doom_getenv_wrapper(const char* name)
|
||||
{
|
||||
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");
|
||||
return home;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ void KeInitializeScheduler(void) {
|
||||
__stosq((unsigned __int64 *)ctx, 0, sizeof(KCONTEXT) / 8);
|
||||
ctx->Rip = (uint64_t)IdleLoop;
|
||||
idle->SavedContext = ctx;
|
||||
KeInitThreadFpuContext(idle);
|
||||
|
||||
gIdleThread = idle;
|
||||
gCurrentThread = idle;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
THREAD_SavedContext EQU 88
|
||||
THREAD_TssRsp0 EQU 104
|
||||
THREAD_Teb EQU 112
|
||||
THREAD_FpuContext EQU 160
|
||||
TSS_RSP0_OFFSET EQU 4
|
||||
|
||||
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)
|
||||
; -------------------------------------------------------------------------
|
||||
KeSwitchContext PROC
|
||||
mov rax, [rcx + THREAD_FpuContext]
|
||||
fxsave64 [rax]
|
||||
|
||||
; Save non-volatile registers onto the current (outgoing) kernel stack.
|
||||
push rbx
|
||||
push rbp
|
||||
@@ -50,6 +54,9 @@ KeSwitchContext PROC
|
||||
mov r8, gTss
|
||||
mov qword ptr [r8 + TSS_RSP0_OFFSET], rax
|
||||
|
||||
mov rax, [rdx + THREAD_FpuContext]
|
||||
fxrstor64 [rax]
|
||||
|
||||
; Restore non-volatile registers of the incoming thread.
|
||||
pop r15
|
||||
pop r14
|
||||
|
||||
+23
-2
@@ -7,6 +7,9 @@
|
||||
#include <mm_virt.h>
|
||||
#include <ob_object.h>
|
||||
|
||||
#define FPU_FCW_DEFAULT 0x037Fu
|
||||
#define FPU_MXCSR_DEFAULT 0x1F80u
|
||||
|
||||
static uint32_t gNextThreadId = 1;
|
||||
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.
|
||||
uint64_t* teb = (uint64_t*)ustack;
|
||||
teb[0] = 0; // ExceptionList (GS:0x00)
|
||||
teb[1] = 0; // StackBase (GS:0x08)
|
||||
teb[2] = 0; // StackLimit (GS:0x10) - __chkstk will see 0 and safely exit
|
||||
teb[1] = userStackTop; // StackBase
|
||||
teb[2] = (uintptr_t)ustack; // StackLimit
|
||||
|
||||
thread->Teb = teb;
|
||||
|
||||
@@ -124,6 +127,7 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui
|
||||
owner->ThreadCount++;
|
||||
KeReleaseSpinlock(&owner->HandleLock, flags);
|
||||
|
||||
KeInitThreadFpuContext(thread);
|
||||
KeReadyThread(thread);
|
||||
*out = thread;
|
||||
return STATUS_SUCCESS;
|
||||
@@ -211,6 +215,7 @@ __declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg,
|
||||
|
||||
if (out) *out = thread;
|
||||
|
||||
KeInitThreadFpuContext(thread);
|
||||
KeReadyThread(thread);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
@@ -233,3 +238,19 @@ void KeUnblockThread(THREAD* 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.
|
||||
// Waiters block on this via SYS_WAIT_THREAD -> KeWaitForEvent.
|
||||
EVENT* ExitEvent;
|
||||
|
||||
void* FpuContext;
|
||||
uint8_t FpuStorage[528];
|
||||
} THREAD;
|
||||
|
||||
__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.
|
||||
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, "write ", 6) == 0) { cmd_write(cmd + 6); return 0; }
|
||||
|
||||
STARTUPINFOA si = { sizeof(si) }; PROCESS_INFORMATION pi;
|
||||
char cmdBuf[MAX_CMD]; strncpy(cmdBuf, cmd, MAX_CMD-1);
|
||||
if (CreateProcessA(NULL, cmdBuf, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
|
||||
PROCESS_INFORMATION pi;
|
||||
char cmdBuf[MAX_CMD];
|
||||
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);
|
||||
CloseHandle(pi.hProcess); CloseHandle(pi.hThread);
|
||||
} else {
|
||||
|
||||
+33
-48
@@ -1139,69 +1139,51 @@ __declspec(dllexport) void WakeAllConditionVariable(CONDITION_VARIABLE* cv) { (v
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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 {
|
||||
struct { DWORD LowPart; long HighPart; } u;
|
||||
long long QuadPart;
|
||||
} 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.
|
||||
// Returns 0 if the leaf is absent or the crystal frequency is not enumerated.
|
||||
// Attempt to read TSC frequency from CPUID leaf 0x15 (Modern Intel/AMD).
|
||||
static long long TscFreqFromCpuid(void) {
|
||||
int info[4];
|
||||
__cpuid(info, 0);
|
||||
if ((unsigned)info[0] < 0x15) return 0;
|
||||
|
||||
__cpuid(info, 0x15);
|
||||
unsigned denom = (unsigned)info[0]; // EAX – TSC/crystal denominator
|
||||
unsigned numer = (unsigned)info[1]; // EBX – TSC/crystal numerator
|
||||
unsigned crystal_hz = (unsigned)info[2]; // ECX – crystal clock in Hz
|
||||
unsigned denom = (unsigned)info[0];
|
||||
unsigned numer = (unsigned)info[1];
|
||||
unsigned crystal_hz = (unsigned)info[2];
|
||||
|
||||
if (!denom || !numer || !crystal_hz) return 0;
|
||||
return (long long)crystal_hz * numer / denom;
|
||||
}
|
||||
|
||||
// Calibrate TSC frequency by timing over two LAPIC-tick boundaries.
|
||||
// Each SysGetTick() unit = QUANTUM_MS ms; we measure across N_TICKS units
|
||||
// for a total gate time of N_TICKS * QUANTUM_MS ms.
|
||||
// Calibrate TSC against SysGetTick (1 unit = 1 microsecond).
|
||||
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 tsc0 = __rdtsc();
|
||||
|
||||
// Wait for N_TICKS more ticks.
|
||||
while ((SysGetTick() - t0) < N_TICKS) { _mm_pause(); }
|
||||
// Simple busy wait for the duration
|
||||
while ((SysGetTick() - t0) < CALIBRATION_US) {
|
||||
_mm_pause();
|
||||
}
|
||||
|
||||
uint64_t tsc1 = __rdtsc();
|
||||
uint64_t ticks_got = SysGetTick() - t0; // actual ticks elapsed (>= N_TICKS)
|
||||
uint64_t t1 = SysGetTick();
|
||||
uint64_t tsc1 = __rdtsc();
|
||||
|
||||
// ticks_got LAPIC ticks = ticks_got * QUANTUM_MS ms = ticks_got * QUANTUM_MS / 1000 s
|
||||
// TSC Hz = (tsc1-tsc0) / (ticks_got * QUANTUM_MS / 1000)
|
||||
// = (tsc1-tsc0) * 1000 / (ticks_got * QUANTUM_MS)
|
||||
return (long long)((tsc1 - tsc0) * 1000ULL / (ticks_got * (uint64_t)QUANTUM_MS));
|
||||
uint64_t delta_tsc = tsc1 - tsc0;
|
||||
uint64_t delta_us = t1 - t0;
|
||||
|
||||
// Hz = (Delta TSC / Delta Microseconds) * 1,000,000
|
||||
return (long long)(delta_tsc * 1000000ULL / delta_us);
|
||||
}
|
||||
|
||||
static long long TscFreqGet(void) {
|
||||
@@ -1220,8 +1202,7 @@ __declspec(dllexport) BOOL QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency)
|
||||
}
|
||||
|
||||
__declspec(dllexport) BOOL QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount) {
|
||||
// Ensure frequency is measured before the first counter read so that the
|
||||
// baseline TSC and the frequency come from the same calibration window.
|
||||
// Ensure calibration happens before the first read.
|
||||
TscFreqGet();
|
||||
if (lpPerformanceCount)
|
||||
lpPerformanceCount->QuadPart = (long long)__rdtsc();
|
||||
@@ -1229,15 +1210,19 @@ __declspec(dllexport) BOOL QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceC
|
||||
}
|
||||
|
||||
__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;
|
||||
// SysGetTick() units = QUANTUM_MS ms = QUANTUM_MS * 10000 * 100-ns intervals.
|
||||
unsigned long long ft = 133186080000000000ULL
|
||||
+ SysGetTick() * ((unsigned long long)QUANTUM_MS * 10000ULL);
|
||||
unsigned char* p = (unsigned char*)lpSystemTimeAsFileTime;
|
||||
for (int i = 0; i < 8; i++) p[i] = (unsigned char)(ft >> (i * 8));
|
||||
|
||||
// Baseline: 2024-01-01 00:00:00 UTC in 100-ns intervals
|
||||
const unsigned long long BASELINE_FT = 133485408000000000ULL;
|
||||
|
||||
// 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