From a37f5b158118324b554d82b3d139de2eb115bd34 Mon Sep 17 00:00:00 2001 From: Maksymilian Wilczynski Date: Tue, 5 May 2026 10:30:13 +0200 Subject: [PATCH] fix ps/2 mouse init, make Stall more precise, rename CurrentProcess to KeGetCurrentProcess, correct type about maneuvers, add file writing to win32 --- OVMF/OVMF_VARS_4M.fd | Bin 540672 -> 540672 bytes OVMF/OVMF_VARS_4M_d.fd | Bin 540672 -> 540672 bytes code/boot/main.c | 5 +-- code/hal/hal_main.c | 20 +++++++-- code/hal/hal_mouse.c | 49 +++++++++++++++++++--- code/hal/hal_timer.c | 53 +++++++++++++++++++----- code/kernel/ke_debug.c | 6 +-- code/kernel/ke_syscall.c | 20 +++------ code/kernel/kmain.c | 16 +++++--- code/kernel/mm_phys.c | 9 ++--- code/kernel/mm_phys.h | 3 +- code/shell/shell.c | 69 ++++++++++++++++++++++++++++--- code/video/video.c | 28 +++++++------ code/win32/kernel32.c | 85 +++++++++++++++++++++++++++++++++++---- 14 files changed, 288 insertions(+), 75 deletions(-) diff --git a/OVMF/OVMF_VARS_4M.fd b/OVMF/OVMF_VARS_4M.fd index 4eebbdac36c104a51b00427e95d7c6d2688f308b..256d01a1a367af7dadfd7209cc5b801a83e90935 100644 GIT binary patch delta 59 zcmZo@P;3CA7RJpJ@+DZdORz9q;@Tefk8u|(qtoVzK^M8&U-2<*f5pe#^`T9GrA>gf OO@OUUfPI+&hXDYRk`z|} delta 58 zcmZo@P;3CA7RD`1*O(`#sVYq_&{Nnh!NPQjtG!H~X?vMGbJvG$s!TlsZ2~N90<3KU MY;6MU%LF(K07Y97)c^nh diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index 62c1822754565bf63a4119d21b1ade6bc13bac15..c143d6eee343e429970e56f3d062463e5263aec3 100644 GIT binary patch delta 98 zcmZo@P;6*W+%Q3pF=F#Xz2{7fk((z5bqg>?L8xdTI21k(t?G)6E@5KL19(+t5hM=&iAOiKjQ3c<8SFl~U$ZUIJHAa#+e`CV@N syIjWY?{b-x!j}oKFl`SIV%fkuxuH0qO@XCNfwfJ6txbV_nF5Cb0Jtw`egFUf diff --git a/code/boot/main.c b/code/boot/main.c index 42281f8..900b28c 100644 --- a/code/boot/main.c +++ b/code/boot/main.c @@ -609,7 +609,7 @@ EFI_STATUS EFIAPI UefiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable } if (EFI_ERROR(ResolveImports(core[0].physBase, core[1].physBase, core[1].virtBase))) { - Print(L"Failed to resolve imports. System halted.\n"); + Print(L"Failed to resolve imports.\n"); while(true); } @@ -804,6 +804,5 @@ EFI_STATUS EFIAPI UefiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable KernelMain(bi); // Never returns. - _disable(); __halt(); - return EFI_SUCCESS; + _disable(); while (true) __halt(); } diff --git a/code/hal/hal_main.c b/code/hal/hal_main.c index 989c8d9..20fc730 100644 --- a/code/hal/hal_main.c +++ b/code/hal/hal_main.c @@ -4,6 +4,7 @@ #include #include #include +#include #include // Declared in other hal_*.c files: @@ -298,9 +299,8 @@ static void WalkAcpi(uintptr_t rsdpPhys) { // --------------------------------------------------------------------------- // HalShutdown - ACPI S5 soft power-off. Does not return. // --------------------------------------------------------------------------- -#include - static void HalShutdown(void) { + // Silence! Stop the world! _disable(); // SLP_EN (bit 13) | SLP_TYPx (bits 12:10) @@ -310,7 +310,7 @@ static void HalShutdown(void) { if (gPm1aCntBlk) __outword(gPm1aCntBlk, valA); if (gPm1bCntBlk) __outword(gPm1bCntBlk, valB); - // Firmware should have powered off by now. If it didn't, halt. + // Die! while (true) __halt(); } @@ -360,6 +360,20 @@ __declspec(dllexport) KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) { gApi.Version = HAL_API_VERSION; gInitialised = 1; + // This shouldn't be here but i have no other way. + // After i send 0xF4 and move the mouse the controller sends an interrupts that doesn't fire + // and because the CPU missed the IRQ 12 edge transition (cli) later when we expect it to fire, it doesn't + // leaving us with no mouse and no keyboard. The dreaded 8042 will hold the CLOCK line low forever. + // To combat this we just go about our day executing the rest of the init and just before the end + // of init we use a decaying poll to grab whatever the controller left for us ("Drain and Pray"). + for (int i = 0; i < 1000; i++) { + if (__inbyte(0x64) & 0x01) { + __inbyte(0x60); // Consume + i = 0; // Reset timeout if we are still getting data + } + gApi.Timer.Stall(1); // Micro-stalls + } + HalDbgPrint("HAL: initialisation complete.\n"); return STATUS_SUCCESS; } diff --git a/code/hal/hal_mouse.c b/code/hal/hal_mouse.c index 9b947fe..727b38c 100644 --- a/code/hal/hal_mouse.c +++ b/code/hal/hal_mouse.c @@ -27,6 +27,7 @@ #include #include #include +#include // -- PS/2 port addresses ------------------------------------------------------- #define PS2_DATA 0x60u @@ -74,10 +75,18 @@ static void Ps2WaitInput(void) { // Wait until the 8042's output buffer has data (bit 0 of status = 1). static void Ps2WaitOutput(void) { - int timeout = 100000; + int timeout = 5000000; while (!(__inbyte(PS2_STAT) & 0x01u) && --timeout) { /* spin */ } } +// Reads from PS2_DATA until the output buffer is empty +static void Ps2FlushOutput(void) { + int timeout = 100000; + while ((__inbyte(PS2_STAT) & 0x01u) && --timeout) { + __inbyte(PS2_DATA); + } +} + // Route the next write to the auxiliary (mouse) device rather than the keyboard. static void MouseSendByte(uint8_t byte) { Ps2WaitInput(); @@ -192,27 +201,55 @@ void HalMouseInitialize(HAL_MOUSE* out, int screenW, int screenH) { gMouseY = screenH / 2; // ----------------------------------------------------------------- - // Step 1: Enable aux interrupts in the 8042 command byte. - // - Bit 1 (0x02): enable auxiliary interrupt (drives IRQ 12). - // - Bit 5 (0x20): disables the aux clock when set; clear it. + // Step 0: Deafen the controller and flush + // ----------------------------------------------------------------- + Ps2WaitInput(); + __outbyte(PS2_CMD, 0xA7u); // Disable Aux port (ignores mouse) + Ps2WaitInput(); + __outbyte(PS2_CMD, 0xADu); // Disable Main port (ignores keyboard) + + // Now flush the buffer. Because the ports are disabled, we guarantee + // no new data can sneak in behind our backs. + Ps2FlushOutput(); + + // ----------------------------------------------------------------- + // Step 1: Safely configure the 8042 Command Byte // ----------------------------------------------------------------- Ps2WaitInput(); __outbyte(PS2_CMD, 0x20u); // read current command byte Ps2WaitOutput(); uint8_t cmd = __inbyte(PS2_DATA); + cmd |= 0x02u; // enable aux interrupt cmd &= ~0x20u; // clear "disable aux clock" + Ps2WaitInput(); __outbyte(PS2_CMD, 0x60u); // write command byte Ps2WaitInput(); __outbyte(PS2_DATA, cmd); + // Re-enable both ports now that config is safely written + Ps2WaitInput(); + __outbyte(PS2_CMD, 0xA8u); // Enable Aux port + Ps2WaitInput(); + __outbyte(PS2_CMD, 0xAEu); // Enable Main port + // ----------------------------------------------------------------- // Step 2: Reset mouse (0xFF). - // Mouse replies: ACK (0xFA), self-test pass (0xAA), ID (0x00). // ----------------------------------------------------------------- MouseSendByte(0xFFu); // reset - MouseRecvByte(); // 0xFA - ACK + + // The BIOS or an earlier phase might have left a movement packet + // partially in flight right as we re-enabled the port. + // We loop and discard anything that isn't the 0xFA ACK. + uint8_t ack; + int retries = 10; + do { + ack = MouseRecvByte(); + } while (ack != 0xFAu && --retries > 0); + + // Thanks to the increased Ps2WaitOutput timeout, we will actually + // wait long enough for the physical hardware self-test to finish here. MouseRecvByte(); // 0xAA - BAT passed MouseRecvByte(); // 0x00 - device ID diff --git a/code/hal/hal_timer.c b/code/hal/hal_timer.c index c308acf..0673f13 100644 --- a/code/hal/hal_timer.c +++ b/code/hal/hal_timer.c @@ -20,6 +20,9 @@ extern HAL_API gApi; static volatile uint32_t* gLapic = NULL; +static uint32_t gApicTicksPerUs = 0; +static uint64_t gHpetTicksPerUs = 0; +static volatile uint64_t* gHpet = NULL; #define LAPIC_REG(off) (*(volatile uint32_t *)((uint8_t *)gLapic + (off))) static volatile uint64_t gTicks = 0; @@ -37,17 +40,41 @@ static void TimerIsr(void) { static uint64_t QueryTicks(void) { return gTicks; } static void Stall(uint32_t us) { - uint64_t start = gTicks; - uint64_t ms = (us + 999) / 1000; + // Priority 1: Use HPET (Highest Precision) + if (gHpet && gHpetTicksPerUs > 0) { + uint64_t start = gHpet[30]; // Offset 0xF0 is the Main Counter + uint64_t delta = (uint64_t)us * gHpetTicksPerUs; + while ((gHpet[30] - start) < delta) { + _mm_pause(); // Hint to CPU we are in a spin-loop + } + return; + } - // Spin up to 100ms worth of IO delays waiting for ticks to advance. - // If gTicks doesn't move (interrupts off), fall back to pure IO delay. - uint32_t io_iters = (uint32_t)(ms * 1000); - for (uint32_t i = 0; i < io_iters; i++) { - if ((gTicks - start) >= ms) return; // interrupt-driven path + // Priority 2: Use APIC Timer (High Precision, but handles wrap-around) + if (gApicTicksPerUs > 0) { + uint32_t total_ticks = us * gApicTicksPerUs; + uint32_t last_val = LAPIC_REG(LAPIC_TIMER_CUR); + uint32_t elapsed = 0; + + while (elapsed < total_ticks) { + uint32_t current = LAPIC_REG(LAPIC_TIMER_CUR); + if (current < last_val) { + elapsed += (last_val - current); + } else if (current > last_val) { + // Handled the reset/wrap at the end of QUANTUM_MS + uint32_t init_val = LAPIC_REG(LAPIC_TIMER_INIT); + elapsed += (last_val + (init_val - current)); + } + last_val = current; + _mm_pause(); + } + return; + } + + // Priority 3: Fallback to Port 0x80 + for (uint32_t i = 0; i < us; i++) { __outbyte(0x80, 0); } - // If we exhausted the IO delay loop we've waited long enough anyway. } // Returns HPET ticks/second, or 0 if HPET is unavailable. @@ -62,7 +89,7 @@ static uint64_t HpetInit(uintptr_t hpetPhys) { if (!periodFs) return 0; // Enable the HPET main counter (General Configuration, offset 0x10, bit 0) - hpet[2] |= 1; // offset 0x10 / 8 = index 2 + hpet[2] |= 1; // offset 0x10 / 8 = index 2 return (uint64_t)1e15 / periodFs; // ticks per second } @@ -129,6 +156,14 @@ KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *o return STATUS_DEVICE_ERROR; } + gApicTicksPerUs = ticksPerMs / 1000; + + if (hpetPhys) { + gHpet = (volatile uint64_t*)gApi.PhysToVirt(hpetPhys); + uint64_t hpetHz = HpetInit(hpetPhys); + gHpetTicksPerUs = hpetHz / 1000000; + } + HalDbgPrint("HAL: APIC timer: %u ticks/ms\n", ticksPerMs); // Configure LAPIC timer in periodic mode diff --git a/code/kernel/ke_debug.c b/code/kernel/ke_debug.c index 5ca52b8..5ec84d7 100644 --- a/code/kernel/ke_debug.c +++ b/code/kernel/ke_debug.c @@ -32,9 +32,9 @@ __declspec(dllexport) void DbgPrint(const char* fmt, ...) { KeReleaseSpinlock(&sl, flags); } -// KePanic - Forces output even if debug was compiled out, then the macro halts. +// KePanic __declspec(dllexport) _Noreturn void KePanic(const char *fmt, ...) { - // We are done, go away + // Silence! Stop the world! _disable(); // Print a bright-red banner so the framebuffer is unmistakable. @@ -50,7 +50,7 @@ __declspec(dllexport) _Noreturn void KePanic(const char *fmt, ...) { vprintf(fmt, va); va_end(va); - // Halt it! + // Die! while (true) __halt(); } diff --git a/code/kernel/ke_syscall.c b/code/kernel/ke_syscall.c index 998904f..e0f7f7c 100644 --- a/code/kernel/ke_syscall.c +++ b/code/kernel/ke_syscall.c @@ -54,21 +54,13 @@ static const HAL_API* gHal; void KeSyscallSetHal(const HAL_API* hal) { gHal = hal; } -// --------------------------------------------------------------------------- -// Helper: get the current process, or NULL if unavailable. -// --------------------------------------------------------------------------- -static inline PROCESS* CurrentProcess(void) { - THREAD* t = KeGetCurrentThread(); - return t ? t->Owner : NULL; -} - // --------------------------------------------------------------------------- // Helper: look up a handle and type-check it in one call. // Bumps the refcount on success; caller must ObDereferenceObject when done. // Returns NULL if the handle is invalid or the type does not match. // --------------------------------------------------------------------------- static OBJECT* HandleToObject(uint32_t handle, OBJECT_TYPE expected) { - PROCESS* proc = CurrentProcess(); + PROCESS* proc = KeGetCurrentProcess(); if (!proc) return NULL; OBJECT* obj = KeLookupHandle(proc, handle); // bumps refcount if (!obj) return NULL; @@ -85,7 +77,7 @@ static OBJECT* HandleToObject(uint32_t handle, OBJECT_TYPE expected) { // On failure, drops the reference it would have stored. // --------------------------------------------------------------------------- static KSTATUS AllocHandleForCurrent(OBJECT* obj, uint32_t* handleOut) { - PROCESS* proc = CurrentProcess(); + PROCESS* proc = KeGetCurrentProcess(); if (!proc) return STATUS_FAILURE; KSTATUS s = KeAllocateHandle(proc, obj, handleOut); if (!KSUCCESS(s)) ObDereferenceObject(obj); @@ -126,7 +118,7 @@ static uint64_t SyscallExitThread(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallCreateThread(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a4; (void)a5; - PROCESS* proc = CurrentProcess(); + PROCESS* proc = KeGetCurrentProcess(); if (!proc) return STATUS_FAILURE; if (a2 >= PriorityCount) return STATUS_INVALID_PARAMETER; @@ -154,7 +146,7 @@ static uint64_t SyscallGetCurrentTid(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallGetCurrentPid(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - PROCESS* p = CurrentProcess(); + PROCESS* p = KeGetCurrentProcess(); return p ? p->ProcessId : 0; } @@ -448,7 +440,7 @@ static uint64_t SyscallQueueRecv(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallCloseHandle(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - PROCESS* proc = CurrentProcess(); + PROCESS* proc = KeGetCurrentProcess(); if (!proc) return STATUS_FAILURE; KeCloseHandle(proc, (uint32_t)a0); return STATUS_SUCCESS; @@ -733,7 +725,7 @@ static uint64_t SyscallCreateProcess(uint64_t a0, uint64_t a1, uint64_t a2, if (!KSUCCESS(s)) { ObDereferenceObject(&proc->Header); return s; } // Give the caller a handle to the new process so they can wait on it. - PROCESS* caller = CurrentProcess(); + PROCESS* caller = KeGetCurrentProcess(); if (!caller) { ObDereferenceObject(&proc->Header); return STATUS_FAILURE; } uint32_t handle; diff --git a/code/kernel/kmain.c b/code/kernel/kmain.c index bf67c2d..c5b2774 100644 --- a/code/kernel/kmain.c +++ b/code/kernel/kmain.c @@ -113,7 +113,7 @@ static void InitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) { continue; } - // memcpy manouvers + // memcpy maneuvers memcpy(path, "drivers\\", 8); memcpy(path + 8, runtimeKeys[i], len + 1); @@ -329,7 +329,7 @@ void SystemInitThread(void* arg) { DbgPrint("SystemInitThread: Failed to create watcher thread (%d) -- leaking proc ref.\n", ws); } } - + MmFreePool(args); } @@ -340,7 +340,10 @@ extern void KernelMain(BOOT_INFO* bootInfo) { // // Physical & Virtual Memory initialization // - MmInitializePhysical(&bootInfo->MemoryMap, bootInfo->PhysicalMapBase); + if (!MmInitializePhysical(&bootInfo->MemoryMap, bootInfo->PhysicalMapBase)) { + _disable(); while (true) __halt(); // there's nothing we can do + } + PHYSICAL_ADDRESS pml4 = (PHYSICAL_ADDRESS)__readcr3(); MmInitializeVirtual(pml4, 4 * 1024 * 1024 /* 4 MB initial pool */); @@ -368,11 +371,12 @@ extern void KernelMain(BOOT_INFO* bootInfo) { .RsdpPhysical = bootInfo->RsdpPhysical, .DebugEnabled = bootInfo->DebugEnabled }; + KSTATUS s = HalInitialize(&halParams); if (!KSUCCESS(s)) { - _disable(); - while (true) __halt(); // there's nothing we can do. + _disable(); while (true) __halt(); // there's nothing we can do. } + const HAL_API* hal = HalGetApi(); gKmainHal = hal; @@ -416,7 +420,7 @@ extern void KernelMain(BOOT_INFO* bootInfo) { DbgPrint("KernelMain: Handing over to scheduler...\n"); - // Ignite the system + // Ignite the system! _enable(); // The Idle Loop: The CPU rests here whenever all other threads are waiting on events. diff --git a/code/kernel/mm_phys.c b/code/kernel/mm_phys.c index 8280406..74a1478 100644 --- a/code/kernel/mm_phys.c +++ b/code/kernel/mm_phys.c @@ -6,9 +6,6 @@ // bootloader at PhysicalWindowBase (always 0xFFFF880000000000). // // Lock: a single spinlock guards Push/Pop. O(1) for both operations. -#include -#include -#include #include #include @@ -47,7 +44,7 @@ static void PushFrame(PHYSICAL_ADDRESS frame) { // --------------------------------------------------------------------------- // Initialisation // --------------------------------------------------------------------------- -void MmInitializePhysical(const MEMORY_MAP* map, uint64_t physicalWindowBase) { +bool MmInitializePhysical(const MEMORY_MAP* map, uint64_t physicalWindowBase) { gPhysBase = physicalWindowBase; // --- Phase 0: Count total usable frames so we can size the stack @@ -71,7 +68,7 @@ void MmInitializePhysical(const MEMORY_MAP* map, uint64_t physicalWindowBase) { } if (!stackPhys) { - while (true) { __halt(); } + return false; } gFrameStack.Stack = (PHYSICAL_ADDRESS *)PhysToVirt(stackPhys); @@ -99,6 +96,8 @@ void MmInitializePhysical(const MEMORY_MAP* map, uint64_t physicalWindowBase) { } } } + + return true; } // --------------------------------------------------------------------------- diff --git a/code/kernel/mm_phys.h b/code/kernel/mm_phys.h index e9328bd..c50936a 100644 --- a/code/kernel/mm_phys.h +++ b/code/kernel/mm_phys.h @@ -3,6 +3,7 @@ // Stack allocator over 4 KB frames, seeded from MEMORY_MAP. #include #include +#include #include #define PAGE_SIZE 4096ULL @@ -11,7 +12,7 @@ typedef uint64_t PHYSICAL_ADDRESS; // Called once by KernelMain with the MEMORY_MAP from BOOT_INFO. -void MmInitializePhysical(const MEMORY_MAP* map, +bool MmInitializePhysical(const MEMORY_MAP* map, uint64_t physicalWindowBase); // Allocate / free a single 4 KB frame. diff --git a/code/shell/shell.c b/code/shell/shell.c index fee49ce..617a6a1 100644 --- a/code/shell/shell.c +++ b/code/shell/shell.c @@ -21,7 +21,9 @@ typedef int BOOL; // File I/O constants #define GENERIC_READ 0x80000000L +#define GENERIC_WRITE 0x40000000L #define OPEN_EXISTING 3 +#define CREATE_ALWAYS 2 #define FILE_SHARE_READ 0x00000001 #define FILE_ATTRIBUTE_DIRECTORY 0x00000010 @@ -75,9 +77,9 @@ typedef struct _WIN32_FIND_DATAA { } WIN32_FIND_DATAA; BOOL CreateProcessA(const char* lpApp, char* lpCmdLine, - void* lpPA, void* lpTA, BOOL bInherit, - DWORD dwFlags, void* lpEnv, const char* lpDir, - STARTUPINFOA* lpSI, PROCESS_INFORMATION* lpPI); + void* lpPA, void* lpTA, BOOL bInherit, + DWORD dwFlags, void* lpEnv, const char* lpDir, + STARTUPINFOA* lpSI, PROCESS_INFORMATION* lpPI); DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMs); BOOL GetExitCodeProcess(HANDLE hProcess, DWORD* lpExitCode); BOOL CloseHandle(HANDLE hObject); @@ -89,10 +91,12 @@ BOOL FindClose(HANDLE hFindFile); BOOL SetCurrentDirectoryA(const char* lpPathName); BOOL DeleteFileA(const char* lpFileName); HANDLE CreateFileA(const char* lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, - void* lpSecurityAttributes, DWORD dwCreationDisposition, - DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); + void* lpSecurityAttributes, DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); BOOL ReadFile(HANDLE hFile, void* lpBuffer, DWORD nNumberOfBytesToRead, DWORD* lpNumberOfBytesRead, void* lpOverlapped); +BOOL WriteFile(HANDLE hFile, const void* lpBuffer, DWORD nNumberOfBytesToWrite, + DWORD* lpNumberOfBytesWritten, void* lpOverlapped); // --------------------------------------------------------------------------- // Global handles @@ -172,6 +176,51 @@ static char* trim(char* s) { // --------------------------------------------------------------------------- // Built-in commands // --------------------------------------------------------------------------- +static void cmd_help() { + print("Available commands:\n" + " help - Show this list\n" + " dir - List files in current directory\n" + " cd - Change directory\n" + " type - Display file contents\n" + " del - Delete a file\n" + " write - Create a file with text\n" + " exit - Close the shell\n"); +} + +static void cmd_write(const char* args) { + char filename[MAX_PATH]; + int i = 0; + + // Parse filename (up to first space) + while (args[i] != ' ' && args[i] != '\0' && i < MAX_PATH - 1) { + filename[i] = args[i]; + i++; + } + filename[i] = '\0'; + + if (args[i] == '\0' || args[i+1] == '\0') { + print("write: usage: write \n"); + return; + } + + const char* text = args + i + 1; // Content starts after the space + + HANDLE hFile = CreateFileA(filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) { + print("write: could not create file\n"); + return; + } + + DWORD written; + if (!WriteFile(hFile, text, (DWORD)strlen(text), &written, 0)) { + print("write: error writing to file\n"); + } else { + print("File written successfully.\n"); + } + + CloseHandle(hFile); +} + static void cmd_dir() { WIN32_FIND_DATAA fd; HANDLE hFind = FindFirstFileA("*", &fd); @@ -226,6 +275,11 @@ static int runCommand(const char* cmd) { return 1; } + if (strcmp(cmd, "help") == 0) { + cmd_help(); + return 0; + } + if (strcmp(cmd, "dir") == 0) { cmd_dir(); return 0; @@ -256,6 +310,11 @@ static int runCommand(const char* cmd) { return 0; } + if (strncmp(cmd, "write ", 6) == 0) { + cmd_write(cmd + 6); + return 0; + } + // --- External Process Execution --- STARTUPINFOA si; PROCESS_INFORMATION pi; diff --git a/code/video/video.c b/code/video/video.c index 92464b4..091f24f 100644 --- a/code/video/video.c +++ b/code/video/video.c @@ -102,17 +102,17 @@ int main() { CloseHandle(h); - HBINK bink = BinkOpen(buf, BINKFROMMEMORY); - if (!bink) { + HBINK b = BinkOpen(buf, BINKFROMMEMORY); + if (!b) { WriteConsoleA(h, "video: BinkOpen failed: ", 24, 0, 0); const char* err = BinkGetError(); - WriteConsoleA(h, err, strlen(err), 0, 0); + WriteConsoleA(h, err, (unsigned int)strlen(err), 0, 0); WriteConsoleA(h, "\n", 1, 0, 0); ExitProcess(1); } - uint32_t width = bink->Width; - uint32_t height = bink->Height; + uint32_t width = b->Width; + uint32_t height = b->Height; uint32_t* fb = HeapAlloc(0, 0, width * height * sizeof(uint32_t)); if (!fb) { @@ -131,19 +131,20 @@ int main() { bmi.bmiHeader.biCompression = BI_RGB; while (1) { - if (BinkWait(bink)) { + if (BinkWait(b)) { _mm_pause(); continue; } - BinkDoFrame(bink); + BinkDoFrame(b); - if (BinkShouldSkip(bink)) { - BinkNextFrame(bink); + if (BinkShouldSkip(b)) { + BinkNextFrame(b); + BinkDoFrame(b); continue; } - BinkCopyToBuffer(bink, fb, width * 4, height, 0, 0, + BinkCopyToBuffer(b, fb, width * 4, height, 0, 0, BINKSURFACE32); // Welcome to the GDI pipeline! @@ -163,10 +164,13 @@ int main() { EndPaint((HWND)0, &ps); - BinkNextFrame(bink); + if (b->FrameNum == b->Frames) + break; + + BinkNextFrame(b); } WriteConsoleA(h, "shell: goodbye\n", 15, 0, 0); - return 0; + ExitProcess(0); } diff --git a/code/win32/kernel32.c b/code/win32/kernel32.c index 030ba69..745c38c 100644 --- a/code/win32/kernel32.c +++ b/code/win32/kernel32.c @@ -23,6 +23,7 @@ #include #include #include +#include // --------------------------------------------------------------------------- // Syscall stubs (from syscall.dll, assembled from syscall_stubs.asm) @@ -155,7 +156,7 @@ __declspec(dllexport) void OutputDebugStringA(const char* str) { // Timer // --------------------------------------------------------------------------- __declspec(dllexport) DWORD GetTickCount(void) { - return (DWORD)SysGetTick(); + return (DWORD)SysGetTick() * 10; } // --------------------------------------------------------------------------- @@ -1041,35 +1042,103 @@ __declspec(dllexport) void WakeAllConditionVariable(CONDITION_VARIABLE* cv) { (v // --------------------------------------------------------------------------- // Timing & Performance Counters -// We report a 1 MHz counter so QPC / QPF is consistent with GetTickCount. +// +// 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; -#define QPC_FREQUENCY 1000000LL // 1 MHz +static volatile long long gTscFreq = 0; // TSC ticks per second; 0 = uninitialised + +// Attempt to read TSC frequency from CPUID leaf 0x15. +// Returns 0 if the leaf is absent or the crystal frequency is not enumerated. +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 + + 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. +static long long TscFreqCalibrate(void) { + const uint64_t N_TICKS = 10; // 10 * 10 ms = 100 ms gate time + + // 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(); + + // Wait for N_TICKS more ticks. + while ((SysGetTick() - t0) < N_TICKS) { _mm_pause(); } + + 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 + // 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)); +} + +static long long TscFreqGet(void) { + if (gTscFreq) return gTscFreq; + + long long f = TscFreqFromCpuid(); + if (!f) f = TscFreqCalibrate(); + + gTscFreq = f; + return f; +} __declspec(dllexport) BOOL QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency) { - if (lpFrequency) lpFrequency->QuadPart = QPC_FREQUENCY; + if (lpFrequency) lpFrequency->QuadPart = TscFreqGet(); return TRUE; } __declspec(dllexport) BOOL QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount) { - // SysGetTick returns milliseconds; scale to microseconds for 1 MHz QPC. + // Ensure frequency is measured before the first counter read so that the + // baseline TSC and the frequency come from the same calibration window. + TscFreqGet(); if (lpPerformanceCount) - lpPerformanceCount->QuadPart = (long long)SysGetTick() * 1000LL; + lpPerformanceCount->QuadPart = (long long)__rdtsc(); return TRUE; } __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 from 1601 in 100ns units: 133186080000000000 + // 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 - + (unsigned long long)SysGetTick() * 10000ULL; + + 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)); }