diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index 9d59754..0c33275 100644 Binary files a/OVMF/OVMF_VARS_4M_d.fd and b/OVMF/OVMF_VARS_4M_d.fd differ diff --git a/code/doom/doom.c b/code/doom/doom.c index dcad46a..c6bce2e 100644 --- a/code/doom/doom.c +++ b/code/doom/doom.c @@ -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; } diff --git a/code/kernel/ke_sched.c b/code/kernel/ke_sched.c index 02924e7..e3452e5 100644 --- a/code/kernel/ke_sched.c +++ b/code/kernel/ke_sched.c @@ -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; diff --git a/code/kernel/ke_switch.asm b/code/kernel/ke_switch.asm index 7b4f4ba..eebfc02 100644 --- a/code/kernel/ke_switch.asm +++ b/code/kernel/ke_switch.asm @@ -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 diff --git a/code/kernel/ke_thread.c b/code/kernel/ke_thread.c index 661e251..19d212e 100644 --- a/code/kernel/ke_thread.c +++ b/code/kernel/ke_thread.c @@ -7,6 +7,9 @@ #include #include +#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; +} diff --git a/code/kernel/ke_thread.h b/code/kernel/ke_thread.h index 75eb0dd..8500bf3 100644 --- a/code/kernel/ke_thread.h +++ b/code/kernel/ke_thread.h @@ -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); diff --git a/code/shell_win32/shell_win32.c b/code/shell_win32/shell_win32.c index 354f84e..b3ddc09 100644 --- a/code/shell_win32/shell_win32.c +++ b/code/shell_win32/shell_win32.c @@ -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 { diff --git a/code/win32/kernel32.c b/code/win32/kernel32.c index 7d6ccb8..aad7567 100644 --- a/code/win32/kernel32.c +++ b/code/win32/kernel32.c @@ -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)); } // ---------------------------------------------------------------------------