diff --git a/OVMF/OVMF_VARS_4M.fd b/OVMF/OVMF_VARS_4M.fd index a943fd5..89d5946 100644 Binary files a/OVMF/OVMF_VARS_4M.fd and b/OVMF/OVMF_VARS_4M.fd differ diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index 36b1d57..c57cc66 100644 Binary files a/OVMF/OVMF_VARS_4M_d.fd and b/OVMF/OVMF_VARS_4M_d.fd differ diff --git a/code/boot/uefi/main.c b/code/boot/uefi/main.c index c25e124..baa4e0f 100644 --- a/code/boot/uefi/main.c +++ b/code/boot/uefi/main.c @@ -264,7 +264,6 @@ typedef struct { char commandLine[64]; } PARSE_STATE; - // Parses boot Ini Integer. static int ParseBootIniInteger(const char* val) { int out = 0; @@ -313,7 +312,7 @@ static void DriversIniCb(const char* section, const char* key, static EFI_PHYSICAL_ADDRESS AllocateApTrampolinePageBelow1M(void) { // AP startup via SIPI requires the first instruction page to live below - // 1 MiB. Some real UEFI firmware exposes no ConventionalMemory below + // 1 MiB. Some real UEFI firmware exposes no ConventionalMemory below // 640 KiB after ExitBootServices, so reserve a loader-data page here while // boot services can still satisfy AllocateMaxAddress. EFI_PHYSICAL_ADDRESS phys = 0x0009F000ULL; @@ -468,7 +467,7 @@ EFI_STATUS LoadPeImage(UINT8* rawFile, UINT64 virtDest, EFI_PHYSICAL_ADDRESS* ou } // Apply Base Relocations (.reloc) - // We calculate the delta between where it wants to be (ImageBase) + // We calculate the delta between where it wants to be (ImageBase) // and where it will actually be (virtDest). UINT64 delta = virtDest - imageBase; diff --git a/code/build/build.c b/code/build/build.c index 6fecfb1..a4c255f 100644 --- a/code/build/build.c +++ b/code/build/build.c @@ -34,6 +34,7 @@ static const Driver_Target STANDALONE_DRIVERS[] = { DRIVER_TARGET_LIBS("ata", "obj\\drivers\\ata", "code\\drivers\\storage\\ata\\ata.c", "bin\\pci.lib"), DRIVER_TARGET_LIBS("ahci", "obj\\drivers\\ahci", "code\\drivers\\storage\\ahci\\ahci.c code\\drivers\\storage\\ahci\\ahci_port.c", "bin\\pci.lib"), DRIVER_TARGET("fat32", "obj\\drivers\\fat32", "code\\drivers\\fs\\fat32\\fat32.c code\\drivers\\fs\\fat32\\fat32_mount.c"), + DRIVER_TARGET("ntfs", "obj\\drivers\\ntfs", "code\\drivers\\fs\\ntfs\\ntfs.c"), DRIVER_TARGET_LIBS("vmwaresvga", "obj\\drivers\\vmwaresvga", "code\\drivers\\display\\vmwaresvga\\vmwaresvga.c", "bin\\pci.lib"), DRIVER_TARGET_LIBS("usb", "obj\\drivers\\usb", "code\\drivers\\bus\\usb\\usb.c code\\drivers\\bus\\usb\\usb_bus.c code\\drivers\\bus\\usb\\usb_composite.c code\\drivers\\bus\\usb\\usb_core.c code\\drivers\\bus\\usb\\usb_xhci.c code\\drivers\\bus\\usb\\usb_hid.c code\\drivers\\bus\\usb\\usb_storage.c code\\drivers\\bus\\usb\\usb_hub.c", "bin\\pci.lib"), DRIVER_TARGET_LIBS("rtl816x", "obj\\drivers\\rtl816x", "code\\drivers\\net\\rtl816x\\rtl816x.c", "bin\\pci.lib"), @@ -46,9 +47,9 @@ void create_layout() { "obj", "bin", "obj\\boot", "obj\\kernel", "obj\\lwip", "obj\\hal", "obj\\syscall", "obj\\runtime", "obj\\win32", "obj\\posix", "obj\\drivers", "obj\\init", "obj\\shell_win32", "obj\\shell_posix", "obj\\shell_native", - "obj\\async_apc", "obj\\async_cancel", "obj\\edit", "obj\\httpget", + "obj\\async_apc", "obj\\async_cancel", "obj\\apc_return_smoke", "obj\\edit", "obj\\httpget", "obj\\tcpserve", "obj\\udp", "obj\\resolve", "obj\\ping", "obj\\doom", "obj\\video", "obj\\memtop", - "obj\\mmap_smoke", "obj\\pcache_smoke", "obj\\pcache_pressure", "obj\\dirty_writeback", "obj\\shared_mmap", + "obj\\file_stat_smoke", "obj\\handle_dup_smoke", "obj\\input_api_smoke", "obj\\input_model_smoke", "obj\\timer_smoke", "obj\\stats_abi_smoke", "obj\\realpath_smoke", "obj\\file_lock_smoke", "obj\\process_protection_smoke", "obj\\socket_addr_smoke", "obj\\syscall_contract_smoke", "obj\\would_block_smoke", "obj\\cwd_peb_smoke", "obj\\mmap_smoke", "obj\\pcache_smoke", "obj\\pcache_pressure", "obj\\dirty_writeback", "obj\\shared_mmap", "obj\\pcache_invalidate", "obj\\pcache_mmap_read_reuse", "obj\\pcache_multithread", "obj\\surface_smoke", "obj\\surface_invalid_rect", "obj\\surface_multithread_present" }; @@ -91,9 +92,9 @@ bool clean_build_object_directories(void) { "obj\\boot", "obj\\kernel", "obj\\lwip", "obj\\hal", "obj\\syscall", "obj\\runtime", "obj\\win32", "obj\\posix", "obj\\init", "obj\\shell_win32", "obj\\shell_posix", "obj\\shell_native", - "obj\\async_apc", "obj\\async_cancel", "obj\\edit", "obj\\httpget", + "obj\\async_apc", "obj\\async_cancel", "obj\\apc_return_smoke", "obj\\edit", "obj\\httpget", "obj\\tcpserve", "obj\\udp", "obj\\resolve", "obj\\ping", - "obj\\doom", "obj\\video", "obj\\memtop", "obj\\mmap_smoke", "obj\\pcache_smoke", "obj\\pcache_pressure", "obj\\dirty_writeback", "obj\\shared_mmap", + "obj\\doom", "obj\\video", "obj\\memtop", "obj\\file_stat_smoke", "obj\\handle_dup_smoke", "obj\\input_api_smoke", "obj\\input_model_smoke", "obj\\timer_smoke", "obj\\stats_abi_smoke", "obj\\realpath_smoke", "obj\\file_lock_smoke", "obj\\process_protection_smoke", "obj\\socket_addr_smoke", "obj\\syscall_contract_smoke", "obj\\would_block_smoke", "obj\\cwd_peb_smoke", "obj\\mmap_smoke", "obj\\pcache_smoke", "obj\\pcache_pressure", "obj\\dirty_writeback", "obj\\shared_mmap", "obj\\pcache_invalidate", "obj\\pcache_mmap_read_reuse", "obj\\pcache_multithread", "obj\\surface_smoke", "obj\\surface_invalid_rect", "obj\\surface_multithread_present" }; @@ -125,7 +126,7 @@ bool create_usb_vhd_if_missing(void) { "attach vdisk\n" "convert gpt\n" "create partition primary\n" - "format fs=fat32 quick\n" + "format fs=ntfs quick\n" "detach vdisk\n" "exit\n", vhd_path, vhd_path @@ -235,7 +236,21 @@ int main(int argc, char **argv) { {"apps\\shell_posix", "bin\\posix.lib obj\\libc.obj", "shell_posix", "/ENTRY:main"}, {"apps\\shell_native", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "shell_native", "/ENTRY:main"}, {"tests\\async_apc", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "async_apc", "/ENTRY:main"}, + {"tests\\apc_return_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "apc_return_smoke", "/ENTRY:main"}, {"tests\\async_cancel", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "async_cancel", "/ENTRY:main"}, + {"tests\\file_stat_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "file_stat_smoke", "/ENTRY:main"}, + {"tests\\handle_dup_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "handle_dup_smoke", "/ENTRY:main"}, + {"tests\\input_api_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "input_api_smoke", "/ENTRY:main"}, + {"tests\\input_model_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "input_model_smoke", "/ENTRY:main"}, + {"tests\\timer_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "timer_smoke", "/ENTRY:main"}, + {"tests\\stats_abi_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "stats_abi_smoke", "/ENTRY:main"}, + {"tests\\realpath_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "realpath_smoke", "/ENTRY:main"}, + {"tests\\file_lock_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "file_lock_smoke", "/ENTRY:main"}, + {"tests\\process_protection_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "process_protection_smoke", "/ENTRY:main"}, + {"tests\\socket_addr_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "socket_addr_smoke", "/ENTRY:main"}, + {"tests\\syscall_contract_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "syscall_contract_smoke", "/ENTRY:main"}, + {"tests\\would_block_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "would_block_smoke", "/ENTRY:main"}, + {"tests\\cwd_peb_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "cwd_peb_smoke", "/ENTRY:main"}, {"tests\\mmap_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "mmap_smoke", "/ENTRY:main"}, {"tests\\pcache_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "pcache_smoke", "/ENTRY:main"}, {"tests\\pcache_pressure", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "pcache_pressure", "/ENTRY:main"}, @@ -302,7 +317,8 @@ int main(int argc, char **argv) { {"bin\\pci.sys", "Z:\\drivers\\pci.sys"}, {"bin\\ps2.sys", "Z:\\drivers\\ps2.sys"}, {"bin\\serial.sys", "Z:\\drivers\\serial.sys"}, {"bin\\ata.sys", "Z:\\drivers\\ata.sys"}, {"bin\\ahci.sys", "Z:\\drivers\\ahci.sys"}, - {"bin\\fat32.sys", "Z:\\drivers\\fat32.sys"}, {"bin\\vmwaresvga.sys", "Z:\\drivers\\vmwaresvga.sys"}, + {"bin\\fat32.sys", "Z:\\drivers\\fat32.sys"}, {"bin\\ntfs.sys", "Z:\\drivers\\ntfs.sys"}, + {"bin\\vmwaresvga.sys", "Z:\\drivers\\vmwaresvga.sys"}, {"bin\\lwip.sys", "Z:\\drivers\\lwip.sys"}, {"bin\\usb.sys", "Z:\\drivers\\usb.sys"}, {"bin\\rtl816x.sys", "Z:\\drivers\\rtl816x.sys"}, {"bin\\rtl8139.sys", "Z:\\drivers\\rtl8139.sys"}, {"code\\drivers\\drivers.ini", "Z:\\drivers\\drivers.ini"}, @@ -316,6 +332,20 @@ int main(int argc, char **argv) { {"bin\\video.exe", "Z:\\apps\\video.exe"}, {"bin\\memtop.exe", "Z:\\apps\\memtop.exe"}, {"bin\\async_apc.exe", "Y:\\apps\\async_apc.exe"}, {"bin\\async_cancel.exe", "Y:\\apps\\async_cancel.exe"}, + {"bin\\apc_return_smoke.exe", "Y:\\apps\\apc_return_smoke.exe"}, + {"bin\\file_stat_smoke.exe", "Y:\\apps\\file_stat_smoke.exe"}, + {"bin\\handle_dup_smoke.exe", "Y:\\apps\\handle_dup_smoke.exe"}, + {"bin\\input_api_smoke.exe", "Y:\\apps\\input_api_smoke.exe"}, + {"bin\\input_model_smoke.exe", "Y:\\apps\\input_model_smoke.exe"}, + {"bin\\timer_smoke.exe", "Y:\\apps\\timer_smoke.exe"}, + {"bin\\stats_abi_smoke.exe", "Y:\\apps\\stats_abi_smoke.exe"}, + {"bin\\realpath_smoke.exe", "Y:\\apps\\realpath_smoke.exe"}, + {"bin\\file_lock_smoke.exe", "Y:\\apps\\file_lock_smoke.exe"}, + {"bin\\process_protection_smoke.exe", "Y:\\apps\\process_protection_smoke.exe"}, + {"bin\\socket_addr_smoke.exe", "Y:\\apps\\socket_addr_smoke.exe"}, + {"bin\\syscall_contract_smoke.exe", "Y:\\apps\\syscall_contract_smoke.exe"}, + {"bin\\would_block_smoke.exe", "Y:\\apps\\would_block_smoke.exe"}, + {"bin\\cwd_peb_smoke.exe", "Y:\\apps\\cwd_peb_smoke.exe"}, {"bin\\mmap_smoke.exe", "Y:\\apps\\mmap_smoke.exe"}, {"bin\\pcache_smoke.exe", "Y:\\apps\\pcache_smoke.exe"}, {"bin\\pcache_pressure.exe", "Y:\\apps\\pcache_pressure.exe"}, {"bin\\dirty_writeback.exe", "Y:\\apps\\dirty_writeback.exe"}, diff --git a/code/build/nob.h b/code/build/nob.h index 2735a93..c1a6a1a 100644 --- a/code/build/nob.h +++ b/code/build/nob.h @@ -325,7 +325,7 @@ typedef struct { // nob_dir_entry_open() - open the directory entry for iteration. // RETURN: -// true - Sucess. +// true - Sucess. // false - Error. I will be logged automatically with nob_log(). NOBDEF bool nob_dir_entry_open(const char *dir_path, Nob_Dir_Entry *dir); // nob_dir_entry_next() - acquire the next file in the directory. @@ -1712,7 +1712,7 @@ NOBDEF bool nob_proc_wait(Nob_Proc proc) #ifdef _WIN32 DWORD result = WaitForSingleObject( proc, // HANDLE hHandle, - INFINITE // DWORD dwMilliseconds + INFINITE // DWORD dwMilliseconds ); if (result == WAIT_FAILED) { @@ -1769,7 +1769,7 @@ static int nob__proc_wait_async(Nob_Proc proc, int ms) #ifdef _WIN32 DWORD result = WaitForSingleObject( proc, // HANDLE hHandle, - ms // DWORD dwMilliseconds + ms // DWORD dwMilliseconds ); if (result == WAIT_TIMEOUT) { diff --git a/code/drivers/bus/pci/pci.c b/code/drivers/bus/pci/pci.c index c8df4b5..8ecc854 100644 --- a/code/drivers/bus/pci/pci.c +++ b/code/drivers/bus/pci/pci.c @@ -23,7 +23,7 @@ static DRIVER_OBJECT gPciDriver; static uint32_t gPciDeviceCount; // PCI MMIO BARs use a private virtual aperture instead of the physical -// direct map. This avoids cacheability aliasing with large direct-map pages +// direct map. This avoids cacheability aliasing with large direct-map pages // and keeps BAR mappings page-granular. #define PCI_MMIO_VA_BASE 0xFFFF900000000000ULL @@ -139,7 +139,7 @@ static void PciDecodeBars(PCI_PDO_EXTENSION* ext) { } // Size BARs with I/O and memory decoding disabled so all-ones probes do - // not accidentally alias a live resource. Restore policy immediately; + // not accidentally alias a live resource. Restore policy immediately; // PciEnableDevice() is the only exported path that changes command bits. uint16_t savedCommand = PciRawRead16(ext->Info.Address.Bus, ext->Info.Address.Device, diff --git a/code/drivers/bus/usb/usb.h b/code/drivers/bus/usb/usb.h index 089db1f..9a5cb93 100644 --- a/code/drivers/bus/usb/usb.h +++ b/code/drivers/bus/usb/usb.h @@ -35,7 +35,6 @@ static __inline void UsbTrace(const char* fmt, ...) { (void)fmt; } #endif - #define PCI_CLASS_SERIAL_BUS 0x0Cu #define PCI_SUBCLASS_USB 0x03u #define PCI_PROGIF_XHCI 0x30u @@ -116,7 +115,6 @@ static __inline void UsbTrace(const char* fmt, ...) { (void)fmt; } #define XHCI_TRB_TYPE_ADDRESS_DEVICE 11u #define XHCI_TRB_TYPE_CONFIGURE_ENDPOINT 12u - #define XHCI_CC_SUCCESS 1u #define XHCI_CC_SHORT_PACKET 13u @@ -205,7 +203,6 @@ static __inline void UsbTrace(const char* fmt, ...) { (void)fmt; } #define SCSI_WRITE_10 0x2Au #define SCSI_READ_CAPACITY_10 0x25u - #define XHCI_RING_TRBS 256u #define XHCI_EVENT_TRBS 256u #define XHCI_DCBAA_ENTRIES 256u @@ -487,8 +484,6 @@ typedef struct USB_PDO_EXTENSION { USB_ENDPOINT_DESCRIPTOR Endpoints[USB_MAX_TRACKED_ENDPOINTS]; } USB_PDO_EXTENSION; - - typedef enum USB_HUB_KIND { UsbHubKindRoot = 1, UsbHubKindExternal = 2, @@ -591,8 +586,6 @@ typedef struct USB_HID_EXTENSION { uint32_t InputDeviceId; } USB_HID_EXTENSION; - - typedef enum USB_STORAGE_EXTENSION_KIND { UsbStorageExtensionInterface = 0x55534946u, UsbStorageExtensionDisk = 0x5553444Bu, @@ -838,7 +831,6 @@ typedef struct XHCI_CONTROLLER { USB_HCD Hcd; } XHCI_CONTROLLER; - extern DRIVER_OBJECT gUsbStorageDriver; extern DRIVER_OBJECT gUsbBusDriver; extern DRIVER_OBJECT gUsbCompositeDriver; diff --git a/code/drivers/bus/usb/usb_bus.c b/code/drivers/bus/usb/usb_bus.c index e5598fd..bd22e27 100644 --- a/code/drivers/bus/usb/usb_bus.c +++ b/code/drivers/bus/usb/usb_bus.c @@ -1,7 +1,7 @@ // USB bus manager. This file owns device addressing, descriptor reads, and child PDO creation. // This is the structural USB bus layer used by the upcoming composite, hub, -// and hotplug stages. The legacy direct xHCI class path is still present for +// and hotplug stages. The legacy direct xHCI class path is still present for // boot HID/storage during migration, but newly enumerated xHCI devices now also // get first-class USB PDOs and can accept internal URB IRPs. #include "usb.h" @@ -187,7 +187,6 @@ static void UsbFormatClass(char* buf, size_t cap, uint8_t cls) { UsbIdAppendHex(buf, cap, &pos, cls, 2); } - // Implements the format Composite operation. static void UsbFormatComposite(char* buf, size_t cap) { size_t pos = 0; @@ -442,7 +441,6 @@ KSTATUS UsbCreateInterfacePdos(USB_HCD* hcd, XHCI_DEVICE* xdev, DEVICE_OBJECT* p return STATUS_SUCCESS; } - // Implements the reference First Child operation. static DEVICE_OBJECT* UsbReferenceFirstChild(DEVICE_OBJECT* parent) { if (!parent) return (DEVICE_OBJECT*)0; @@ -487,7 +485,7 @@ KSTATUS UsbRemovePdoSubtree(DEVICE_OBJECT* pdo, bool surprise) { return STATUS_INVALID_PARAMETER; // Child PDOs may be owned either directly by the PDO or by an attached FDO - // such as usb-composite or usb-hub. The attached driver's PnP handler is + // such as usb-composite or usb-hub. The attached driver's PnP handler is // therefore responsible for removing its own child PDOs before forwarding // REMOVE_DEVICE to the lower PDO. (void)UsbRemoveDeviceObjectChildren(pdo, surprise); diff --git a/code/drivers/bus/usb/usb_composite.c b/code/drivers/bus/usb/usb_composite.c index 6889152..f63d256 100644 --- a/code/drivers/bus/usb/usb_composite.c +++ b/code/drivers/bus/usb/usb_composite.c @@ -46,7 +46,7 @@ KSTATUS UsbCompositeDispatchPnp(DEVICE_OBJECT* device, IRP* irp) { // * an FDO attached above a USB device PDO, and // * interface PDOs that it creates for class drivers. // Forwarded PnP IRPs from HID/storage land on the interface PDO, so the - // PDO must use the generic USB PDO PnP handler. Treating its + // PDO must use the generic USB PDO PnP handler. Treating its // USB_PDO_EXTENSION as USB_COMPOSITE_EXTENSION corrupts the dispatch path. if (device->Type == DeviceObjectPhysical) return UsbDispatchPdoPnp(device, irp); diff --git a/code/drivers/bus/usb/usb_core.c b/code/drivers/bus/usb/usb_core.c index 4e70b68..429f6bc 100644 --- a/code/drivers/bus/usb/usb_core.c +++ b/code/drivers/bus/usb/usb_core.c @@ -25,7 +25,6 @@ KSTATUS XhciSetConfiguration(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, return STATUS_SUCCESS; } - // Returns tracked Interface By Number. static USB_TRACKED_INTERFACE* UsbFindTrackedInterfaceByNumber(XHCI_DEVICE* dev, uint8_t interfaceNumber) { if (!dev) return (USB_TRACKED_INTERFACE*)0; @@ -111,11 +110,11 @@ void XhciLogConfigurationTree(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, } } else { - // Alternate settings are not independent USB interfaces. Real + // Alternate settings are not independent USB interfaces. Real // UVC cameras often expose one VideoStreaming interface with // many alternate settings, and publishing each alternate setting // as a separate PDO causes duplicate interface devices and can - // exhaust USB_MAX_TRACKED_INTERFACES. Track only alternate + // exhaust USB_MAX_TRACKED_INTERFACES. Track only alternate // setting zero until a function driver explicitly selects a // different setting. @@ -211,8 +210,6 @@ void XhciLogConfigurationTree(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, } } - - KSTATUS XhciDiscoverAndSetConfiguration(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, const USB_DEVICE_DESCRIPTOR* devDesc) { if (devDesc->bNumConfigurations == 0) { @@ -254,7 +251,6 @@ KSTATUS XhciDiscoverAndSetConfiguration(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, return STATUS_SUCCESS; } - // Implements the rollback Enumerated Slot operation. static void XhciRollbackEnumeratedSlot(XHCI_CONTROLLER* xhci, uint8_t slotId) { if (!xhci || slotId == 0 || slotId > XHCI_MAX_TRACKED_SLOTS) @@ -279,7 +275,6 @@ static void XhciRollbackEnumeratedSlot(XHCI_CONTROLLER* xhci, uint8_t slotId) { memset(dev, 0, sizeof(*dev)); } - static KSTATUS XhciEnumeratePathEx(XHCI_CONTROLLER* xhci, uint8_t logicalPort, uint8_t speed, @@ -435,7 +430,6 @@ KSTATUS XhciEnumeratePort(XHCI_CONTROLLER* xhci, uint8_t port) { return XhciEnumeratePortEx(xhci, port, (uint8_t*)0); } - // Implements the enumerate Root Ports operation. void XhciEnumerateRootPorts(XHCI_CONTROLLER* xhci) { uint32_t attempted = 0; diff --git a/code/drivers/bus/usb/usb_hid.c b/code/drivers/bus/usb/usb_hid.c index 0534d2c..b8d050c 100644 --- a/code/drivers/bus/usb/usb_hid.c +++ b/code/drivers/bus/usb/usb_hid.c @@ -11,7 +11,6 @@ bool XhciHidKeyInReport(const uint8_t* keys, uint8_t key) { return false; } - INPUT_KEY XhciHidModifierKey(uint8_t bit) { switch (bit) { case 0: return INPUT_KEY_LEFT_CTRL; @@ -26,7 +25,6 @@ INPUT_KEY XhciHidModifierKey(uint8_t bit) { } } - // Submits hid Key. void XhciSubmitHidKey(INPUT_KEY key, bool pressed) { if (key == INPUT_KEY_NONE) return; @@ -36,7 +34,6 @@ void XhciSubmitHidKey(INPUT_KEY key, bool pressed) { KeInputSubmitKey(&ev); } - // Processes boot Keyboard Report. void XhciProcessBootKeyboardReport(XHCI_DEVICE* dev, const uint8_t* report, uint16_t len) { if (len < 8) return; @@ -66,7 +63,6 @@ void XhciProcessBootKeyboardReport(XHCI_DEVICE* dev, const uint8_t* report, uint memcpy(dev->HidLastKeys, keys, sizeof(dev->HidLastKeys)); } - // Processes boot Mouse Report. void XhciProcessBootMouseReport(XHCI_DEVICE* dev, const uint8_t* report, uint16_t len) { if (len < 3) return; @@ -88,7 +84,6 @@ void XhciProcessBootMouseReport(XHCI_DEVICE* dev, const uint8_t* report, uint16_ dev->HidLastButtons = buttons; } - // Implements the arm Hid Interrupt In operation. KSTATUS XhciArmHidInterruptIn(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { if (!dev->HidActive || dev->HidArmed || !dev->HidRing || !dev->HidBuffer) @@ -117,7 +112,6 @@ KSTATUS XhciArmHidInterruptIn(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { return STATUS_SUCCESS; } - // Implements the arm All Hid operation. void XhciArmAllHid(XHCI_CONTROLLER* xhci) { for (uint8_t slot = 1; slot <= XHCI_MAX_TRACKED_SLOTS; slot++) { @@ -131,7 +125,6 @@ void XhciArmAllHid(XHCI_CONTROLLER* xhci) { } } - static void XhciHandleLegacyHidTransferEvent(XHCI_CONTROLLER* xhci, uint64_t trbPhys, uint32_t completionCode, uint8_t slotId, uint8_t endpointId, uint32_t residue) { @@ -163,7 +156,6 @@ static void XhciHandleLegacyHidTransferEvent(XHCI_CONTROLLER* xhci, uint64_t trb } } - // Starts hid Interrupts. KSTATUS XhciStartHidInterrupts(XHCI_CONTROLLER* xhci) { if (!xhci || xhci->HidDeviceCount == 0) @@ -173,7 +165,6 @@ KSTATUS XhciStartHidInterrupts(XHCI_CONTROLLER* xhci) { return STATUS_SUCCESS; } - // Updates hid Boot Protocol. KSTATUS XhciSetHidBootProtocol(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { USB_SETUP_PACKET setup; @@ -205,7 +196,6 @@ KSTATUS XhciSetHidBootProtocol(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { return STATUS_SUCCESS; } - // Creates hid Memory. KSTATUS XhciAllocateHidMemory(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { if (dev->HidRing && dev->HidBuffer) return STATUS_SUCCESS; @@ -232,7 +222,6 @@ KSTATUS XhciAllocateHidMemory(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { return STATUS_SUCCESS; } - // Implements the configure Hid Endpoint operation. KSTATUS XhciConfigureHidEndpoint(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { if (!dev->HidCandidate) @@ -308,7 +297,6 @@ KSTATUS XhciConfigureHidEndpoint(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { return STATUS_SUCCESS; } - // PDO/FDO boot HID function driver path. DRIVER_OBJECT gUsbHidDriver; @@ -324,7 +312,6 @@ static const IO_DRIVER_ID_TABLE gUsbHidIdTable = { sizeof(gUsbHidIds) / sizeof(gUsbHidIds[0]), }; - // Implements the hid Is Qemu Absolute Tablet Pdo operation. static bool UsbHidIsQemuAbsoluteTabletPdo(const USB_PDO_EXTENSION* pdoExt, const USB_ENDPOINT_DESCRIPTOR* ep) { if (!pdoExt || !pdoExt->Device || !ep) @@ -358,7 +345,6 @@ static const USB_ENDPOINT_DESCRIPTOR* UsbHidFindInterruptInPdo(const USB_PDO_EXT return (const USB_ENDPOINT_DESCRIPTOR*)0; } - // Implements the hid Process Boot Keyboard Report operation. static void UsbHidProcessBootKeyboardReport(USB_HID_EXTENSION* ext, const uint8_t* report, uint16_t len) { if (!ext || len < 8) return; @@ -630,7 +616,6 @@ static KSTATUS UsbHidStartDevice(DEVICE_OBJECT* device, USB_HID_EXTENSION* ext) return STATUS_SUCCESS; } - // Implements the hid Dispatch Pnp operation. KSTATUS UsbHidDispatchPnp(DEVICE_OBJECT* device, IRP* irp) { if (!device || !irp) diff --git a/code/drivers/bus/usb/usb_hub.c b/code/drivers/bus/usb/usb_hub.c index 583115f..055d3ae 100644 --- a/code/drivers/bus/usb/usb_hub.c +++ b/code/drivers/bus/usb/usb_hub.c @@ -1,6 +1,6 @@ // USB hub driver. This file owns hub probing, port status handling, and child-device enumeration. -// Root hubs and external hubs are represented by the same FDO abstraction. The +// Root hubs and external hubs are represented by the same FDO abstraction. The // xHCI root hub feeds port-change events directly into this driver, while an // external hub feeds the same port state machine from its interrupt-IN change // bitmap. @@ -77,9 +77,9 @@ static KSTATUS UsbHubEnumerateRootPortWithRetries(USB_HUB_EXTENSION* hub, // On real Intel laptop controllers some internal full-speed devices // respond with xHCI USB Transaction Error during Address Device while - // the port is still settling. Retry that boundedly while the port is + // the port is still settling. Retry that boundedly while the port is // held in ENUMERATING state so queued PSC events cannot start parallel - // enumerations. Descriptor/control timeouts are not retried here; they + // enumerations. Descriptor/control timeouts are not retried here; they // are usually dead/firmware-owned ports and retrying them stalls boot. if (last != STATUS_DEVICE_ERROR || attempt == maxAttempts) @@ -898,7 +898,7 @@ KSTATUS UsbHubDispatchPnp(DEVICE_OBJECT* device, IRP* irp) { if (hub->WorkerSemaphore) KeSignalSemaphore(hub->WorkerSemaphore); - // Remove downstream PDOs owned by this hub FDO. The explicit + // Remove downstream PDOs owned by this hub FDO. The explicit // slot arrays are still used as the hardware lookup table; the // child PDO tree is the authoritative software lifetime tree. (void)UsbRemoveDeviceObjectChildren(device, true); diff --git a/code/drivers/bus/usb/usb_storage.c b/code/drivers/bus/usb/usb_storage.c index 1a032aa..85e0a61 100644 --- a/code/drivers/bus/usb/usb_storage.c +++ b/code/drivers/bus/usb/usb_storage.c @@ -1,8 +1,8 @@ // USB mass-storage driver. This file owns bulk-only transport and storage-device exposure. // The storage driver now binds as a USB function driver to an interface PDO -// (USB\CLASS_08&SUBCLASS_06&PROT_50). It creates a disk FDO above that -// interface stack and issues BOT transfers through USB URBs. The xHCI HCD +// (USB\CLASS_08&SUBCLASS_06&PROT_50). It creates a disk FDO above that +// interface stack and issues BOT transfers through USB URBs. The xHCI HCD // still backs the bulk URBs with the existing per-device rings; later stages // will make endpoint rings fully pipe-owned and asynchronous. #include "usb.h" @@ -597,7 +597,7 @@ static KSTATUS UsbStorageStartDevice(DEVICE_OBJECT* device, USB_STORAGE_EXTENSIO } // The disk namespace publishes an arrival notification synchronously from - // IoRegisterDiskDevice(). Mark the storage FDO usable before publishing the + // IoRegisterDiskDevice(). Mark the storage FDO usable before publishing the // disk so filesystem hotplug callbacks can read sector 0 immediately. xdev->MassActive = true; storage->Started = true; @@ -785,7 +785,6 @@ KSTATUS UsbStorageDispatchPnp(DEVICE_OBJECT* device, IRP* irp) { return STATUS_INVALID_PARAMETER; } - // Implements the storage Add Device operation. KSTATUS UsbStorageAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObject) { if (!driver || !physicalDeviceObject || physicalDeviceObject->Type != DeviceObjectPhysical) diff --git a/code/drivers/bus/usb/usb_xhci.c b/code/drivers/bus/usb/usb_xhci.c index 847cab8..9824be5 100644 --- a/code/drivers/bus/usb/usb_xhci.c +++ b/code/drivers/bus/usb/usb_xhci.c @@ -7,7 +7,6 @@ uint32_t UsbAlignUp32(uint32_t value, uint32_t align) { return (value + align - 1u) & ~(align - 1u); } - // Implements the barrier operation. void XhciBarrier(void) { _ReadWriteBarrier(); @@ -15,31 +14,26 @@ void XhciBarrier(void) { _ReadWriteBarrier(); } - // Implements the read8 operation. uint8_t XhciRead8(volatile uint8_t* base, uint32_t reg) { return *(volatile uint8_t*)(base + reg); } - // Implements the read16 operation. uint16_t XhciRead16(volatile uint8_t* base, uint32_t reg) { return *(volatile uint16_t*)(base + reg); } - // Implements the read32 operation. uint32_t XhciRead32(volatile uint8_t* base, uint32_t reg) { return *(volatile uint32_t*)(base + reg); } - // Implements the write32 operation. void XhciWrite32(volatile uint8_t* base, uint32_t reg, uint32_t value) { *(volatile uint32_t*)(base + reg) = value; } - // Implements the read64 operation. uint64_t XhciRead64(volatile uint8_t* base, uint32_t reg) { uint32_t lo = XhciRead32(base, reg); @@ -47,14 +41,12 @@ uint64_t XhciRead64(volatile uint8_t* base, uint32_t reg) { return ((uint64_t)hi << 32) | lo; } - // Implements the write64 operation. void XhciWrite64(volatile uint8_t* base, uint32_t reg, uint64_t value) { XhciWrite32(base, reg, (uint32_t)value); XhciWrite32(base, reg + 4u, (uint32_t)(value >> 32)); } - // Implements the delay Us operation. void UsbDelayUs(const HAL_API* hal, uint32_t us) { if (hal && hal->Timer.Stall) hal->Timer.Stall(us); @@ -63,7 +55,6 @@ void UsbDelayUs(const HAL_API* hal, uint32_t us) { } } - bool UsbWaitSet32(const HAL_API* hal, volatile uint8_t* base, uint32_t reg, uint32_t mask, uint32_t timeoutUs) { for (uint32_t i = 0; i < timeoutUs; i++) { @@ -73,7 +64,6 @@ bool UsbWaitSet32(const HAL_API* hal, volatile uint8_t* base, uint32_t reg, return false; } - bool UsbWaitClear32(const HAL_API* hal, volatile uint8_t* base, uint32_t reg, uint32_t mask, uint32_t timeoutUs) { for (uint32_t i = 0; i < timeoutUs; i++) { @@ -83,15 +73,14 @@ bool UsbWaitClear32(const HAL_API* hal, volatile uint8_t* base, uint32_t reg, return false; } - void* UsbDmaAllocPages(XHCI_CONTROLLER* xhci, size_t pages, PHYSICAL_ADDRESS* outPhys) { if (!outPhys || pages == 0) return NULL; - // Most xHCI runtime objects are one page. Do not route those through the + // Most xHCI runtime objects are one page. Do not route those through the // physically-contiguous allocator: on real machines that allocator is a - // scarce DMA primitive, while a single frame is already contiguous. QEMU + // scarce DMA primitive, while a single frame is already contiguous. QEMU // hid this because it had simple low memory; laptop firmware does not. PHYSICAL_ADDRESS phys = 0; if (pages == 1) { @@ -112,62 +101,52 @@ void* UsbDmaAllocPages(XHCI_CONTROLLER* xhci, size_t pages, return virt; } - // Implements the scratchpad Count operation. uint16_t XhciScratchpadCount(uint32_t hcs2) { return (uint16_t)((((hcs2 >> 16) & 0x3E0u) | ((hcs2 >> 27) & 0x1Fu)) & 0x3FFu); } - // Implements the trb Type operation. uint32_t XhciTrbType(const XHCI_TRB* trb) { return (trb->Control >> XHCI_TRB_TYPE_SHIFT) & XHCI_TRB_TYPE_MASK; } - // Implements the completion Code operation. uint32_t XhciCompletionCode(const XHCI_TRB* trb) { return (trb->Status >> 24) & 0xFFu; } - // Implements the event Slot Id operation. uint8_t XhciEventSlotId(const XHCI_TRB* trb) { return (uint8_t)(trb->Control >> 24); } - // Implements the event Endpoint Id operation. uint8_t XhciEventEndpointId(const XHCI_TRB* trb) { return (uint8_t)((trb->Control >> 16) & 0x1Fu); } - // Implements the transfer Residue operation. uint32_t XhciTransferResidue(const XHCI_TRB* trb) { return trb->Status & 0xFFFFFFu; } - // Implements the input Control Context operation. uint32_t* XhciInputControlContext(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { (void)xhci; return (uint32_t*)dev->InputContext; } - // Implements the input Slot Context operation. uint32_t* XhciInputSlotContext(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { return (uint32_t*)(dev->InputContext + xhci->ContextBytes); } - uint32_t* XhciInputEndpointContext(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, uint8_t endpointId) { return (uint32_t*)(dev->InputContext + (uint32_t)(endpointId + 1u) * xhci->ContextBytes); } - // Implements the ep Max Packet operation. uint32_t XhciEpMaxPacket(uint8_t speed) { switch (speed) { @@ -180,7 +159,6 @@ uint32_t XhciEpMaxPacket(uint8_t speed) { } } - // Implements the setup Packet To U64 operation. uint64_t UsbSetupPacketToU64(const USB_SETUP_PACKET* setup) { uint64_t v = 0; @@ -192,7 +170,6 @@ uint64_t UsbSetupPacketToU64(const USB_SETUP_PACKET* setup) { return v; } - const char* XhciPortSpeedName(uint32_t speed) { switch (speed) { case 1: return "full"; @@ -204,8 +181,6 @@ const char* XhciPortSpeedName(uint32_t speed) { } } - - // Parses supported Protocols. void XhciParseSupportedProtocols(XHCI_CONTROLLER* xhci) { memset(xhci->PortProtocolMajor, 0, sizeof(xhci->PortProtocolMajor)); @@ -253,7 +228,6 @@ void XhciParseSupportedProtocols(XHCI_CONTROLLER* xhci) { } } - // Implements the port Protocol Major operation. uint8_t XhciPortProtocolMajor(XHCI_CONTROLLER* xhci, uint8_t port, uint32_t portsc) { if (port <= XHCI_MAX_ROOT_PORTS && xhci->PortProtocolMajor[port]) @@ -277,12 +251,10 @@ const char* UsbEndpointTypeName(uint8_t type) { } } - const char* UsbDirectionName(uint8_t endpointAddress) { return (endpointAddress & USB_ENDPOINT_DIR_IN) ? "in" : "out"; } - const char* UsbInterfaceClassName(uint8_t klass, uint8_t subclass, uint8_t proto) { if (klass == USB_CLASS_HID) { if (subclass == 1 && proto == 1) return "hid-boot-keyboard"; @@ -297,7 +269,6 @@ const char* UsbInterfaceClassName(uint8_t klass, uint8_t subclass, uint8_t proto return "class"; } - // Implements the log Port Status operation. void XhciLogPortStatus(XHCI_CONTROLLER* xhci) { for (uint8_t port = 1; port <= xhci->MaxPorts; port++) { @@ -346,7 +317,6 @@ void XhciPowerRootPorts(XHCI_CONTROLLER* xhci) { } } - // Implements the ack Interrupter0 operation. void XhciAckInterrupter0(XHCI_CONTROLLER* xhci) { if (!xhci->Runtime) return; @@ -355,7 +325,6 @@ void XhciAckInterrupter0(XHCI_CONTROLLER* xhci) { XhciWrite32(ir0, 0x00u, (iman & XHCI_IMAN_IE) | XHCI_IMAN_IP); } - // Updates erdp. void XhciUpdateErdp(XHCI_CONTROLLER* xhci) { volatile uint8_t* ir0 = xhci->Runtime + 0x20u; @@ -363,7 +332,6 @@ void XhciUpdateErdp(XHCI_CONTROLLER* xhci) { XhciWrite64(ir0, 0x18u, erdp | (1ull << 3)); // EHB clear } - // Returns command Waiter Locked. static XHCI_WAIT_TARGET* XhciFindCommandWaiterLocked(XHCI_CONTROLLER* xhci, uint64_t commandPhys) { for (XHCI_WAIT_TARGET* w = xhci->WaiterList; w; w = w->Next) { @@ -373,7 +341,6 @@ static XHCI_WAIT_TARGET* XhciFindCommandWaiterLocked(XHCI_CONTROLLER* xhci, uint return (XHCI_WAIT_TARGET*)0; } - static XHCI_WAIT_TARGET* XhciFindTransferWaiterLocked(XHCI_CONTROLLER* xhci, uint64_t transferPhys, uint8_t slotId, @@ -389,7 +356,6 @@ static XHCI_WAIT_TARGET* XhciFindTransferWaiterLocked(XHCI_CONTROLLER* xhci, return (XHCI_WAIT_TARGET*)0; } - static XHCI_WAIT_TARGET* XhciClaimCommandWaiter(XHCI_CONTROLLER* xhci, uint64_t commandPhys, uint32_t completionCode, @@ -406,7 +372,6 @@ static XHCI_WAIT_TARGET* XhciClaimCommandWaiter(XHCI_CONTROLLER* xhci, return w; } - static XHCI_WAIT_TARGET* XhciClaimTransferWaiter(XHCI_CONTROLLER* xhci, uint64_t transferPhys, uint8_t slotId, @@ -427,7 +392,6 @@ static XHCI_WAIT_TARGET* XhciClaimTransferWaiter(XHCI_CONTROLLER* xhci, return w; } - // Implements the drain Event Ring operation. bool XhciDrainEventRing(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target) { (void)target; @@ -484,7 +448,6 @@ bool XhciDrainEventRing(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target) { return processed; } - // Implements the drain Event Ring Locked operation. bool XhciDrainEventRingLocked(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target) { if (!xhci) return false; @@ -494,7 +457,6 @@ bool XhciDrainEventRingLocked(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target) { return processed; } - // Waits for target Complete. bool XhciWaitTargetComplete(const XHCI_WAIT_TARGET* target) { if (!target) return false; @@ -503,7 +465,6 @@ bool XhciWaitTargetComplete(const XHCI_WAIT_TARGET* target) { return true; } - // Registers wait Target. void XhciRegisterWaitTarget(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target) { if (!xhci || !target || target->Registered) @@ -516,7 +477,6 @@ void XhciRegisterWaitTarget(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target) { KeReleaseSpinlock(&xhci->WaitLock, flags); } - // Implements the unregister Wait Target operation. void XhciUnregisterWaitTarget(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target) { if (!xhci || !target || !target->Registered) @@ -536,7 +496,6 @@ void XhciUnregisterWaitTarget(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target) { KeReleaseSpinlock(&xhci->WaitLock, flags); } - KSTATUS XhciWaitForArmedTarget(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target, uint32_t timeoutUs) { if (!xhci || !target) @@ -566,7 +525,6 @@ KSTATUS XhciWaitForArmedTarget(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target, return STATUS_TIMEOUT; } - KSTATUS XhciWaitForTarget(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target, uint32_t timeoutUs) { if (!xhci || !target) @@ -575,7 +533,6 @@ KSTATUS XhciWaitForTarget(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target, return XhciWaitForArmedTarget(xhci, target, timeoutUs); } - void XhciEventDpcRoutine(KDPC* dpc, void* context, void* systemArgument1, void* systemArgument2) { (void)dpc; @@ -592,7 +549,6 @@ void XhciEventDpcRoutine(KDPC* dpc, void* context, } } - #define XHCI_CONTROLLER_MAGIC 0x58484349u // XHCI #define XHCI_MAX_CONTROLLERS 8u @@ -630,7 +586,6 @@ void XhciIsr(void) { } } - // Implements the bios Handoff operation. KSTATUS XhciBiosHandoff(XHCI_CONTROLLER* xhci) { uint32_t xecp = (xhci->Hcc1 >> 16) & 0xFFFFu; @@ -664,7 +619,7 @@ KSTATUS XhciBiosHandoff(XHCI_CONTROLLER* xhci) { } } - // Clear legacy SMI enables/status. RW1C status bits tolerate this. + // Clear legacy SMI enables/status. RW1C status bits tolerate this. XhciWrite32(xhci->Mmio, off + 4u, 0xFFFFFFFFu); return STATUS_SUCCESS; } @@ -675,7 +630,6 @@ KSTATUS XhciBiosHandoff(XHCI_CONTROLLER* xhci) { return STATUS_SUCCESS; } - // Implements the stop And Reset operation. KSTATUS XhciStopAndReset(XHCI_CONTROLLER* xhci) { uint32_t cmd = XhciRead32(xhci->Op, XHCI_OP_USBCMD); @@ -709,7 +663,6 @@ KSTATUS XhciStopAndReset(XHCI_CONTROLLER* xhci) { return STATUS_SUCCESS; } - // Creates scratchpads. KSTATUS XhciAllocateScratchpads(XHCI_CONTROLLER* xhci) { if (xhci->ScratchpadCount == 0) return STATUS_SUCCESS; @@ -744,7 +697,6 @@ KSTATUS XhciAllocateScratchpads(XHCI_CONTROLLER* xhci) { return STATUS_SUCCESS; } - // Creates runtime Memory. KSTATUS XhciAllocateRuntimeMemory(XHCI_CONTROLLER* xhci) { xhci->Dcbaa = (uint64_t*)UsbDmaAllocPages(xhci, 1, &xhci->DcbaaPhys); @@ -786,7 +738,6 @@ KSTATUS XhciAllocateRuntimeMemory(XHCI_CONTROLLER* xhci) { return STATUS_SUCCESS; } - // Implements the program Runtime operation. KSTATUS XhciProgramRuntime(XHCI_CONTROLLER* xhci) { uint8_t slots = xhci->MaxSlots; @@ -808,13 +759,11 @@ KSTATUS XhciProgramRuntime(XHCI_CONTROLLER* xhci) { return STATUS_SUCCESS; } - // Implements the command Trb Phys operation. uint64_t XhciCommandTrbPhys(XHCI_CONTROLLER* xhci, uint32_t index) { return xhci->CommandRingPhys + (uint64_t)index * sizeof(XHCI_TRB); } - static KSTATUS XhciCommandRingAppend(XHCI_CONTROLLER* xhci, uint64_t parameter, uint32_t status, @@ -850,7 +799,6 @@ static KSTATUS XhciCommandRingAppend(XHCI_CONTROLLER* xhci, return STATUS_SUCCESS; } - KSTATUS XhciSubmitCommand(XHCI_CONTROLLER* xhci, uint64_t parameter, uint32_t status, @@ -892,7 +840,6 @@ KSTATUS XhciSubmitCommand(XHCI_CONTROLLER* xhci, return waitStatus; } - // Submits noop Command. KSTATUS XhciSubmitNoopCommand(XHCI_CONTROLLER* xhci) { XHCI_COMMAND_RESULT result; @@ -913,7 +860,6 @@ KSTATUS XhciSubmitNoopCommand(XHCI_CONTROLLER* xhci) { return st; } - // Enables slot. KSTATUS XhciEnableSlot(XHCI_CONTROLLER* xhci, uint8_t* outSlotId) { XHCI_COMMAND_RESULT result; @@ -938,8 +884,6 @@ KSTATUS XhciEnableSlot(XHCI_CONTROLLER* xhci, uint8_t* outSlotId) { return STATUS_SUCCESS; } - - // Disables slot. KSTATUS XhciDisableSlot(XHCI_CONTROLLER* xhci, uint8_t slotId) { if (!xhci || slotId == 0) @@ -982,7 +926,6 @@ KSTATUS XhciAddressDevice(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { return STATUS_SUCCESS; } - // Starts the requested operation. KSTATUS XhciStart(XHCI_CONTROLLER* xhci) { uint32_t cmd = XhciRead32(xhci->Op, XHCI_OP_USBCMD); @@ -1000,7 +943,6 @@ KSTATUS XhciStart(XHCI_CONTROLLER* xhci) { return STATUS_SUCCESS; } - // Enables interrupts. void XhciEnableInterrupts(XHCI_CONTROLLER* xhci) { volatile uint8_t* ir0 = xhci->Runtime + 0x20u; @@ -1011,13 +953,11 @@ void XhciEnableInterrupts(XHCI_CONTROLLER* xhci) { XhciWrite32(xhci->Op, XHCI_OP_USBCMD, cmd | XHCI_USBCMD_INTE | XHCI_USBCMD_HSEE); } - // Implements the port Reg operation. uint32_t XhciPortReg(uint8_t port) { return XHCI_OP_PORT_BASE + ((uint32_t)port - 1u) * XHCI_OP_PORT_STRIDE; } - // Clears port Changes. void XhciClearPortChanges(XHCI_CONTROLLER* xhci, uint8_t port) { uint32_t off = XhciPortReg(port); @@ -1025,16 +965,15 @@ void XhciClearPortChanges(XHCI_CONTROLLER* xhci, uint8_t port) { uint32_t changes = portsc & XHCI_PORTSC_CHANGE_MASK; if (changes) { - // PORTSC is a mixed register. The change bits are RW1C, but many + // PORTSC is a mixed register. The change bits are RW1C, but many // other bits are either RO or have side effects when written as one - // (PED disables the port; PR starts a reset). Preserve only Port + // (PED disables the port; PR starts a reset). Preserve only Port // Power and write one to the RW1C change bits. XhciWrite32(xhci->Op, off, (portsc & XHCI_PORTSC_PP) | changes); } } - // Implements the reset Port operation. KSTATUS XhciResetPort(XHCI_CONTROLLER* xhci, uint8_t port, uint8_t* outSpeed) { uint32_t off = XhciPortReg(port); @@ -1050,9 +989,9 @@ KSTATUS XhciResetPort(XHCI_CONTROLLER* xhci, uint8_t port, uint8_t* outSpeed) { if (protocol >= 3u || speed >= 4u) { - // SuperSpeed ports are enabled by link training. Do not issue a + // SuperSpeed ports are enabled by link training. Do not issue a // USB2-style PORTSC.PR reset; on real Intel controllers that can make - // the link drop during early enumeration. If firmware/controller + // the link drop during early enumeration. If firmware/controller // already has PR set, just wait for it to settle. bool enabled = false; @@ -1118,7 +1057,6 @@ KSTATUS XhciResetPort(XHCI_CONTROLLER* xhci, uint8_t port, uint8_t* outSpeed) { return STATUS_SUCCESS; } - // Creates device Memory. KSTATUS XhciAllocateDeviceMemory(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { dev->InputContext = (uint8_t*)UsbDmaAllocPages(xhci, 1, &dev->InputContextPhys); @@ -1143,7 +1081,6 @@ KSTATUS XhciAllocateDeviceMemory(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { return STATUS_SUCCESS; } - // Builds default Control Context. void XhciBuildDefaultControlContext(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { memset(dev->InputContext, 0, PAGE_SIZE); @@ -1187,13 +1124,11 @@ void XhciBuildDefaultControlContext(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { XhciBarrier(); } - // Implements the ep0 Trb Phys operation. uint64_t XhciEp0TrbPhys(XHCI_DEVICE* dev, uint32_t index) { return dev->Ep0RingPhys + (uint64_t)index * sizeof(XHCI_TRB); } - KSTATUS XhciEp0AppendTrb(XHCI_DEVICE* dev, uint64_t parameter, uint32_t status, @@ -1229,7 +1164,6 @@ KSTATUS XhciEp0AppendTrb(XHCI_DEVICE* dev, return STATUS_SUCCESS; } - KSTATUS XhciControlInTimeout(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, const USB_SETUP_PACKET* setup, void* buffer, @@ -1298,7 +1232,6 @@ KSTATUS XhciControlInTimeout(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, return STATUS_DEVICE_ERROR; } - KSTATUS XhciControlIn(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, const USB_SETUP_PACKET* setup, void* buffer, @@ -1308,7 +1241,6 @@ KSTATUS XhciControlIn(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, USB_XHCI_CONTROL_TIMEOUT_US); } - KSTATUS XhciControlNoData(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, const USB_SETUP_PACKET* setup) { uint64_t setupValue = UsbSetupPacketToU64(setup); @@ -1359,7 +1291,6 @@ KSTATUS XhciControlNoData(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, return STATUS_DEVICE_ERROR; } - KSTATUS XhciGetDeviceDescriptorTimeout(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, USB_DEVICE_DESCRIPTOR* outDesc, uint32_t timeoutUs) { @@ -1389,14 +1320,12 @@ KSTATUS XhciGetDeviceDescriptorTimeout(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, return STATUS_SUCCESS; } - KSTATUS XhciGetDeviceDescriptor(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, USB_DEVICE_DESCRIPTOR* outDesc) { return XhciGetDeviceDescriptorTimeout(xhci, dev, outDesc, USB_XHCI_CONTROL_TIMEOUT_US); } - KSTATUS XhciGetConfigurationDescriptor(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, uint8_t configurationIndex, USB_CONFIGURATION_DESCRIPTOR* outConfig, @@ -1456,7 +1385,6 @@ KSTATUS XhciGetConfigurationDescriptor(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, return STATUS_SUCCESS; } - // Implements the endpoint Id From Address operation. uint8_t XhciEndpointIdFromAddress(uint8_t endpointAddress) { uint8_t number = endpointAddress & USB_ENDPOINT_NUMBER_MASK; @@ -1464,14 +1392,13 @@ uint8_t XhciEndpointIdFromAddress(uint8_t endpointAddress) { return (uint8_t)(number * 2u + ((endpointAddress & USB_ENDPOINT_DIR_IN) ? 1u : 0u)); } - // Implements the interrupt Interval operation. uint8_t XhciInterruptInterval(uint8_t speed, uint8_t usbInterval) { if (usbInterval == 0) usbInterval = 1; if (speed == 3 || speed == 4 || speed == 5) { // High-speed and newer interrupt endpoints already encode bInterval as - // an exponent in USB descriptors. xHCI stores the zero-based exponent. + // an exponent in USB descriptors. xHCI stores the zero-based exponent. if (usbInterval > 16) usbInterval = 16; return (uint8_t)(usbInterval - 1u); } @@ -1488,7 +1415,6 @@ uint8_t XhciInterruptInterval(uint8_t speed, uint8_t usbInterval) { return interval; } - KSTATUS XhciRingAppend(XHCI_TRB* ring, PHYSICAL_ADDRESS ringPhys, uint32_t trbCount, uint32_t* enqueue, uint8_t* cycle, uint64_t parameter, uint32_t status, @@ -1523,7 +1449,6 @@ KSTATUS XhciRingAppend(XHCI_TRB* ring, PHYSICAL_ADDRESS ringPhys, return STATUS_SUCCESS; } - void XhciInitialiseTransferRing(XHCI_TRB* ring, PHYSICAL_ADDRESS ringPhys, uint32_t trbCount, uint8_t cycle) { memset(ring, 0, trbCount * sizeof(XHCI_TRB)); @@ -1536,7 +1461,6 @@ void XhciInitialiseTransferRing(XHCI_TRB* ring, PHYSICAL_ADDRESS ringPhys, cycle; } - KSTATUS XhciBulkTransfer(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, uint8_t endpointId, XHCI_TRB* ring, @@ -1595,9 +1519,6 @@ KSTATUS XhciBulkTransfer(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, return STATUS_SUCCESS; } - - - // Implements the configure Pipe Endpoint operation. KSTATUS XhciConfigurePipeEndpoint(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, USB_PIPE* pipe) { if (!xhci || !dev || !pipe || !pipe->Open) diff --git a/code/drivers/display/vmwaresvga/vmwaresvga.c b/code/drivers/display/vmwaresvga/vmwaresvga.c index 3060af8..d7b8740 100644 --- a/code/drivers/display/vmwaresvga/vmwaresvga.c +++ b/code/drivers/display/vmwaresvga/vmwaresvga.c @@ -254,10 +254,10 @@ static void VmwSvgaCursorCommit(VMWSVGA_DEVICE* v) { return; } - // Legacy QEMU SVGA cursors are frontend/host cursors. Re-defining the + // Legacy QEMU SVGA cursors are frontend/host cursors. Re-defining the // cursor image from the input path can stall the FIFO/frontend after a // GTK focus/grab transition, so cursor activity must only update the - // legacy position/visibility registers. The image is defined once when + // legacy position/visibility registers. The image is defined once when // SetCursorImage runs. VmwSvgaWriteReg(v, SVGA_REG_CURSOR_ID, 0u); @@ -315,7 +315,7 @@ static uint32_t VmwSvgaPackMonoCursorDword(const uint8_t* bits, int y, uint32_t dwordIndex) { - // SVGA classic cursor scanlines are padded to 32 bits. QEMU stores + // SVGA classic cursor scanlines are padded to 32 bits. QEMU stores // FIFO dwords in guest-endian order and then interprets the result as // bytes via cursor_set_mono(), whose first pixel is bit 0x80 of byte 0. // Therefore byte 0 of the numeric dword must contain pixels 0..7, @@ -351,7 +351,7 @@ static int VmwSvgaClassicCursorWidth(int width) { // For widths that are not a multiple of 32 pixels, those two row // strides differ and every other row is decoded from padding bytes. // Define the classic cursor with a 32-pixel-aligned visible width and - // leave the extra pixels transparent. The real cursor image remains + // leave the extra pixels transparent. The real cursor image remains // at the left edge and the hotspot is unchanged. return (width + 31) & ~31; @@ -364,11 +364,11 @@ static void VmwSvgaBuildMonoCursorBits(const uint32_t* argbPixels, uint8_t* andBits, uint8_t* xorBits) { - // Classic SVGA cursors are AND/XOR masks. The most compatible path is + // Classic SVGA cursors are AND/XOR masks. The most compatible path is // a 1bpp AND mask plus a 1bpp XOR mask: // transparent: AND=1 XOR=0 - // black: AND=0 XOR=0 - // white: AND=0 XOR=1 + // black: AND=0 XOR=0 + // white: AND=0 XOR=1 // This intentionally discards antialiasing, but avoids QEMU/GTK's // fragile 32bpp classic host-cursor path. @@ -509,7 +509,6 @@ static KSTATUS VmwSvgaDefineCursorImage(VMWSVGA_DEVICE* v, int width, int height return STATUS_SUCCESS; } - // Implements the align Up32 operation. static uint32_t VmwSvgaAlignUp32(uint32_t value, uint32_t alignment) { return (value + alignment - 1u) & ~(alignment - 1u); @@ -607,9 +606,9 @@ static KSTATUS VmwSvgaInitializeScreenObject(VMWSVGA_DEVICE* v) { ((v->Capabilities & SVGA_CAP_SCREEN_OBJECT_2) != 0); // Real VMware can advertise SCREEN_OBJECT_2 without also setting the - // original FIFO_CAP_SCREEN_OBJECT bit. The command set is still the + // original FIFO_CAP_SCREEN_OBJECT bit. The command set is still the // Screen Object command set: DEFINE_SCREEN, DEFINE_GMRFB, and - // BLIT_GMRFB_TO_SCREEN. Treat either capability generation as usable. + // BLIT_GMRFB_TO_SCREEN. Treat either capability generation as usable. if (!hasScreenObject1 && !hasScreenObject2) { DbgPrint("VMWSVGA: screen object unavailable: screenObject1=%u screenObject2=%u caps=0x%x fifoCaps=0x%x\n", diff --git a/code/drivers/display/vmwaresvga/vmwaresvga.h b/code/drivers/display/vmwaresvga/vmwaresvga.h index feb8567..5641a07 100644 --- a/code/drivers/display/vmwaresvga/vmwaresvga.h +++ b/code/drivers/display/vmwaresvga/vmwaresvga.h @@ -127,7 +127,7 @@ #define SVGA_SCREEN_OBJECT_ID_PRIMARY 0u #define SVGA_GMRFB_FORMAT_32BPP_24DEPTH ((32u) | (24u << 8)) -// The production path is legacy GFB + UPDATE. Screen objects are kept behind +// The production path is legacy GFB + UPDATE. Screen objects are kept behind // an opt-in switch until the SO/GMRFB path is independently validated on real // VMware hosts. diff --git a/code/drivers/drivers.ini b/code/drivers/drivers.ini index 57ef400..677508f 100644 --- a/code/drivers/drivers.ini +++ b/code/drivers/drivers.ini @@ -6,6 +6,7 @@ ahci.sys ata.sys usb.sys fat32.sys +ntfs.sys [runtime] lwip.sys diff --git a/code/drivers/fs/fat32/fat32.c b/code/drivers/fs/fat32/fat32.c index 836901c..2ca4980 100644 --- a/code/drivers/fs/fat32/fat32.c +++ b/code/drivers/fs/fat32/fat32.c @@ -97,6 +97,7 @@ typedef struct { #pragma pack(pop) +#define ATTR_VOLUME_ID 0x08 #define ATTR_DIRECTORY 0x10 #define ATTR_ARCHIVE 0x20 #define ATTR_LFN 0x0F @@ -109,10 +110,10 @@ typedef struct { // FAT32_VOL forward declaration (needed by cache helpers). typedef struct FAT32_VOL_S FAT32_VOL; -// FAT sector cache (fixes #1 and #5) +// FAT sector cache (fixes #1 and #5) // 32 direct-mapped slots, indexed by (lba % FAT_CACHE_SLOTS). // Write-back: WriteFatEntry modifies the in-cache bytes and marks the slot -// dirty. FlushFatCache() propagates dirty slots to every FAT copy at once, +// dirty. FlushFatCache() propagates dirty slots to every FAT copy at once, // replacing the original per-entry 2x(read+write) pattern with a single // write per dirty sector per FAT copy. @@ -155,12 +156,12 @@ typedef struct FAT32_CHAIN_CACHE_ENTRY { uint32_t NextCluster; } FAT32_CHAIN_CACHE_ENTRY; -// The FAT sector cache is global and direct-mapped. mmap-backed video can fault +// The FAT sector cache is global and direct-mapped. mmap-backed video can fault // file pages from multiple Bink worker threads at the same time; without // serialization one thread can evict/reload a cache slot while another thread is // still reading a FAT entry from that slot, corrupting cluster-chain traversal. // Use a sleepable mutex rather than a spinlock because cache misses perform disk -// I/O. The static mutex avoids allocating an object from the paging path. +// I/O. The static mutex avoids allocating an object from the paging path. static MUTEX gFatCacheMutex; static SPINLOCK gFatCacheMutexInitLock = SPINLOCK_INIT; static int gFatCacheMutexReady; @@ -224,7 +225,7 @@ static KSTATUS FatCacheRead(FAT32_VOL *v, uint64_t lba, uint8_t **out) { e->lba = lba; e->valid = true; e->dirty = false; - *out = e->data; + *out = e->data; return STATUS_SUCCESS; } @@ -327,7 +328,7 @@ static void FlushFatCache(FAT32_VOL *v) { FatCacheRelease(held); } -// FAT read / write (through cache). +// FAT read / write (through cache). static uint32_t FatEntry(FAT32_VOL *v, uint32_t cluster) { uint32_t fatOffset = cluster * 4; @@ -368,7 +369,7 @@ static KSTATUS WriteFatEntry(FAT32_VOL *v, uint32_t cluster, uint32_t value) { return STATUS_SUCCESS; } -// Cluster allocation (fix #3: next-free hint + two-phase wrap-around scan). +// Cluster allocation (fix #3: next-free hint + two-phase wrap-around scan). static uint32_t AllocClusterEx(FAT32_VOL *v, int zeroCluster) { if (v->TotalClusters == 0) return 0; @@ -385,7 +386,7 @@ static uint32_t AllocClusterEx(FAT32_VOL *v, int zeroCluster) { v->NextFreeHint = c + 1; // Normal file growth zeroes newly allocated clusters so old - // disk contents are never exposed. Swap/pagefile creation is + // disk contents are never exposed. Swap/pagefile creation is // the one exception: every allocated slot is overwritten before // it can ever be read back, so skipping these writes avoids a // slow boot-time full-pagefile zero pass. @@ -404,7 +405,6 @@ static uint32_t AllocClusterEx(FAT32_VOL *v, int zeroCluster) { return 0; } - static uint32_t AllocCluster(FAT32_VOL *v) { return AllocClusterEx(v, 1); } @@ -703,9 +703,9 @@ static int FatIsValidShortChar(char c) { } } -// True when the user spelling can refer to a normal FAT 8.3 entry. This is +// True when the user spelling can refer to a normal FAT 8.3 entry. This is // intentionally structural and case-insensitive: "doom1.wad" must still open -// an existing short entry named DOOM1.WAD. Leading-dot names are not 8.3. +// an existing short entry named DOOM1.WAD. Leading-dot names are not 8.3. static int FatNameIsStructurallyShort83V(SYS_STRING_VIEW name) { if (!SysSvIsValid(name) || name.Length == 0) return 0; if (name.Data[0] == '.') return 0; @@ -757,7 +757,7 @@ static void FatSplitShortNameV(SYS_STRING_VIEW name, size_t* baseEnd, } // Creation uses LFN only when the spelling cannot be represented by a short -// entry, or when mixed-case 8.3 spelling would be lost. Plain lowercase +// entry, or when mixed-case 8.3 spelling would be lost. Plain lowercase // 8.3 names are represented with FAT's NT case bits, not VFAT LFN entries. static int FatNameNeedsLfnForCreateV(SYS_STRING_VIEW name) { if (!FatNameIsStructurallyShort83V(name)) return 1; @@ -991,7 +991,7 @@ static KSTATUS DeleteLegacyBrokenDotAliasV(FAT32_VOL *v, uint32_t dirCluster, for (int j = 0; j < 8; j++) if (dir[i].Name[j] != ' ') emptyBase = 0; if (!emptyBase) continue; - // Do not free the cluster chain here. The broken entry may have + // Do not free the cluster chain here. The broken entry may have // aliased existing data; deleting the directory entry is enough to // hide it from hosts and avoids freeing clusters still owned by a // valid file. @@ -1024,7 +1024,6 @@ static void LfnAppend(char *lfn, const LFN_ENTRY *e) { } } - // Directory search and view-native path traversal. static int Fat32IsPathSep(char c) { return c == '\\' || c == '/'; @@ -1109,7 +1108,7 @@ static void Fat32PathCacheAddDirectoryStats(FAT32_VOL* v, uint64_t clusters, uin if (!v) return; uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock); v->Stats.DirectorySearches++; - v->Stats.DirectoryClustersRead += clusters; + v->Stats.DirectoryBlocksRead += clusters; v->Stats.DirectoryEntriesScanned += entries; KeReleaseSpinlock(&v->PathCacheLock, flags); } @@ -1199,15 +1198,6 @@ void Fat32QueryStats(IO_FILESYSTEM* fs, IO_FILESYSTEM_STATS* out) { KeReleaseSpinlock(&v->PathCacheLock, flags); } - -// Adds FAT-chain traversal counters shared by the read path helpers. -static void Fat32StatsAddChainWalk(FAT32_VOL* v, uint64_t steps) { - if (!v || !steps) return; - uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock); - v->Stats.FatChainWalkSteps += steps; - KeReleaseSpinlock(&v->PathCacheLock, flags); -} - // Records one data read span and its physically contiguous cluster merges. static void Fat32StatsAddReadRun(FAT32_VOL* v, uint32_t sectors, uint32_t merges) { if (!v || !sectors) return; @@ -1235,12 +1225,6 @@ static void Fat32FileInvalidateChainCache(FAT32_FILE* f) { memset(f->ChainCache, 0, sizeof(f->ChainCache)); f->ChainCacheClock = 0; KeReleaseSpinlock(&f->ChainCacheLock, flags); - - FAT32_VOL* v = f->Vol; - if (!v) return; - flags = KeAcquireSpinlock(&v->PathCacheLock); - v->Stats.FatChainCacheInvalidations++; - KeReleaseSpinlock(&v->PathCacheLock, flags); } // Locates the best cached starting point for a logical cluster index. @@ -1281,15 +1265,6 @@ static bool Fat32ChainCacheLookupStart(FAT32_FILE* f, uint32_t logicalIndex, *outCluster = bestCluster; } KeReleaseSpinlock(&f->ChainCacheLock, flags); - - FAT32_VOL* v = f->Vol; - if (v) { - flags = KeAcquireSpinlock(&v->PathCacheLock); - v->Stats.FatChainCacheLookups++; - if (hit) v->Stats.FatChainCacheHits++; - else v->Stats.FatChainCacheMisses++; - KeReleaseSpinlock(&v->PathCacheLock, flags); - } return hit; } @@ -1309,12 +1284,6 @@ static void Fat32ChainCacheStore(FAT32_FILE* f, uint32_t logicalStart, e->Length = length; e->NextCluster = nextCluster; KeReleaseSpinlock(&f->ChainCacheLock, flags); - - FAT32_VOL* v = f->Vol; - if (!v) return; - flags = KeAcquireSpinlock(&v->PathCacheLock); - v->Stats.FatChainCacheStores++; - KeReleaseSpinlock(&v->PathCacheLock, flags); } // Resolves a logical file cluster through the cached FAT-chain extent map. @@ -1331,13 +1300,10 @@ static uint32_t Fat32ResolveFileCluster(FAT32_FILE* f, uint32_t logicalIndex) { cluster = f->StartCluster; } - uint64_t steps = 0; while (currentLogical < logicalIndex && cluster >= 2 && cluster < FAT32_EOC) { cluster = FatEntry(f->Vol, cluster); currentLogical++; - steps++; } - Fat32StatsAddChainWalk(f->Vol, steps); return cluster; } @@ -1355,7 +1321,6 @@ static bool Fat32BuildReadRun(FAT32_FILE* f, uint32_t logicalIndex, uint32_t runClusters = 1; uint32_t last = startCluster; uint32_t next = FatEntry(v, last); - uint64_t steps = 1; while (next == last + 1 && next < FAT32_EOC && runSectors + v->SectorsPerCluster <= maxSectors) { @@ -1363,10 +1328,8 @@ static bool Fat32BuildReadRun(FAT32_FILE* f, uint32_t logicalIndex, runClusters++; runSectors += v->SectorsPerCluster; next = FatEntry(v, last); - steps++; } - Fat32StatsAddChainWalk(v, steps); Fat32ChainCacheStore(f, logicalIndex, startCluster, runClusters, next); *outRunSectors = runSectors; *outNextCluster = next; @@ -1414,8 +1377,8 @@ static int SearchDirV(FAT32_VOL *v, uint32_t dirCluster, SYS_STRING_VIEW name, if (match) { *outCluster = ((uint32_t)dir[i].FirstClusterHi << 16) | dir[i].FirstClusterLo; - *outSize = dir[i].FileSize; - *outAttr = dir[i].Attr; + *outSize = dir[i].FileSize; + *outAttr = dir[i].Attr; if (outDirentLba || outDirentOffset) { uint64_t eb = (uint64_t)i * sizeof(DIRENT); if (outDirentLba) *outDirentLba = clusterLba + eb / v->BytesPerSector; @@ -1453,6 +1416,7 @@ static int Fat32PathNextComponent(SYS_STRING_VIEW path, size_t* cursor, static KSTATUS Fat32LookupPathV(FAT32_VOL *v, SYS_STRING_VIEW path, uint32_t *outCluster, uint32_t *outSize, + uint8_t *outAttr, uint64_t *outDirentLba, uint32_t *outDirentOffset) { if (!v || !outCluster || !outSize || !SysSvIsValid(path)) return STATUS_INVALID_PARAMETER; @@ -1461,12 +1425,16 @@ static KSTATUS Fat32LookupPathV(FAT32_VOL *v, SYS_STRING_VIEW path, uint32_t cluster = v->RootCluster, size = 0; uint8_t attr = ATTR_DIRECTORY; + uint64_t finalDirentLba = 0; + uint32_t finalDirentOffset = 0; if (outDirentLba) *outDirentLba = 0; if (outDirentOffset) *outDirentOffset = 0; if (Fat32PathCacheLookup(v, path, outCluster, outSize, &attr, - outDirentLba, outDirentOffset)) + outDirentLba, outDirentOffset)) { + if (outAttr) *outAttr = attr; return STATUS_SUCCESS; + } size_t cursor = 0; SYS_STRING_VIEW component; @@ -1482,21 +1450,23 @@ static KSTATUS Fat32LookupPathV(FAT32_VOL *v, SYS_STRING_VIEW path, return STATUS_NOT_FOUND; cluster = childCluster; size = childSize; attr = childAttr; + finalDirentLba = childLba; + finalDirentOffset = childOffset; if (outDirentLba) *outDirentLba = childLba; if (outDirentOffset) *outDirentOffset = childOffset; } *outCluster = cluster; *outSize = size; + if (outAttr) *outAttr = attr; Fat32PathCacheStore(v, path, cluster, size, attr, - outDirentLba ? *outDirentLba : 0, - outDirentOffset ? *outDirentOffset : 0); + finalDirentLba, finalDirentOffset); return STATUS_SUCCESS; } static KSTATUS TraverseV(FAT32_VOL *v, SYS_STRING_VIEW path, uint32_t *outCluster, uint32_t *outSize) { - return Fat32LookupPathV(v, path, outCluster, outSize, NULL, NULL); + return Fat32LookupPathV(v, path, outCluster, outSize, NULL, NULL, NULL); } static KSTATUS SplitPathV(SYS_STRING_VIEW path, SYS_STRING_VIEW* parent, @@ -1546,7 +1516,7 @@ static int FindFreeDirent(FAT32_VOL *v, uint32_t dirCluster, uint8_t first = (uint8_t)dir[i].Name[0]; if (first == 0xE5 || first == 0x00) { uint64_t eb = (uint64_t)i * sizeof(DIRENT); - *outLba = clusterLba + eb / v->BytesPerSector; + *outLba = clusterLba + eb / v->BytesPerSector; *outOffset = (uint32_t)(eb % v->BytesPerSector); MmFreePool(clusterBuf); return 1; } @@ -1564,7 +1534,6 @@ static int FindFreeDirent(FAT32_VOL *v, uint32_t dirCluster, return 0; } - // Find a contiguous run of free directory entries within one directory cluster. // This is required for VFAT LFN creation because N long-name entries must // immediately precede the final short entry. @@ -1619,6 +1588,284 @@ static int FindFreeDirentRun(FAT32_VOL *v, uint32_t dirCluster, uint32_t needed, return 0; } +// Finds the parent directory and final component for a create or delete path. +static KSTATUS Fat32ResolveParentForMutation(FAT32_VOL *v, SYS_STRING_VIEW path, + uint32_t *parentClusterOut, + SYS_STRING_VIEW *filenameOut) { + if (!v || !parentClusterOut || !filenameOut) + return STATUS_INVALID_PARAMETER; + + SYS_STRING_VIEW parentPath; + SYS_STRING_VIEW filenameView; + KSTATUS s = SplitPathV(path, &parentPath, &filenameView); + if (!KSUCCESS(s)) return s; + + uint32_t parentCluster = 0; + uint32_t parentSize = 0; + uint8_t parentAttr = 0; + s = Fat32LookupPathV(v, parentPath, &parentCluster, &parentSize, + &parentAttr, NULL, NULL); + if (!KSUCCESS(s)) return s; + if (!(parentAttr & ATTR_DIRECTORY)) return STATUS_NOT_FOUND; + + *parentClusterOut = parentCluster; + *filenameOut = filenameView; + return STATUS_SUCCESS; +} + +// Creates a FAT directory entry run for a new file or directory. +static KSTATUS Fat32CreateDirent(FAT32_VOL *v, SYS_STRING_VIEW path, + uint8_t attr, uint32_t firstCluster, + uint32_t fileSize, + uint64_t *direntLbaOut, + uint32_t *direntOffsetOut) { + if (!v || !SysSvIsValid(path) || path.Length == 0) + return STATUS_INVALID_PARAMETER; + + uint32_t parentCluster = 0; + SYS_STRING_VIEW filenameView; + KSTATUS s = Fat32ResolveParentForMutation(v, path, &parentCluster, &filenameView); + if (!KSUCCESS(s)) return s; + + { + uint32_t ec, es; + uint8_t ea; + uint64_t existingLba = 0; + uint32_t existingOffset = 0; + if (SearchDirV(v, parentCluster, filenameView, &ec, &es, &ea, + &existingLba, &existingOffset)) { + return STATUS_OBJECT_NAME_COLLISION; + } + } + + int useLfn = FatNameNeedsLfnForCreateV(filenameView); + char shortName[8], shortExt[3]; + if (useLfn) { + s = DeleteLegacyBrokenDotAliasV(v, parentCluster, filenameView); + if (!KSUCCESS(s)) return s; + if (!MakeUniqueShortAliasV(v, parentCluster, filenameView, shortName, shortExt)) + return STATUS_OBJECT_NAME_COLLISION; + } else { + Make83NameV(filenameView, shortName, shortExt); + } + + size_t nameLen = filenameView.Length; + if (nameLen == 0 || nameLen >= MAX_LFN) return STATUS_INVALID_PARAMETER; + uint32_t lfnCount = useLfn ? (uint32_t)((nameLen + 12u) / 13u) : 0; + uint32_t neededEntries = lfnCount + 1u; + + uint32_t entryCluster, firstIndex; + if (!FindFreeDirentRun(v, parentCluster, neededEntries, &entryCluster, &firstIndex)) + return STATUS_NO_MEMORY; + + uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + if (!clusterBuf) return STATUS_NO_MEMORY; + + uint64_t clusterLba = ClusterToLba(v, entryCluster); + s = ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf); + if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; } + + DIRENT *dir = (DIRENT *)clusterBuf; + uint32_t shortIndex = firstIndex + lfnCount; + + if (useLfn) { + uint8_t checksum = LfnChecksum(shortName, shortExt); + for (uint32_t slot = 0; slot < lfnCount; slot++) { + uint32_t ordinal = lfnCount - slot; + BuildLfnEntry((LFN_ENTRY *)&dir[firstIndex + slot], filenameView.Data, + nameLen, ordinal, lfnCount, checksum); + } + } + + DIRENT *d = &dir[shortIndex]; + memset(d, 0, sizeof(DIRENT)); + memcpy(d->Name, shortName, 8); + memcpy(d->Ext, shortExt, 3); + d->Attr = attr; + d->Reserved = FatShortNameCaseFlagsV(filenameView); + d->FirstClusterHi = (uint16_t)(firstCluster >> 16); + d->FirstClusterLo = (uint16_t)(firstCluster & 0xFFFFu); + d->FileSize = fileSize; + { + FAT32_ENCODED_TIME now; + FatQueryEncodedTime(&now); + d->CrtTimeTenth = now.CreateTimeTenth; + d->CrtTime = now.Time; + d->CrtDate = now.Date; + d->LastAccDate = now.Date; + d->WrtTime = now.Time; + d->WrtDate = now.Date; + } + + s = WriteSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf); + MmFreePool(clusterBuf); + if (!KSUCCESS(s)) return s; + + uint64_t shortEntryByte = (uint64_t)shortIndex * sizeof(DIRENT); + uint64_t direntLba = clusterLba + shortEntryByte / v->BytesPerSector; + uint32_t direntOffset = (uint32_t)(shortEntryByte % v->BytesPerSector); + if (direntLbaOut) *direntLbaOut = direntLba; + if (direntOffsetOut) *direntOffsetOut = direntOffset; + + Fat32PathCacheInvalidate(v); + return STATUS_SUCCESS; +} + +// Initializes one dot entry in a new directory cluster. +static void Fat32InitDotEntry(DIRENT *d, const char *name, + uint32_t firstCluster, + const FAT32_ENCODED_TIME *now) { + memset(d, 0, sizeof(*d)); + memset(d->Name, ' ', sizeof(d->Name)); + memset(d->Ext, ' ', sizeof(d->Ext)); + d->Name[0] = name[0]; + if (name[1]) d->Name[1] = name[1]; + d->Attr = ATTR_DIRECTORY; + d->FirstClusterHi = (uint16_t)(firstCluster >> 16); + d->FirstClusterLo = (uint16_t)(firstCluster & 0xFFFFu); + d->CrtTimeTenth = now->CreateTimeTenth; + d->CrtTime = now->Time; + d->CrtDate = now->Date; + d->LastAccDate = now->Date; + d->WrtTime = now->Time; + d->WrtDate = now->Date; +} + +// Writes the initial contents of a newly allocated FAT directory cluster. +static KSTATUS Fat32InitializeDirectoryCluster(FAT32_VOL *v, uint32_t dirCluster, + uint32_t parentCluster) { + if (!v || dirCluster < 2) + return STATUS_INVALID_PARAMETER; + + uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + if (!clusterBuf) return STATUS_NO_MEMORY; + memset(clusterBuf, 0, v->BytesPerCluster); + + FAT32_ENCODED_TIME now; + FatQueryEncodedTime(&now); + DIRENT *dir = (DIRENT *)clusterBuf; + Fat32InitDotEntry(&dir[0], ".", dirCluster, &now); + Fat32InitDotEntry(&dir[1], "..", parentCluster, &now); + + KSTATUS s = WriteSectors(v, ClusterToLba(v, dirCluster), + v->SectorsPerCluster, clusterBuf); + MmFreePool(clusterBuf); + return s; +} + +// Tests whether a FAT directory entry is one of the synthetic dot entries. +static int Fat32IsDotEntry(const DIRENT *d) { + if (!d || !(d->Attr & ATTR_DIRECTORY)) return 0; + if (d->Name[0] != '.') return 0; + if (d->Name[1] == ' ') + return 1; + return d->Name[1] == '.' && d->Name[2] == ' '; +} + +// Tests whether a directory contains any real child entries. +static KSTATUS Fat32DirectoryIsEmpty(FAT32_VOL *v, uint32_t dirCluster, + int *emptyOut) { + if (!v || !emptyOut || dirCluster < 2) + return STATUS_INVALID_PARAMETER; + + uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + if (!clusterBuf) return STATUS_NO_MEMORY; + + uint32_t cluster = dirCluster; + while (cluster >= 2 && cluster < FAT32_EOC) { + KSTATUS s = ReadSectors(v, ClusterToLba(v, cluster), + v->SectorsPerCluster, clusterBuf); + if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; } + + uint32_t entries = v->BytesPerCluster / sizeof(DIRENT); + DIRENT *dir = (DIRENT *)clusterBuf; + for (uint32_t i = 0; i < entries; i++) { + uint8_t first = (uint8_t)dir[i].Name[0]; + if (first == 0x00) { MmFreePool(clusterBuf); *emptyOut = 1; return STATUS_SUCCESS; } + if (first == 0xE5 || dir[i].Attr == ATTR_LFN) continue; + if ((dir[i].Attr & ATTR_VOLUME_ID) && !(dir[i].Attr & ATTR_DIRECTORY)) continue; + if (Fat32IsDotEntry(&dir[i])) continue; + + MmFreePool(clusterBuf); + *emptyOut = 0; + return STATUS_SUCCESS; + } + + cluster = FatEntry(v, cluster); + } + + MmFreePool(clusterBuf); + *emptyOut = 1; + return STATUS_SUCCESS; +} + +// Deletes the directory-entry run for one child name from a parent directory. +static KSTATUS Fat32DeleteDirentRunV(FAT32_VOL *v, uint32_t parentCluster, + SYS_STRING_VIEW name) { + if (!v || !Fat32NameViewIsValid(name)) + return STATUS_INVALID_PARAMETER; + + uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + if (!clusterBuf) return STATUS_NO_MEMORY; + + char lfn[MAX_LFN]; + int hasLfn = 0; + uint32_t lfnStart = 0; + memset(lfn, 0, sizeof(lfn)); + + char wantName83[8], wantExt83[3]; + int allowShortMatch = FatNameIsStructurallyShort83V(name); + Make83NameV(name, wantName83, wantExt83); + + uint32_t cluster = parentCluster; + while (cluster >= 2 && cluster < FAT32_EOC) { + uint64_t clusterLba = ClusterToLba(v, cluster); + KSTATUS s = ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf); + if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; } + + uint32_t entries = v->BytesPerCluster / sizeof(DIRENT); + DIRENT *dir = (DIRENT *)clusterBuf; + hasLfn = 0; + memset(lfn, 0, sizeof(lfn)); + for (uint32_t i = 0; i < entries; i++) { + uint8_t first = (uint8_t)dir[i].Name[0]; + if (first == 0x00) { MmFreePool(clusterBuf); return STATUS_NOT_FOUND; } + if (first == 0xE5) { hasLfn = 0; memset(lfn, 0, sizeof(lfn)); continue; } + if (dir[i].Attr == ATTR_LFN) { + if (!hasLfn) lfnStart = i; + LfnAppend(lfn, (LFN_ENTRY *)&dir[i]); + hasLfn = 1; + continue; + } + + int match = 0; + if (hasLfn && lfn[0]) match = LfnMatchV(lfn, name); + if (!match && allowShortMatch) + match = MatchName83V(dir[i].Name, dir[i].Ext, name); + if (!match && allowShortMatch) + match = MatchRaw83(dir[i].Name, dir[i].Ext, wantName83, wantExt83); + + if (match) { + uint32_t start = hasLfn ? lfnStart : i; + for (uint32_t j = start; j <= i; j++) + dir[j].Name[0] = (char)0xE5; + s = WriteSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf); + MmFreePool(clusterBuf); + if (KSUCCESS(s)) Fat32PathCacheInvalidate(v); + return s; + } + + hasLfn = 0; + memset(lfn, 0, sizeof(lfn)); + } + + cluster = FatEntry(v, cluster); + } + + MmFreePool(clusterBuf); + return STATUS_NOT_FOUND; +} + // Open / Close. KSTATUS Fat32Open(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx) { if (!fs || !fileCtx || !SysSvIsValid(path) || path.Length == 0) @@ -1626,7 +1873,7 @@ KSTATUS Fat32Open(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx) { FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; - KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, &direntLba, &direntOffset); + KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, NULL, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE)); @@ -1646,7 +1893,7 @@ void Fat32Close(IO_FILESYSTEM *fs, void *fileCtx) { MmFreePool(fileCtx); } -// Read (fix #4: contiguous-run coalescing) +// Read (fix #4: contiguous-run coalescing) // Cluster-chain lookup is cached per open file, and data spans are allowed to // grow beyond the AHCI bounce size because the device layer now splits large // sector requests for the concrete controller. @@ -1722,7 +1969,7 @@ KSTATUS Fat32Read(IO_FILESYSTEM *fs, void *fileCtx, if (totalRead != wanted) { // The directory entry says the file has more data than the cluster chain - // or disk read actually supplied. Clear the caller's remainder so stale + // or disk read actually supplied. Clear the caller's remainder so stale // memory cannot be interpreted as file contents. memset(out, 0, wanted - totalRead); } @@ -1734,8 +1981,8 @@ KSTATUS Fat32Read(IO_FILESYSTEM *fs, void *fileCtx, return STATUS_SUCCESS; } -// Write (fix #4: contiguous-run coalescing) -// Same strategy as read. Whole-cluster-aligned runs skip the read-modify- +// Write (fix #4: contiguous-run coalescing) +// Same strategy as read. Whole-cluster-aligned runs skip the read-modify- // write; partial head/tail clusters still require it. KSTATUS Fat32Write(IO_FILESYSTEM *fs, void *fileCtx, @@ -1852,121 +2099,99 @@ KSTATUS Fat32Write(IO_FILESYSTEM *fs, void *fileCtx, } // Create. -// Creates the requested object. +// Creates the requested file and fails if the name already exists. KSTATUS Fat32Create(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx) { if (!fs || !fileCtx || !SysSvIsValid(path) || path.Length == 0) return STATUS_INVALID_PARAMETER; FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; - SYS_STRING_VIEW parentPath; - SYS_STRING_VIEW filenameView; - KSTATUS s = SplitPathV(path, &parentPath, &filenameView); + uint64_t direntLba = 0; + uint32_t direntOffset = 0; + KSTATUS s = Fat32CreateDirent(v, path, ATTR_ARCHIVE, 0, 0, + &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; - uint32_t parentCluster, parentSize; - s = TraverseV(v, parentPath, &parentCluster, &parentSize); - if (!KSUCCESS(s)) return s; - - { - uint32_t ec, es; uint8_t ea; - uint64_t existingLba = 0; uint32_t existingOffset = 0; - if (SearchDirV(v, parentCluster, filenameView, &ec, &es, &ea, - &existingLba, &existingOffset)) { - if (ea & ATTR_DIRECTORY) - return STATUS_OBJECT_NAME_COLLISION; - - FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE)); - if (!f) return STATUS_NO_MEMORY; - f->StartCluster = ec; - f->FileSize = es; - f->Vol = v; - f->DirentLba = existingLba; - f->DirentOffset = existingOffset; - - // Create means overwrite by default for regular files. Reuse the - // existing directory entry, drop the old cluster chain, and return - // an open zero-length file instead of allocating a duplicate name. - KSTATUS ts = Fat32Truncate(fs, f, 0, 0); - if (!KSUCCESS(ts)) { MmFreePool(f); return ts; } - *fileCtx = f; - return STATUS_SUCCESS; - } - } - - int useLfn = FatNameNeedsLfnForCreateV(filenameView); - char shortName[8], shortExt[3]; - if (useLfn) { - s = DeleteLegacyBrokenDotAliasV(v, parentCluster, filenameView); - if (!KSUCCESS(s)) return s; - if (!MakeUniqueShortAliasV(v, parentCluster, filenameView, shortName, shortExt)) - return STATUS_OBJECT_NAME_COLLISION; - } else { - Make83NameV(filenameView, shortName, shortExt); - } - - size_t nameLen = filenameView.Length; - if (nameLen == 0 || nameLen >= MAX_LFN) return STATUS_INVALID_PARAMETER; - uint32_t lfnCount = useLfn ? (uint32_t)((nameLen + 12u) / 13u) : 0; - uint32_t neededEntries = lfnCount + 1u; - - uint32_t entryCluster, firstIndex; - if (!FindFreeDirentRun(v, parentCluster, neededEntries, &entryCluster, &firstIndex)) - return STATUS_NO_MEMORY; - - uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); - if (!clusterBuf) return STATUS_NO_MEMORY; - - uint64_t clusterLba = ClusterToLba(v, entryCluster); - s = ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf); - if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; } - - DIRENT *dir = (DIRENT *)clusterBuf; - uint32_t shortIndex = firstIndex + lfnCount; - - if (useLfn) { - uint8_t checksum = LfnChecksum(shortName, shortExt); - for (uint32_t slot = 0; slot < lfnCount; slot++) { - uint32_t ordinal = lfnCount - slot; - BuildLfnEntry((LFN_ENTRY *)&dir[firstIndex + slot], filenameView.Data, - nameLen, ordinal, lfnCount, checksum); - } - } - - DIRENT *d = &dir[shortIndex]; - memset(d, 0, sizeof(DIRENT)); - memcpy(d->Name, shortName, 8); - memcpy(d->Ext, shortExt, 3); - d->Attr = ATTR_ARCHIVE; - d->Reserved = FatShortNameCaseFlagsV(filenameView); - { - FAT32_ENCODED_TIME now; - FatQueryEncodedTime(&now); - d->CrtTimeTenth = now.CreateTimeTenth; - d->CrtTime = now.Time; - d->CrtDate = now.Date; - d->LastAccDate = now.Date; - d->WrtTime = now.Time; - d->WrtDate = now.Date; - } - - s = WriteSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf); - MmFreePool(clusterBuf); - if (!KSUCCESS(s)) return s; - - uint64_t shortEntryByte = (uint64_t)shortIndex * sizeof(DIRENT); - uint64_t direntLba = clusterLba + shortEntryByte / v->BytesPerSector; - uint32_t direntOffset = (uint32_t)(shortEntryByte % v->BytesPerSector); - FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE)); if (!f) return STATUS_NO_MEMORY; - f->StartCluster = 0; f->FileSize = 0; f->Vol = v; - f->DirentLba = direntLba; f->DirentOffset = direntOffset; + f->StartCluster = 0; + f->FileSize = 0; + f->Vol = v; + f->DirentLba = direntLba; + f->DirentOffset = direntOffset; Fat32FileInitChainCache(f); - Fat32PathCacheInvalidate(v); *fileCtx = f; return STATUS_SUCCESS; } +// Creates a directory and initializes its dot entries. +KSTATUS Fat32CreateDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) { + if (!fs || !SysSvIsValid(path) || path.Length == 0) + return STATUS_INVALID_PARAMETER; + + FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; + uint32_t parentCluster = 0; + SYS_STRING_VIEW filenameView; + KSTATUS s = Fat32ResolveParentForMutation(v, path, &parentCluster, &filenameView); + if (!KSUCCESS(s)) return s; + (void)filenameView; + + uint32_t dirCluster = AllocCluster(v); + if (!dirCluster) return STATUS_NO_MEMORY; + + s = Fat32InitializeDirectoryCluster(v, dirCluster, parentCluster); + if (!KSUCCESS(s)) { + FreeClusterChain(v, dirCluster); + FlushFatCache(v); + return s; + } + + s = Fat32CreateDirent(v, path, ATTR_DIRECTORY, dirCluster, 0, NULL, NULL); + if (!KSUCCESS(s)) { + FreeClusterChain(v, dirCluster); + FlushFatCache(v); + return s; + } + + FlushFatCache(v); + return STATUS_SUCCESS; +} + +// Removes an empty directory. +KSTATUS Fat32RemoveDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) { + if (!fs || !SysSvIsValid(path) || path.Length == 0) + return STATUS_INVALID_PARAMETER; + + FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; + uint32_t cluster = 0; + uint32_t size = 0; + uint8_t attr = 0; + uint64_t direntLba = 0; + uint32_t direntOffset = 0; + KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, &attr, + &direntLba, &direntOffset); + if (!KSUCCESS(s)) return s; + if (!direntLba) return STATUS_ACCESS_DENIED; + if (!(attr & ATTR_DIRECTORY)) return STATUS_ACCESS_DENIED; + + int empty = 0; + s = Fat32DirectoryIsEmpty(v, cluster, &empty); + if (!KSUCCESS(s)) return s; + if (!empty) return STATUS_ACCESS_DENIED; + + uint32_t parentCluster = 0; + SYS_STRING_VIEW filenameView; + s = Fat32ResolveParentForMutation(v, path, &parentCluster, &filenameView); + if (!KSUCCESS(s)) return s; + + s = Fat32DeleteDirentRunV(v, parentCluster, filenameView); + if (!KSUCCESS(s)) return s; + + if (cluster >= 2) FreeClusterChain(v, cluster); + FlushFatCache(v); + Fat32PathCacheInvalidate(v); + return STATUS_SUCCESS; +} + // Truncate. KSTATUS Fat32Truncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize, uint32_t fileFlags) { (void)fs; @@ -2044,7 +2269,7 @@ KSTATUS Fat32Delete(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) { FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; - KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, &direntLba, &direntOffset); + KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, NULL, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; if (!direntLba) return STATUS_ACCESS_DENIED; @@ -2066,6 +2291,139 @@ uint64_t Fat32GetSize(IO_FILESYSTEM *fs, void *fileCtx) { (void)fs; return ((FAT32_FILE *)fileCtx)->FileSize; } +// Returns a stable volume identity for metadata and cache keys. +static uint64_t Fat32VolumeIdentity(FAT32_VOL *v) { + if (!v) return 0; + return ((uint64_t)v->DriveIndex << 56) ^ + ((uint64_t)v->PartitionLba << 8) ^ + ((uint64_t)v->RootCluster << 1) ^ + 0x4641543332494445ULL; +} + +// Converts FAT attributes to the public file type. +static uint32_t Fat32FileTypeFromAttr(uint8_t attr) { + return (attr & ATTR_DIRECTORY) ? SYS_FILE_TYPE_DIRECTORY : SYS_FILE_TYPE_REGULAR; +} + +// Fills public metadata from one FAT directory entry. +static void Fat32FillStatFromDirent(FAT32_VOL *v, const DIRENT *d, + uint64_t direntLba, uint32_t direntOffset, + SYS_FILE_STAT *out) { + memset(out, 0, sizeof(*out)); + out->Size = sizeof(*out); + out->Type = Fat32FileTypeFromAttr(d->Attr); + out->Attributes = d->Attr; + out->LinkCount = 1; + out->FileSize = d->FileSize; + out->VolumeId = Fat32VolumeIdentity(v); + + uint32_t firstCluster = ((uint32_t)d->FirstClusterHi << 16) | d->FirstClusterLo; + out->FileId = direntLba ^ + ((uint64_t)direntOffset << 32) ^ + ((uint64_t)firstCluster << 1); + + FatDecodeDateTime(d->CrtDate, d->CrtTime, d->CrtTimeTenth, &out->CreationTime); + FatDecodeAccessDate(d->LastAccDate, &out->LastAccessTime); + FatDecodeDateTime(d->WrtDate, d->WrtTime, 0, &out->LastWriteTime); + out->ChangeTime = out->LastWriteTime; +} + +// Fills public metadata for the filesystem root. +static void Fat32FillRootStat(FAT32_VOL *v, SYS_FILE_STAT *out) { + memset(out, 0, sizeof(*out)); + out->Size = sizeof(*out); + out->Type = SYS_FILE_TYPE_DIRECTORY; + out->Attributes = SYS_FILE_ATTR_DIRECTORY; + out->LinkCount = 1; + out->FileSize = 0; + out->VolumeId = Fat32VolumeIdentity(v); + out->FileId = ((uint64_t)v->RootCluster << 1) ^ 0x524f4f54ULL; +} + +// Queries metadata for an open FAT32 file. +KSTATUS Fat32QueryInfo(IO_FILESYSTEM *fs, void *fileCtx, SYS_FILE_STAT *out) { + (void)fs; + if (!fileCtx || !out) return STATUS_INVALID_PARAMETER; + + FAT32_FILE *f = (FAT32_FILE *)fileCtx; + FAT32_VOL *v = f->Vol; + if (!v) return STATUS_INVALID_PARAMETER; + + if (f->DirentLba == 0) { + if (f->StartCluster == v->RootCluster && f->FileSize == 0) { + Fat32FillRootStat(v, out); + } else { + memset(out, 0, sizeof(*out)); + out->Size = sizeof(*out); + out->Type = SYS_FILE_TYPE_REGULAR; + out->Attributes = SYS_FILE_ATTR_ARCHIVE; + out->LinkCount = 1; + out->FileSize = f->FileSize; + out->VolumeId = Fat32VolumeIdentity(v); + out->FileId = ((uint64_t)f->StartCluster << 1); + } + return STATUS_SUCCESS; + } + + uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); + if (!sec) return STATUS_NO_MEMORY; + + KSTATUS s = ReadSectors(v, f->DirentLba, 1, sec); + if (!KSUCCESS(s)) { MmFreePool(sec); return s; } + + DIRENT *d = (DIRENT *)(sec + f->DirentOffset); + if ((uint8_t)d->Name[0] == 0x00 || (uint8_t)d->Name[0] == 0xE5) { + MmFreePool(sec); + return STATUS_NOT_FOUND; + } + + Fat32FillStatFromDirent(v, d, f->DirentLba, f->DirentOffset, out); + f->StartCluster = ((uint32_t)d->FirstClusterHi << 16) | d->FirstClusterLo; + f->FileSize = d->FileSize; + MmFreePool(sec); + return STATUS_SUCCESS; +} + +// Queries metadata for a FAT32 path. +KSTATUS Fat32Stat(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, SYS_FILE_STAT *out) { + if (!fs || !out || !SysSvIsValid(path)) return STATUS_INVALID_PARAMETER; + + FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; + if (!v) return STATUS_INVALID_PARAMETER; + + uint32_t cluster = 0; + uint32_t size = 0; + uint8_t attr = ATTR_DIRECTORY; + uint64_t direntLba = 0; + uint32_t direntOffset = 0; + KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, &attr, &direntLba, &direntOffset); + if (!KSUCCESS(s)) return s; + (void)cluster; + + if (direntLba == 0) { + Fat32FillRootStat(v, out); + return STATUS_SUCCESS; + } + + uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); + if (!sec) return STATUS_NO_MEMORY; + + s = ReadSectors(v, direntLba, 1, sec); + if (!KSUCCESS(s)) { MmFreePool(sec); return s; } + + DIRENT *d = (DIRENT *)(sec + direntOffset); + if ((uint8_t)d->Name[0] == 0x00 || (uint8_t)d->Name[0] == 0xE5) { + MmFreePool(sec); + return STATUS_NOT_FOUND; + } + + Fat32FillStatFromDirent(v, d, direntLba, direntOffset, out); + out->Type = Fat32FileTypeFromAttr(attr); + out->FileSize = size; + MmFreePool(sec); + return STATUS_SUCCESS; +} + // Mixes FAT directory metadata into a per-file content generation. static uint64_t Fat32DirentGeneration(const DIRENT *d) { if (!d) @@ -2123,10 +2481,7 @@ KSTATUS Fat32QueryIdentity(IO_FILESYSTEM *fs, void *fileCtx, IO_FILE_IDENTITY *o MmFreePool(sec); } - out->VolumeId = ((uint64_t)v->DriveIndex << 56) ^ - ((uint64_t)v->PartitionLba << 8) ^ - ((uint64_t)v->RootCluster << 1) ^ - 0x4641543332494445ULL; + out->VolumeId = Fat32VolumeIdentity(v); out->FileId = f->DirentLba ^ ((uint64_t)f->DirentOffset << 32) ^ ((uint64_t)startCluster << 1); @@ -2135,7 +2490,7 @@ KSTATUS Fat32QueryIdentity(IO_FILESYSTEM *fs, void *fileCtx, IO_FILE_IDENTITY *o return STATUS_SUCCESS; } -// Directory enumeration (fix #2: cluster buffer cached in context) +// Directory enumeration (fix #2: cluster buffer cached in context) // Previously: allocate + ReadSectors + return 1 entry + free, every call. // Now: buffer lives in FAT32_DIR_CTX; ReadSectors fires only on cluster // boundaries. @@ -2418,7 +2773,7 @@ static uint32_t Fat32EnumerateDiskVolumes(uint8_t diskIndex, uint32_t size = ReadLe32(entry + 12); if (!start || !size) continue; - // FAT32 CHS/LBA plus EFI System Partition. The VBR probe is the + // FAT32 CHS/LBA plus EFI System Partition. The VBR probe is the // authority, so accepting 0xEF here lets FAT32 ESPs mount too. if (type == 0x0B || type == 0x0C || type == 0xEF) Fat32AddCandidate(&probe, out, &count, maxCount, start, size); @@ -2928,7 +3283,7 @@ static uint32_t Fat32TryMountDisk(uint8_t disk) { return 0; } - // Only remember a completed probe. A transient hotplug/readiness failure + // Only remember a completed probe. A transient hotplug/readiness failure // must not permanently suppress a later mount attempt for the same disk // index after a device is unplugged and replugged. gDiskScanned[disk] = true; @@ -3029,7 +3384,7 @@ static void Fat32UnmountDisk(uint8_t diskIndex, IO_DEVICE_CHANGE change) { continue; // Surprise removal means the backing device may already be gone; do not - // attempt writeback here. Drop cached FAT sectors instead. + // attempt writeback here. Drop cached FAT sectors instead. FatCacheInvalidate(); (void)IoUnregisterFilesystem(i); memset(&gVols[i], 0, sizeof(gVols[i])); diff --git a/code/drivers/fs/fat32/fat32.h b/code/drivers/fs/fat32/fat32.h index 702ee48..46ce269 100644 --- a/code/drivers/fs/fat32/fat32.h +++ b/code/drivers/fs/fat32/fat32.h @@ -28,9 +28,13 @@ KSTATUS Fat32Write(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, const void KSTATUS Fat32ReadAsync(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, void *buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void *completeContext); KSTATUS Fat32WriteAsync(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, const void *buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void *completeContext); KSTATUS Fat32Create(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx); +KSTATUS Fat32CreateDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path); +KSTATUS Fat32RemoveDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path); KSTATUS Fat32Delete(IO_FILESYSTEM *fs, SYS_STRING_VIEW path); KSTATUS Fat32Truncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize, uint32_t fileFlags); uint64_t Fat32GetSize(IO_FILESYSTEM *fs, void *fileCtx); +KSTATUS Fat32QueryInfo(IO_FILESYSTEM *fs, void *fileCtx, SYS_FILE_STAT *out); +KSTATUS Fat32Stat(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, SYS_FILE_STAT *out); KSTATUS Fat32QueryIdentity(IO_FILESYSTEM *fs, void *fileCtx, IO_FILE_IDENTITY *out); KSTATUS Fat32OpenDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW dirPath, void **dirCtx); KSTATUS Fat32ReadDir(IO_FILESYSTEM *fs, void *dirCtx, KDIR_ENTRY *out); diff --git a/code/drivers/fs/fat32/fat32_mount.c b/code/drivers/fs/fat32/fat32_mount.c index ce1137e..3974e7d 100644 --- a/code/drivers/fs/fat32/fat32_mount.c +++ b/code/drivers/fs/fat32/fat32_mount.c @@ -12,8 +12,12 @@ void Fat32InitVtable(IO_FILESYSTEM* fs, FAT32_VOL* vol) { fs->WriteAsync = Fat32WriteAsync; fs->Create = Fat32Create; fs->Delete = Fat32Delete; + fs->CreateDir = Fat32CreateDir; + fs->RemoveDir = Fat32RemoveDir; fs->Truncate = Fat32Truncate; fs->GetSize = Fat32GetSize; + fs->QueryInfo = Fat32QueryInfo; + fs->Stat = Fat32Stat; fs->QueryIdentity = Fat32QueryIdentity; fs->Close = Fat32Close; fs->DriverCtx = vol; diff --git a/code/drivers/fs/ntfs/LICENCE.ntfs-efi b/code/drivers/fs/ntfs/LICENCE.ntfs-efi new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/code/drivers/fs/ntfs/LICENCE.ntfs-efi @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/code/drivers/fs/ntfs/ntfs.c b/code/drivers/fs/ntfs/ntfs.c new file mode 100644 index 0000000..4a7a3ff --- /dev/null +++ b/code/drivers/fs/ntfs/ntfs.c @@ -0,0 +1,1715 @@ +// Native read-only NTFS filesystem driver for NTLite. +// Portions are based on ntfs-efi by Mark Harmstone (2023), GPLv2-or-later. +// The port removes UEFI protocol/header dependencies and targets NTLite's IO_FILESYSTEM ABI. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NTFS_MAX_VOLUMES 8u +#define NTFS_MAX_PARTITIONS_PER_DISK 32u +#define NTFS_SECTOR_SIZE 512u +#define NTFS_FS_NAME "NTFS " +#define NTFS_UPCASE_TABLE_CHARS 0x10000u +#define NTFS_UPCASE_TABLE_BYTES (NTFS_UPCASE_TABLE_CHARS * sizeof(uint16_t)) + +#ifndef NTFS_TRACE_ENABLED +#define NTFS_TRACE_ENABLED 0 +#endif + +#if NTFS_TRACE_ENABLED +#define NtfsTrace DbgPrint +#else +#define NtfsTrace(...) ((void)0) +#endif + +#define NTFS_MFT_INODE 0ull +#define NTFS_ROOT_DIR_INODE 5ull +#define NTFS_UPCASE_INODE 10ull + +#define NTFS_FILE_SIGNATURE 0x454c4946u /* FILE */ +#define NTFS_INDEX_SIGNATURE 0x58444e49u /* INDX */ + +#define NTFS_ATTR_STANDARD_INFORMATION 0x10u +#define NTFS_ATTR_ATTRIBUTE_LIST 0x20u +#define NTFS_ATTR_FILE_NAME 0x30u +#define NTFS_ATTR_DATA 0x80u +#define NTFS_ATTR_INDEX_ROOT 0x90u +#define NTFS_ATTR_INDEX_ALLOCATION 0xA0u +#define NTFS_ATTR_END 0xffffffffu + +#define NTFS_FORM_RESIDENT 0u +#define NTFS_FORM_NONRESIDENT 1u + +#define NTFS_ATTRIBUTE_FLAG_COMPRESSION_MASK 0x00ffu +#define NTFS_ATTRIBUTE_FLAG_ENCRYPTED 0x4000u + +#define NTFS_FILE_ATTRIBUTE_READONLY 0x00000001u +#define NTFS_FILE_ATTRIBUTE_HIDDEN 0x00000002u +#define NTFS_FILE_ATTRIBUTE_SYSTEM 0x00000004u +#define NTFS_FILE_ATTRIBUTE_DIRECTORY 0x00000010u +#define NTFS_FILE_ATTRIBUTE_ARCHIVE 0x00000020u +#define NTFS_FILE_ATTRIBUTE_DIRECTORY_MFT 0x10000000u + +#define NTFS_INDEX_ENTRY_SUBNODE 1u +#define NTFS_INDEX_ENTRY_LAST 2u + +#define NTFS_FILE_NAME_POSIX 0u +#define NTFS_FILE_NAME_WINDOWS 1u +#define NTFS_FILE_NAME_DOS 2u +#define NTFS_FILE_NAME_WINDOWS_AND_DOS 3u + +#pragma pack(push, 1) + +typedef struct NTFS_BOOT_SECTOR { + uint8_t Jmp[3]; + uint8_t FsName[8]; + uint16_t BytesPerSector; + uint8_t SectorsPerCluster; + uint16_t ReservedSectors; + uint8_t Unused1[5]; + uint8_t Media; + uint8_t Unused2[2]; + uint16_t SectorsPerTrack; + uint16_t Heads; + uint32_t HiddenSectors; + uint32_t Unused3; + uint32_t Unknown; + uint64_t TotalSectors; + uint64_t MFT; + uint64_t MFTMirr; + int8_t ClustersPerMFTRecord; + uint8_t Padding1[3]; + int8_t ClustersPerIndexRecord; + uint8_t Padding2[3]; + uint64_t SerialNumber; + uint32_t Checksum; +} NTFS_BOOT_SECTOR; + +typedef struct NTFS_MULTI_SECTOR_HEADER { + uint32_t Signature; + uint16_t UpdateSequenceArrayOffset; + uint16_t UpdateSequenceArraySize; +} NTFS_MULTI_SECTOR_HEADER; + +typedef struct NTFS_FILE_RECORD_SEGMENT_HEADER { + NTFS_MULTI_SECTOR_HEADER MultiSectorHeader; + uint64_t LogFileSequenceNumber; + uint16_t SequenceNumber; + uint16_t HardLinkCount; + uint16_t FirstAttributeOffset; + uint16_t Flags; + uint32_t EntryUsedSize; + uint32_t EntryAllocatedSize; + uint64_t BaseFileRecordSegment; + uint16_t NextAttributeID; +} NTFS_FILE_RECORD_SEGMENT_HEADER; + +typedef struct NTFS_ATTRIBUTE_RECORD_HEADER { + uint32_t TypeCode; + uint16_t RecordLength; + uint16_t RecordLengthHigh; + uint8_t FormCode; + uint8_t NameLength; + uint16_t NameOffset; + uint16_t Flags; + uint16_t Instance; + union { + struct { + uint32_t ValueLength; + uint16_t ValueOffset; + uint8_t Reserved[2]; + } Resident; + struct { + uint64_t LowestVcn; + uint64_t HighestVcn; + uint16_t MappingPairsOffset; + uint16_t CompressionUnit; + uint32_t Padding; + uint64_t AllocatedLength; + uint64_t FileSize; + uint64_t ValidDataLength; + uint64_t TotalAllocated; + } Nonresident; + } Form; +} NTFS_ATTRIBUTE_RECORD_HEADER; + +typedef struct NTFS_STANDARD_INFORMATION { + int64_t CreationTime; + int64_t LastAccessTime; + int64_t LastWriteTime; + int64_t ChangeTime; + uint32_t FileAttributes; + uint32_t MaximumVersions; + uint32_t VersionNumber; + uint32_t ClassId; + uint32_t OwnerId; + uint32_t SecurityId; + uint64_t QuotaCharged; + uint64_t USN; +} NTFS_STANDARD_INFORMATION; + +typedef struct NTFS_INDEX_NODE_HEADER { + uint32_t FirstEntry; + uint32_t TotalSize; + uint32_t AllocatedSize; + uint32_t Flags; +} NTFS_INDEX_NODE_HEADER; + +typedef struct NTFS_INDEX_ENTRY { + uint64_t FileReference; + uint16_t EntryLength; + uint16_t StreamLength; + uint32_t Flags; +} NTFS_INDEX_ENTRY; + +typedef struct NTFS_INDEX_ROOT { + uint32_t AttributeType; + uint32_t CollationRule; + uint32_t BytesPerIndexRecord; + uint8_t ClustersPerIndexRecord; + uint8_t Padding[3]; + NTFS_INDEX_NODE_HEADER NodeHeader; +} NTFS_INDEX_ROOT; + +typedef struct NTFS_INDEX_RECORD { + NTFS_MULTI_SECTOR_HEADER MultiSectorHeader; + uint64_t SequenceNumber; + uint64_t Vcn; + NTFS_INDEX_NODE_HEADER Header; + uint16_t UpdateSequence; +} NTFS_INDEX_RECORD; + +typedef struct NTFS_FILE_NAME_ATTR { + uint64_t Parent; + int64_t CreationTime; + int64_t LastAccessTime; + int64_t LastWriteTime; + int64_t ChangeTime; + uint64_t AllocationSize; + uint64_t EndOfFile; + uint32_t FileAttributes; + uint32_t EaSize; + uint8_t FileNameLength; + uint8_t Namespace; + uint16_t FileName[1]; +} NTFS_FILE_NAME_ATTR; + +typedef struct NTFS_GPT_GUID { + uint32_t Data1; + uint16_t Data2; + uint16_t Data3; + uint8_t Data4[8]; +} NTFS_GPT_GUID; + +typedef struct NTFS_GPT_HEADER { + char Signature[8]; + uint32_t Revision; + uint32_t HeaderSize; + uint32_t HeaderCRC32; + uint32_t Reserved; + uint64_t MyLBA; + uint64_t AlternateLBA; + uint64_t FirstUsableLBA; + uint64_t LastUsableLBA; + NTFS_GPT_GUID DiskGUID; + uint64_t PartitionEntryLBA; + uint32_t NumberOfPartitionEntries; + uint32_t SizeOfPartitionEntry; + uint32_t PartitionEntryArrayCRC32; +} NTFS_GPT_HEADER; + +typedef struct NTFS_GPT_ENTRY { + NTFS_GPT_GUID PartitionTypeGUID; + NTFS_GPT_GUID UniquePartitionGUID; + uint64_t StartingLBA; + uint64_t EndingLBA; + uint64_t Attributes; + uint16_t PartitionName[36]; +} NTFS_GPT_ENTRY; + +typedef struct NTFS_MBR_PARTITION { + uint8_t BootIndicator; + uint8_t StartingChs[3]; + uint8_t OsType; + uint8_t EndingChs[3]; + uint32_t StartingLba; + uint32_t SizeInLba; +} NTFS_MBR_PARTITION; + +#pragma pack(pop) + +typedef struct NTFS_RUN { + LIST_ENTRY Link; + uint64_t Vcn; + uint64_t Lcn; + uint64_t Length; + bool Sparse; +} NTFS_RUN; + +typedef struct NTFS_VOLUME { + uint8_t DriveIndex; + uint8_t VolumeIndex; + uint64_t PartitionLba; + uint64_t PartitionSize; + uint64_t SerialNumber; + uint32_t BytesPerSector; + uint32_t SectorsPerCluster; + uint32_t BytesPerCluster; + uint64_t TotalSectors; + uint64_t MftStartCluster; + uint64_t FileRecordSize; + LIST_ENTRY MftRuns; + uint16_t* Upcase; +} NTFS_VOLUME; + +typedef struct NTFS_FILE { + NTFS_VOLUME* Vol; + uint64_t Inode; + uint64_t Size; + uint64_t ValidDataLength; + uint64_t PhysicalSize; + uint32_t Attributes; + bool IsDirectory; + NTFS_STANDARD_INFORMATION StandardInfo; + LIST_ENTRY DataRuns; + LIST_ENTRY IndexRuns; + uint8_t* ResidentData; + size_t ResidentDataSize; + NTFS_INDEX_ROOT* IndexRoot; + size_t IndexRootSize; +} NTFS_FILE; + +typedef struct NTFS_DIRENT_CACHE { + uint64_t Inode; + uint64_t FileSize; + uint64_t AllocSize; + uint32_t Attributes; + int64_t CreationTime; + int64_t LastAccessTime; + int64_t LastWriteTime; + char Name[SYS_DIR_NAME_MAX]; +} NTFS_DIRENT_CACHE; + +typedef struct NTFS_DIR_CTX { + NTFS_DIRENT_CACHE* Entries; + uint32_t Count; + uint32_t Capacity; + uint32_t Position; +} NTFS_DIR_CTX; + +typedef struct NTFS_PARTITION_CANDIDATE { + uint64_t StartLba; + uint64_t SectorCount; +} NTFS_PARTITION_CANDIDATE; + +typedef struct NTFS_ATTR_VIEW { + const NTFS_ATTRIBUTE_RECORD_HEADER* Attr; + const uint8_t* ResidentData; + size_t ResidentSize; +} NTFS_ATTR_VIEW; + +typedef struct NTFS_FIND_CTX { + NTFS_VOLUME* Vol; + const uint16_t* Name; + size_t NameLength; + uint64_t Inode; + bool Found; +} NTFS_FIND_CTX; + +typedef struct NTFS_COLLECT_CTX { + NTFS_VOLUME* Vol; + NTFS_DIR_CTX* Dir; + KSTATUS Status; + bool HideNtfsMetadata; +} NTFS_COLLECT_CTX; + +static NTFS_VOLUME gVolumes[NTFS_MAX_VOLUMES]; +static IO_FILESYSTEM gFilesystems[NTFS_MAX_VOLUMES]; +static bool gVolumeMounted[NTFS_MAX_VOLUMES]; +static bool gVolumeBusy[NTFS_MAX_VOLUMES]; +static bool gDiskScanned[NTFS_MAX_VOLUMES]; +static SPINLOCK gNtfsMountLock = SPINLOCK_INIT; +static uint32_t gMountedCount; + +static uint64_t NtfsAlignUp(uint64_t value, uint64_t alignment) { + if (alignment == 0) return value; + uint64_t rem = value % alignment; + return rem ? value + (alignment - rem) : value; +} + +static bool NtfsIsPowerOfTwo32(uint32_t value) { + return value != 0 && (value & (value - 1u)) == 0; +} + +static uint64_t NtfsFileReferenceNumber(uint64_t reference) { + return reference & 0x0000ffffffffffffull; +} + +static void NtfsInitRunList(LIST_ENTRY* list) { + LIST_INIT(list); +} + +static void NtfsFreeRuns(LIST_ENTRY* list) { + while (!LIST_IS_EMPTY(list)) { + LIST_ENTRY* node = ListPopHead(list); + NTFS_RUN* run = CONTAINING_RECORD(node, NTFS_RUN, Link); + MmFreePool(run); + } +} + +static void NtfsDebugDumpRuns(const char* tag, const LIST_ENTRY* runs) { + if (!tag || !runs) return; + uint32_t i = 0; + for (LIST_ENTRY* node = runs->Fwd; node != runs && i < 16u; node = node->Fwd, i++) { + NTFS_RUN* run = CONTAINING_RECORD(node, NTFS_RUN, Link); + NtfsTrace("NTFS: %s run[%u]: vcn=%llu lcn=%llu len=%llu sparse=%u\n", + tag, i, + (unsigned long long)run->Vcn, + (unsigned long long)run->Lcn, + (unsigned long long)run->Length, + run->Sparse ? 1u : 0u); + } + if (i == 0) NtfsTrace("NTFS: %s runlist empty\n", tag); +} + +static KSTATUS NtfsReadSectors(NTFS_VOLUME* vol, uint64_t lba, uint32_t count, void* buffer) { + if (!vol || !buffer || count == 0) return STATUS_INVALID_PARAMETER; + if (vol->PartitionSize && lba >= vol->PartitionSize) return STATUS_DEVICE_ERROR; + if (vol->PartitionSize && (uint64_t)count > vol->PartitionSize - lba) return STATUS_DEVICE_ERROR; + return IoReadDiskSectors(vol->DriveIndex, vol->PartitionLba + lba, count, buffer); +} + +static KSTATUS NtfsReadBytesDirect(NTFS_VOLUME* vol, uint64_t byteOffset, uint64_t byteCount, void* buffer) { + if (!vol || !buffer) return STATUS_INVALID_PARAMETER; + if ((byteOffset % vol->BytesPerSector) != 0 || (byteCount % vol->BytesPerSector) != 0) + return STATUS_INVALID_PARAMETER; + uint64_t lba = byteOffset / vol->BytesPerSector; + uint64_t sectors = byteCount / vol->BytesPerSector; + if (sectors > 0xffffffffull) return STATUS_INVALID_PARAMETER; + return NtfsReadSectors(vol, lba, (uint32_t)sectors, buffer); +} + +static KSTATUS NtfsProcessFixups(NTFS_MULTI_SECTOR_HEADER* header, uint64_t length, uint32_t sectorSize) { + if (!header || sectorSize == 0 || length < sectorSize) return STATUS_INVALID_PARAMETER; + uint64_t sectors = length / sectorSize; + if (header->UpdateSequenceArraySize < sectors + 1) return STATUS_INVALID_PARAMETER; + if (header->UpdateSequenceArrayOffset + (uint64_t)header->UpdateSequenceArraySize * sizeof(uint16_t) > length) + return STATUS_INVALID_PARAMETER; + + uint16_t* seq = (uint16_t*)((uint8_t*)header + header->UpdateSequenceArrayOffset); + uint8_t* ptr = (uint8_t*)header + sectorSize - sizeof(uint16_t); + for (uint64_t i = 0; i < sectors; i++) { + if (*(uint16_t*)ptr != seq[0]) return STATUS_CHECKSUM_MISMATCH; + *(uint16_t*)ptr = seq[i + 1u]; + ptr += sectorSize; + } + return STATUS_SUCCESS; +} + +static int64_t NtfsReadSignedLe(const uint8_t* p, uint8_t bytes) { + uint64_t value = 0; + for (uint8_t i = 0; i < bytes; i++) value |= ((uint64_t)p[i]) << (8u * i); + if (bytes && (p[bytes - 1u] & 0x80u)) { + for (uint8_t i = bytes; i < 8u; i++) value |= 0xffull << (8u * i); + } + return (int64_t)value; +} + +static uint32_t NtfsAttributeRecordLength(const NTFS_ATTRIBUTE_RECORD_HEADER* attr) { + if (!attr) return 0; + return (uint32_t)attr->RecordLength | ((uint32_t)attr->RecordLengthHigh << 16); +} + +static uint32_t NtfsAttributeMinimumLength(uint8_t formCode) { + if (formCode == NTFS_FORM_RESIDENT) { + return (uint32_t)(offsetof(NTFS_ATTRIBUTE_RECORD_HEADER, Form.Resident.Reserved) + + sizeof(((NTFS_ATTRIBUTE_RECORD_HEADER*)0)->Form.Resident.Reserved)); + } + if (formCode == NTFS_FORM_NONRESIDENT) { + return (uint32_t)(offsetof(NTFS_ATTRIBUTE_RECORD_HEADER, Form.Nonresident.ValidDataLength) + + sizeof(((NTFS_ATTRIBUTE_RECORD_HEADER*)0)->Form.Nonresident.ValidDataLength)); + } + return (uint32_t)offsetof(NTFS_ATTRIBUTE_RECORD_HEADER, Form); +} + +static KSTATUS NtfsReadMappings(NTFS_VOLUME* vol, const NTFS_ATTRIBUTE_RECORD_HEADER* attr, LIST_ENTRY* runs) { + if (!vol || !attr || !runs) return STATUS_INVALID_PARAMETER; + if (attr->FormCode != NTFS_FORM_NONRESIDENT) return STATUS_INVALID_PARAMETER; + if (attr->Flags & NTFS_ATTRIBUTE_FLAG_ENCRYPTED) return STATUS_NOT_SUPPORTED; + if (attr->Flags & NTFS_ATTRIBUTE_FLAG_COMPRESSION_MASK) return STATUS_NOT_SUPPORTED; + uint32_t attrLen = NtfsAttributeRecordLength(attr); + if (attrLen < NtfsAttributeMinimumLength(NTFS_FORM_NONRESIDENT)) return STATUS_INVALID_PARAMETER; + if (attr->Form.Nonresident.MappingPairsOffset >= attrLen) return STATUS_INVALID_PARAMETER; + + uint64_t limitBytes = attr->Form.Nonresident.AllocatedLength; + if (limitBytes == 0) limitBytes = attr->Form.Nonresident.FileSize; + if (limitBytes == 0) limitBytes = attr->Form.Nonresident.ValidDataLength; + uint64_t maxCluster = NtfsAlignUp(limitBytes, vol->BytesPerCluster) / vol->BytesPerCluster; + if (maxCluster == 0) return STATUS_SUCCESS; + + const uint8_t* stream = (const uint8_t*)attr + attr->Form.Nonresident.MappingPairsOffset; + const uint8_t* end = (const uint8_t*)attr + attrLen; + uint64_t nextVcn = attr->Form.Nonresident.LowestVcn; + int64_t currentLcn = 0; + + while (stream < end && *stream != 0) { + uint8_t header = *stream++; + uint8_t lenBytes = header & 0x0fu; + uint8_t lcnBytes = header >> 4; + if (lenBytes == 0 || lenBytes > 8 || lcnBytes > 8) return STATUS_INVALID_PARAMETER; + if ((size_t)(end - stream) < (size_t)lenBytes + (size_t)lcnBytes) return STATUS_INVALID_PARAMETER; + + int64_t runClustersSigned = NtfsReadSignedLe(stream, lenBytes); + stream += lenBytes; + if (runClustersSigned <= 0) return STATUS_INVALID_PARAMETER; + uint64_t currentVcn = nextVcn; + nextVcn += (uint64_t)runClustersSigned; + if (nextVcn > maxCluster) nextVcn = maxCluster; + + NTFS_RUN* run = (NTFS_RUN*)MmAllocatePool(sizeof(NTFS_RUN)); + if (!run) return STATUS_NO_MEMORY; + memset(run, 0, sizeof(*run)); + run->Vcn = currentVcn; + run->Length = nextVcn - currentVcn; + if (lcnBytes == 0) { + run->Sparse = true; + run->Lcn = 0; + } else { + currentLcn += NtfsReadSignedLe(stream, lcnBytes); + stream += lcnBytes; + if (currentLcn < 0) { MmFreePool(run); return STATUS_INVALID_PARAMETER; } + run->Sparse = false; + run->Lcn = (uint64_t)currentLcn; + } + ListInsertTail(runs, &run->Link); + if (nextVcn >= maxCluster) break; + } + + return STATUS_SUCCESS; +} + +static KSTATUS NtfsReadFromRuns(NTFS_VOLUME* vol, const LIST_ENTRY* runs, uint64_t offset, void* buffer, uint64_t size) { + if (!vol || !runs || (!buffer && size)) return STATUS_INVALID_PARAMETER; + if (size == 0) return STATUS_SUCCESS; + + uint8_t* out = (uint8_t*)buffer; + uint64_t clusterSize = vol->BytesPerCluster; + uint64_t vcn = offset / clusterSize; + uint64_t endOffset = offset + size; + uint64_t lastVcn = NtfsAlignUp(endOffset, clusterSize) / clusterSize; + + for (LIST_ENTRY* node = runs->Fwd; node != runs && size; node = node->Fwd) { + NTFS_RUN* run = CONTAINING_RECORD(node, NTFS_RUN, Link); + if (run->Vcn >= lastVcn || run->Vcn + run->Length <= vcn) continue; + + uint64_t runByteStart = run->Vcn * clusterSize; + uint64_t runByteEnd = (run->Vcn + run->Length) * clusterSize; + if (offset < runByteStart) { + uint64_t gap = runByteStart - offset; + if (gap > size) gap = size; + memset(out, 0, (size_t)gap); + out += gap; + offset += gap; + size -= gap; + vcn = offset / clusterSize; + if (!size) break; + } + + uint64_t chunkStart = offset > runByteStart ? offset : runByteStart; + uint64_t chunkEnd = endOffset < runByteEnd ? endOffset : runByteEnd; + if (chunkEnd <= chunkStart) continue; + uint64_t chunkSize = chunkEnd - chunkStart; + uint64_t mappingOffset = chunkStart - runByteStart; + + if (run->Sparse) { + memset(out, 0, (size_t)chunkSize); + } else { + uint64_t byteOnVolume = run->Lcn * clusterSize + mappingOffset; + KSTATUS s = NtfsReadBytesDirect(vol, byteOnVolume, chunkSize, out); + if (!KSUCCESS(s)) return s; + } + + out += chunkSize; + offset += chunkSize; + size -= chunkSize; + vcn = offset / clusterSize; + } + + if (size) memset(out, 0, (size_t)size); + return STATUS_SUCCESS; +} + +static bool NtfsAttrNameEqualsAscii(const NTFS_ATTRIBUTE_RECORD_HEADER* attr, const char* ascii) { + if (!attr || !ascii) return false; + size_t len = strlen(ascii); + if (attr->NameLength != len) return false; + if (attr->NameLength == 0) return len == 0; + uint32_t attrLen = NtfsAttributeRecordLength(attr); + uint32_t nameBytes = (uint32_t)attr->NameLength * sizeof(uint16_t); + if (attr->NameOffset < offsetof(NTFS_ATTRIBUTE_RECORD_HEADER, Form) || + attr->NameOffset + nameBytes > attrLen) { + return false; + } + const uint16_t* name = (const uint16_t*)((const uint8_t*)attr + attr->NameOffset); + for (size_t i = 0; i < len; i++) { + if (name[i] != (uint16_t)(uint8_t)ascii[i]) return false; + } + return true; +} + +static KSTATUS NtfsForEachRecordAttribute(NTFS_VOLUME* vol, + uint64_t inode, + const NTFS_FILE_RECORD_SEGMENT_HEADER* record, + bool (*callback)(const NTFS_ATTR_VIEW* view, void* ctx), + void* ctx) { + if (!vol || !record || !callback) return STATUS_INVALID_PARAMETER; + uint32_t offset = record->FirstAttributeOffset; + uint32_t recordBytes = record->EntryUsedSize ? record->EntryUsedSize : record->EntryAllocatedSize; + if (recordBytes == 0 || recordBytes > vol->FileRecordSize) recordBytes = (uint32_t)vol->FileRecordSize; + + if (offset >= recordBytes || offset < sizeof(NTFS_FILE_RECORD_SEGMENT_HEADER)) { + NtfsTrace("NTFS: attrs: inode=%llu bad firstAttr=%u recordBytes=%u fileRecordSize=%llu\n", + (unsigned long long)inode, offset, recordBytes, (unsigned long long)vol->FileRecordSize); + return STATUS_INVALID_PARAMETER; + } + + while (offset + offsetof(NTFS_ATTRIBUTE_RECORD_HEADER, Form) <= recordBytes) { + const NTFS_ATTRIBUTE_RECORD_HEADER* attr = (const NTFS_ATTRIBUTE_RECORD_HEADER*)((const uint8_t*)record + offset); + if (attr->TypeCode == NTFS_ATTR_END) break; + uint32_t attrLen = NtfsAttributeRecordLength(attr); + uint32_t minAttrLen = NtfsAttributeMinimumLength(attr->FormCode); + if (attrLen < minAttrLen || attrLen == 0) { + NtfsTrace("NTFS: attrs: inode=%llu bad attr off=%u type=0x%x len=%u form=%u min=%u\n", + (unsigned long long)inode, offset, attr->TypeCode, attrLen, attr->FormCode, minAttrLen); + return STATUS_INVALID_PARAMETER; + } + if (offset + attrLen > recordBytes) { + NtfsTrace("NTFS: attrs: inode=%llu attr overruns off=%u type=0x%x len=%u recordBytes=%u used=%u alloc=%u\n", + (unsigned long long)inode, offset, attr->TypeCode, attrLen, recordBytes, + record->EntryUsedSize, record->EntryAllocatedSize); + return STATUS_INVALID_PARAMETER; + } + + NTFS_ATTR_VIEW view; + memset(&view, 0, sizeof(view)); + view.Attr = attr; + if (attr->FormCode == NTFS_FORM_RESIDENT) { + if ((uint32_t)attr->Form.Resident.ValueOffset < minAttrLen || + (uint32_t)attr->Form.Resident.ValueOffset + attr->Form.Resident.ValueLength > attrLen) { + NtfsTrace("NTFS: attrs: inode=%llu resident bounds off=%u type=0x%x attrLen=%u valueOff=%u valueLen=%u\n", + (unsigned long long)inode, offset, attr->TypeCode, attrLen, + attr->Form.Resident.ValueOffset, attr->Form.Resident.ValueLength); + return STATUS_INVALID_PARAMETER; + } + view.ResidentData = (const uint8_t*)attr + attr->Form.Resident.ValueOffset; + view.ResidentSize = attr->Form.Resident.ValueLength; + } else if (attr->FormCode != NTFS_FORM_NONRESIDENT) { + NtfsTrace("NTFS: attrs: inode=%llu unsupported form=%u off=%u type=0x%x\n", + (unsigned long long)inode, attr->FormCode, offset, attr->TypeCode); + return STATUS_INVALID_PARAMETER; + } + + if (!callback(&view, ctx)) break; + offset += attrLen; + } + return STATUS_SUCCESS; +} + +static KSTATUS NtfsValidateFileRecord(NTFS_VOLUME* vol, uint64_t inode, void* record, const char* source) { + NTFS_FILE_RECORD_SEGMENT_HEADER* fr = (NTFS_FILE_RECORD_SEGMENT_HEADER*)record; + if (fr->MultiSectorHeader.Signature != NTFS_FILE_SIGNATURE) { + const uint8_t* b = (const uint8_t*)record; + NtfsTrace("NTFS: record: inode=%llu %s bad signature=0x%x bytes=%02x %02x %02x %02x mftOff=%llu\n", + (unsigned long long)inode, source ? source : "?", fr->MultiSectorHeader.Signature, + b[0], b[1], b[2], b[3], (unsigned long long)(inode * vol->FileRecordSize)); + return STATUS_INVALID_PARAMETER; + } + + KSTATUS s = NtfsProcessFixups(&fr->MultiSectorHeader, vol->FileRecordSize, vol->BytesPerSector); + if (!KSUCCESS(s)) { + NtfsTrace("NTFS: record: inode=%llu %s fixup failed st=0x%x sig=0x%x usaOff=%u usaCount=%u recordSize=%llu sector=%u\n", + (unsigned long long)inode, source ? source : "?", s, fr->MultiSectorHeader.Signature, + fr->MultiSectorHeader.UpdateSequenceArrayOffset, + fr->MultiSectorHeader.UpdateSequenceArraySize, + (unsigned long long)vol->FileRecordSize, vol->BytesPerSector); + return s; + } + return STATUS_SUCCESS; +} + +static KSTATUS NtfsReadRecord(NTFS_VOLUME* vol, uint64_t inode, void* record) { + if (!vol || !record) return STATUS_INVALID_PARAMETER; + uint64_t mftOffset = inode * vol->FileRecordSize; + KSTATUS s = NtfsReadFromRuns(vol, &vol->MftRuns, mftOffset, record, vol->FileRecordSize); + if (!KSUCCESS(s)) { + NtfsTrace("NTFS: record: inode=%llu runlist read failed st=0x%x mftOff=%llu size=%llu\n", + (unsigned long long)inode, s, (unsigned long long)mftOffset, + (unsigned long long)vol->FileRecordSize); + NtfsDebugDumpRuns("mft", &vol->MftRuns); + return s; + } + + s = NtfsValidateFileRecord(vol, inode, record, "runlist"); + if (KSUCCESS(s)) return s; + + /* Diagnostic and conservative rescue path: on a normal NTFS volume, early MFT records + are contiguous from the boot-sector $MFT LCN. If the decoded runlist gives garbage, + try that direct address and log whether it works. This also keeps the system usable + while we narrow the runlist bug down. */ + uint8_t* direct = (uint8_t*)MmAllocatePool((size_t)vol->FileRecordSize); + if (!direct) return s; + uint64_t directByte = vol->MftStartCluster * vol->BytesPerCluster + mftOffset; + KSTATUS ds = NtfsReadBytesDirect(vol, directByte, vol->FileRecordSize, direct); + if (KSUCCESS(ds)) ds = NtfsValidateFileRecord(vol, inode, direct, "boot-mft-direct"); + else NtfsTrace("NTFS: record: inode=%llu direct read failed st=0x%x byte=%llu\n", + (unsigned long long)inode, ds, (unsigned long long)directByte); + if (KSUCCESS(ds)) { + memcpy(record, direct, (size_t)vol->FileRecordSize); + NtfsTrace("NTFS: record: inode=%llu used boot-sector $MFT direct fallback; decoded MFT runlist is suspect\n", + (unsigned long long)inode); + NtfsDebugDumpRuns("mft", &vol->MftRuns); + MmFreePool(direct); + return STATUS_SUCCESS; + } + MmFreePool(direct); + return s; +} + +static void NtfsInitFile(NTFS_FILE* file, NTFS_VOLUME* vol, uint64_t inode) { + memset(file, 0, sizeof(*file)); + file->Vol = vol; + file->Inode = inode; + NtfsInitRunList(&file->DataRuns); + NtfsInitRunList(&file->IndexRuns); +} + +static void NtfsCleanupFile(NTFS_FILE* file) { + if (!file) return; + NtfsFreeRuns(&file->DataRuns); + NtfsFreeRuns(&file->IndexRuns); + if (file->ResidentData) MmFreePool(file->ResidentData); + if (file->IndexRoot) MmFreePool(file->IndexRoot); + memset(file, 0, sizeof(*file)); +} + +typedef struct NTFS_LOAD_FILE_CTX { + NTFS_FILE* File; + KSTATUS Status; +} NTFS_LOAD_FILE_CTX; + +static bool NtfsLoadFileAttributeCallback(const NTFS_ATTR_VIEW* view, void* rawCtx) { + NTFS_LOAD_FILE_CTX* ctx = (NTFS_LOAD_FILE_CTX*)rawCtx; + NTFS_FILE* file = ctx->File; + const NTFS_ATTRIBUTE_RECORD_HEADER* attr = view->Attr; + + switch (attr->TypeCode) { + case NTFS_ATTR_STANDARD_INFORMATION: { + if (attr->FormCode == NTFS_FORM_RESIDENT && view->ResidentData) { + size_t toCopy = view->ResidentSize < sizeof(file->StandardInfo) ? view->ResidentSize : sizeof(file->StandardInfo); + memcpy(&file->StandardInfo, view->ResidentData, toCopy); + file->Attributes = file->StandardInfo.FileAttributes; + } + break; + } + case NTFS_ATTR_FILE_NAME: { + if (attr->FormCode == NTFS_FORM_RESIDENT && view->ResidentSize >= offsetof(NTFS_FILE_NAME_ATTR, FileName)) { + const NTFS_FILE_NAME_ATTR* fn = (const NTFS_FILE_NAME_ATTR*)view->ResidentData; + file->Attributes = fn->FileAttributes; + file->IsDirectory = (fn->FileAttributes & NTFS_FILE_ATTRIBUTE_DIRECTORY_MFT) != 0 || + (fn->FileAttributes & NTFS_FILE_ATTRIBUTE_DIRECTORY) != 0; + if (file->Size == 0) file->Size = fn->EndOfFile; + if (file->PhysicalSize == 0) file->PhysicalSize = fn->AllocationSize; + } + break; + } + case NTFS_ATTR_DATA: { + if (attr->NameLength != 0) break; + if (attr->FormCode == NTFS_FORM_RESIDENT) { + file->Size = view->ResidentSize; + file->ValidDataLength = view->ResidentSize; + file->PhysicalSize = view->ResidentSize; + if (view->ResidentSize) { + file->ResidentData = (uint8_t*)MmAllocatePool(view->ResidentSize); + if (!file->ResidentData) { ctx->Status = STATUS_NO_MEMORY; return false; } + memcpy(file->ResidentData, view->ResidentData, view->ResidentSize); + file->ResidentDataSize = view->ResidentSize; + } + } else if (attr->FormCode == NTFS_FORM_NONRESIDENT) { + file->Size = attr->Form.Nonresident.FileSize; + file->ValidDataLength = attr->Form.Nonresident.ValidDataLength; + file->PhysicalSize = attr->Form.Nonresident.AllocatedLength; + ctx->Status = NtfsReadMappings(file->Vol, attr, &file->DataRuns); + if (!KSUCCESS(ctx->Status)) return false; + } + break; + } + case NTFS_ATTR_INDEX_ROOT: { + if (NtfsAttrNameEqualsAscii(attr, "$I30") && attr->FormCode == NTFS_FORM_RESIDENT && view->ResidentSize) { + file->IndexRoot = (NTFS_INDEX_ROOT*)MmAllocatePool(view->ResidentSize); + if (!file->IndexRoot) { ctx->Status = STATUS_NO_MEMORY; return false; } + memcpy(file->IndexRoot, view->ResidentData, view->ResidentSize); + file->IndexRootSize = view->ResidentSize; + file->IsDirectory = true; + } + break; + } + case NTFS_ATTR_INDEX_ALLOCATION: { + if (NtfsAttrNameEqualsAscii(attr, "$I30") && attr->FormCode == NTFS_FORM_NONRESIDENT) { + ctx->Status = NtfsReadMappings(file->Vol, attr, &file->IndexRuns); + if (!KSUCCESS(ctx->Status)) return false; + } + break; + } + case NTFS_ATTR_ATTRIBUTE_LIST: + /* Complex attribute-list continuations are not expanded in this first native port. */ + break; + default: + break; + } + + return true; +} + +static KSTATUS NtfsLoadFile(NTFS_VOLUME* vol, uint64_t inode, NTFS_FILE* file) { + if (!vol || !file) return STATUS_INVALID_PARAMETER; + NtfsInitFile(file, vol, inode); + void* record = MmAllocatePool((size_t)vol->FileRecordSize); + if (!record) return STATUS_NO_MEMORY; + KSTATUS s = NtfsReadRecord(vol, inode, record); + if (!KSUCCESS(s)) { MmFreePool(record); NtfsCleanupFile(file); return s; } + + NTFS_LOAD_FILE_CTX ctx; + ctx.File = file; + ctx.Status = STATUS_SUCCESS; + s = NtfsForEachRecordAttribute(vol, inode, (const NTFS_FILE_RECORD_SEGMENT_HEADER*)record, + NtfsLoadFileAttributeCallback, &ctx); + MmFreePool(record); + if (!KSUCCESS(s)) { NtfsCleanupFile(file); return s; } + if (!KSUCCESS(ctx.Status)) { NtfsCleanupFile(file); return ctx.Status; } + if (file->ValidDataLength == 0 && file->ResidentDataSize) file->ValidDataLength = file->ResidentDataSize; + return STATUS_SUCCESS; +} + +static KSTATUS NtfsReadFileData(NTFS_FILE* file, uint64_t offset, void* buffer, size_t size, size_t* bytesRead) { + if (bytesRead) *bytesRead = 0; + if (!file || !buffer || !bytesRead) return STATUS_INVALID_PARAMETER; + if (file->IsDirectory) return STATUS_ACCESS_DENIED; + if (offset >= file->Size || size == 0) return STATUS_SUCCESS; + + uint64_t remaining = file->Size - offset; + if ((uint64_t)size > remaining) size = (size_t)remaining; + + if (file->ResidentData) { + if (offset >= file->ResidentDataSize) { + memset(buffer, 0, size); + } else { + size_t avail = file->ResidentDataSize - (size_t)offset; + size_t copy = size < avail ? size : avail; + memcpy(buffer, file->ResidentData + offset, copy); + if (copy < size) memset((uint8_t*)buffer + copy, 0, size - copy); + } + *bytesRead = size; + return STATUS_SUCCESS; + } + + uint64_t validEnd = offset + size; + if (validEnd > file->ValidDataLength) validEnd = file->ValidDataLength; + if (offset >= file->ValidDataLength) { + memset(buffer, 0, size); + *bytesRead = size; + return STATUS_SUCCESS; + } + + uint64_t sectorSize = file->Vol->BytesPerSector; + uint64_t startAligned = offset & ~(sectorSize - 1u); + uint64_t endAligned = NtfsAlignUp(validEnd, sectorSize); + uint8_t* tmp = NULL; + uint8_t* ioBuffer = (uint8_t*)buffer; + uint64_t ioSize = endAligned - startAligned; + + if (startAligned != offset || endAligned != validEnd) { + tmp = (uint8_t*)MmAllocatePool((size_t)ioSize); + if (!tmp) return STATUS_NO_MEMORY; + ioBuffer = tmp; + } + + KSTATUS s = NtfsReadFromRuns(file->Vol, &file->DataRuns, startAligned, ioBuffer, ioSize); + if (!KSUCCESS(s)) { if (tmp) MmFreePool(tmp); return s; } + + if (tmp) { + memcpy(buffer, tmp + (offset - startAligned), (size_t)(validEnd - offset)); + MmFreePool(tmp); + } + if (validEnd < offset + size) { + memset((uint8_t*)buffer + (validEnd - offset), 0, (size_t)(offset + size - validEnd)); + } + *bytesRead = size; + return STATUS_SUCCESS; +} + +static uint16_t NtfsUpcaseChar(NTFS_VOLUME* vol, uint16_t c) { + if (vol && vol->Upcase) return vol->Upcase[c]; + if (c >= (uint16_t)'a' && c <= (uint16_t)'z') return (uint16_t)(c - 32u); + return c; +} + +static int NtfsCompareUtf16Name(NTFS_VOLUME* vol, const uint16_t* a, size_t alen, const uint16_t* b, size_t blen) { + size_t n = alen < blen ? alen : blen; + for (size_t i = 0; i < n; i++) { + uint16_t ca = NtfsUpcaseChar(vol, a[i]); + uint16_t cb = NtfsUpcaseChar(vol, b[i]); + if (ca < cb) return -1; + if (ca > cb) return 1; + } + if (alen < blen) return -1; + if (alen > blen) return 1; + return 0; +} + +static size_t NtfsUtf16ToAscii(const uint16_t* src, size_t len, char* dst, size_t dstSize) { + if (!dst || dstSize == 0) return 0; + size_t out = 0; + for (size_t i = 0; i < len && out + 1 < dstSize; i++) { + uint16_t c = src[i]; + dst[out++] = (c >= 0x20u && c < 0x7fu) ? (char)c : '?'; + } + dst[out] = '\0'; + return out; +} + +static void NtfsWinTimeToSystemTime(int64_t win, SYS_SYSTEM_TIME* out) { + memset(out, 0, sizeof(*out)); + if (win <= 0) return; + + uint64_t unixSeconds = (uint64_t)(win / 10000000ll); + if (unixSeconds >= 11644473600ull) unixSeconds -= 11644473600ull; + else unixSeconds = 0; + out->EpochSeconds = unixSeconds; + out->Nanosecond = (uint32_t)((win % 10000000ll) * 100ll); + out->Flags = SYS_TIME_FLAG_VALID; + out->TimeZoneMinutes = SYS_TIME_UNSPECIFIED_TZ; + + uint64_t days = unixSeconds / 86400ull; + uint32_t rem = (uint32_t)(unixSeconds % 86400ull); + out->Hour = (uint8_t)(rem / 3600u); + out->Minute = (uint8_t)((rem % 3600u) / 60u); + out->Second = (uint8_t)(rem % 60u); + out->DayOfWeek = (uint8_t)((days + 4u) % 7u); + + int64_t z = (int64_t)days + 719468ll; + int64_t era = (z >= 0 ? z : z - 146096ll) / 146097ll; + uint32_t doe = (uint32_t)(z - era * 146097ll); + uint32_t yoe = (doe - doe / 1460u + doe / 36524u - doe / 146096u) / 365u; + int64_t y = (int64_t)yoe + era * 400ll; + uint32_t doy = doe - (365u * yoe + yoe / 4u - yoe / 100u); + uint32_t mp = (5u * doy + 2u) / 153u; + uint32_t d = doy - (153u * mp + 2u) / 5u + 1u; + uint32_t m = mp + (mp < 10u ? 3u : (uint32_t)-9); + y += (m <= 2u); + out->Year = (uint16_t)y; + out->Month = (uint8_t)m; + out->Day = (uint8_t)d; +} + +static uint32_t NtfsSysAttrsFromNtfs(uint32_t attr, bool isDir) { + uint32_t out = 0; + if (isDir || (attr & NTFS_FILE_ATTRIBUTE_DIRECTORY) || (attr & NTFS_FILE_ATTRIBUTE_DIRECTORY_MFT)) out |= SYS_FILE_ATTR_DIRECTORY; + if (attr & NTFS_FILE_ATTRIBUTE_READONLY) out |= SYS_FILE_ATTR_READ_ONLY; + if (attr & NTFS_FILE_ATTRIBUTE_HIDDEN) out |= SYS_FILE_ATTR_HIDDEN; + if (attr & NTFS_FILE_ATTRIBUTE_SYSTEM) out |= SYS_FILE_ATTR_SYSTEM; + if (attr & NTFS_FILE_ATTRIBUTE_ARCHIVE) out |= SYS_FILE_ATTR_ARCHIVE; + return out; +} + +static void NtfsFillStat(NTFS_FILE* file, SYS_FILE_STAT* out) { + memset(out, 0, sizeof(*out)); + out->Size = sizeof(*out); + out->Type = file->IsDirectory ? SYS_FILE_TYPE_DIRECTORY : SYS_FILE_TYPE_REGULAR; + out->Attributes = NtfsSysAttrsFromNtfs(file->Attributes, file->IsDirectory); + out->LinkCount = 1; + out->FileSize = file->Size; + out->VolumeId = file->Vol->SerialNumber; + out->FileId = file->Inode; + NtfsWinTimeToSystemTime(file->StandardInfo.CreationTime, &out->CreationTime); + NtfsWinTimeToSystemTime(file->StandardInfo.LastAccessTime, &out->LastAccessTime); + NtfsWinTimeToSystemTime(file->StandardInfo.LastWriteTime, &out->LastWriteTime); + NtfsWinTimeToSystemTime(file->StandardInfo.ChangeTime, &out->ChangeTime); +} + +typedef bool (*NTFS_INDEX_ENTRY_CALLBACK)(const NTFS_FILE_NAME_ATTR* fn, uint64_t inode, void* ctx); + +static KSTATUS NtfsWalkIndexBuffer(NTFS_VOLUME* vol, + NTFS_INDEX_ROOT* root, + const LIST_ENTRY* indexRuns, + const NTFS_INDEX_NODE_HEADER* nodeHeader, + const uint8_t* nodeBase, + size_t nodeBytes, + NTFS_INDEX_ENTRY_CALLBACK callback, + void* ctx, + uint32_t depth) { + if (!vol || !root || !nodeHeader || !nodeBase || !callback) return STATUS_INVALID_PARAMETER; + if (depth > 16u) return STATUS_INVALID_PARAMETER; + if (nodeHeader->FirstEntry > nodeBytes) { + NtfsTrace("NTFS: index walk: firstEntry=%u nodeBytes=%llu depth=%u\n", + nodeHeader->FirstEntry, (unsigned long long)nodeBytes, depth); + return STATUS_INVALID_PARAMETER; + } + + uint32_t offset = nodeHeader->FirstEntry; + uint32_t limit = nodeHeader->TotalSize; + if (limit == 0 || limit > nodeBytes) limit = (uint32_t)nodeBytes; + + while (offset + sizeof(NTFS_INDEX_ENTRY) <= limit) { + const NTFS_INDEX_ENTRY* ent = (const NTFS_INDEX_ENTRY*)(nodeBase + offset); + if (ent->EntryLength < sizeof(NTFS_INDEX_ENTRY) || offset + ent->EntryLength > limit) { + NtfsTrace("NTFS: index walk: bad entry off=%u len=%u limit=%u flags=0x%x depth=%u\n", + offset, ent->EntryLength, limit, ent->Flags, depth); + return STATUS_INVALID_PARAMETER; + } + + if (!(ent->Flags & NTFS_INDEX_ENTRY_LAST) && ent->StreamLength >= offsetof(NTFS_FILE_NAME_ATTR, FileName)) { + const NTFS_FILE_NAME_ATTR* fn = (const NTFS_FILE_NAME_ATTR*)((const uint8_t*)ent + sizeof(NTFS_INDEX_ENTRY)); + size_t needed = offsetof(NTFS_FILE_NAME_ATTR, FileName) + (size_t)fn->FileNameLength * sizeof(uint16_t); + if (needed <= ent->StreamLength && fn->Namespace != NTFS_FILE_NAME_DOS) { + if (!callback(fn, NtfsFileReferenceNumber(ent->FileReference), ctx)) return STATUS_SUCCESS; + } + } + + if (ent->Flags & NTFS_INDEX_ENTRY_SUBNODE) { + if (ent->EntryLength < sizeof(NTFS_INDEX_ENTRY) + sizeof(uint64_t)) return STATUS_INVALID_PARAMETER; + if (!indexRuns || LIST_IS_EMPTY(indexRuns)) { + NtfsTrace("NTFS: index walk: subnode present but no $INDEX_ALLOCATION runs depth=%u off=%u\n", + depth, offset); + return STATUS_NOT_SUPPORTED; + } + uint64_t childVcn = *(const uint64_t*)((const uint8_t*)ent + ent->EntryLength - sizeof(uint64_t)); + uint64_t childOffset; + if (root->BytesPerIndexRecord < vol->BytesPerCluster) childOffset = childVcn * vol->BytesPerSector; + else childOffset = childVcn * (uint64_t)vol->BytesPerCluster; + + uint8_t* record = (uint8_t*)MmAllocatePool(root->BytesPerIndexRecord); + if (!record) return STATUS_NO_MEMORY; + KSTATUS s = NtfsReadFromRuns(vol, indexRuns, childOffset, record, root->BytesPerIndexRecord); + if (KSUCCESS(s)) { + NTFS_INDEX_RECORD* ir = (NTFS_INDEX_RECORD*)record; + if (ir->MultiSectorHeader.Signature != NTFS_INDEX_SIGNATURE) { + NtfsTrace("NTFS: index walk: child VCN=%llu offset=%llu signature=0x%x, expected INDX\n", + (unsigned long long)childVcn, + (unsigned long long)childOffset, + ir->MultiSectorHeader.Signature); + s = STATUS_INVALID_PARAMETER; + } + else s = NtfsProcessFixups(&ir->MultiSectorHeader, root->BytesPerIndexRecord, vol->BytesPerSector); + if (KSUCCESS(s)) { + const uint8_t* recNodeBase = (const uint8_t*)&ir->Header; + size_t recNodeBytes = root->BytesPerIndexRecord - offsetof(NTFS_INDEX_RECORD, Header); + s = NtfsWalkIndexBuffer(vol, root, indexRuns, &ir->Header, recNodeBase, recNodeBytes, callback, ctx, depth + 1u); + } + } + MmFreePool(record); + if (!KSUCCESS(s)) { + NtfsTrace("NTFS: index walk: child VCN=%llu failed st=0x%x depth=%u\n", + (unsigned long long)childVcn, s, depth); + return s; + } + } + + if (ent->Flags & NTFS_INDEX_ENTRY_LAST) break; + offset += ent->EntryLength; + } + + return STATUS_SUCCESS; +} + +static KSTATUS NtfsWalkDirectory(NTFS_FILE* dir, NTFS_INDEX_ENTRY_CALLBACK callback, void* ctx) { + if (!dir || !dir->IsDirectory || !dir->IndexRoot || !callback) return STATUS_INVALID_PARAMETER; + if (dir->IndexRootSize < sizeof(NTFS_INDEX_ROOT)) return STATUS_INVALID_PARAMETER; + const uint8_t* nodeBase = (const uint8_t*)&dir->IndexRoot->NodeHeader; + size_t nodeBytes = dir->IndexRootSize - offsetof(NTFS_INDEX_ROOT, NodeHeader); + return NtfsWalkIndexBuffer(dir->Vol, dir->IndexRoot, &dir->IndexRuns, + &dir->IndexRoot->NodeHeader, nodeBase, nodeBytes, + callback, ctx, 0u); +} + +static bool NtfsFindEntryCallback(const NTFS_FILE_NAME_ATTR* fn, uint64_t inode, void* rawCtx) { + NTFS_FIND_CTX* ctx = (NTFS_FIND_CTX*)rawCtx; + if (NtfsCompareUtf16Name(ctx->Vol, ctx->Name, ctx->NameLength, fn->FileName, fn->FileNameLength) == 0) { + ctx->Inode = inode; + ctx->Found = true; + return false; + } + return true; +} + +static KSTATUS NtfsFindInDirectory(NTFS_VOLUME* vol, uint64_t dirInode, const uint16_t* name, size_t nameLength, uint64_t* outInode) { + if (!vol || !name || !nameLength || !outInode) return STATUS_INVALID_PARAMETER; + NTFS_FILE dir; + KSTATUS s = NtfsLoadFile(vol, dirInode, &dir); + if (!KSUCCESS(s)) return s; + if (!dir.IsDirectory) { NtfsCleanupFile(&dir); return STATUS_NOT_FOUND; } + + NTFS_FIND_CTX ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.Vol = vol; + ctx.Name = name; + ctx.NameLength = nameLength; + s = NtfsWalkDirectory(&dir, NtfsFindEntryCallback, &ctx); + NtfsCleanupFile(&dir); + if (!KSUCCESS(s)) return s; + if (!ctx.Found) return STATUS_NOT_FOUND; + *outInode = ctx.Inode; + return STATUS_SUCCESS; +} + +static KSTATUS NtfsLookupPath(NTFS_VOLUME* vol, SYS_STRING_VIEW path, uint64_t* outInode) { + if (!vol || !outInode || !SysSvIsValid(path)) return STATUS_INVALID_PARAMETER; + uint64_t inode = NTFS_ROOT_DIR_INODE; + size_t pos = 0; + + while (pos < path.Length && (path.Data[pos] == '\\' || path.Data[pos] == '/')) pos++; + if (pos >= path.Length) { *outInode = inode; return STATUS_SUCCESS; } + + while (pos < path.Length) { + while (pos < path.Length && (path.Data[pos] == '\\' || path.Data[pos] == '/')) pos++; + if (pos >= path.Length) break; + size_t start = pos; + while (pos < path.Length && path.Data[pos] != '\\' && path.Data[pos] != '/') pos++; + size_t len = pos - start; + if (len == 0) continue; + if (len == 1 && path.Data[start] == '.') continue; + if (len == 2 && path.Data[start] == '.' && path.Data[start + 1u] == '.') return STATUS_INVALID_PARAMETER; + if (len > 255u) return STATUS_INVALID_PARAMETER; + + uint16_t segment[260]; + for (size_t i = 0; i < len; i++) segment[i] = (uint16_t)(uint8_t)path.Data[start + i]; + KSTATUS s = NtfsFindInDirectory(vol, inode, segment, len, &inode); + if (!KSUCCESS(s)) return s; + } + + *outInode = inode; + return STATUS_SUCCESS; +} + +static bool NtfsShouldHideDirectoryEntry(const NTFS_FILE_NAME_ATTR* fn, bool hideNtfsMetadata) { + if (!fn) return true; + if (fn->FileNameLength == 0) return true; + if (fn->FileNameLength == 1 && fn->FileName[0] == (uint16_t)'.') return true; + + /* The NTFS root directory exposes metadata files such as $MFT, $Bitmap, + $LogFile, and $Extend. They are filesystem implementation details, not + normal user directory entries, so keep them out of directory listings. + Lookup still sees the raw index when the driver needs metadata internally. */ + if (hideNtfsMetadata && fn->FileName[0] == (uint16_t)'$') return true; + return false; +} + +static bool NtfsCollectEntryCallback(const NTFS_FILE_NAME_ATTR* fn, uint64_t inode, void* rawCtx) { + NTFS_COLLECT_CTX* ctx = (NTFS_COLLECT_CTX*)rawCtx; + NTFS_DIR_CTX* dir = ctx->Dir; + if (NtfsShouldHideDirectoryEntry(fn, ctx->HideNtfsMetadata)) return true; + if (dir->Count == dir->Capacity) { + uint32_t newCap = dir->Capacity ? dir->Capacity * 2u : 32u; + if (newCap < dir->Capacity) { ctx->Status = STATUS_NO_MEMORY; return false; } + NTFS_DIRENT_CACHE* newer = (NTFS_DIRENT_CACHE*)MmReallocatePool(dir->Entries, (size_t)newCap * sizeof(NTFS_DIRENT_CACHE)); + if (!newer) { ctx->Status = STATUS_NO_MEMORY; return false; } + dir->Entries = newer; + dir->Capacity = newCap; + } + + NTFS_DIRENT_CACHE* e = &dir->Entries[dir->Count++]; + memset(e, 0, sizeof(*e)); + e->Inode = inode; + e->FileSize = fn->EndOfFile; + e->AllocSize = fn->AllocationSize; + e->Attributes = fn->FileAttributes; + e->CreationTime = fn->CreationTime; + e->LastAccessTime = fn->LastAccessTime; + e->LastWriteTime = fn->LastWriteTime; + NtfsUtf16ToAscii(fn->FileName, fn->FileNameLength, e->Name, sizeof(e->Name)); + ctx->Status = STATUS_SUCCESS; + return true; +} + +static KSTATUS NtfsOpen(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, void** fileCtx) { + if (!fs || !fileCtx || !SysSvIsValid(path)) return STATUS_INVALID_PARAMETER; + NTFS_VOLUME* vol = (NTFS_VOLUME*)fs->DriverCtx; + if (!vol) return STATUS_INVALID_PARAMETER; + + uint64_t inode; + KSTATUS s = NtfsLookupPath(vol, path, &inode); + if (!KSUCCESS(s)) return s; + + NTFS_FILE* file = (NTFS_FILE*)MmAllocatePool(sizeof(NTFS_FILE)); + if (!file) return STATUS_NO_MEMORY; + s = NtfsLoadFile(vol, inode, file); + if (!KSUCCESS(s)) { MmFreePool(file); return s; } + *fileCtx = file; + return STATUS_SUCCESS; +} + +static void NtfsClose(IO_FILESYSTEM* fs, void* fileCtx) { + (void)fs; + NTFS_FILE* file = (NTFS_FILE*)fileCtx; + if (!file) return; + NtfsCleanupFile(file); + MmFreePool(file); +} + +static KSTATUS NtfsRead(IO_FILESYSTEM* fs, void* fileCtx, uint64_t offset, void* buf, size_t size, size_t* bytesRead) { + (void)fs; + return NtfsReadFileData((NTFS_FILE*)fileCtx, offset, buf, size, bytesRead); +} + +static KSTATUS NtfsWrite(IO_FILESYSTEM* fs, void* fileCtx, uint64_t offset, const void* buf, size_t size, size_t* bytesWritten) { + (void)fs; (void)fileCtx; (void)offset; (void)buf; (void)size; + if (bytesWritten) *bytesWritten = 0; + return STATUS_NOT_SUPPORTED; +} + +static KSTATUS NtfsCreate(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, void** fileCtx) { + (void)fs; (void)path; (void)fileCtx; + return STATUS_NOT_SUPPORTED; +} + +static KSTATUS NtfsDelete(IO_FILESYSTEM* fs, SYS_STRING_VIEW path) { + (void)fs; (void)path; + return STATUS_NOT_SUPPORTED; +} + +static KSTATUS NtfsTruncate(IO_FILESYSTEM* fs, void* fileCtx, uint64_t newSize, uint32_t fileFlags) { + (void)fs; (void)fileCtx; (void)newSize; (void)fileFlags; + return STATUS_NOT_SUPPORTED; +} + +static uint64_t NtfsGetSize(IO_FILESYSTEM* fs, void* fileCtx) { + (void)fs; + NTFS_FILE* file = (NTFS_FILE*)fileCtx; + return file ? file->Size : 0; +} + +static KSTATUS NtfsQueryInfo(IO_FILESYSTEM* fs, void* fileCtx, SYS_FILE_STAT* out) { + (void)fs; + if (!fileCtx || !out) return STATUS_INVALID_PARAMETER; + NtfsFillStat((NTFS_FILE*)fileCtx, out); + return STATUS_SUCCESS; +} + +static KSTATUS NtfsStat(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, SYS_FILE_STAT* out) { + if (!fs || !out || !SysSvIsValid(path)) return STATUS_INVALID_PARAMETER; + void* ctx = NULL; + KSTATUS s = NtfsOpen(fs, path, &ctx); + if (!KSUCCESS(s)) return s; + s = NtfsQueryInfo(fs, ctx, out); + NtfsClose(fs, ctx); + return s; +} + +static KSTATUS NtfsQueryIdentity(IO_FILESYSTEM* fs, void* fileCtx, IO_FILE_IDENTITY* out) { + (void)fs; + if (!fileCtx || !out) return STATUS_INVALID_PARAMETER; + NTFS_FILE* file = (NTFS_FILE*)fileCtx; + out->VolumeId = file->Vol->SerialNumber; + out->FileId = file->Inode; + out->Generation = (uint64_t)file->StandardInfo.LastWriteTime ^ ((uint64_t)file->StandardInfo.ChangeTime << 1u); + out->Size = file->Size; + return STATUS_SUCCESS; +} + +static KSTATUS NtfsOpenDir(IO_FILESYSTEM* fs, SYS_STRING_VIEW dirPath, void** dirCtx) { + if (!fs || !dirCtx || !SysSvIsValid(dirPath)) return STATUS_INVALID_PARAMETER; + NTFS_VOLUME* vol = (NTFS_VOLUME*)fs->DriverCtx; + if (!vol) return STATUS_INVALID_PARAMETER; + + uint64_t inode; + KSTATUS s = NtfsLookupPath(vol, dirPath, &inode); + if (!KSUCCESS(s)) { + NtfsTrace("NTFS: opendir: lookup failed drive=%u pathLen=%llu st=0x%x\n", + vol->VolumeIndex, (unsigned long long)dirPath.Length, s); + return s; + } + + NTFS_FILE dir; + s = NtfsLoadFile(vol, inode, &dir); + if (!KSUCCESS(s)) { + NtfsTrace("NTFS: opendir: load inode=%llu failed st=0x%x\n", + (unsigned long long)inode, s); + return s; + } + if (!dir.IsDirectory) { + NtfsTrace("NTFS: opendir: inode=%llu is not directory attr=0x%x indexRoot=%p\n", + (unsigned long long)inode, dir.Attributes, dir.IndexRoot); + NtfsCleanupFile(&dir); + return STATUS_NOT_FOUND; + } + if (!dir.IndexRoot) { + NtfsTrace("NTFS: opendir: inode=%llu has no $I30 $INDEX_ROOT\n", + (unsigned long long)inode); + } else { + NtfsTrace("NTFS: opendir: inode=%llu indexRootSize=%llu indexRecordBytes=%u indexRunsEmpty=%u\n", + (unsigned long long)inode, + (unsigned long long)dir.IndexRootSize, + dir.IndexRoot->BytesPerIndexRecord, + LIST_IS_EMPTY(&dir.IndexRuns) ? 1u : 0u); + } + + NTFS_DIR_CTX* ctx = (NTFS_DIR_CTX*)MmAllocatePool(sizeof(NTFS_DIR_CTX)); + if (!ctx) { NtfsCleanupFile(&dir); return STATUS_NO_MEMORY; } + memset(ctx, 0, sizeof(*ctx)); + + NTFS_COLLECT_CTX collect; + memset(&collect, 0, sizeof(collect)); + collect.Vol = vol; + collect.Dir = ctx; + collect.Status = STATUS_SUCCESS; + collect.HideNtfsMetadata = (inode == NTFS_ROOT_DIR_INODE); + s = NtfsWalkDirectory(&dir, NtfsCollectEntryCallback, &collect); + NtfsCleanupFile(&dir); + if (!KSUCCESS(s)) { + NtfsTrace("NTFS: opendir: walk inode=%llu failed st=0x%x collected=%u\n", + (unsigned long long)inode, s, ctx->Count); + if (ctx->Entries) MmFreePool(ctx->Entries); + MmFreePool(ctx); + return s; + } + if (!KSUCCESS(collect.Status)) { + NtfsTrace("NTFS: opendir: collect inode=%llu failed st=0x%x collected=%u\n", + (unsigned long long)inode, collect.Status, ctx->Count); + if (ctx->Entries) MmFreePool(ctx->Entries); + MmFreePool(ctx); + return collect.Status; + } + + NtfsTrace("NTFS: opendir: inode=%llu OK entries=%u\n", + (unsigned long long)inode, ctx->Count); + + *dirCtx = ctx; + return STATUS_SUCCESS; +} + +static KSTATUS NtfsReadDir(IO_FILESYSTEM* fs, void* dirCtx, KDIR_ENTRY* out) { + (void)fs; + if (!dirCtx || !out) return STATUS_INVALID_PARAMETER; + NTFS_DIR_CTX* ctx = (NTFS_DIR_CTX*)dirCtx; + if (ctx->Position >= ctx->Count) return STATUS_NO_MORE_FILES; + NTFS_DIRENT_CACHE* e = &ctx->Entries[ctx->Position++]; + memset(out, 0, sizeof(*out)); + strncpy(out->Name, e->Name, sizeof(out->Name) - 1u); + out->Attr = (uint8_t)NtfsSysAttrsFromNtfs(e->Attributes, (e->Attributes & NTFS_FILE_ATTRIBUTE_DIRECTORY_MFT) != 0); + out->FileSize = e->FileSize > 0xffffffffull ? 0xffffffffu : (uint32_t)e->FileSize; + NtfsWinTimeToSystemTime(e->CreationTime, &out->CreateTime); + NtfsWinTimeToSystemTime(e->LastAccessTime, &out->LastAccessTime); + NtfsWinTimeToSystemTime(e->LastWriteTime, &out->LastWriteTime); + return STATUS_SUCCESS; +} + +static void NtfsCloseDir(IO_FILESYSTEM* fs, void* dirCtx) { + (void)fs; + NTFS_DIR_CTX* ctx = (NTFS_DIR_CTX*)dirCtx; + if (!ctx) return; + if (ctx->Entries) MmFreePool(ctx->Entries); + MmFreePool(ctx); +} + +static void NtfsQueryStats(IO_FILESYSTEM* fs, IO_FILESYSTEM_STATS* out) { + (void)fs; + if (out) memset(out, 0, sizeof(*out)); +} + +static void NtfsInitVtable(IO_FILESYSTEM* fs, NTFS_VOLUME* vol) { + memset(fs, 0, sizeof(*fs)); + fs->Open = NtfsOpen; + fs->Read = NtfsRead; + fs->Write = NtfsWrite; + fs->Create = NtfsCreate; + fs->Delete = NtfsDelete; + fs->Truncate = NtfsTruncate; + fs->GetSize = NtfsGetSize; + fs->QueryInfo = NtfsQueryInfo; + fs->Stat = NtfsStat; + fs->QueryIdentity = NtfsQueryIdentity; + fs->Close = NtfsClose; + fs->QueryStats = NtfsQueryStats; + fs->DriverCtx = vol; + fs->OpenDir = NtfsOpenDir; + fs->ReadDir = NtfsReadDir; + fs->CloseDir = NtfsCloseDir; +} + +static bool NtfsGuidIsZero(const NTFS_GPT_GUID* guid) { + static const NTFS_GPT_GUID zeroGuid = {0}; + return memcmp(guid, &zeroGuid, sizeof(*guid)) == 0; +} + +static bool NtfsIsValidBootSector(const NTFS_BOOT_SECTOR* bs) { + if (!bs) return false; + if (memcmp(bs->FsName, NTFS_FS_NAME, 8) != 0) return false; + if (!NtfsIsPowerOfTwo32(bs->BytesPerSector)) return false; + if (bs->BytesPerSector != NTFS_SECTOR_SIZE) return false; + if (!NtfsIsPowerOfTwo32(bs->SectorsPerCluster)) return false; + if (bs->TotalSectors == 0 || bs->MFT == 0) return false; + return true; +} + +static uint64_t NtfsDecodeRecordSize(int8_t clusters, uint32_t bytesPerCluster) { + if (clusters > 0) return (uint64_t)(uint8_t)clusters * bytesPerCluster; + if (clusters < 0 && clusters > -64) return 1ull << (uint8_t)(-clusters); + return 0; +} + +typedef struct NTFS_MFT_CTX { + NTFS_VOLUME* Vol; + KSTATUS Status; + bool Found; +} NTFS_MFT_CTX; + +static bool NtfsMftAttributeCallback(const NTFS_ATTR_VIEW* view, void* rawCtx) { + NTFS_MFT_CTX* ctx = (NTFS_MFT_CTX*)rawCtx; + const NTFS_ATTRIBUTE_RECORD_HEADER* attr = view->Attr; + if (attr->TypeCode == NTFS_ATTR_DATA && attr->NameLength == 0 && attr->FormCode == NTFS_FORM_NONRESIDENT) { + ctx->Status = NtfsReadMappings(ctx->Vol, attr, &ctx->Vol->MftRuns); + ctx->Found = KSUCCESS(ctx->Status); + return false; + } + return true; +} + +static KSTATUS NtfsReadMftRuns(NTFS_VOLUME* vol) { + void* record = MmAllocatePool((size_t)vol->FileRecordSize); + if (!record) return STATUS_NO_MEMORY; + uint64_t mftByte = vol->MftStartCluster * vol->BytesPerCluster; + KSTATUS s = NtfsReadBytesDirect(vol, mftByte, vol->FileRecordSize, record); + if (KSUCCESS(s)) { + NTFS_FILE_RECORD_SEGMENT_HEADER* fr = (NTFS_FILE_RECORD_SEGMENT_HEADER*)record; + if (fr->MultiSectorHeader.Signature != NTFS_FILE_SIGNATURE) s = STATUS_INVALID_PARAMETER; + else s = NtfsProcessFixups(&fr->MultiSectorHeader, vol->FileRecordSize, vol->BytesPerSector); + } + if (!KSUCCESS(s)) { MmFreePool(record); return s; } + + NTFS_MFT_CTX ctx; + ctx.Vol = vol; + ctx.Status = STATUS_NOT_FOUND; + ctx.Found = false; + s = NtfsForEachRecordAttribute(vol, NTFS_MFT_INODE, (const NTFS_FILE_RECORD_SEGMENT_HEADER*)record, + NtfsMftAttributeCallback, &ctx); + MmFreePool(record); + if (!KSUCCESS(s)) return s; + if (!ctx.Found) return STATUS_NOT_FOUND; + if (KSUCCESS(ctx.Status)) NtfsDebugDumpRuns("mft", &vol->MftRuns); + return ctx.Status; +} + +static KSTATUS NtfsInitUpcaseIdentity(NTFS_VOLUME* vol) { + if (!vol) return STATUS_INVALID_PARAMETER; + vol->Upcase = (uint16_t*)MmAllocatePool(NTFS_UPCASE_TABLE_BYTES); + if (!vol->Upcase) return STATUS_NO_MEMORY; + for (uint32_t i = 0; i < NTFS_UPCASE_TABLE_CHARS; i++) vol->Upcase[i] = (uint16_t)i; + for (uint16_t c = (uint16_t)'a'; c <= (uint16_t)'z'; c++) vol->Upcase[c] = (uint16_t)(c - 32u); + return STATUS_SUCCESS; +} + +static void NtfsCleanupVolume(NTFS_VOLUME* vol) { + if (!vol) return; + if (vol->MftRuns.Fwd) NtfsFreeRuns(&vol->MftRuns); + if (vol->Upcase) { + MmFreePool(vol->Upcase); + vol->Upcase = (uint16_t*)0; + } +} + +static void NtfsTryLoadUpcase(NTFS_VOLUME* vol) { + NTFS_FILE upcase; + if (!KSUCCESS(NtfsLoadFile(vol, NTFS_UPCASE_INODE, &upcase))) return; + if (vol->Upcase && upcase.Size >= NTFS_UPCASE_TABLE_BYTES) { + size_t br = 0; + (void)NtfsReadFileData(&upcase, 0, vol->Upcase, NTFS_UPCASE_TABLE_BYTES, &br); + } + NtfsCleanupFile(&upcase); +} + +static KSTATUS NtfsProbeAndPrepareVolume(NTFS_VOLUME* vol, uint8_t disk, uint64_t startLba, uint64_t sectors) { + if (!vol) return STATUS_INVALID_PARAMETER; + memset(vol, 0, sizeof(*vol)); + vol->DriveIndex = disk; + vol->PartitionLba = startLba; + vol->PartitionSize = sectors; + vol->BytesPerSector = NTFS_SECTOR_SIZE; + NtfsInitRunList(&vol->MftRuns); + + uint8_t sector[NTFS_SECTOR_SIZE]; + memset(sector, 0, sizeof(sector)); + KSTATUS s = NtfsReadSectors(vol, 0, 1, sector); + if (!KSUCCESS(s)) return s; + + const NTFS_BOOT_SECTOR* bs = (const NTFS_BOOT_SECTOR*)sector; + if (!NtfsIsValidBootSector(bs)) return STATUS_NOT_SUPPORTED; + + vol->BytesPerSector = bs->BytesPerSector; + vol->SectorsPerCluster = bs->SectorsPerCluster; + vol->BytesPerCluster = vol->BytesPerSector * vol->SectorsPerCluster; + vol->TotalSectors = bs->TotalSectors; + vol->PartitionSize = sectors ? sectors : bs->TotalSectors; + if (vol->PartitionSize > bs->TotalSectors) vol->PartitionSize = bs->TotalSectors; + vol->SerialNumber = bs->SerialNumber; + vol->MftStartCluster = bs->MFT; + vol->FileRecordSize = NtfsDecodeRecordSize(bs->ClustersPerMFTRecord, vol->BytesPerCluster); + if (vol->FileRecordSize == 0 || vol->FileRecordSize > 65536ull || (vol->FileRecordSize % vol->BytesPerSector) != 0) + return STATUS_INVALID_PARAMETER; + s = NtfsInitUpcaseIdentity(vol); + if (!KSUCCESS(s)) return s; + + s = NtfsReadMftRuns(vol); + if (!KSUCCESS(s)) { NtfsCleanupVolume(vol); return s; } + NtfsTryLoadUpcase(vol); + return STATUS_SUCCESS; +} + +static uint32_t NtfsEnumerateDiskPartitions(uint8_t disk, NTFS_PARTITION_CANDIDATE* parts, uint32_t maxParts, bool* mediaReadable) { + if (mediaReadable) *mediaReadable = false; + if (!parts || maxParts == 0) return 0; + uint8_t sector[NTFS_SECTOR_SIZE]; + NTFS_VOLUME raw; + memset(&raw, 0, sizeof(raw)); + raw.DriveIndex = disk; + raw.PartitionLba = 0; + raw.PartitionSize = IoDiskSectorCount(disk); + raw.BytesPerSector = NTFS_SECTOR_SIZE; + NtfsInitRunList(&raw.MftRuns); + + if (!KSUCCESS(NtfsReadSectors(&raw, 0, 1, sector))) return 0; + if (mediaReadable) *mediaReadable = true; + + uint32_t count = 0; + NTFS_BOOT_SECTOR* vbr = (NTFS_BOOT_SECTOR*)sector; + if (NtfsIsValidBootSector(vbr)) { + parts[count].StartLba = 0; + parts[count].SectorCount = vbr->TotalSectors; + count++; + if (count >= maxParts) return count; + } + + if (sector[510] != 0x55u || sector[511] != 0xaau) return count; + + NTFS_MBR_PARTITION* mbr = (NTFS_MBR_PARTITION*)(sector + 446u); + bool protectiveMbr = false; + for (uint32_t i = 0; i < 4u; i++) if (mbr[i].OsType == 0xeeu) protectiveMbr = true; + + if (protectiveMbr) { + uint8_t gptSector[NTFS_SECTOR_SIZE]; + if (!KSUCCESS(NtfsReadSectors(&raw, 1, 1, gptSector))) return count; + NTFS_GPT_HEADER* hdr = (NTFS_GPT_HEADER*)gptSector; + if (memcmp(hdr->Signature, "EFI PART", 8) != 0 || hdr->SizeOfPartitionEntry < sizeof(NTFS_GPT_ENTRY)) return count; + uint32_t entries = hdr->NumberOfPartitionEntries; + if (entries > 256u) entries = 256u; + uint32_t entrySize = hdr->SizeOfPartitionEntry; + uint8_t entrySector[NTFS_SECTOR_SIZE]; + for (uint32_t i = 0; i < entries && count < maxParts; i++) { + uint64_t byteOff = (uint64_t)i * entrySize; + uint64_t lba = hdr->PartitionEntryLBA + byteOff / NTFS_SECTOR_SIZE; + uint32_t inSector = (uint32_t)(byteOff % NTFS_SECTOR_SIZE); + if (inSector + sizeof(NTFS_GPT_ENTRY) > NTFS_SECTOR_SIZE) continue; + if (!KSUCCESS(NtfsReadSectors(&raw, lba, 1, entrySector))) break; + NTFS_GPT_ENTRY* e = (NTFS_GPT_ENTRY*)(entrySector + inSector); + if (NtfsGuidIsZero(&e->PartitionTypeGUID)) continue; + if (e->EndingLBA < e->StartingLBA) continue; + parts[count].StartLba = e->StartingLBA; + parts[count].SectorCount = e->EndingLBA - e->StartingLBA + 1ull; + count++; + } + return count; + } + + for (uint32_t i = 0; i < 4u && count < maxParts; i++) { + if (mbr[i].OsType == 0 || mbr[i].SizeInLba == 0) continue; + parts[count].StartLba = mbr[i].StartingLba; + parts[count].SectorCount = mbr[i].SizeInLba; + count++; + } + return count; +} + +static int NtfsReserveFreeVolumeIndex(void) { + uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock); + for (uint8_t i = 0; i < NTFS_MAX_VOLUMES; i++) { + if (!gVolumeMounted[i] && !gVolumeBusy[i] && !IoFilesystemPresent(i)) { + gVolumeBusy[i] = true; + KeReleaseSpinlock(&gNtfsMountLock, flags); + return (int)i; + } + } + KeReleaseSpinlock(&gNtfsMountLock, flags); + return -1; +} + +static void NtfsReleaseReservedVolumeIndex(uint8_t volumeIndex) { + if (volumeIndex >= NTFS_MAX_VOLUMES) return; + uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock); + gVolumeBusy[volumeIndex] = false; + KeReleaseSpinlock(&gNtfsMountLock, flags); +} + +static bool NtfsBeginDiskScan(uint8_t disk) { + if (disk >= NTFS_MAX_VOLUMES) return false; + uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock); + if (gDiskScanned[disk]) { + KeReleaseSpinlock(&gNtfsMountLock, flags); + return false; + } + gDiskScanned[disk] = true; + KeReleaseSpinlock(&gNtfsMountLock, flags); + return true; +} + +static void NtfsEndDiskScan(uint8_t disk, bool completed) { + if (disk >= NTFS_MAX_VOLUMES) return; + if (completed) return; + uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock); + gDiskScanned[disk] = false; + KeReleaseSpinlock(&gNtfsMountLock, flags); +} + +static uint32_t NtfsTryMountDisk(uint8_t disk) { + if (disk >= NTFS_MAX_VOLUMES) return 0; + if (!IoDiskDevicePresent(disk)) return 0; + if (!NtfsBeginDiskScan(disk)) return 0; + + bool scanCompleted = false; + bool mediaReadable = false; + NTFS_PARTITION_CANDIDATE parts[NTFS_MAX_PARTITIONS_PER_DISK]; + uint32_t partCount = NtfsEnumerateDiskPartitions(disk, parts, NTFS_MAX_PARTITIONS_PER_DISK, &mediaReadable); + if (!mediaReadable) { + DbgPrint("NTFS: disk %u: media probe deferred; sector 0 unreadable\n", disk); + NtfsEndDiskScan(disk, false); + return 0; + } + scanCompleted = true; + + uint32_t mounted = 0; + for (uint32_t p = 0; p < partCount; p++) { + int freeVolume = NtfsReserveFreeVolumeIndex(); + if (freeVolume < 0) break; + uint8_t volumeIndex = (uint8_t)freeVolume; + KSTATUS s = NtfsProbeAndPrepareVolume(&gVolumes[volumeIndex], disk, parts[p].StartLba, parts[p].SectorCount); + if (!KSUCCESS(s)) { + NtfsCleanupVolume(&gVolumes[volumeIndex]); + memset(&gVolumes[volumeIndex], 0, sizeof(gVolumes[volumeIndex])); + NtfsReleaseReservedVolumeIndex(volumeIndex); + continue; + } + gVolumes[volumeIndex].VolumeIndex = volumeIndex; + + NtfsInitVtable(&gFilesystems[volumeIndex], &gVolumes[volumeIndex]); + IO_VOLUME_PARAMS params; + params.DiskIndex = disk; + params.PartitionLba = parts[p].StartLba; + params.PartitionSize = gVolumes[volumeIndex].PartitionSize; + s = IoRegisterFilesystem(volumeIndex, &gFilesystems[volumeIndex], ¶ms); + if (KSUCCESS(s)) { + uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock); + gVolumeMounted[volumeIndex] = true; + gVolumeBusy[volumeIndex] = false; + gMountedCount++; + KeReleaseSpinlock(&gNtfsMountLock, flags); + mounted++; + DbgPrint("NTFS: disk %u part %u mounted as drive %u (lba=%llu mft_record=%llu).\n", + disk, p, volumeIndex, + (unsigned long long)parts[p].StartLba, + (unsigned long long)gVolumes[volumeIndex].FileRecordSize); + } else { + NtfsCleanupVolume(&gVolumes[volumeIndex]); + memset(&gVolumes[volumeIndex], 0, sizeof(gVolumes[volumeIndex])); + memset(&gFilesystems[volumeIndex], 0, sizeof(gFilesystems[volumeIndex])); + NtfsReleaseReservedVolumeIndex(volumeIndex); + DbgPrint("NTFS: disk %u part %u mount failed status=0x%x.\n", disk, p, s); + } + } + NtfsEndDiskScan(disk, scanCompleted); + if (partCount && !mounted && gMountedCount >= NTFS_MAX_VOLUMES) + DbgPrint("NTFS: disk %u: volume table full\n", disk); + return mounted; +} + +static void NtfsUnmountDisk(uint8_t diskIndex, IO_DEVICE_CHANGE change) { + uint32_t removed = 0; + for (uint8_t i = 0; i < NTFS_MAX_VOLUMES; i++) { + if (!gVolumeMounted[i] || gVolumes[i].DriveIndex != diskIndex) continue; + (void)IoUnregisterFilesystem(i); + NtfsCleanupVolume(&gVolumes[i]); + memset(&gVolumes[i], 0, sizeof(gVolumes[i])); + memset(&gFilesystems[i], 0, sizeof(gFilesystems[i])); + uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock); + gVolumeMounted[i] = false; + gVolumeBusy[i] = false; + if (gMountedCount) gMountedCount--; + KeReleaseSpinlock(&gNtfsMountLock, flags); + removed++; + } + if (diskIndex < NTFS_MAX_VOLUMES) { + uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock); + gDiskScanned[diskIndex] = false; + KeReleaseSpinlock(&gNtfsMountLock, flags); + } + if (removed) { + DbgPrint("NTFS: disk %u %s; unmounted %u volume(s); total=%u\n", + diskIndex, + change == IoDeviceSurpriseRemoved ? "surprise removed" : "removed", + removed, + gMountedCount); + } +} + +static void NtfsDiskArrival(uint8_t driveIndex, DEVICE_OBJECT* device, void* context) { + (void)device; + (void)context; + uint32_t mounted = NtfsTryMountDisk(driveIndex); + if (mounted) DbgPrint("NTFS: hotplug scan mounted %u new volume(s); total=%u\n", mounted, gMountedCount); +} + +static void NtfsDiskChange(uint8_t driveIndex, DEVICE_OBJECT* device, IO_DEVICE_CHANGE change, void* context) { + (void)device; + (void)context; + switch (change) { + case IoDeviceArrived: + NtfsDiskArrival(driveIndex, device, context); + break; + case IoDeviceSurpriseRemoved: + case IoDeviceRemoved: + NtfsUnmountDisk(driveIndex, change); + break; + case IoDeviceQueryRemove: + default: + break; + } +} + +KSTATUS DriverEntry(const HAL_API* hal) { + DbgPrint("NTFS: Init\n"); + if (!hal) return STATUS_INVALID_PARAMETER; + (void)hal; + memset(gVolumes, 0, sizeof(gVolumes)); + memset(gFilesystems, 0, sizeof(gFilesystems)); + memset(gVolumeMounted, 0, sizeof(gVolumeMounted)); + memset(gVolumeBusy, 0, sizeof(gVolumeBusy)); + memset(gDiskScanned, 0, sizeof(gDiskScanned)); + gMountedCount = 0; + + KSTATUS s = IoRegisterDiskChangeCallback(NtfsDiskChange, (void*)0); + if (!KSUCCESS(s)) DbgPrint("NTFS: disk-change callback registration failed st=0x%x\n", s); + s = IoEnumerateDiskDevices(NtfsDiskArrival, (void*)0); + if (!KSUCCESS(s)) DbgPrint("NTFS: initial disk enumeration failed st=0x%x\n", s); + + if (!gMountedCount) { + DbgPrint("NTFS: no mountable NTFS volumes found.\n"); + return STATUS_NOT_SUPPORTED; + } + DbgPrint("NTFS: Init complete; mounted %u volume(s).\n", gMountedCount); + return STATUS_SUCCESS; +} diff --git a/code/drivers/input/ps2/ps2.c b/code/drivers/input/ps2/ps2.c index e133246..b1d45bf 100644 --- a/code/drivers/input/ps2/ps2.c +++ b/code/drivers/input/ps2/ps2.c @@ -2,8 +2,13 @@ #include "ps2.h" +static KDPC gKeyboardDpc; static KDPC gMouseDpc; static volatile bool gKeyboardNextExtended; +static INPUT_KEY_EVENT gKeyboardDpcQueue[KEYBOARD_DPC_QUEUE_SIZE]; +static volatile int gKeyboardDpcHead; +static volatile int gKeyboardDpcTail; +static volatile uint8_t gKeyboardDown[256]; static volatile uint8_t gMousePacket[3]; static volatile int gMousePacketIndex; static PS2_MOUSE_PACKET gMouseDpcQueue[MOUSE_DPC_QUEUE_SIZE]; @@ -121,6 +126,40 @@ static INPUT_KEY Ps2Set1ToKey(uint8_t make, bool extended) { } } +// Implements the ps2 Keyboard Dpc Routine operation. +static void Ps2KeyboardDpcRoutine(KDPC* dpc, void* context, void* systemArgument1, void* systemArgument2) { + (void)dpc; + (void)context; + (void)systemArgument1; + (void)systemArgument2; + + while (gKeyboardDpcHead != gKeyboardDpcTail) { + INPUT_KEY_EVENT event = gKeyboardDpcQueue[gKeyboardDpcHead]; + gKeyboardDpcHead = (gKeyboardDpcHead + 1) & (int)KEYBOARD_DPC_QUEUE_MASK; + KeInputSubmitKey(&event); + } +} + +// Implements the ps2 Queue Keyboard Event operation. +static void Ps2QueueKeyboardEvent(INPUT_KEY key, bool pressed) { + if (key >= sizeof(gKeyboardDown)) + return; + + bool wasDown = gKeyboardDown[key] != 0; + if (pressed == wasDown) + return; + + int next = (gKeyboardDpcTail + 1) & (int)KEYBOARD_DPC_QUEUE_MASK; + if (next == gKeyboardDpcHead) + return; + + gKeyboardDown[key] = pressed ? 1u : 0u; + gKeyboardDpcQueue[gKeyboardDpcTail].Key = key; + gKeyboardDpcQueue[gKeyboardDpcTail].Pressed = pressed; + gKeyboardDpcTail = next; + KeInsertQueueDpc(&gKeyboardDpc, 0, 0); +} + // Implements the ps2 Keyboard Isr operation. static void Ps2KeyboardIsr(void) { uint8_t sc = __inbyte(PS2_DATA); @@ -137,11 +176,7 @@ static void Ps2KeyboardIsr(void) { INPUT_KEY key = Ps2Set1ToKey(make, extended); if (key == INPUT_KEY_NONE) return; - INPUT_KEY_EVENT event = { - .Key = key, - .Pressed = !released, - }; - KeInputSubmitKey(&event); + Ps2QueueKeyboardEvent(key, !released); } // Implements the ps2 Mouse Dpc Routine operation. @@ -224,7 +259,7 @@ static void Ps2ControllerEnableInterrupts(void) { // Implements the ps2 Keyboard Initialize operation. static void Ps2KeyboardInitialize(void) { - // Enable scanning. If the keyboard is absent, the timeout/ACK failure is + // Enable scanning. If the keyboard is absent, the timeout/ACK failure is // non-fatal; the IRQ path simply never produces events. if (!Ps2SendKeyboard(0xF4u)) DbgPrint("ps2: keyboard enable did not ACK\n"); @@ -254,10 +289,11 @@ KSTATUS DriverEntry(const HAL_API* hal) { KeInputRegisterDevice("ps2-keyboard", 0, &keyboardId); KeInputRegisterDevice("ps2-mouse", 0, &mouseId); + KeInitializeDpc(&gKeyboardDpc, Ps2KeyboardDpcRoutine, 0); KeInitializeDpc(&gMouseDpc, Ps2MouseDpcRoutine, 0); // Keep 8042 IRQ generation disabled while doing polled controller/device - // setup. The old ordering routed IRQ1/IRQ12 first, which let ACK/BAT bytes + // setup. The old ordering routed IRQ1/IRQ12 first, which let ACK/BAT bytes // race the ISR and get consumed as fake key/mouse input before the polled // init path could read them. Ps2ControllerPrepare(); diff --git a/code/drivers/input/ps2/ps2.h b/code/drivers/input/ps2/ps2.h index bfcadec..2a07990 100644 --- a/code/drivers/input/ps2/ps2.h +++ b/code/drivers/input/ps2/ps2.h @@ -27,6 +27,8 @@ #define SC1_MAKE_MASK 0x7Fu #define SC1_EXT_PREFIX 0xE0u +#define KEYBOARD_DPC_QUEUE_SIZE 64u +#define KEYBOARD_DPC_QUEUE_MASK (KEYBOARD_DPC_QUEUE_SIZE - 1u) #define MOUSE_DPC_QUEUE_SIZE 64u #define MOUSE_DPC_QUEUE_MASK (MOUSE_DPC_QUEUE_SIZE - 1u) diff --git a/code/drivers/input/serial/serial.c b/code/drivers/input/serial/serial.c index e051893..853dc3f 100644 --- a/code/drivers/input/serial/serial.c +++ b/code/drivers/input/serial/serial.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #define SERIAL_COM1_BASE 0x3F8u @@ -35,6 +36,9 @@ #define SERIAL_FCR_CLEAR_TX 0x04u #define SERIAL_FCR_TRIGGER_14 0xC0u +#define SERIAL_RX_QUEUE_SIZE 1024u +#define SERIAL_RX_QUEUE_MASK (SERIAL_RX_QUEUE_SIZE - 1u) + typedef enum SERIAL_ESCAPE_STATE { SerialEscapeNone = 0, SerialEscapeEsc, @@ -42,9 +46,14 @@ typedef enum SERIAL_ESCAPE_STATE { SerialEscapeCsiNumber, } SERIAL_ESCAPE_STATE; +static KDPC gSerialRxDpc; static volatile SERIAL_ESCAPE_STATE gSerialEscapeState; static volatile uint8_t gSerialCsiNumber; static volatile bool gSerialDropNextLf; +static uint8_t gSerialRxQueue[SERIAL_RX_QUEUE_SIZE]; +static volatile uint32_t gSerialRxHead; +static volatile uint32_t gSerialRxTail; +static volatile uint32_t gSerialRxDropped; // Reads one byte from a COM register. static uint8_t SerialRead(uint16_t reg) { @@ -254,12 +263,41 @@ static void SerialReceiveByte(uint8_t ch) { SerialSubmitPrintable(ch); } +// Handles queued receive bytes at DISPATCH_LEVEL. +static void SerialRxDpcRoutine(KDPC* dpc, void* context, void* systemArgument1, void* systemArgument2) { + (void)dpc; + (void)context; + (void)systemArgument1; + (void)systemArgument2; + + while (gSerialRxHead != gSerialRxTail) { + uint32_t head = gSerialRxHead; + uint8_t ch = gSerialRxQueue[head]; + gSerialRxHead = (head + 1u) & SERIAL_RX_QUEUE_MASK; + SerialReceiveByte(ch); + } +} + +// Queues a received byte for bottom-half processing. +static void SerialQueueRxByte(uint8_t ch) { + uint32_t tail = gSerialRxTail; + uint32_t next = (tail + 1u) & SERIAL_RX_QUEUE_MASK; + if (next == gSerialRxHead) { + gSerialRxDropped++; + return; + } + + gSerialRxQueue[tail] = ch; + gSerialRxTail = next; + KeInsertQueueDpc(&gSerialRxDpc, 0, 0); +} + // Handles receive interrupts for COM1. static void SerialIsr(void) { for (uint32_t i = 0; i < 256u; i++) { if ((SerialRead(SERIAL_REG_LSR) & SERIAL_LSR_DATA_READY) == 0) break; - SerialReceiveByte(SerialRead(SERIAL_REG_DATA)); + SerialQueueRxByte(SerialRead(SERIAL_REG_DATA)); } } @@ -289,9 +327,13 @@ KSTATUS DriverEntry(const HAL_API* hal) { if (!KSUCCESS(st)) return st; + KeInitializeDpc(&gSerialRxDpc, SerialRxDpcRoutine, 0); gSerialEscapeState = SerialEscapeNone; gSerialCsiNumber = 0; gSerialDropNextLf = false; + gSerialRxHead = 0; + gSerialRxTail = 0; + gSerialRxDropped = 0; SerialInitializeCom1(); KeIrqRegister(SERIAL_COM1_IRQ, SerialIsr); diff --git a/code/drivers/net/lwip/include/arch/sys_arch.h b/code/drivers/net/lwip/include/arch/sys_arch.h index 7fd5c68..43934a8 100644 --- a/code/drivers/net/lwip/include/arch/sys_arch.h +++ b/code/drivers/net/lwip/include/arch/sys_arch.h @@ -32,12 +32,12 @@ typedef struct sys_mbox* sys_mbox_t; #define sys_mbox_set_invalid_val(mbox) do { (mbox) = NULL; } while (0) /* - * lwIP only treats sys_thread_t as an opaque handle. Keep it pointer-sized. + * lwIP only treats sys_thread_t as an opaque handle. Keep it pointer-sized. * - * Do NOT make this a by-value incomplete struct. MSVC may compile callers + * Do NOT make this a by-value incomplete struct. MSVC may compile callers * that see only the incomplete type with a hidden struct-return parameter, * which shifts sys_thread_new(name, thread, arg, ...) so the callee receives - * thread == name and arg == thread. The first tcpip_thread then jumps into + * thread == name and arg == thread. The first tcpip_thread then jumps into * the literal string "tcpip" as code. */ typedef void* sys_thread_t; diff --git a/code/drivers/net/lwip/include/lwipopts.h b/code/drivers/net/lwip/include/lwipopts.h index 0c8dc00..0b0a0bc 100644 --- a/code/drivers/net/lwip/include/lwipopts.h +++ b/code/drivers/net/lwip/include/lwipopts.h @@ -2,9 +2,9 @@ /* * lwipopts.h - kernel port configuration for lwIP 2.2.1. * - * This is intentionally a practical first configuration: IPv4, ARP, ICMP, - * UDP, TCP, DHCP, DNS and netconn are enabled; IPv6, PPP, - * SNMP, lwIP's POSIX socket API, and app protocols stay off. + * This is intentionally a practical first configuration: IPv4, IPv6, ARP, + * ICMP, ICMPv6, UDP, TCP, DHCP, DNS and netconn are enabled; PPP, SNMP, + * lwIP's POSIX socket API, and app protocols stay off. */ #define NO_SYS 0 @@ -28,10 +28,11 @@ #define MEMP_NUM_SYS_TIMEOUT 32 #define LWIP_IPV4 1 -#define LWIP_IPV6 0 +#define LWIP_IPV6 1 #define LWIP_ARP 1 #define LWIP_ETHERNET 1 #define LWIP_ICMP 1 +#define LWIP_ICMP6 1 #define LWIP_IGMP 0 #define LWIP_RAW 1 #define LWIP_UDP 1 @@ -39,6 +40,10 @@ #define LWIP_DHCP 1 #define LWIP_AUTOIP 0 #define LWIP_DNS 1 +#define LWIP_ND6_QUEUEING 1 +#define LWIP_IPV6_AUTOCONFIG 1 +#define LWIP_IPV6_MLD 1 +#define MEMP_NUM_ND6_QUEUE 8 #define TCP_MSS 1460 #define TCP_WND (4 * TCP_MSS) @@ -81,6 +86,8 @@ #define CHECKSUM_CHECK_IP 1 #define CHECKSUM_CHECK_UDP 1 #define CHECKSUM_CHECK_TCP 1 +#define CHECKSUM_GEN_ICMP6 1 +#define CHECKSUM_CHECK_ICMP6 1 #define LWIP_TIMERS 1 #define LWIP_TCPIP_TIMEOUT 1 diff --git a/code/drivers/net/lwip/include/stdlib.h b/code/drivers/net/lwip/include/stdlib.h index 4e1a0ad..04dcda8 100644 --- a/code/drivers/net/lwip/include/stdlib.h +++ b/code/drivers/net/lwip/include/stdlib.h @@ -5,9 +5,9 @@ /* * Minimal freestanding stdlib shim for lwIP. * - * lwIP 2.2.1 includes in a couple of files only for atoi(). If MSVC + * lwIP 2.2.1 includes in a couple of files only for atoi(). If MSVC * finds the UCRT header, it drags errno.h into the kernel build and then lwIP's - * own errno values collide with UCRT's. Put this header first in lwIP's include + * own errno values collide with UCRT's. Put this header first in lwIP's include * path so those sources stay freestanding. */ static __inline int atoi(const char* s) { diff --git a/code/drivers/net/lwip/lwip_sources.txt b/code/drivers/net/lwip/lwip_sources.txt index 6555522..eed9768 100644 --- a/code/drivers/net/lwip/lwip_sources.txt +++ b/code/drivers/net/lwip/lwip_sources.txt @@ -39,6 +39,18 @@ third_party/lwip/src/core/ipv4/ip4_frag.c third_party/lwip/src/core/ipv4/ip4.c third_party/lwip/src/core/ipv4/ip4_addr.c + +# IPv6 +third_party/lwip/src/core/ipv6/dhcp6.c +third_party/lwip/src/core/ipv6/ethip6.c +third_party/lwip/src/core/ipv6/icmp6.c +third_party/lwip/src/core/ipv6/inet6.c +third_party/lwip/src/core/ipv6/ip6_frag.c +third_party/lwip/src/core/ipv6/ip6.c +third_party/lwip/src/core/ipv6/ip6_addr.c +third_party/lwip/src/core/ipv6/mld6.c +third_party/lwip/src/core/ipv6/nd6.c + # netconn/socket API third_party/lwip/src/api/api_lib.c third_party/lwip/src/api/api_msg.c diff --git a/code/drivers/net/lwip/lwip_stack.c b/code/drivers/net/lwip/lwip_stack.c index 5490d0d..7ba4ee4 100644 --- a/code/drivers/net/lwip/lwip_stack.c +++ b/code/drivers/net/lwip/lwip_stack.c @@ -22,13 +22,16 @@ #include #include #include +#include #include #include #include +#include #include #include #include #include +#include #include #define LWIP_MAX_NETIFS 8u @@ -42,12 +45,18 @@ typedef struct LWIP_NETIF_CONFIG { uint8_t UseDhcp; - uint8_t HasDns; + uint8_t HasDns4; + uint8_t EnableIpv6; + uint8_t Ipv6Autoconfig; + uint8_t HasStaticIpv6; + uint8_t HasDns6; char Section[64]; ip4_addr_t Address; ip4_addr_t Netmask; ip4_addr_t Gateway; - ip4_addr_t Dns; + ip4_addr_t Dns4; + ip6_addr_t Address6; + ip6_addr_t Dns6; } LWIP_NETIF_CONFIG; typedef struct LWIP_BINDING { @@ -94,19 +103,19 @@ static KSTATUS LwipProviderAttachDevice(NET_DEVICE* device); static KSTATUS LwipProviderReceiveFrame(NET_DEVICE* device, const void* frame, size_t length); static KSTATUS LwipProviderSocketCreate(uint32_t domain, uint32_t type, uint32_t protocol, void** outSocket); static void LwipProviderSocketClose(void* socket); -static KSTATUS LwipProviderBind(void* socket, const SYS_SOCKET_ADDRESS_IPV4_K* address, size_t addressSize); +static KSTATUS LwipProviderBind(void* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize); static KSTATUS LwipProviderListen(void* socket, uint32_t backlog); -static KSTATUS LwipProviderAccept(void* socket, void** acceptedOut, SYS_SOCKET_ADDRESS_IPV4_K* remoteOut); -static KSTATUS LwipProviderConnect(void* socket, const SYS_SOCKET_ADDRESS_IPV4_K* address, size_t addressSize); +static KSTATUS LwipProviderAccept(void* socket, void** acceptedOut, SYS_SOCKET_ADDRESS_K* remoteOut); +static KSTATUS LwipProviderConnect(void* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize); static KSTATUS LwipProviderSend(void* socket, const void* buffer, size_t length, uint32_t flags, size_t* sentOut); static KSTATUS LwipProviderReceive(void* socket, void* buffer, size_t length, uint32_t flags, size_t* receivedOut); -static KSTATUS LwipProviderSendTo(void* socket, const void* buffer, size_t length, uint32_t flags, const SYS_SOCKET_ADDRESS_IPV4_K* remote, size_t remoteSize, size_t* sentOut); -static KSTATUS LwipProviderReceiveFrom(void* socket, void* buffer, size_t length, uint32_t flags, SYS_SOCKET_ADDRESS_IPV4_K* remoteOut, size_t* receivedOut); +static KSTATUS LwipProviderSendTo(void* socket, const void* buffer, size_t length, uint32_t flags, const SYS_SOCKET_ADDRESS_K* remote, size_t remoteSize, size_t* sentOut); +static KSTATUS LwipProviderReceiveFrom(void* socket, void* buffer, size_t length, uint32_t flags, SYS_SOCKET_ADDRESS_K* remoteOut, size_t* receivedOut); static KSTATUS LwipProviderShutdown(void* socket, uint32_t how); static KSTATUS LwipProviderSetOption(void* socket, uint32_t option, const void* value, size_t valueSize); static KSTATUS LwipProviderGetOption(void* socket, uint32_t option, void* value, size_t valueSize, size_t* actualSizeOut); static KSTATUS LwipProviderPoll(void* socket, uint32_t events, uint32_t* returnedEventsOut); -static KSTATUS LwipProviderResolveHostIpv4(const char* name, SYS_SOCKET_ADDRESS_IPV4_K* addressOut); +static KSTATUS LwipProviderResolveHost(const char* name, uint32_t family, SYS_SOCKET_ADDRESS_K* addressOut); static const NET_STACK_PROVIDER gLwipProvider = { NET_STACK_PROVIDER_VERSION, @@ -127,7 +136,7 @@ static const NET_STACK_PROVIDER gLwipProvider = { LwipProviderSetOption, LwipProviderGetOption, LwipProviderPoll, - LwipProviderResolveHostIpv4, + LwipProviderResolveHost, }; // Implements the err To Status operation. @@ -137,7 +146,7 @@ static KSTATUS LwipErrToStatus(err_t err) { case ERR_MEM: return STATUS_NO_MEMORY; case ERR_BUF: return STATUS_BUFFER_TOO_SMALL; case ERR_TIMEOUT: return STATUS_TIMEOUT; - case ERR_WOULDBLOCK:return STATUS_TIMEOUT; + case ERR_WOULDBLOCK:return STATUS_WOULD_BLOCK; case ERR_RTE: return STATUS_NOT_FOUND; case ERR_VAL: return STATUS_INVALID_PARAMETER; case ERR_ARG: return STATUS_INVALID_PARAMETER; @@ -209,6 +218,57 @@ static KSTATUS LwipGetSettingIpv4(const char* section, const char* key, return STATUS_SUCCESS; } +static KSTATUS LwipGetSettingIpv6(const char* section, const char* key, + ip6_addr_t* out, int required) { + char value[LWIP_SETTINGS_VALUE_MAX]; + KSTATUS st = KeGetSettingString(section, key, value, sizeof(value)); + if (st == STATUS_NOT_FOUND && !required) + return STATUS_NOT_FOUND; + if (!KSUCCESS(st)) + return st; + if (!ip6addr_aton(value, out)) { + DbgPrint("lwIP: invalid IPv6 setting [%s] %s=%s\n", section, key, value); + return STATUS_INVALID_PARAMETER; + } + return STATUS_SUCCESS; +} + +static KSTATUS LwipLoadIpv6Config(const char* section, LWIP_NETIF_CONFIG* cfg) { + char mode[LWIP_SETTINGS_VALUE_MAX]; + KSTATUS st = KeGetSettingString(section, "ipv6", mode, sizeof(mode)); + if (st == STATUS_NOT_FOUND) { + cfg->EnableIpv6 = 1; + cfg->Ipv6Autoconfig = 1; + } else if (!KSUCCESS(st)) { + return st; + } else if (strcmp(mode, "off") == 0 || strcmp(mode, "disabled") == 0) { + cfg->EnableIpv6 = 0; + } else if (strcmp(mode, "auto") == 0) { + cfg->EnableIpv6 = 1; + cfg->Ipv6Autoconfig = 1; + } else if (strcmp(mode, "static") == 0) { + cfg->EnableIpv6 = 1; + cfg->HasStaticIpv6 = 1; + st = LwipGetSettingIpv6(section, "address6", &cfg->Address6, 1); + if (!KSUCCESS(st)) + return st; + } else { + DbgPrint("lwIP: invalid IPv6 mode [%s] ipv6=%s\n", section, mode); + return STATUS_INVALID_PARAMETER; + } + + if (!cfg->EnableIpv6) + return STATUS_SUCCESS; + + st = LwipGetSettingIpv6(section, "dns6", &cfg->Dns6, 0); + if (KSUCCESS(st)) + cfg->HasDns6 = 1; + else if (st != STATUS_NOT_FOUND) + return st; + + return STATUS_SUCCESS; +} + // Copies section Name. static KSTATUS LwipCopySectionName(char* out, size_t outSize, const char* prefix, const char* name) { if (!out || !outSize || !prefix || !name) @@ -248,20 +308,20 @@ static KSTATUS LwipLoadConfigFromSection(const char* section, LWIP_NETIF_CONFIG* if (!KSUCCESS(st)) return st; st = LwipGetSettingIpv4(section, "gateway", &cfg->Gateway, 1); if (!KSUCCESS(st)) return st; - st = LwipGetSettingIpv4(section, "dns", &cfg->Dns, 1); + st = LwipGetSettingIpv4(section, "dns", &cfg->Dns4, 1); if (!KSUCCESS(st)) return st; - cfg->HasDns = 1; - return STATUS_SUCCESS; + cfg->HasDns4 = 1; + return LwipLoadIpv6Config(section, cfg); } if (strcmp(mode, "dhcp") == 0) { cfg->UseDhcp = 1; - st = LwipGetSettingIpv4(section, "dns", &cfg->Dns, 0); + st = LwipGetSettingIpv4(section, "dns", &cfg->Dns4, 0); if (KSUCCESS(st)) - cfg->HasDns = 1; + cfg->HasDns4 = 1; else if (st != STATUS_NOT_FOUND) return st; - return STATUS_SUCCESS; + return LwipLoadIpv6Config(section, cfg); } DbgPrint("lwIP: invalid network mode [%s] mode=%s\n", section, mode); @@ -372,18 +432,71 @@ static KSTATUS LwipSocketAllocateWithConn(uint32_t domain, uint32_t type, uint32 return STATUS_SUCCESS; } +static int LwipSocketAddressValid(const SYS_SOCKET_ADDRESS_K* address, size_t addressSize) { + if (!address || addressSize < sizeof(SYS_SOCKET_ADDRESS_K)) + return 0; + if (address->Size < sizeof(SYS_SOCKET_ADDRESS_K)) + return 0; + return address->Family == SYS_AF_INET || address->Family == SYS_AF_INET6; +} + +static int LwipSocketFamilyMatches(const LWIP_PROVIDER_SOCKET* s, const SYS_SOCKET_ADDRESS_K* address) { + if (!s || !address) + return 0; + if (s->Domain == SYS_AF_INET) + return address->Family == SYS_AF_INET; + if (s->Domain == SYS_AF_INET6) + return address->Family == SYS_AF_INET6; + return 0; +} + // Implements the socket Make Ip operation. -static void LwipSocketMakeIp(const SYS_SOCKET_ADDRESS_IPV4_K* address, ip_addr_t* out) { - IP_ADDR4(out, address->Address[0], address->Address[1], address->Address[2], address->Address[3]); +static KSTATUS LwipSocketMakeIp(const SYS_SOCKET_ADDRESS_K* address, ip_addr_t* out) { + if (!address || !out) + return STATUS_INVALID_PARAMETER; + if (address->Family == SYS_AF_INET) { + IP_ADDR4(out, address->Address[0], address->Address[1], address->Address[2], address->Address[3]); + return STATUS_SUCCESS; + } + if (address->Family == SYS_AF_INET6) { + uint32_t a0 = ((uint32_t)address->Address[0] << 24) | ((uint32_t)address->Address[1] << 16) | + ((uint32_t)address->Address[2] << 8) | (uint32_t)address->Address[3]; + uint32_t a1 = ((uint32_t)address->Address[4] << 24) | ((uint32_t)address->Address[5] << 16) | + ((uint32_t)address->Address[6] << 8) | (uint32_t)address->Address[7]; + uint32_t a2 = ((uint32_t)address->Address[8] << 24) | ((uint32_t)address->Address[9] << 16) | + ((uint32_t)address->Address[10] << 8) | (uint32_t)address->Address[11]; + uint32_t a3 = ((uint32_t)address->Address[12] << 24) | ((uint32_t)address->Address[13] << 16) | + ((uint32_t)address->Address[14] << 8) | (uint32_t)address->Address[15]; + IP_ADDR6_HOST(out, a0, a1, a2, a3); + ip6_addr_set_zone(ip_2_ip6(out), (u8_t)address->ScopeId); + return STATUS_SUCCESS; + } + return STATUS_NOT_SUPPORTED; } // Implements the socket Fill Address operation. -static void LwipSocketFillAddress(SYS_SOCKET_ADDRESS_IPV4_K* out, const ip_addr_t* ip, u16_t port) { +static void LwipSocketFillAddress(SYS_SOCKET_ADDRESS_K* out, const ip_addr_t* ip, u16_t port) { if (!out) return; memset(out, 0, sizeof(*out)); - out->Family = SYS_AF_INET; + out->Size = sizeof(*out); out->Port = port; + if (!ip) + return; + if (IP_IS_V6(ip)) { + out->Family = SYS_AF_INET6; + out->ScopeId = ip6_addr_zone(ip_2_ip6(ip)); + for (size_t i = 0; i < 4; ++i) { + uint32_t word = lwip_htonl(ip_2_ip6(ip)->addr[i]); + out->Address[i * 4 + 0] = (uint8_t)(word >> 24); + out->Address[i * 4 + 1] = (uint8_t)(word >> 16); + out->Address[i * 4 + 2] = (uint8_t)(word >> 8); + out->Address[i * 4 + 3] = (uint8_t)word; + } + return; + } + + out->Family = SYS_AF_INET; out->Address[0] = (uint8_t)ip4_addr1(ip_2_ip4(ip)); out->Address[1] = (uint8_t)ip4_addr2(ip_2_ip4(ip)); out->Address[2] = (uint8_t)ip4_addr3(ip_2_ip4(ip)); @@ -428,11 +541,17 @@ static err_t LwipNetifInit(struct netif* netif) { netif->name[0] = 'e'; netif->name[1] = 'n'; netif->output = etharp_output; +#if LWIP_IPV6 + netif->output_ip6 = ethip6_output; +#endif netif->linkoutput = LwipLowLevelOutput; netif->mtu = (u16_t)(device->Mtu ? device->Mtu : NET_MTU_ETHERNET); netif->hwaddr_len = NET_ETHERNET_ADDRESS_LENGTH; memcpy(netif->hwaddr, device->HardwareAddress, NET_ETHERNET_ADDRESS_LENGTH); netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET; +#if LWIP_IPV6_MLD + netif->flags |= NETIF_FLAG_MLD6; +#endif return ERR_OK; } @@ -476,12 +595,29 @@ static void LwipAttachCallback(void* arg) { if (netif_default == NULL) netif_set_default(n); - if (b->Config.HasDns) { + if (b->Config.HasDns4) { ip_addr_t dns; - ip_addr_copy_from_ip4(dns, b->Config.Dns); + ip_addr_copy_from_ip4(dns, b->Config.Dns4); dns_setserver(0, &dns); } +#if LWIP_IPV6 + if (b->Config.EnableIpv6) { + netif_create_ip6_linklocal_address(n, 1); + if (b->Config.Ipv6Autoconfig) + n->ip6_autoconfig_enabled = 1; + if (b->Config.HasStaticIpv6) { + netif_ip6_addr_set(n, 1, &b->Config.Address6); + netif_ip6_addr_set_state(n, 1, IP6_ADDR_PREFERRED); + } + if (b->Config.HasDns6) { + ip_addr_t dns6; + ip_addr_copy_from_ip6(dns6, b->Config.Dns6); + dns_setserver(1, &dns6); + } + } +#endif + if (b->Config.UseDhcp) { #if LWIP_DHCP err_t dhcpErr = dhcp_start(n); @@ -586,11 +722,12 @@ static KSTATUS LwipProviderAttachDevice(NET_DEVICE* device) { } gBindings[device->Index] = b; - DbgPrint("lwIP: attached %s as netif %c%c%u using [%s] mode=%s\n", + DbgPrint("lwIP: attached %s as netif %c%c%u using [%s] mode=%s ipv6=%s\n", device->Name, b->Netif.name[0], b->Netif.name[1], b->Netif.num, b->Config.Section, - b->Config.UseDhcp ? "dhcp" : "static"); + b->Config.UseDhcp ? "dhcp" : "static", + b->Config.EnableIpv6 ? (b->Config.HasStaticIpv6 ? "static" : "auto") : "off"); return STATUS_SUCCESS; } @@ -636,26 +773,27 @@ static KSTATUS LwipProviderSocketCreate(uint32_t domain, uint32_t type, uint32_t if (!gLwipInitialized) return STATUS_NOT_SUPPORTED; - if (domain != SYS_AF_INET) + if (domain != SYS_AF_INET && domain != SYS_AF_INET6) return STATUS_NOT_SUPPORTED; + int ipv6 = domain == SYS_AF_INET6; enum netconn_type netconnType; u8_t rawProtocol = 0; if (type == SYS_SOCK_STREAM) { if (protocol != 0 && protocol != SYS_IPPROTO_TCP) return STATUS_NOT_SUPPORTED; - netconnType = NETCONN_TCP; + netconnType = ipv6 ? NETCONN_TCP_IPV6 : NETCONN_TCP; protocol = SYS_IPPROTO_TCP; } else if (type == SYS_SOCK_DGRAM) { if (protocol != 0 && protocol != SYS_IPPROTO_UDP) return STATUS_NOT_SUPPORTED; - netconnType = NETCONN_UDP; + netconnType = ipv6 ? NETCONN_UDP_IPV6 : NETCONN_UDP; protocol = SYS_IPPROTO_UDP; } else if (type == SYS_SOCK_RAW) { - if (protocol != SYS_IPPROTO_ICMP) + if ((!ipv6 && protocol != SYS_IPPROTO_ICMP) || (ipv6 && protocol != SYS_IPPROTO_ICMPV6)) return STATUS_NOT_SUPPORTED; - netconnType = NETCONN_RAW; - rawProtocol = IP_PROTO_ICMP; + netconnType = ipv6 ? NETCONN_RAW_IPV6 : NETCONN_RAW; + rawProtocol = ipv6 ? IP6_NEXTH_ICMP6 : IP_PROTO_ICMP; } else { return STATUS_NOT_SUPPORTED; } @@ -703,15 +841,15 @@ static void LwipProviderSocketClose(void* socket) { } // Implements the provider Bind operation. -static KSTATUS LwipProviderBind(void* socket, const SYS_SOCKET_ADDRESS_IPV4_K* address, size_t addressSize) { +static KSTATUS LwipProviderBind(void* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize) { LWIP_PROVIDER_SOCKET* s = (LWIP_PROVIDER_SOCKET*)socket; if (!s || !address) return STATUS_INVALID_PARAMETER; - if (addressSize < sizeof(SYS_SOCKET_ADDRESS_IPV4_K)) + if (!LwipSocketAddressValid(address, addressSize)) return STATUS_BUFFER_TOO_SMALL; if ((s->Type != SYS_SOCK_STREAM && s->Type != SYS_SOCK_DGRAM) || !s->Conn) return STATUS_INVALID_PARAMETER; - if (address->Family != SYS_AF_INET) + if (!LwipSocketFamilyMatches(s, address)) return STATUS_NOT_SUPPORTED; KSTATUS st = LwipSocketLock(s); @@ -724,7 +862,11 @@ static KSTATUS LwipProviderBind(void* socket, const SYS_SOCKET_ADDRESS_IPV4_K* a } ip_addr_t ip; - LwipSocketMakeIp(address, &ip); + st = LwipSocketMakeIp(address, &ip); + if (!KSUCCESS(st)) { + LwipSocketUnlock(s); + return st; + } err_t err = netconn_bind(s->Conn, &ip, address->Port); if (err == ERR_OK) s->Bound = 1; @@ -763,7 +905,7 @@ static KSTATUS LwipProviderListen(void* socket, uint32_t backlog) { } // Implements the provider Accept operation. -static KSTATUS LwipProviderAccept(void* socket, void** acceptedOut, SYS_SOCKET_ADDRESS_IPV4_K* remoteOut) { +static KSTATUS LwipProviderAccept(void* socket, void** acceptedOut, SYS_SOCKET_ADDRESS_K* remoteOut) { LWIP_PROVIDER_SOCKET* s = (LWIP_PROVIDER_SOCKET*)socket; if (!s || !acceptedOut) return STATUS_INVALID_PARAMETER; @@ -807,11 +949,12 @@ static KSTATUS LwipProviderAccept(void* socket, void** acceptedOut, SYS_SOCKET_A ip_addr_t peerIp; u16_t peerPort = 0; err = netconn_peer(acceptedConn, &peerIp, &peerPort); - if (err == ERR_OK && IP_IS_V4(&peerIp)) { + if (err == ERR_OK) { LwipSocketFillAddress(remoteOut, &peerIp, peerPort); } else { memset(remoteOut, 0, sizeof(*remoteOut)); - remoteOut->Family = SYS_AF_INET; + remoteOut->Size = sizeof(*remoteOut); + remoteOut->Family = s->Domain; } } @@ -820,15 +963,15 @@ static KSTATUS LwipProviderAccept(void* socket, void** acceptedOut, SYS_SOCKET_A } // Implements the provider Connect operation. -static KSTATUS LwipProviderConnect(void* socket, const SYS_SOCKET_ADDRESS_IPV4_K* address, size_t addressSize) { +static KSTATUS LwipProviderConnect(void* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize) { LWIP_PROVIDER_SOCKET* s = (LWIP_PROVIDER_SOCKET*)socket; if (!s || !address) return STATUS_INVALID_PARAMETER; - if (addressSize < sizeof(SYS_SOCKET_ADDRESS_IPV4_K)) + if (!LwipSocketAddressValid(address, addressSize)) return STATUS_BUFFER_TOO_SMALL; if ((s->Type != SYS_SOCK_STREAM && s->Type != SYS_SOCK_DGRAM) || !s->Conn) return STATUS_INVALID_PARAMETER; - if (address->Family != SYS_AF_INET) + if (!LwipSocketFamilyMatches(s, address)) return STATUS_NOT_SUPPORTED; KSTATUS st = LwipSocketLock(s); @@ -845,7 +988,11 @@ static KSTATUS LwipProviderConnect(void* socket, const SYS_SOCKET_ADDRESS_IPV4_K } ip_addr_t ip; - LwipSocketMakeIp(address, &ip); + st = LwipSocketMakeIp(address, &ip); + if (!KSUCCESS(st)) { + LwipSocketUnlock(s); + return st; + } err_t err = netconn_connect(s->Conn, &ip, address->Port); if (err == ERR_OK) @@ -1007,13 +1154,13 @@ static KSTATUS LwipProviderReceive(void* socket, void* buffer, size_t length, ui } static KSTATUS LwipProviderSendTo(void* socket, const void* buffer, size_t length, uint32_t flags, - const SYS_SOCKET_ADDRESS_IPV4_K* remote, size_t remoteSize, size_t* sentOut) { + const SYS_SOCKET_ADDRESS_K* remote, size_t remoteSize, size_t* sentOut) { LWIP_PROVIDER_SOCKET* s = (LWIP_PROVIDER_SOCKET*)socket; if (sentOut) *sentOut = 0; if (!s || (!buffer && length) || !remote) return STATUS_INVALID_PARAMETER; - if (remoteSize < sizeof(SYS_SOCKET_ADDRESS_IPV4_K)) + if (!LwipSocketAddressValid(remote, remoteSize)) return STATUS_BUFFER_TOO_SMALL; if (flags != 0) return STATUS_NOT_SUPPORTED; @@ -1021,7 +1168,7 @@ static KSTATUS LwipProviderSendTo(void* socket, const void* buffer, size_t lengt return STATUS_INVALID_PARAMETER; if (s->Type == SYS_SOCK_RAW && length > LWIP_PROVIDER_SOCKET_RAW_MAX_BYTES) return STATUS_INVALID_PARAMETER; - if (remote->Family != SYS_AF_INET) + if (!LwipSocketFamilyMatches(s, remote)) return STATUS_NOT_SUPPORTED; if (length == 0) return STATUS_SUCCESS; @@ -1046,8 +1193,11 @@ static KSTATUS LwipProviderSendTo(void* socket, const void* buffer, size_t lengt err_t err = netbuf_ref(nb, buffer, (u16_t)length); if (err == ERR_OK) { ip_addr_t ip; - LwipSocketMakeIp(remote, &ip); - err = netconn_sendto(s->Conn, nb, &ip, remote->Port); + KSTATUS makeStatus = LwipSocketMakeIp(remote, &ip); + if (KSUCCESS(makeStatus)) + err = netconn_sendto(s->Conn, nb, &ip, remote->Port); + else + err = ERR_ARG; } netbuf_delete(nb); @@ -1059,7 +1209,7 @@ static KSTATUS LwipProviderSendTo(void* socket, const void* buffer, size_t lengt } static KSTATUS LwipProviderReceiveFrom(void* socket, void* buffer, size_t length, uint32_t flags, - SYS_SOCKET_ADDRESS_IPV4_K* remoteOut, size_t* receivedOut) { + SYS_SOCKET_ADDRESS_K* remoteOut, size_t* receivedOut) { LWIP_PROVIDER_SOCKET* s = (LWIP_PROVIDER_SOCKET*)socket; if (receivedOut) *receivedOut = 0; @@ -1114,11 +1264,12 @@ static KSTATUS LwipProviderReceiveFrom(void* socket, void* buffer, size_t length if (remoteOut) { const ip_addr_t* from = netbuf_fromaddr(nb); - if (from && IP_IS_V4(from)) { + if (from) { LwipSocketFillAddress(remoteOut, from, netbuf_fromport(nb)); } else { memset(remoteOut, 0, sizeof(*remoteOut)); - remoteOut->Family = SYS_AF_INET; + remoteOut->Size = sizeof(*remoteOut); + remoteOut->Family = s->Domain; } } @@ -1271,27 +1422,30 @@ static KSTATUS LwipProviderShutdown(void* socket, uint32_t how) { return LwipErrToStatus(err); } -// Implements the provider Resolve Host Ipv4 operation. -static KSTATUS LwipProviderResolveHostIpv4(const char* name, SYS_SOCKET_ADDRESS_IPV4_K* addressOut) { +// Implements the provider Resolve Host operation. +static KSTATUS LwipProviderResolveHost(const char* name, uint32_t family, SYS_SOCKET_ADDRESS_K* addressOut) { if (!name || !addressOut) return STATUS_INVALID_PARAMETER; if (!gLwipInitialized) return STATUS_NOT_SUPPORTED; + u8_t dnsType; + if (family == SYS_AF_INET) + dnsType = NETCONN_DNS_IPV4; + else if (family == SYS_AF_INET6) + dnsType = NETCONN_DNS_IPV6; + else + return STATUS_NOT_SUPPORTED; + ip_addr_t resolved; - err_t err = netconn_gethostbyname_addrtype(name, &resolved, NETCONN_DNS_IPV4); + err_t err = netconn_gethostbyname_addrtype(name, &resolved, dnsType); if (err != ERR_OK) return LwipErrToStatus(err); - if (!IP_IS_V4(&resolved)) + if ((family == SYS_AF_INET && !IP_IS_V4(&resolved)) || + (family == SYS_AF_INET6 && !IP_IS_V6(&resolved))) return STATUS_NOT_FOUND; - memset(addressOut, 0, sizeof(*addressOut)); - addressOut->Family = SYS_AF_INET; - addressOut->Port = 0; - addressOut->Address[0] = (uint8_t)ip4_addr1(ip_2_ip4(&resolved)); - addressOut->Address[1] = (uint8_t)ip4_addr2(ip_2_ip4(&resolved)); - addressOut->Address[2] = (uint8_t)ip4_addr3(ip_2_ip4(&resolved)); - addressOut->Address[3] = (uint8_t)ip4_addr4(ip_2_ip4(&resolved)); + LwipSocketFillAddress(addressOut, &resolved, 0); return STATUS_SUCCESS; } diff --git a/code/drivers/net/lwip/lwip_sys_arch.c b/code/drivers/net/lwip/lwip_sys_arch.c index 8ceb47a..446af3b 100644 --- a/code/drivers/net/lwip/lwip_sys_arch.c +++ b/code/drivers/net/lwip/lwip_sys_arch.c @@ -5,8 +5,7 @@ #include #include #include -#pragma intrinsic(_disable) -#pragma intrinsic(_enable) +#include #include #include #include @@ -41,7 +40,6 @@ struct sys_mbox { uint32_t Valid; }; - static uint64_t (*gLwipQueryTicksUs)(void) = 0; static uint32_t gRandState = 0x4b4e4554u; // 'KNET' @@ -70,7 +68,7 @@ u32_t sys_jiffies(void) { return sys_now(); } -// lwIP's core/sys.c provides sys_msleep() when it is not a macro. Do not +// lwIP's core/sys.c provides sys_msleep() when it is not a macro. Do not // define it here as well, or MSVC links both sys.obj and lwip_sys_arch.obj // with a duplicate sys_msleep symbol. @@ -360,15 +358,10 @@ sys_thread_t sys_thread_new(const char* name, lwip_thread_fn thread, void* arg, } sys_prot_t sys_arch_protect(void) { - uintptr_t flags = __readeflags(); - _disable(); - return (sys_prot_t)flags; + return (sys_prot_t)HalDisableInterrupts(); } -// Implements the sys arch unprotect operation. +// Restores the interrupt state saved by sys_arch_protect. void sys_arch_unprotect(sys_prot_t pval) { - if (((uintptr_t)pval) & 0x200u) - _enable(); - else - _disable(); + HalRestoreInterrupts((uintptr_t)pval); } diff --git a/code/drivers/net/rtl8139/rtl8139.c b/code/drivers/net/rtl8139/rtl8139.c index a733277..15e8a7c 100644 --- a/code/drivers/net/rtl8139/rtl8139.c +++ b/code/drivers/net/rtl8139/rtl8139.c @@ -63,10 +63,6 @@ static void Rtl8139Write32(RTL8139_DEVICE* r, uint32_t reg, uint32_t value) { else __outdword((unsigned short)(r->IoBase + reg), value); } - - - - // Implements the reset operation. static KSTATUS Rtl8139Reset(RTL8139_DEVICE* r) { Rtl8139Write8(r, RTL8139_REG_CMD, RTL8139_CMD_RST); @@ -93,7 +89,7 @@ static KSTATUS Rtl8139AllocateDma(RTL8139_DEVICE* r) { if (!r->RxPhys || !r->TxPhys) return STATUS_NO_MEMORY; - // RTL8139 has 32-bit DMA address registers. This is fine for typical QEMU + // RTL8139 has 32-bit DMA address registers. This is fine for typical QEMU // and low-memory hobby OS configs, but make failure explicit. if ((r->RxPhys + RTL8139_RX_ALLOC_SIZE - 1) > 0xFFFFFFFFULL || (r->TxPhys + RTL8139_TX_SLOTS * RTL8139_TX_BUF_SIZE - 1) > 0xFFFFFFFFULL) { diff --git a/code/drivers/storage/ahci/ahci.c b/code/drivers/storage/ahci/ahci.c index b2b6318..311df21 100644 --- a/code/drivers/storage/ahci/ahci.c +++ b/code/drivers/storage/ahci/ahci.c @@ -49,9 +49,7 @@ static inline uint64_t cmd_tbl_phys(AHCI_DRIVE *drv, int slot) { + (uint64_t)slot * PORT_CMDTBL_SIZE; } -// Port engine start / stop (polling safe). - - +// Port engine start / stop (polling safe). // Port memory initialisation. @@ -148,7 +146,7 @@ static KSTATUS port_issue_cmd_poll(AHCI_DRIVE *drv) { return STATUS_SUCCESS; } -// Interrupt bottom half. The ISR owns hardware acknowledgement and status +// Interrupt bottom half. The ISR owns hardware acknowledgement and status // capture; this DPC wakes the worker thread after the hard IRQ has unwound. static void AhciCompletionDpc(KDPC *dpc, void *context, @@ -182,9 +180,9 @@ static void AhciIsr(void) { uint32_t portBit = (1u << drv->portNo); if (!(pending & portBit)) continue; - // Capture and clear port IS before queueing the bottom half. The + // Capture and clear port IS before queueing the bottom half. The // waiter reads lastIs to check for errors without racing the next - // command. OR in case hardware reports multiple causes before the + // command. OR in case hardware reports multiple causes before the // DPC releases the worker. uint32_t portIs = drv->port->is; drv->port->is = portIs; // W1C - write back to clear @@ -383,7 +381,6 @@ static KSTATUS do_write(AHCI_DRIVE *drv, uint64_t lba, // Async IRP dispatch. - static KSTATUS AhciGetIrpReadWrite(IRP* irp, bool* outWrite, uint64_t* outLba, uint32_t* outSectors, void** outBuffer) { if (!irp || !outWrite || !outLba || !outSectors || !outBuffer) @@ -407,7 +404,7 @@ static KSTATUS AhciGetIrpReadWrite(IRP* irp, bool* outWrite, uint64_t* outLba, // AhciDispatchReadWrite - DEVICE_OBJECT dispatch entry for disk IRPs. // Accepted IRPs are queued to the per-drive worker and completed later with -// IoCompleteRequest(). Rejected IRPs are returned synchronously and are not +// IoCompleteRequest(). Rejected IRPs are returned synchronously and are not // completed. static KSTATUS AhciDispatchReadWrite(DEVICE_OBJECT *device, IRP *irp) { if (!device || !irp) return STATUS_INVALID_PARAMETER; @@ -592,26 +589,23 @@ static void AhciWorker(uintptr_t arg) { // AHCI controller initialisation. - - // Before memory init: only check physical link. Signature is not yet valid. // After COMRESET + FIS receive: check signature for SATA disk. - // Issue a COMRESET on the port, wait for link re-establishment and D2H FIS. // FRE must already be set (port_init_memory does this) so the HBA can // capture the signature FIS the device sends after the reset. // PORT_IE bits we care about: -// DHRS (bit 0) - D2H Register FIS received - normal completion -// PSS (bit 1) - PIO Setup FIS received -// DSS (bit 2) - DMA Setup FIS received -// SDBS (bit 3) - Set Device Bits FIS received -// TFES (bit 30) - Task File Error Status - error -// HBFS (bit 29) - Host Bus Fatal error -// HBDS (bit 28) - Host Bus Data error -// IFS (bit 27) - Interface Fatal error. +// DHRS (bit 0) - D2H Register FIS received - normal completion +// PSS (bit 1) - PIO Setup FIS received +// DSS (bit 2) - DMA Setup FIS received +// SDBS (bit 3) - Set Device Bits FIS received +// TFES (bit 30) - Task File Error Status - error +// HBFS (bit 29) - Host Bus Fatal error +// HBDS (bit 28) - Host Bus Data error +// IFS (bit 27) - Interface Fatal error. #define PORT_IE_NORMAL (0x0000000Fu) #define PORT_IE_ERRORS (PORT_IS_TFES | PORT_IS_HBFS | PORT_IS_HBDS | PORT_IS_IFS) #define PORT_IE_ALL (PORT_IE_NORMAL | PORT_IE_ERRORS) diff --git a/code/drivers/storage/ahci/ahci.h b/code/drivers/storage/ahci/ahci.h index 2fa29fc..8f4f2a3 100644 --- a/code/drivers/storage/ahci/ahci.h +++ b/code/drivers/storage/ahci/ahci.h @@ -221,7 +221,7 @@ typedef volatile struct { // Constants. -// PCI/AHCI binding. PCI enumeration, BAR decoding, interrupt line +// PCI/AHCI binding. PCI enumeration, BAR decoding, interrupt line // discovery, and command-register policy are owned by pci.sys. #define AHCI_PCI_BAR_ABAR 5u #define AHCI_CONTROLLER_MAGIC 0x41484349u // 'AHCI' diff --git a/code/drivers/storage/ahci/ahci_port.c b/code/drivers/storage/ahci/ahci_port.c index 67c00e2..428b3d3 100644 --- a/code/drivers/storage/ahci/ahci_port.c +++ b/code/drivers/storage/ahci/ahci_port.c @@ -28,7 +28,6 @@ KSTATUS ahci_map_mmio(uint64_t physBase, uint64_t length) { return STATUS_SUCCESS; } - // Implements the port stop operation. void port_stop(HBA_PORT *port) { // Clear ST (bit 0). @@ -45,7 +44,6 @@ void port_stop(HBA_PORT *port) { } } - // Implements the port start operation. void port_start(HBA_PORT *port) { // Wait until CR is clear before setting ST. @@ -56,7 +54,6 @@ void port_start(HBA_PORT *port) { port->cmd |= PORT_CMD_ST; } - // Implements the hba reset operation. KSTATUS hba_reset(HBA_MEM *hba) { // BIOS/OS handoff (spec §10.6.3). @@ -91,7 +88,6 @@ KSTATUS hba_reset(HBA_MEM *hba) { return STATUS_SUCCESS; } - // Implements the port link up operation. bool port_link_up(HBA_PORT *port) { uint32_t det = port->ssts & 0x0F; @@ -99,20 +95,17 @@ bool port_link_up(HBA_PORT *port) { return (det == PORT_DET_PRESENT && ipm == PORT_IPM_ACTIVE); } - // Implements the port is sata disk operation. bool port_is_sata_disk(HBA_PORT *port) { return port->sig == SATA_SIG_ATA; } - // Implements the ahci delay ms operation. void ahci_delay_ms(uint32_t ms) { for (uint32_t i = 0; i < ms * 1000; i++) __outbyte(0x80, 0); } - // Implements the port comreset operation. KSTATUS port_comreset(AHCI_DRIVE *drv) { HBA_PORT *port = drv->port; diff --git a/code/kernel/hal/hal_main.c b/code/kernel/hal/hal_main.c index 357815b..041a85d 100644 --- a/code/kernel/hal/hal_main.c +++ b/code/kernel/hal/hal_main.c @@ -2,11 +2,11 @@ #include #include +#include #include #include #include #include -#pragma intrinsic(_disable) #include void HalScreenInitialize(void* (*AllocatePool)(size_t size), uintptr_t fb, int w, int h, int stride, @@ -92,26 +92,41 @@ static uint8_t gSlpTypB = 0; static int gS5Found = 0; int debugEnabled = 0; -int _fltused; // MSVC floating-point helper anchor -// Implements the hal putchar operation. -static void hal_putchar(char c) { - gApi.Screen.PrintChar(c, 0xCCCCCC, 0x012456); -} -// Implements the dbg Print operation. -void HalDbgPrint(const char* fmt, ...) { - if (!debugEnabled) return; +int _fltused; // MSVC floating-point helper anchor. +// Writes a formatted early HAL diagnostic through the output router. +void HalDbgPrint(const char* fmt, ...) { + if (!debugEnabled || !fmt || !gApi.Output.Write) + return; + + char line[1024]; va_list args; va_start(args, fmt); - vprintf(fmt, args); + int count = vsnprintf_(line, sizeof(line), fmt, args); va_end(args); + + size_t length = 0; + if (count > 0) { + length = (size_t)count; + if (length >= sizeof(line)) + length = sizeof(line) - 1u; + } + + HAL_OUTPUT_RECORD record; + record.Stream = HAL_OUTPUT_STREAM_DEBUG; + record.Flags = 0; + record.Foreground = 0x00CCCCCCu; + record.Background = 0x00012456u; + record.Buffer = line; + record.Length = length; + gApi.Output.Write(&record); } static void *HalPhysToVirt(uint64_t phys) { return (void *)(phys + gPhysBase); } -// FADT (Fixed ACPI Description Table) offsets used here. ACPI 2.0+ +// FADT (Fixed ACPI Description Table) offsets used here. ACPI 2.0+ // systems may put the real DSDT and PM1 control registers in the extended // X_* fields, so do not rely only on the legacy 32-bit fields. #define FADT_DSDT_OFFSET 40u @@ -155,7 +170,7 @@ static uint16_t AcpiReadGasIoPort(const void* table, uint32_t tableLen, uint32_t memcpy(&gas, (const uint8_t*)table + off, sizeof(gas)); - // 0x01 == System I/O. HalShutdown currently writes PM1_CNT with outw(), + // 0x01 == System I/O. HalShutdown currently writes PM1_CNT with outw(), // so ignore MMIO GAS entries until the HAL grows MMIO register access here. if (gas.AddressSpaceId != 0x01 || gas.Address == 0 || gas.Address > 0xFFFF) return 0; @@ -257,7 +272,7 @@ static int AmlReadInteger(const uint8_t** pp, const uint8_t* end, uint8_t* out) return 1; } -// Scan DSDT AML for the _S5_ Name object and extract SLP_TYP values. Real +// Scan DSDT AML for the _S5_ Name object and extract SLP_TYP values. Real // firmware commonly encodes the name as "\\_S5_" instead of just "_S5_", and // package integers may be ZeroOp/OneOp/Byte/Word/DWord/QWord constants. static void ScanDsdtForS5(const uint8_t* aml, uint32_t len) { @@ -343,7 +358,6 @@ static void ParseFadt(const ACPISDTHeader* header) { } } - // Implements the add Cpu Record operation. static void HalAddCpuRecord(uint32_t uid, uint32_t lapicId, uint32_t flags) { if (gCpuTopology.CpuCount >= HAL_MAX_CPUS) @@ -470,7 +484,7 @@ static void WalkAcpi(uintptr_t rsdpPhys) { } // Parse HPET. else if (memcmp(header->Signature, "HPET", 4) == 0) { - // HPET base address block is located at offset 44 + // HPET base address block is located at offset 44 // (which is 8 bytes past the 36-byte ACPISDTHeader). gHpetPhys = *(uint64_t*)((uint8_t*)header + 44) & ~0xFFFULL; } @@ -479,13 +493,13 @@ static void WalkAcpi(uintptr_t rsdpPhys) { // HalShutdown - ACPI S5 soft power-off. Does not return. static void HalShutdown(void) { - // Silence! Stop the world! - _disable(); + // Refuse further maskable interrupts before entering firmware power state. + (void)HalDisableInterrupts(); if (!gS5Found || !gPm1aCntBlk) { - // Refuse to guess. Guessing SLP_TYP is exactly how real hardware gets + // Refuse to guess. Guessing SLP_TYP is exactly how real hardware gets // put into S3/S4 sleep instead of S5 soft-off. - while (true) __halt(); + HalStopProcessor(); } // SLP_EN (bit 13) | SLP_TYPx (bits 12:10). @@ -495,8 +509,7 @@ static void HalShutdown(void) { __outword(gPm1aCntBlk, valA); if (gPm1bCntBlk) __outword(gPm1bCntBlk, valB); - // Die! - while (true) __halt(); + HalStopProcessor(); } __declspec(dllexport) KSTATUS HalGetProcessorTopology(HAL_CPU_TOPOLOGY* out) { @@ -542,14 +555,16 @@ __declspec(dllexport) KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) { gApi.PhysToVirt = HalPhysToVirt; debugEnabled = initParams->Debug; - // Screen first so DbgPrint works immediately. + HalOutputInitialize(&gApi.Output); + HalSerialInitialize(&gApi.Output); + + // Screen follows serial so diagnostics still work without a framebuffer. HalScreenInitialize(initParams->AllocatePool, initParams->FramebufferBase + initParams->PhysicalWindowBase, (int)initParams->FramebufferWidth, (int)initParams->FramebufferHeight, (int)initParams->FramebufferStride, &gApi.Screen); - - printf_set_putchar(hal_putchar); + HalScreenRegisterOutputEndpoint(&gApi.Output); HalDbgPrint("HAL: screen %dx%d\n", initParams->FramebufferWidth, initParams->FramebufferHeight); diff --git a/code/kernel/hal/hal_output.c b/code/kernel/hal/hal_output.c new file mode 100644 index 0000000..2dd31b2 --- /dev/null +++ b/code/kernel/hal/hal_output.c @@ -0,0 +1,142 @@ +// HAL output router. This file fans typed text streams out to registered physical and runtime endpoints. + +#include +#include +#include +#include +#include + +#define HAL_OUTPUT_ENDPOINT_CAPACITY 8u + +typedef struct HAL_OUTPUT_SLOT { + HAL_OUTPUT_ENDPOINT Endpoint; + volatile long Published; +} HAL_OUTPUT_SLOT; + +static HAL_OUTPUT_SLOT gOutputSlots[HAL_OUTPUT_ENDPOINT_CAPACITY]; +static volatile long gOutputRegistrationLock; +static volatile long long gOutputRecordCount; +static volatile long long gOutputByteCount; +static volatile long long gOutputEndpointWriteCount; +static volatile long long gOutputEmergencyRecordCount; +static volatile long gOutputEndpointCount; + +// Acquires the endpoint registration lock. +static void HalOutputAcquireRegistrationLock(void) { + while (_InterlockedCompareExchange(&gOutputRegistrationLock, 1, 0) != 0) + _mm_pause(); +} + +// Releases the endpoint registration lock. +static void HalOutputReleaseRegistrationLock(void) { + _InterlockedExchange(&gOutputRegistrationLock, 0); +} + +// Registers one output endpoint in the fixed endpoint table. +static KSTATUS HalOutputRegisterEndpoint(const HAL_OUTPUT_ENDPOINT* endpoint, uint32_t* endpointIdOut) { + if (!endpoint || !endpointIdOut || !endpoint->Name || !endpoint->Write || + endpoint->StreamMask == 0 || (endpoint->StreamMask & ~HAL_OUTPUT_STREAM_ALL) != 0) + return STATUS_INVALID_PARAMETER; + + KSTATUS status = STATUS_NO_MEMORY; + HalOutputAcquireRegistrationLock(); + for (uint32_t i = 0; i < HAL_OUTPUT_ENDPOINT_CAPACITY; i++) { + HAL_OUTPUT_SLOT* slot = &gOutputSlots[i]; + if (_InterlockedCompareExchange(&slot->Published, 0, 0) != 0) + continue; + + slot->Endpoint = *endpoint; + _ReadWriteBarrier(); + _InterlockedExchange(&slot->Published, 1); + _InterlockedIncrement(&gOutputEndpointCount); + *endpointIdOut = i; + status = STATUS_SUCCESS; + break; + } + HalOutputReleaseRegistrationLock(); + return status; +} + +// Replaces the stream subscription mask for a registered endpoint. +static KSTATUS HalOutputSetEndpointStreams(uint32_t endpointId, uint32_t streamMask) { + if (endpointId >= HAL_OUTPUT_ENDPOINT_CAPACITY || + (streamMask & ~HAL_OUTPUT_STREAM_ALL) != 0) + return STATUS_INVALID_PARAMETER; + + HAL_OUTPUT_SLOT* slot = &gOutputSlots[endpointId]; + if (_InterlockedCompareExchange(&slot->Published, 0, 0) == 0) + return STATUS_NOT_FOUND; + + _InterlockedExchange((volatile long*)&slot->Endpoint.StreamMask, (long)streamMask); + return STATUS_SUCCESS; +} + +// Fans one text record out to every subscribed endpoint. +static void HalOutputWrite(const HAL_OUTPUT_RECORD* record) { + if (!record || !record->Buffer || record->Length == 0 || + (record->Stream & HAL_OUTPUT_STREAM_ALL) == 0 || + (record->Stream & (record->Stream - 1u)) != 0) + return; + + _InterlockedIncrement64(&gOutputRecordCount); + _InterlockedExchangeAdd64(&gOutputByteCount, (long long)record->Length); + if (record->Flags & HAL_OUTPUT_WRITE_EMERGENCY) + _InterlockedIncrement64(&gOutputEmergencyRecordCount); + + for (uint32_t i = 0; i < HAL_OUTPUT_ENDPOINT_CAPACITY; i++) { + HAL_OUTPUT_SLOT* slot = &gOutputSlots[i]; + if (_InterlockedCompareExchange(&slot->Published, 0, 0) == 0) + continue; + + uint32_t streams = (uint32_t)_InterlockedCompareExchange( + (volatile long*)&slot->Endpoint.StreamMask, 0, 0); + if ((streams & record->Stream) == 0) + continue; + if ((record->Flags & HAL_OUTPUT_WRITE_EMERGENCY) && + !(slot->Endpoint.Capabilities & HAL_OUTPUT_ENDPOINT_PANIC_SAFE)) + continue; + + HAL_OUTPUT_ENDPOINT_WRITE write = slot->Endpoint.Write; + void* context = slot->Endpoint.Context; + if (!write) + continue; + + write(context, record); + _InterlockedIncrement64(&gOutputEndpointWriteCount); + } +} + +// Returns a snapshot of output router counters. +static void HalOutputQueryStats(HAL_OUTPUT_STATS* statsOut) { + if (!statsOut) + return; + + memset(statsOut, 0, sizeof(*statsOut)); + statsOut->RecordCount = (uint64_t)_InterlockedCompareExchange64(&gOutputRecordCount, 0, 0); + statsOut->ByteCount = (uint64_t)_InterlockedCompareExchange64(&gOutputByteCount, 0, 0); + statsOut->EndpointWriteCount = (uint64_t)_InterlockedCompareExchange64(&gOutputEndpointWriteCount, 0, 0); + statsOut->EmergencyRecordCount = (uint64_t)_InterlockedCompareExchange64(&gOutputEmergencyRecordCount, 0, 0); + statsOut->EndpointCount = (uint32_t)_InterlockedCompareExchange(&gOutputEndpointCount, 0, 0); +} + +// Initializes the fixed HAL output endpoint table. +void HalOutputInitialize(HAL_OUTPUT_ROUTER* output) { + if (!output) + return; + + memset(gOutputSlots, 0, sizeof(gOutputSlots)); + gOutputRegistrationLock = 0; + gOutputRecordCount = 0; + gOutputByteCount = 0; + gOutputEndpointWriteCount = 0; + gOutputEmergencyRecordCount = 0; + gOutputEndpointCount = 0; + + memset(output, 0, sizeof(*output)); + output->RegisterEndpoint = HalOutputRegisterEndpoint; + output->SetEndpointStreams = HalOutputSetEndpointStreams; + output->Write = HalOutputWrite; + output->QueryStats = HalOutputQueryStats; + output->SerialEndpointId = HAL_OUTPUT_ENDPOINT_INVALID; + output->BootFramebufferEndpointId = HAL_OUTPUT_ENDPOINT_INVALID; +} diff --git a/code/kernel/hal/hal_processor.c b/code/kernel/hal/hal_processor.c new file mode 100644 index 0000000..860ba78 --- /dev/null +++ b/code/kernel/hal/hal_processor.c @@ -0,0 +1,42 @@ +// HAL processor control exports. This file owns interrupt masking and processor halt instructions. + +#include +#include +#include +#pragma intrinsic(_disable) +#pragma intrinsic(_enable) + +// Masks interrupts and returns the previous RFLAGS value. +__declspec(dllexport) uintptr_t HalDisableInterrupts(void) { + uintptr_t flags = __readeflags(); + _disable(); + return flags; +} + +// Restores a previously saved RFLAGS value. +__declspec(dllexport) void HalRestoreInterrupts(uintptr_t flags) { + __writeeflags(flags); +} + +// Enables maskable interrupts. +__declspec(dllexport) void HalEnableInterrupts(void) { + _enable(); +} + +// Enables interrupts and halts until an interrupt arrives. +__declspec(dllexport) void HalWaitForInterrupt(void) { + _enable(); + __halt(); +} + +// Halts once without changing interrupt state. +__declspec(dllexport) void HalHalt(void) { + __halt(); +} + +// Masks interrupts and permanently stops the current processor. +__declspec(dllexport) _Noreturn void HalStopProcessor(void) { + _disable(); + for (;;) + __halt(); +} diff --git a/code/kernel/hal/hal_screen.c b/code/kernel/hal/hal_screen.c index 7808ddb..eb695f0 100644 --- a/code/kernel/hal/hal_screen.c +++ b/code/kernel/hal/hal_screen.c @@ -5,9 +5,9 @@ // each byte is the leftmost pixel. #include #include -#include #include #include +#include static uint32_t* gFb = (uint32_t *)0; static uint32_t* gBackbuffer = (uint32_t *)0; @@ -142,7 +142,7 @@ static void ScreenScroll(int lines) { memmove(shadow, shadow + lines * gStride, (size_t)(gHeight - lines) * rowBytes); - // Fill the revealed rows with the console background. Do not memset here: + // Fill the revealed rows with the console background. Do not memset here: // the background color is not zero. uint32_t* fill = shadow + (gHeight - lines) * gStride; for (int y = 0; y < lines; y++) { @@ -170,11 +170,6 @@ static void ScreenPrintChar(char c, uint32_t fg, uint32_t bg) { int cols = gWidth / FONT_W; int rows = gHeight / FONT_H; - // Mirror to serial. - while ((__inbyte(0x3FD) & 0x20) == 0); - if (c == '\n') __outbyte(0x3F8, '\r'); - __outbyte(0x3F8, c); - if (c == '\n') { gCursorX = 0; gCursorY++; @@ -198,7 +193,7 @@ static void ScreenPrintChar(char c, uint32_t fg, uint32_t bg) { } // Mouse cursor – 12×19 monochrome arrow sprite -// 0 = transparent 1 = black outline 2 = white fill +// 0 = transparent 1 = black outline 2 = white fill // The hotspot is at (0,0) (top-left pixel = tip of the arrow). #define CURSOR_W 16 #define CURSOR_H 25 @@ -318,6 +313,33 @@ void HalScreenEraseCursor(void) { gCursorDrawY = -1; } +// Writes one HAL output record through the boot framebuffer text console. +static void HalScreenWriteOutputRecord(void* context, const HAL_OUTPUT_RECORD* record) { + (void)context; + if (!record || !record->Buffer) + return; + + for (size_t i = 0; i < record->Length; i++) + ScreenPrintChar(record->Buffer[i], record->Foreground, record->Background); +} + +// Registers the boot framebuffer text endpoint. +void HalScreenRegisterOutputEndpoint(HAL_OUTPUT_ROUTER* output) { + if (!output || !output->RegisterEndpoint || !gFb || gWidth <= 0 || gHeight <= 0) + return; + + HAL_OUTPUT_ENDPOINT endpoint; + endpoint.Name = "boot-framebuffer"; + endpoint.StreamMask = HAL_OUTPUT_STREAM_ALL; + endpoint.Capabilities = HAL_OUTPUT_ENDPOINT_PANIC_SAFE; + endpoint.Write = HalScreenWriteOutputRecord; + endpoint.Context = 0; + + uint32_t endpointId = HAL_OUTPUT_ENDPOINT_INVALID; + if (KSUCCESS(output->RegisterEndpoint(&endpoint, &endpointId))) + output->BootFramebufferEndpointId = endpointId; +} + // Populate the HAL_SCREEN vtable. void HalScreenInitialize(void* (*AllocatePool)(size_t size), uintptr_t fb, int w, int h, int stride, HAL_SCREEN* out) { diff --git a/code/kernel/hal/hal_serial.c b/code/kernel/hal/hal_serial.c new file mode 100644 index 0000000..909242e --- /dev/null +++ b/code/kernel/hal/hal_serial.c @@ -0,0 +1,78 @@ +// HAL serial output endpoint. This file owns polling COM1 writes and subscribes serial to HAL text streams. + +#include +#include +#include +#include + +#define HAL_COM1_DATA_PORT 0x03F8u +#define HAL_COM1_STATUS_PORT 0x03FDu +#define HAL_COM1_TX_READY 0x20u + +static volatile long gSerialWriteLock; + +// Waits until COM1 can accept one byte. +static void HalSerialWaitForTransmit(void) { + while ((__inbyte(HAL_COM1_STATUS_PORT) & HAL_COM1_TX_READY) == 0) + _mm_pause(); +} + +// Writes one byte to COM1. +static void HalSerialWriteByte(uint8_t value) { + HalSerialWaitForTransmit(); + __outbyte(HAL_COM1_DATA_PORT, value); +} + +// Acquires the normal serial writer lock. +static void HalSerialAcquireWriteLock(void) { + while (_InterlockedCompareExchange(&gSerialWriteLock, 1, 0) != 0) + _mm_pause(); +} + +// Releases the normal serial writer lock. +static void HalSerialReleaseWriteLock(void) { + _InterlockedExchange(&gSerialWriteLock, 0); +} + +// Writes one output record through polling COM1. +static void HalSerialWriteRecord(void* context, const HAL_OUTPUT_RECORD* record) { + (void)context; + if (!record || !record->Buffer) + return; + + bool emergency = (record->Flags & HAL_OUTPUT_WRITE_EMERGENCY) != 0; + uintptr_t interruptFlags = 0; + if (!emergency) { + interruptFlags = HalDisableInterrupts(); + HalSerialAcquireWriteLock(); + } + + for (size_t i = 0; i < record->Length; i++) { + char c = record->Buffer[i]; + if (c == '\n') + HalSerialWriteByte('\r'); + HalSerialWriteByte((uint8_t)c); + } + + if (!emergency) { + HalSerialReleaseWriteLock(); + HalRestoreInterrupts(interruptFlags); + } +} + +// Registers the polling serial endpoint. +void HalSerialInitialize(HAL_OUTPUT_ROUTER* output) { + if (!output || !output->RegisterEndpoint) + return; + + HAL_OUTPUT_ENDPOINT endpoint; + endpoint.Name = "serial0"; + endpoint.StreamMask = HAL_OUTPUT_STREAM_ALL; + endpoint.Capabilities = HAL_OUTPUT_ENDPOINT_PANIC_SAFE; + endpoint.Write = HalSerialWriteRecord; + endpoint.Context = 0; + + uint32_t endpointId = HAL_OUTPUT_ENDPOINT_INVALID; + if (KSUCCESS(output->RegisterEndpoint(&endpoint, &endpointId))) + output->SerialEndpointId = endpointId; +} diff --git a/code/kernel/hal/hal_timer.c b/code/kernel/hal/hal_timer.c index 109f98b..b2d4467 100644 --- a/code/kernel/hal/hal_timer.c +++ b/code/kernel/hal/hal_timer.c @@ -81,6 +81,11 @@ static uint64_t QueryTicks(void) { return (baseTicks1 * TIMER_PERIOD_MS * 1000ull) + subQuantumUs; } +// Returns the frequency used by QueryTicks. +static uint64_t QueryTickFrequency(void) { + return 1000000ull; +} + // Implements the stall operation. static void Stall(uint32_t us) { // Priority 1: Use HPET (Highest Precision). @@ -224,9 +229,10 @@ KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *o // after enabling their local APIC. HalTimerConfigureCurrentCpu(); - out->QueryTicks = QueryTicks; - out->Stall = Stall; - out->Isr = TimerIsr; - out->IrqNumber = 0; + out->QueryTicks = QueryTicks; + out->QueryTickFrequency = QueryTickFrequency; + out->Stall = Stall; + out->Isr = TimerIsr; + out->IrqNumber = 0; return STATUS_SUCCESS; } diff --git a/code/kernel/include/kernel/hal/hal_apic.h b/code/kernel/include/kernel/hal/hal_apic.h index 38f3f24..fb3ff65 100644 --- a/code/kernel/include/kernel/hal/hal_apic.h +++ b/code/kernel/include/kernel/hal/hal_apic.h @@ -10,7 +10,6 @@ void HalApicUnmaskIrq(uint8_t irq); void HalApicSendEoi(void); void HalApicInitialize(uintptr_t lapicPhys, uintptr_t ioapicPhys); - #define HAL_MAX_CPUS 32u typedef struct HAL_CPU_INFO { diff --git a/code/kernel/include/kernel/hal/hal_output.h b/code/kernel/include/kernel/hal/hal_output.h new file mode 100644 index 0000000..7f9bf95 --- /dev/null +++ b/code/kernel/include/kernel/hal/hal_output.h @@ -0,0 +1,14 @@ +// HAL output router internals. This file connects built-in endpoint drivers to the public HAL output table. + +#pragma once + +#include + +// Initializes the fixed HAL output endpoint table. +void HalOutputInitialize(HAL_OUTPUT_ROUTER* output); + +// Registers the polling serial endpoint. +void HalSerialInitialize(HAL_OUTPUT_ROUTER* output); + +// Registers the boot framebuffer text endpoint. +void HalScreenRegisterOutputEndpoint(HAL_OUTPUT_ROUTER* output); diff --git a/code/kernel/include/kernel/io_file.h b/code/kernel/include/kernel/io_file.h index 2947603..c57f8ba 100644 --- a/code/kernel/include/kernel/io_file.h +++ b/code/kernel/include/kernel/io_file.h @@ -12,6 +12,7 @@ #include #include #include +#include // Kernel/filesystem code uses the public directory-entry ABI directly. typedef SYS_DIR_ENTRY KDIR_ENTRY; @@ -26,6 +27,7 @@ typedef void (*IO_FS_ASYNC_COMPLETE)(void* completeContext, size_t information); // Filesystem-level cache and traversal statistics. +// Drivers map private metadata work onto these generic counters. typedef struct IO_FILESYSTEM_STATS { uint64_t PathLookups; uint64_t PathCacheHits; @@ -34,14 +36,8 @@ typedef struct IO_FILESYSTEM_STATS { uint64_t PathCacheInvalidations; uint64_t PathCacheGeneration; uint64_t DirectorySearches; - uint64_t DirectoryClustersRead; + uint64_t DirectoryBlocksRead; uint64_t DirectoryEntriesScanned; - uint64_t FatChainCacheLookups; - uint64_t FatChainCacheHits; - uint64_t FatChainCacheMisses; - uint64_t FatChainCacheStores; - uint64_t FatChainCacheInvalidations; - uint64_t FatChainWalkSteps; uint64_t DataReadRuns; uint64_t DataReadSectors; uint64_t DataReadRunMerges; @@ -107,6 +103,12 @@ typedef struct IO_FILESYSTEM { // Return the file size in bytes. uint64_t (*GetSize)(struct IO_FILESYSTEM* fs, void* fileCtx); + // Query metadata for an open file or path. + KSTATUS (*QueryInfo)(struct IO_FILESYSTEM* fs, void* fileCtx, + SYS_FILE_STAT* out); + KSTATUS (*Stat)(struct IO_FILESYSTEM* fs, SYS_STRING_VIEW path, + SYS_FILE_STAT* out); + // Return a stable file identity for caches that outlive one open handle. KSTATUS (*QueryIdentity)(struct IO_FILESYSTEM* fs, void* fileCtx, IO_FILE_IDENTITY* out); @@ -120,8 +122,6 @@ typedef struct IO_FILESYSTEM { // Optional driver-private data. void* DriverCtx; - // Directory enumeration (optional - may be NULL if not supported). - // Open a directory iterator for 'dirPath' (relative or drive-prefixed). // Returns an opaque per-iteration driver cookie in *dirCtx. KSTATUS (*OpenDir)(struct IO_FILESYSTEM* fs, @@ -137,7 +137,6 @@ typedef struct IO_FILESYSTEM { void (*CloseDir)(struct IO_FILESYSTEM* fs, void* dirCtx); } IO_FILESYSTEM; - // IO_STATUS_BLOCK - completion status visible to async callers. typedef struct IO_STATUS_BLOCK { volatile KSTATUS Status; @@ -152,6 +151,7 @@ typedef struct IO_ASYNC_FILE_REQUEST_OBJECT IO_ASYNC_FILE_REQUEST_OBJECT; // FILE_OBJECT - the kernel object returned to callers. #define IO_FILE_FLAG_NO_CACHE 0x00000001u #define IO_FILE_FLAG_SKIP_ZERO_EXTEND 0x00000002u +#define IO_FILE_FLAG_APPEND 0x00000004u typedef struct FILE_OBJECT { OBJECT Header; // must be first @@ -227,7 +227,12 @@ __declspec(dllexport) KSTATUS IoRemoveDir (SYS_STRING_VIEW path); __declspec(dllexport) KSTATUS IoTruncateFile(FILE_OBJECT* file, uint64_t newSize); __declspec(dllexport) KSTATUS IoSeekFile(FILE_OBJECT* file, uint64_t offset); __declspec(dllexport) uint64_t IoGetFileSize(FILE_OBJECT* file); +__declspec(dllexport) KSTATUS IoGetFileStat(FILE_OBJECT* file, SYS_FILE_STAT* out); +__declspec(dllexport) KSTATUS IoStatFile(SYS_STRING_VIEW path, SYS_FILE_STAT* out); __declspec(dllexport) KSTATUS IoQueryFileIdentity(FILE_OBJECT* file, IO_FILE_IDENTITY* out); +__declspec(dllexport) KSTATUS IoLockFile(FILE_OBJECT* file, struct PROCESS* owner, uint32_t mode, uint64_t offset, uint64_t length, uint64_t timeoutMilliseconds); +__declspec(dllexport) KSTATUS IoUnlockFile(FILE_OBJECT* file, struct PROCESS* owner, uint64_t offset, uint64_t length); +__declspec(dllexport) void IoReleaseFileLocksForProcess(struct PROCESS* owner); __declspec(dllexport) void IoCloseFile(FILE_OBJECT* file); // Directory enumeration API. @@ -237,4 +242,4 @@ __declspec(dllexport) void IoCloseDir(DIR_OBJECT* dir); // Current Working Directory. __declspec(dllexport) KSTATUS IoSetCwd(SYS_STRING_VIEW path); -__declspec(dllexport) KSTATUS IoGetCwd(char* buf, size_t size); +__declspec(dllexport) KSTATUS IoRealPath(SYS_STRING_VIEW path, char* buffer, size_t bufferSize, size_t* requiredSizeOut); diff --git a/code/kernel/include/kernel/ke_apc.h b/code/kernel/include/kernel/ke_apc.h index 6d63f72..2ff5956 100644 --- a/code/kernel/include/kernel/ke_apc.h +++ b/code/kernel/include/kernel/ke_apc.h @@ -35,10 +35,7 @@ typedef struct KUSER_APC KUSER_APC; typedef struct SYS_USER_APC_FRAME_K { uint64_t Magic; - uint64_t OriginalRip; - uint64_t OriginalRsp; - uint64_t OriginalRflags; - uint64_t OriginalRax; + uint64_t ReturnToken; uint64_t Routine; uint64_t NormalContext; uint64_t SystemArgument1; diff --git a/code/kernel/include/kernel/ke_debug.h b/code/kernel/include/kernel/ke_debug.h index e899776..c5e6619 100644 --- a/code/kernel/include/kernel/ke_debug.h +++ b/code/kernel/include/kernel/ke_debug.h @@ -7,6 +7,7 @@ #include #include #include +#include #define KE_DEBUG_CONSOLE 0x00000001u #define KE_DEBUG_SELFTEST 0x00000002u @@ -22,8 +23,7 @@ #define KE_DEBUG_VM_ASSERTS 0x00000400u #define KE_DEBUG_SYSCALL_ASSERTS 0x00000800u -void KeInitDebug(void (*PrintPtr)(char, uint32_t, uint32_t), uint32_t flags); -void KeSetDebugConsoleWriter(void (*PrintPtr)(char, uint32_t, uint32_t)); +void KeInitDebug(const HAL_API* hal, uint32_t flags); uint32_t KeGetDebugFlags(void); __declspec(dllexport) void DbgPrint(const char* fmt, ...); __declspec(dllexport) void KePrintConsole(const char* buf, size_t len); diff --git a/code/kernel/include/kernel/ke_display.h b/code/kernel/include/kernel/ke_display.h index 24e871c..86965ef 100644 --- a/code/kernel/include/kernel/ke_display.h +++ b/code/kernel/include/kernel/ke_display.h @@ -16,7 +16,6 @@ #endif #include - typedef struct KE_CONSOLE_INFO { uint32_t Columns; uint32_t Rows; @@ -55,7 +54,6 @@ DISPLAY_API KSTATUS KeDisplayGetConsoleInfo(KE_CONSOLE_INFO* outInfo); DISPLAY_API KSTATUS KeDisplaySetConsoleCursor(int col, int row); DISPLAY_API KSTATUS KeDisplayFillConsoleChars(char c, int col, int row, size_t length, size_t* writtenOut); DISPLAY_API KSTATUS KeDisplayFillConsoleAttrs(uint16_t attr, int col, int row, size_t length, size_t* writtenOut); -void KeDisplayDebugPrintChar(char c, uint32_t fg, uint32_t bg); DISPLAY_API KSTATUS KeDisplayPresent(const DISPLAY_RECT* rects, size_t rectCount); DISPLAY_API KSTATUS KeDisplaySync(void); DISPLAY_API KSTATUS KeDisplayQueryInfo(KE_DISPLAY_INFO* outInfo); diff --git a/code/kernel/include/kernel/ke_input.h b/code/kernel/include/kernel/ke_input.h index e8bc586..bb3eeec 100644 --- a/code/kernel/include/kernel/ke_input.h +++ b/code/kernel/include/kernel/ke_input.h @@ -23,7 +23,8 @@ __declspec(dllexport) void KeInputSubmitMouseAbsolute(const INPUT_MOUSE_ABSOLUTE __declspec(dllexport) void KeInputSubmitMouseAbsoluteScaled(uint32_t rawX, uint32_t rawY, uint32_t maxX, uint32_t maxY, int wheel, uint8_t buttons); INPUT_KEY KeInputKbPoll(void); -KSTATUS KeInputWaitKey(uint8_t alertable); +KSTATUS KeInputWaitKey(uint8_t alertable, uint64_t timeoutMs); +void KeInputTimerTick(void); bool KeInputIsKeyDown(INPUT_KEY key); bool KeInputPeekEvent(INPUT_KEY_RECORD* record); bool KeInputReadEvent(INPUT_KEY_RECORD* record); @@ -33,8 +34,8 @@ INPUT_KEY KeInputReadKey(void); char KeInputReadChar(void); uint32_t KeInputGetModifiers(void); -bool KeInputMousePoll(void); -bool KeInputMouseRead(int* dx, int* dy, uint8_t* buttons); +bool KeInputMousePeek(INPUT_MOUSE_EVENT* event); +bool KeInputMouseRead(INPUT_MOUSE_EVENT* event); void KeInputMouseGetPosition(int* x, int* y); void KeInputMouseSetPosition(int x, int y); void KeInputMouseSetBounds(int w, int h); diff --git a/code/kernel/include/kernel/ke_irp.h b/code/kernel/include/kernel/ke_irp.h index 53999a6..af254e3 100644 --- a/code/kernel/include/kernel/ke_irp.h +++ b/code/kernel/include/kernel/ke_irp.h @@ -1,7 +1,7 @@ // Kernel ke irp declarations shared by implementation files and kernel clients. #pragma once -// ke_irp.h - I/O Request Packet and remove-lock primitives +// ke_irp.h - I/O Request Packet and remove-lock primitives // IRPs are stack-based. A caller builds an IRP with one or more // IO_STACK_LOCATION entries, submits it to a DEVICE_OBJECT, and the I/O @@ -142,7 +142,7 @@ __declspec(dllexport) KSTATUS IoAcquireRemoveLock(IO_REMOVE_LOCK* lock, IRP* irp __declspec(dllexport) void IoReleaseRemoveLock(IO_REMOVE_LOCK* lock, IRP* irp); __declspec(dllexport) void IoReleaseRemoveLockAndWait(IO_REMOVE_LOCK* lock, IRP* removeIrp); -// IRP_QUEUE (per-device, driver-internal). +// IRP_QUEUE (per-device, driver-internal). typedef struct IRP_QUEUE { SPINLOCK Lock; diff --git a/code/kernel/include/kernel/ke_process.h b/code/kernel/include/kernel/ke_process.h index 8097df4..11db8ae 100644 --- a/code/kernel/include/kernel/ke_process.h +++ b/code/kernel/include/kernel/ke_process.h @@ -17,6 +17,10 @@ #define PROCESS_MODULE_MAX 16 #define PROCESS_CWD_MAX 512 +#define KE_PROCESS_FLAG_SYSTEM 0x00000001u +#define KE_PROCESS_FLAG_IDLE 0x00000002u +#define KE_PROCESS_FLAG_TERMINATION_PROTECTED 0x00000004u + #define HANDLE_RIGHT_READ 0x00000001u #define HANDLE_RIGHT_WRITE 0x00000002u #define HANDLE_RIGHT_WAIT 0x00000004u @@ -24,6 +28,7 @@ #define HANDLE_RIGHT_DUPLICATE 0x00000010u #define HANDLE_RIGHT_QUERY 0x00000020u #define HANDLE_RIGHT_TERMINATE 0x00000040u +#define HANDLE_RIGHT_LOCK 0x00000080u #define HANDLE_RIGHT_ALL 0xFFFFFFFFu typedef struct HANDLE_ENTRY { @@ -42,6 +47,7 @@ typedef struct PROCESS { OBJECT Header; // must be first uint32_t ProcessId; + uint32_t Flags; char Name[PROCESS_NAME_MAX]; // UTF-8 char CurrentDirectory[PROCESS_CWD_MAX]; @@ -59,7 +65,7 @@ typedef struct PROCESS { HANDLE_ENTRY Handles[HANDLE_TABLE_SIZE]; SPINLOCK HandleLock; - int ExitCode; + int32_t ExitCode; volatile long Terminating; volatile long Exited; volatile long ResourcesReleased; @@ -101,11 +107,12 @@ KSTATUS KeSetupUserProcessEnvironment(PROCESS* process, SYS_STRING_VIEW imagePath, SYS_STRING_VIEW commandLine, SYS_STRING_VIEW currentDirectory); -void KeExitProcess(PROCESS* process, int exitCode); -KSTATUS KeTerminateProcess(PROCESS* process, int exitCode); +KSTATUS KeExitProcess(PROCESS* process, int32_t exitCode); +KSTATUS KeTerminateProcess(PROCESS* process, int32_t exitCode); void KeCompleteProcessExitIfLastThread(PROCESS* process); int KeIsProcessTerminating(PROCESS* process); -KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut); +int KeIsProcessTerminationProtected(PROCESS* process); +KSTATUS KeGetProcessExitCode(PROCESS* process, int32_t* exitCodeOut); // Handle table operations. KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut); @@ -114,6 +121,7 @@ void KeCloseHandle(PROCESS* process, uint32_t handle); OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle); OBJECT* KeLookupHandleWithRights(PROCESS* process, uint32_t handle, uint32_t requiredRights); uint32_t KeGetHandleRights(PROCESS* process, uint32_t handle); +KSTATUS KeDuplicateHandleToProcess(PROCESS* sourceProcess, uint32_t sourceHandle, PROCESS* targetProcess, uint32_t desiredRights, uint32_t duplicateFlags, uint32_t* targetHandleOut); // Return the global process list head (kernel-internal). LIST_ENTRY* KeGetProcessList(void); diff --git a/code/kernel/include/kernel/ke_queue.h b/code/kernel/include/kernel/ke_queue.h index 89065f7..b429752 100644 --- a/code/kernel/include/kernel/ke_queue.h +++ b/code/kernel/include/kernel/ke_queue.h @@ -1,34 +1,38 @@ // Kernel ke queue declarations shared by implementation files and kernel clients. #pragma once -// ke_queue.h - fixed-depth message queue -// Processes share an address space, so a pointer in Param[] is valid -// on both sides. The queue is only a synchronisation and ordering tool. +// Fixed-depth message queue. +// Processes share an address space, so a pointer in Param[] is valid on both sides. +// The queue orders fixed control records and does not copy pointed-to payloads. #include -#include #include #include #include +#include #include -#define QUEUE_MESSAGE_SIZE 64 // bytes, fixed +#define QUEUE_MESSAGE_SIZE 56 // Bytes, fixed ABI copy size. #define QUEUE_DEPTH 256 typedef struct QUEUE_MESSAGE { uint32_t Type; uint32_t Flags; - uint64_t Param[6]; // 48 bytes of payload -} QUEUE_MESSAGE; // total: 64 bytes + uint64_t Param[6]; // 48 bytes of payload. +} QUEUE_MESSAGE; + +typedef char QUEUE_MESSAGE_SIZE_CHECK[ + (sizeof(QUEUE_MESSAGE) == QUEUE_MESSAGE_SIZE) ? 1 : -1 +]; typedef struct QUEUE { OBJECT Header; SPINLOCK Lock; QUEUE_MESSAGE Ring[QUEUE_DEPTH]; uint32_t Head, Tail, Count; - LIST_ENTRY SendWaiters; - LIST_ENTRY RecvWaiters; + EVENT* NotEmptyEvent; + EVENT* NotFullEvent; } QUEUE; KSTATUS KeQueueCreate(QUEUE **out); -KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, bool blocking); -KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, bool blocking); +KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, uint64_t timeoutMilliseconds); +KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, uint64_t timeoutMilliseconds); diff --git a/code/kernel/include/kernel/ke_smp.h b/code/kernel/include/kernel/ke_smp.h index 81faac0..28aa2ce 100644 --- a/code/kernel/include/kernel/ke_smp.h +++ b/code/kernel/include/kernel/ke_smp.h @@ -15,7 +15,6 @@ KSTATUS KeStartApplicationProcessors(void); // park loop into their idle threads. KSTATUS KeAdmitApplicationProcessorsToScheduler(void); - // Decode CPUID topology and build per-KCPU package/core/SMT sibling masks. // KeStartApplicationProcessors calls this after MADT enumeration; it is also // safe to call again after failed AP startup disables a CPU record. diff --git a/code/kernel/include/kernel/ke_spinlock.h b/code/kernel/include/kernel/ke_spinlock.h index 9129ade..dac93b4 100644 --- a/code/kernel/include/kernel/ke_spinlock.h +++ b/code/kernel/include/kernel/ke_spinlock.h @@ -13,8 +13,8 @@ // Never hold a spinlock across a wait/blocking operation. #include #include -#pragma intrinsic(_disable) #include +#include typedef struct SPINLOCK { volatile long Locked; @@ -44,8 +44,7 @@ static __forceinline KIRQL KiSpinlockCookieIrql(uintptr_t cookie) { // Save RFLAGS, mask interrupts, raise to DISPATCH_LEVEL if needed, acquire. // If already running at DIRQL, the higher IRQL is preserved. static __forceinline uintptr_t KeAcquireSpinlock(SPINLOCK* sl) { - uintptr_t flags = __readeflags(); - _disable(); + uintptr_t flags = HalDisableInterrupts(); KIRQL oldIrql = KeGetCurrentIrql(); if (oldIrql < DISPATCH_LEVEL) @@ -64,13 +63,12 @@ static __forceinline void KeReleaseSpinlock(SPINLOCK* sl, uintptr_t cookie) { _InterlockedExchange(&sl->Locked, 0); KeLowerIrql(oldIrql); - __writeeflags(flags); + HalRestoreInterrupts(flags); } // Implements the try Acquire Spinlock operation. static __forceinline int KeTryAcquireSpinlock(SPINLOCK* sl, uintptr_t* outCookie) { - uintptr_t flags = __readeflags(); - _disable(); + uintptr_t flags = HalDisableInterrupts(); KIRQL oldIrql = KeGetCurrentIrql(); if (oldIrql < DISPATCH_LEVEL) @@ -82,7 +80,7 @@ static __forceinline int KeTryAcquireSpinlock(SPINLOCK* sl, uintptr_t* outCookie } KeLowerIrql(oldIrql); - __writeeflags(flags); + HalRestoreInterrupts(flags); return 0; } diff --git a/code/kernel/include/kernel/ke_thread.h b/code/kernel/include/kernel/ke_thread.h index b742384..ccdfff3 100644 --- a/code/kernel/include/kernel/ke_thread.h +++ b/code/kernel/include/kernel/ke_thread.h @@ -147,11 +147,17 @@ struct THREAD { uintptr_t UserApcDispatcher; uint8_t UserApcDeliverable; uint8_t UserApcDeliveryActive; + uintptr_t UserApcFrameUserAddress; + uint64_t UserApcReturnToken; + uint64_t UserApcSavedRip; + uint64_t UserApcSavedRsp; + uint64_t UserApcSavedRflags; + uint64_t UserApcSavedRax; }; -// Creates and queues a user thread. On success, *out receives a referenced +// Creates and queues a user thread. On success, *out receives a referenced // THREAD pointer owned by the caller; release it with ObDereferenceObject() -// after copying any needed IDs/state. The live scheduler/thread lifetime is +// after copying any needed IDs/state. The live scheduler/thread lifetime is // separate and remains valid until the thread exits and is reaped. __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg, THREAD_PRIORITY priority, THREAD** out); diff --git a/code/kernel/include/kernel/ke_timer.h b/code/kernel/include/kernel/ke_timer.h new file mode 100644 index 0000000..1cf7040 --- /dev/null +++ b/code/kernel/include/kernel/ke_timer.h @@ -0,0 +1,35 @@ +// Kernel timer object declarations shared by implementation files and kernel clients. + +#pragma once +// ke_timer.h - waitable one-shot and periodic timers. +#include +#include +#include +#include +#include +#include + +typedef struct KTIMER { + OBJECT Header; + EVENT* Event; + LIST_ENTRY Link; + uint64_t DueTick; + uint64_t PeriodTicks; + uint8_t Active; + uint8_t Linked; +} KTIMER; + +// Creates an unset waitable timer object. +KSTATUS KeTimerCreate(KTIMER** out); + +// Arms or rearms a timer with a relative due time and optional period. +KSTATUS KeTimerSet(KTIMER* timer, uint64_t dueTimeMilliseconds, uint64_t periodMilliseconds); + +// Cancels a timer and clears any retained signal. +KSTATUS KeTimerCancel(KTIMER* timer); + +// Returns the event that backs timer waits. +EVENT* KeTimerWaitEvent(KTIMER* timer); + +// Services expired timers from the scheduler tick path. +void KeTimerTick(void); diff --git a/code/kernel/include/kernel/ke_work.h b/code/kernel/include/kernel/ke_work.h index ef75164..379ebb3 100644 --- a/code/kernel/include/kernel/ke_work.h +++ b/code/kernel/include/kernel/ke_work.h @@ -5,9 +5,9 @@ #include #include -// Work items are caller-owned embedded objects. They may be queued from +// Work items are caller-owned embedded objects. They may be queued from // interrupt/DPC/spinlock context and run later on a persistent system worker -// thread at PASSIVE_LEVEL. Queueing the same item while it is already queued +// thread at PASSIVE_LEVEL. Queueing the same item while it is already queued // is coalesced. typedef void (*KWORK_ROUTINE)(void* context); diff --git a/code/kernel/include/kernel/ldr_image.h b/code/kernel/include/kernel/ldr_image.h index fffcda2..a2a46e0 100644 --- a/code/kernel/include/kernel/ldr_image.h +++ b/code/kernel/include/kernel/ldr_image.h @@ -43,5 +43,6 @@ KSTATUS LdrLoadModuleForProcess(struct PROCESS* process, const char* filename, u // Called by KernelMain to register all modules from BOOT_INFO so that // LdrGetExport can find them. struct BOOT_INFO; // forward declaration -void LdrRegisterModulesFromBootInfo(const struct BOOT_INFO* bootInfo); +void LdrRegisterModulesFromBootInfo(const struct BOOT_INFO* bootInfo); void LdrRegisterModule(const char* name, uintptr_t base); +const char* LdrFindKernelModuleByAddress(uintptr_t address, uintptr_t* baseOut, size_t* sizeOut); diff --git a/code/kernel/include/kernel/mm_pte.h b/code/kernel/include/kernel/mm_pte.h index f357884..8b04aca 100644 --- a/code/kernel/include/kernel/mm_pte.h +++ b/code/kernel/include/kernel/mm_pte.h @@ -19,18 +19,17 @@ // The physical direct map is a kernel-only alias used to reach page-table frames and early physical memory. #define PHYS_MAP_BASE 0xFFFF880000000000ULL - // Page-table telemetry separates cumulative operations from the current number // of 4 KiB leaf mappings owned by this layer. Huge boot mappings may exist but // are not counted as leaves here. typedef enum MM_PTE_QUERY_PURPOSE { MmPteQueryGeneric = 0, - MmPteQueryUserProbe, - MmPteQueryHeap, - MmPteQueryVad, - MmPteQueryDebug, - MmPteQuerySelfTest, - MmPteQueryPurposeCount, + MmPteQueryUserProbe = 1, + MmPteQueryHeap = 2, + MmPteQueryVad = 3, + MmPteQueryDebug = 4, + MmPteQuerySelfTest = 5, + MmPteQueryPurposeCount = 6, } MM_PTE_QUERY_PURPOSE; typedef struct MM_PTE_STATS { diff --git a/code/kernel/include/kernel/net_socket.h b/code/kernel/include/kernel/net_socket.h index 25fcd1b..1bd3aa4 100644 --- a/code/kernel/include/kernel/net_socket.h +++ b/code/kernel/include/kernel/net_socket.h @@ -26,16 +26,16 @@ typedef struct SOCKET_OBJECT { } SOCKET_OBJECT; KSTATUS NetSocketCreate(uint32_t domain, uint32_t type, uint32_t protocol, SOCKET_OBJECT** out); -KSTATUS NetSocketBind(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_IPV4_K* address, size_t addressSize); +KSTATUS NetSocketBind(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize); KSTATUS NetSocketListen(SOCKET_OBJECT* socket, uint32_t backlog); -KSTATUS NetSocketAccept(SOCKET_OBJECT* socket, SOCKET_OBJECT** acceptedOut, SYS_SOCKET_ADDRESS_IPV4_K* remoteOut); -KSTATUS NetSocketConnect(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_IPV4_K* address, size_t addressSize); +KSTATUS NetSocketAccept(SOCKET_OBJECT* socket, SOCKET_OBJECT** acceptedOut, SYS_SOCKET_ADDRESS_K* remoteOut); +KSTATUS NetSocketConnect(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize); KSTATUS NetSocketSend(SOCKET_OBJECT* socket, const void* buffer, size_t length, uint32_t flags, size_t* sentOut); KSTATUS NetSocketReceive(SOCKET_OBJECT* socket, void* buffer, size_t length, uint32_t flags, size_t* receivedOut); -KSTATUS NetSocketSendTo(SOCKET_OBJECT* socket, const void* buffer, size_t length, uint32_t flags, const SYS_SOCKET_ADDRESS_IPV4_K* remote, size_t remoteSize, size_t* sentOut); -KSTATUS NetSocketReceiveFrom(SOCKET_OBJECT* socket, void* buffer, size_t length, uint32_t flags, SYS_SOCKET_ADDRESS_IPV4_K* remoteOut, size_t* receivedOut); +KSTATUS NetSocketSendTo(SOCKET_OBJECT* socket, const void* buffer, size_t length, uint32_t flags, const SYS_SOCKET_ADDRESS_K* remote, size_t remoteSize, size_t* sentOut); +KSTATUS NetSocketReceiveFrom(SOCKET_OBJECT* socket, void* buffer, size_t length, uint32_t flags, SYS_SOCKET_ADDRESS_K* remoteOut, size_t* receivedOut); KSTATUS NetSocketShutdown(SOCKET_OBJECT* socket, uint32_t how); KSTATUS NetSocketSetOption(SOCKET_OBJECT* socket, uint32_t option, const void* value, size_t valueSize); KSTATUS NetSocketGetOption(SOCKET_OBJECT* socket, uint32_t option, void* value, size_t valueSize, size_t* actualSizeOut); KSTATUS NetSocketPoll(SOCKET_OBJECT* socket, uint32_t events, uint32_t* returnedEventsOut); -KSTATUS NetResolveHostIpv4(const char* name, SYS_SOCKET_ADDRESS_IPV4_K* addressOut); +KSTATUS NetResolveHost(const char* name, uint32_t family, SYS_SOCKET_ADDRESS_K* addressOut); diff --git a/code/kernel/include/kernel/ob_object.h b/code/kernel/include/kernel/ob_object.h index ab09519..7d76772 100644 --- a/code/kernel/include/kernel/ob_object.h +++ b/code/kernel/include/kernel/ob_object.h @@ -27,6 +27,7 @@ typedef enum OBJECT_TYPE { ObjectTypeSurface = 16, ObjectTypeInputQueue = 17, ObjectTypeImageSection = 18, + ObjectTypeTimer = 19, } OBJECT_TYPE; typedef struct OBJECT { diff --git a/code/kernel/include/kernel/syscall_nr.h b/code/kernel/include/kernel/syscall_nr.h index ba67364..02260a4 100644 --- a/code/kernel/include/kernel/syscall_nr.h +++ b/code/kernel/include/kernel/syscall_nr.h @@ -1,120 +1,141 @@ -// Kernel syscall nr declarations shared by implementation files and kernel clients. +// Kernel syscall number declarations shared by implementation files and kernel clients. #pragma once -// syscall_nr.h -// INT 0x40 calling convention, MS x64: -// RAX = syscall number -// RCX, RDX, R8, R9 = args 0-3 -// R10, R11 = args 4-5 (loaded by SYSCALL_STUB6) -// RAX = return value. -#define SYS_EXIT_PROCESS 0 -#define SYS_EXIT_THREAD 1 -#define SYS_CREATE_PROCESS 2 -#define SYS_WAIT_SINGLE 3 -#define SYS_GET_PROCESS_EXIT_CODE 4 -#define SYS_KILL_PROCESS 5 -#define SYS_GET_PROCESS_LIST 6 -#define SYS_GET_PROCESS_PID 7 -#define SYS_CREATE_THREAD 8 -#define SYS_RETIRED_WAIT_THREAD 9 -#define SYS_GET_CURRENT_TID 10 -#define SYS_GET_CURRENT_PID 11 -#define SYS_SET_THREAD_PRIORITY 12 -#define SYS_YIELD 13 -#define SYS_SLEEP 14 -#define SYS_SET_USER_APC_DISPATCHER 15 -#define SYS_APC_RETURN 16 -#define SYS_ALLOC_VM 17 -#define SYS_FREE_VM 18 -#define SYS_PROTECT_VM 19 -#define SYS_QUERY_VM 20 -#define SYS_CLOSE_HANDLE 21 -#define SYS_CREATE_MUTEX 22 -#define SYS_RETIRED_ACQUIRE_MUTEX 23 -#define SYS_TRY_ACQUIRE_MUTEX 24 -#define SYS_RELEASE_MUTEX 25 -#define SYS_CREATE_SEM 26 -#define SYS_RETIRED_WAIT_SEM 27 -#define SYS_TRY_WAIT_SEM 28 -#define SYS_SIGNAL_SEM 29 -#define SYS_CREATE_EVENT 30 -#define SYS_SIGNAL_EVENT 31 -#define SYS_CLEAR_EVENT 32 -#define SYS_RETIRED_WAIT_EVENT 33 -#define SYS_CREATE_QUEUE 34 -#define SYS_SEND_QUEUE 35 -#define SYS_RECV_QUEUE 36 -#define SYS_PUBLISH_QUEUE 37 -#define SYS_OPEN_NAMED_QUEUE 38 -#define SYS_DUPLICATE_QUEUE 39 -#define SYS_GET_NAMED_QUEUE_OWNER_PID 40 -#define SYS_OPEN_FILE 41 -#define SYS_CREATE_FILE 42 -#define SYS_READ_FILE 43 -#define SYS_WRITE_FILE 44 -#define SYS_READ_FILE_ASYNC 45 -#define SYS_WRITE_FILE_ASYNC 46 -#define SYS_CANCEL_IO 47 -#define SYS_SEEK_FILE 48 -#define SYS_GET_FILE_POS 49 -#define SYS_GET_FILE_SIZE 50 -#define SYS_TRUNCATE_FILE 51 -#define SYS_DELETE_FILE 52 -#define SYS_RENAME_FILE 53 -#define SYS_OPEN_DIR 54 -#define SYS_READ_DIR 55 -#define SYS_CREATE_DIR 56 -#define SYS_REMOVE_DIR 57 -#define SYS_SET_CWD 58 -#define SYS_GET_CWD 59 -#define SYS_WRITE_CONSOLE 60 -#define SYS_GET_CONSOLE_INFO 61 -#define SYS_SET_CONSOLE_CURSOR 62 -#define SYS_FILL_CONSOLE_CHARACTER 63 -#define SYS_FILL_CONSOLE_ATTRIBUTE 64 -#define SYS_PUT_SCREEN_CHAR 65 -#define SYS_CLEAR_SCREEN 66 -#define SYS_GET_SCREEN_SIZE 67 -#define SYS_QUERY_DISPLAY_INFO 68 -#define SYS_POLL_KEYBOARD 69 -#define SYS_IS_KEY_DOWN 70 -#define SYS_READ_KEYBOARD_CHAR 71 -#define SYS_READ_KEY 72 -#define SYS_GET_KEY_MODIFIERS 73 -#define SYS_POLL_MOUSE 74 -#define SYS_READ_MOUSE 75 -#define SYS_GET_MOUSE_POS 76 -#define SYS_GET_TICK 77 -#define SYS_PRINT_DEBUG 78 -#define SYS_QUERY_TRAP_FRAME 79 -#define SYS_CREATE_SOCKET 80 -#define SYS_BIND_SOCKET 81 -#define SYS_LISTEN_SOCKET 82 -#define SYS_ACCEPT_SOCKET 83 -#define SYS_CONNECT_SOCKET 84 -#define SYS_SEND_SOCKET 85 -#define SYS_RECV_SOCKET 86 -#define SYS_SHUTDOWN_SOCKET 87 -#define SYS_SEND_TO_SOCKET 88 -#define SYS_RECV_FROM_SOCKET 89 -#define SYS_RESOLVE_HOST 90 -#define SYS_SET_SOCKET_OPTION 91 -#define SYS_GET_SOCKET_OPTION 92 -#define SYS_POLL 93 -#define SYS_GET_MEMORY_STATUS 94 -#define SYS_QUERY_KERNEL_STATS 95 -#define SYS_GET_SYSTEM_TIME 96 -#define SYS_CREATE_FILE_MAPPING 97 -#define SYS_MAP_FILE 98 -#define SYS_CREATE_SURFACE 99 -#define SYS_MAP_SURFACE 100 -#define SYS_QUERY_SURFACE 101 -#define SYS_PRESENT_SURFACE 102 -#define SYS_GET_THREAD_LIST 103 -#define SYS_READ_INPUT_EVENT 104 -#define SYS_PEEK_INPUT_EVENT 105 -#define SYS_WAIT_MANY 106 -#define SYS_OPEN_INPUT_QUEUE 107 -#define SYS_FLUSH_FILE 108 -#define SYS_MAX 109 +// INT 0x40 calling convention, MS x64. +// RAX selects the syscall number. RCX, RDX, R8, and R9 carry args 0-3. +// R10 and R11 carry args 4-5 and are loaded by six-argument stubs. +// RAX carries the return value. + +// Process, threads, waits, and APC. +#define SYS_EXIT_PROCESS 0 +#define SYS_EXIT_THREAD 1 +#define SYS_CREATE_PROCESS 2 +#define SYS_OPEN_PROCESS 3 +#define SYS_GET_PROCESS_EXIT_CODE 4 +#define SYS_KILL_PROCESS 5 +#define SYS_GET_PROCESS_LIST 6 +#define SYS_GET_PROCESS_PID 7 +#define SYS_CREATE_THREAD 8 +#define SYS_GET_THREAD_LIST 9 +#define SYS_GET_CURRENT_TID 10 +#define SYS_GET_CURRENT_PID 11 +#define SYS_SET_THREAD_PRIORITY 12 +#define SYS_YIELD 13 +#define SYS_SLEEP 14 +#define SYS_WAIT_SINGLE 15 +#define SYS_WAIT_MANY 16 +#define SYS_SET_USER_APC_DISPATCHER 17 +#define SYS_APC_RETURN 18 + +// Virtual memory, mappings, and handles. +#define SYS_ALLOC_VM 19 +#define SYS_FREE_VM 20 +#define SYS_PROTECT_VM 21 +#define SYS_QUERY_VM 22 +#define SYS_CLOSE_HANDLE 23 +#define SYS_DUPLICATE_HANDLE 24 +#define SYS_CREATE_FILE_MAPPING 25 +#define SYS_MAP_FILE 26 + +// Synchronization objects. +#define SYS_CREATE_MUTEX 27 +#define SYS_TRY_ACQUIRE_MUTEX 28 +#define SYS_RELEASE_MUTEX 29 +#define SYS_CREATE_SEM 30 +#define SYS_TRY_WAIT_SEM 31 +#define SYS_SIGNAL_SEM 32 +#define SYS_CREATE_EVENT 33 +#define SYS_SIGNAL_EVENT 34 +#define SYS_CLEAR_EVENT 35 + +// Queues and named queues. +#define SYS_CREATE_QUEUE 36 +#define SYS_SEND_QUEUE 37 +#define SYS_RECV_QUEUE 38 +#define SYS_PUBLISH_QUEUE 39 +#define SYS_OPEN_NAMED_QUEUE 40 +#define SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS 41 + +// Files, directories, and current directory. +#define SYS_OPEN_FILE 42 +#define SYS_READ_FILE 43 +#define SYS_WRITE_FILE 44 +#define SYS_READ_FILE_ASYNC 45 +#define SYS_WRITE_FILE_ASYNC 46 +#define SYS_CANCEL_IO 47 +#define SYS_SEEK_FILE 48 +#define SYS_GET_FILE_POS 49 +#define SYS_GET_FILE_SIZE 50 +#define SYS_GET_FILE_STAT 51 +#define SYS_STAT_FILE 52 +#define SYS_TRUNCATE_FILE 53 +#define SYS_FLUSH_FILE 54 +#define SYS_DELETE_FILE 55 +#define SYS_RENAME_FILE 56 +#define SYS_LOCK_FILE 57 +#define SYS_UNLOCK_FILE 58 +#define SYS_OPEN_DIR 59 +#define SYS_READ_DIR 60 +#define SYS_CREATE_DIR 61 +#define SYS_REMOVE_DIR 62 +#define SYS_SET_CWD 63 +#define SYS_REAL_PATH 64 + +// Console, display, and surfaces. +#define SYS_WRITE_CONSOLE 65 +#define SYS_GET_CONSOLE_INFO 66 +#define SYS_SET_CONSOLE_CURSOR 67 +#define SYS_FILL_CONSOLE_CHARACTER 68 +#define SYS_FILL_CONSOLE_ATTRIBUTE 69 +#define SYS_PUT_SCREEN_CHAR 70 +#define SYS_CLEAR_SCREEN 71 +#define SYS_GET_SCREEN_SIZE 72 +#define SYS_QUERY_DISPLAY_INFO 73 +#define SYS_CREATE_SURFACE 74 +#define SYS_MAP_SURFACE 75 +#define SYS_QUERY_SURFACE 76 +#define SYS_PRESENT_SURFACE 77 + +// Input. +#define SYS_POLL_KEYBOARD 78 +#define SYS_IS_KEY_DOWN 79 +#define SYS_READ_KEYBOARD_CHAR 80 +#define SYS_READ_KEY 81 +#define SYS_GET_KEY_MODIFIERS 82 +#define SYS_OPEN_INPUT_QUEUE 83 +#define SYS_READ_INPUT_EVENT 84 +#define SYS_PEEK_INPUT_EVENT 85 +#define SYS_PEEK_MOUSE 86 +#define SYS_READ_MOUSE 87 +#define SYS_GET_MOUSE_POS 88 + +// Time, debug, and system information. +#define SYS_CREATE_TIMER 89 +#define SYS_SET_TIMER 90 +#define SYS_CANCEL_TIMER 91 +#define SYS_GET_TICK 92 +#define SYS_GET_TICK_FREQUENCY 93 +#define SYS_GET_SYSTEM_TIME 94 +#define SYS_PRINT_DEBUG 95 +#define SYS_QUERY_TRAP_FRAME 96 +#define SYS_GET_MEMORY_STATUS 97 +#define SYS_QUERY_KERNEL_STATS 98 + +// Native sockets and polling. +#define SYS_CREATE_SOCKET 99 +#define SYS_BIND_SOCKET 100 +#define SYS_LISTEN_SOCKET 101 +#define SYS_ACCEPT_SOCKET 102 +#define SYS_CONNECT_SOCKET 103 +#define SYS_SEND_SOCKET 104 +#define SYS_RECV_SOCKET 105 +#define SYS_SHUTDOWN_SOCKET 106 +#define SYS_SEND_TO_SOCKET 107 +#define SYS_RECV_FROM_SOCKET 108 +#define SYS_RESOLVE_HOST 109 +#define SYS_SET_SOCKET_OPTION 110 +#define SYS_GET_SOCKET_OPTION 111 +#define SYS_POLL 112 + +#define SYS_MAX 113 diff --git a/code/kernel/io_device.c b/code/kernel/io_device.c index 89aeb71..52c2527 100644 --- a/code/kernel/io_device.c +++ b/code/kernel/io_device.c @@ -253,7 +253,6 @@ __declspec(dllexport) KSTATUS IoRegisterPhysicalDevice(DEVICE_OBJECT* pdo) { return STATUS_SUCCESS; } - __declspec(dllexport) KSTATUS IoSetDeviceName(DEVICE_OBJECT* device, SYS_STRING_VIEW name) { if (!device || !SysSvIsValid(name)) diff --git a/code/kernel/io_file.c b/code/kernel/io_file.c index e53c878..d1e0df4 100644 --- a/code/kernel/io_file.c +++ b/code/kernel/io_file.c @@ -67,6 +67,167 @@ static SPINLOCK gAsyncFileQueueLock = SPINLOCK_INIT; static LIST_ENTRY gAsyncFileQueue; static SEMAPHORE* gAsyncFileSemaphore; +typedef struct IO_FILE_LOCK_ENTRY { + LIST_ENTRY Link; + IO_FILE_IDENTITY Identity; + PROCESS* Owner; + uint64_t Offset; + uint64_t Length; + uint32_t Mode; +} IO_FILE_LOCK_ENTRY; + +typedef struct IO_FILE_LOCK_WAITER { + LIST_ENTRY Link; + IO_FILE_IDENTITY Identity; + PROCESS* Owner; + uint64_t Offset; + uint64_t Length; + uint32_t Mode; + EVENT* Event; + uint8_t Armed; +} IO_FILE_LOCK_WAITER; + +static SPINLOCK gFileLockLock = SPINLOCK_INIT; +static LIST_ENTRY gFileLocks; +static LIST_ENTRY gFileLockWaiters; +static uint8_t gFileLocksInitialized; + +// Initializes the global advisory file-lock lists on first use. +static void KiIoEnsureFileLockState(void) { + uintptr_t flags = KeAcquireSpinlock(&gFileLockLock); + if (!gFileLocksInitialized) { + LIST_INIT(&gFileLocks); + LIST_INIT(&gFileLockWaiters); + gFileLocksInitialized = 1; + } + KeReleaseSpinlock(&gFileLockLock, flags); +} + +// Compares the stable filesystem identity fields used for lock ownership. +static bool KiIoSameFileIdentity(const IO_FILE_IDENTITY* a, const IO_FILE_IDENTITY* b) { + if (!a || !b) return false; + return a->VolumeId == b->VolumeId && + a->FileId == b->FileId && + a->Generation == b->Generation; +} + +// Returns the exclusive end offset for a public lock range. +static uint64_t KiIoFileLockEnd(uint64_t offset, uint64_t length) { + if (length == SYS_FILE_LOCK_TO_EOF) + return UINT64_MAX; + uint64_t end = offset + length; + if (end < offset) + return UINT64_MAX; + return end; +} + +// Tests whether two byte ranges overlap. +static bool KiIoFileLockRangesOverlap(uint64_t aOffset, uint64_t aLength, + uint64_t bOffset, uint64_t bLength) { + uint64_t aEnd = KiIoFileLockEnd(aOffset, aLength); + uint64_t bEnd = KiIoFileLockEnd(bOffset, bLength); + return aOffset < bEnd && bOffset < aEnd; +} + +// Tests whether one existing lock conflicts with a requested lock. +static bool KiIoFileLockConflicts(const IO_FILE_LOCK_ENTRY* lock, + const IO_FILE_IDENTITY* identity, + PROCESS* owner, + uint32_t mode, + uint64_t offset, + uint64_t length) { + if (!lock || !identity || !owner) + return false; + if (lock->Owner == owner) + return false; + if (!KiIoSameFileIdentity(&lock->Identity, identity)) + return false; + if (!KiIoFileLockRangesOverlap(lock->Offset, lock->Length, offset, length)) + return false; + if (lock->Mode == SYS_FILE_LOCK_EXCLUSIVE || mode == SYS_FILE_LOCK_EXCLUSIVE) + return true; + return false; +} + +// Tests whether the requested lock is currently blocked by another process. +static bool KiIoFileLockHasConflictLocked(const IO_FILE_IDENTITY* identity, + PROCESS* owner, + uint32_t mode, + uint64_t offset, + uint64_t length) { + LIST_ENTRY* head = &gFileLocks; + for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) { + IO_FILE_LOCK_ENTRY* lock = CONTAINING_RECORD(node, IO_FILE_LOCK_ENTRY, Link); + if (KiIoFileLockConflicts(lock, identity, owner, mode, offset, length)) + return true; + } + return false; +} + +// Returns the scheduler tick value used by finite file-lock waits. +static uint64_t KiIoFileLockReadSchedulerTick(void) { + KE_SCHED_STATS stats; + KeGetSchedulerStats(&stats); + return stats.Ticks; +} + +// Converts a millisecond timeout to at least one scheduler tick. +static uint64_t KiIoFileLockTimeoutTicks(uint64_t timeoutMilliseconds) { + uint64_t ticks = timeoutMilliseconds / SCHED_TICK_MS; + if ((timeoutMilliseconds % SCHED_TICK_MS) != 0) + ticks++; + return ticks ? ticks : 1; +} + +// Computes an absolute scheduler-tick deadline for a finite timeout. +static uint64_t KiIoFileLockTimeoutDeadline(uint64_t timeoutMilliseconds) { + uint64_t now = KiIoFileLockReadSchedulerTick(); + uint64_t ticks = KiIoFileLockTimeoutTicks(timeoutMilliseconds); + uint64_t deadline = now + ticks; + return deadline < now ? UINT64_MAX : deadline; +} + +// Computes the remaining wait time for a finite timeout deadline. +static uint64_t KiIoFileLockRemainingMilliseconds(uint64_t deadlineTick) { + uint64_t now = KiIoFileLockReadSchedulerTick(); + if (deadlineTick <= now) + return 0; + + uint64_t ticks = deadlineTick - now; + if (ticks > UINT64_MAX / SCHED_TICK_MS) + return UINT64_MAX; + return ticks * SCHED_TICK_MS; +} + +// Waits for a file-lock conflict to change. +static KSTATUS KiIoWaitForFileLockChange(EVENT* event, uint64_t timeoutMilliseconds, uint64_t deadlineTick) { + if (!event) + return STATUS_INVALID_PARAMETER; + + uint64_t waitMilliseconds = timeoutMilliseconds; + if (timeoutMilliseconds != SYS_WAIT_TIMEOUT_INFINITE) { + waitMilliseconds = KiIoFileLockRemainingMilliseconds(deadlineTick); + if (waitMilliseconds == 0) + return STATUS_TIMEOUT; + } + + EVENT* events[1] = { event }; + return KeWaitForMultipleEvents(events, 1, 0, waitMilliseconds, (uint32_t*)0); +} + +// Wakes waiters whose requested file identity may have become available. +static void KiIoSignalFileLockWaitersLocked(const IO_FILE_IDENTITY* identity) { + if (!identity || !gFileLocksInitialized) + return; + + LIST_ENTRY* head = &gFileLockWaiters; + for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) { + IO_FILE_LOCK_WAITER* waiter = CONTAINING_RECORD(node, IO_FILE_LOCK_WAITER, Link); + if (waiter->Armed && waiter->Event && KiIoSameFileIdentity(&waiter->Identity, identity)) + KeSignalEvent(waiter->Event); + } +} + // Implements the io Async File Request Destroy operation. static void KiIoAsyncFileRequestDestroy(OBJECT* obj) { IO_ASYNC_FILE_REQUEST_OBJECT* req = @@ -416,7 +577,6 @@ __declspec(dllexport) OBJECT* IoAsyncFileRequestObject(IO_ASYNC_FILE_REQUEST_OBJ return req ? &req->Header : (OBJECT*)0; } - // Returns the wait event owned by an async file request. __declspec(dllexport) EVENT* IoGetAsyncFileRequestWaitEvent(IO_ASYNC_FILE_REQUEST_OBJECT* req) { return req ? req->WaitEvent : (EVENT*)0; @@ -488,25 +648,14 @@ static size_t KiFormatCwd(uint8_t drive, SYS_STRING_VIEW rel, char* buf, size_t buf[0] = (char)('0' + drive); buf[1] = ':'; buf[2] = '/'; - if (rel.Length) memcpy(buf + 3, rel.Data, rel.Length); + for (size_t i = 0; i < rel.Length; i++) { + char c = rel.Data[i]; + buf[3 + i] = (c == '\\') ? '/' : c; + } buf[3 + rel.Length] = '\0'; return needed; } -// Returns cwd. -__declspec(dllexport) KSTATUS IoGetCwd(char* buf, size_t size) { - if (!buf || size == 0) return STATUS_INVALID_PARAMETER; - - PROCESS* proc = KeGetCurrentProcess(); - if (!proc) return STATUS_ACCESS_DENIED; - - uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); - size_t needed = KiFormatCwd(proc->CurrentDrive, SysSvFromParts(proc->CurrentDirectory, proc->CurrentDirectoryLength), buf, size); - KeReleaseSpinlock(&proc->CwdLock, flags); - - return needed && needed <= size ? STATUS_SUCCESS : STATUS_BUFFER_TOO_SMALL; -} - // Implements the sv Has Drive Root operation. static int KiSvHasDriveRoot(SYS_STRING_VIEW path) { return path.Length >= 3 && @@ -615,6 +764,29 @@ static KSTATUS KiResolvePathV(SYS_STRING_VIEW input, KRESOLVED_PATH* out) { return STATUS_SUCCESS; } +// Builds a canonical native path using the kernel-owned process CWD. +__declspec(dllexport) KSTATUS IoRealPath(SYS_STRING_VIEW path, char* buffer, size_t bufferSize, size_t* requiredSizeOut) { + if (requiredSizeOut) *requiredSizeOut = 0; + if (path.Length && !path.Data) return STATUS_INVALID_PARAMETER; + if (path.Length == 0) return STATUS_INVALID_PARAMETER; + if (!buffer && bufferSize != 0) return STATUS_INVALID_PARAMETER; + + KRESOLVED_PATH resolved; + KSTATUS s = KiResolvePathV(path, &resolved); + if (!KSUCCESS(s)) return s; + + char formatted[SYS_CWD_MAX]; + size_t required = KiFormatCwd(resolved.Drive, resolved.Path, formatted, sizeof(formatted)); + if (!required || required > sizeof(formatted)) return STATUS_BUFFER_TOO_SMALL; + + if (requiredSizeOut) *requiredSizeOut = required; + if (!buffer) return STATUS_SUCCESS; + if (bufferSize < required) return STATUS_BUFFER_TOO_SMALL; + + memcpy(buffer, formatted, required); + return STATUS_SUCCESS; +} + // Updates cwd. __declspec(dllexport) KSTATUS IoSetCwd(SYS_STRING_VIEW path) { if (path.Length && !path.Data) return STATUS_INVALID_PARAMETER; @@ -722,14 +894,8 @@ __declspec(dllexport) void IoQueryFilesystemStats(IO_FILESYSTEM_STATS* out) { if (one.PathCacheGeneration > out->PathCacheGeneration) out->PathCacheGeneration = one.PathCacheGeneration; out->DirectorySearches += one.DirectorySearches; - out->DirectoryClustersRead += one.DirectoryClustersRead; + out->DirectoryBlocksRead += one.DirectoryBlocksRead; out->DirectoryEntriesScanned += one.DirectoryEntriesScanned; - out->FatChainCacheLookups += one.FatChainCacheLookups; - out->FatChainCacheHits += one.FatChainCacheHits; - out->FatChainCacheMisses += one.FatChainCacheMisses; - out->FatChainCacheStores += one.FatChainCacheStores; - out->FatChainCacheInvalidations += one.FatChainCacheInvalidations; - out->FatChainWalkSteps += one.FatChainWalkSteps; out->DataReadRuns += one.DataReadRuns; out->DataReadSectors += one.DataReadSectors; out->DataReadRunMerges += one.DataReadRunMerges; @@ -785,7 +951,6 @@ __declspec(dllexport) KSTATUS IoOpenFile(SYS_STRING_VIEW path, FILE_OBJECT **out return STATUS_SUCCESS; } - static uint64_t KiAlignUpPage64(uint64_t value) { return (value + PAGE_SIZE - 1u) & ~(uint64_t)(PAGE_SIZE - 1u); } @@ -976,7 +1141,7 @@ __declspec(dllexport) KSTATUS IoWriteFile(FILE_OBJECT* file, const void* buf, if (!file || !buf || !bytesWritten) return STATUS_INVALID_PARAMETER; uintptr_t flags = KeAcquireSpinlock(&file->Lock); - uint64_t offset = file->Offset; + uint64_t offset = (file->Flags & IO_FILE_FLAG_APPEND) ? file->Size : file->Offset; KeReleaseSpinlock(&file->Lock, flags); KSTATUS s = IoWriteFileAt(file, offset, buf, size, bytesWritten); @@ -1130,6 +1295,45 @@ __declspec(dllexport) uint64_t IoGetFileSize(FILE_OBJECT* file) { return size; } +// Queries metadata for an open file object. +__declspec(dllexport) KSTATUS IoGetFileStat(FILE_OBJECT* file, SYS_FILE_STAT* out) { + if (!file || !out) return STATUS_INVALID_PARAMETER; + memset(out, 0, sizeof(*out)); + out->Size = sizeof(*out); + out->Type = SYS_FILE_TYPE_REGULAR; + out->LinkCount = 1; + out->FileSize = IoGetFileSize(file); + + if (!file->Fs || !file->Fs->QueryInfo) + return STATUS_SUCCESS; + + KSTATUS s = file->Fs->QueryInfo(file->Fs, file->FileCtx, out); + if (KSUCCESS(s)) { + uintptr_t flags = KeAcquireSpinlock(&file->Lock); + file->Size = out->FileSize; + KeReleaseSpinlock(&file->Lock, flags); + } + return s; +} + +// Queries metadata for a filesystem path. +__declspec(dllexport) KSTATUS IoStatFile(SYS_STRING_VIEW path, SYS_FILE_STAT* out) { + if ((!path.Data && path.Length) || path.Length == 0 || !out) return STATUS_INVALID_PARAMETER; + if (!KeCanWait()) return STATUS_INVALID_IRQL; + + KRESOLVED_PATH resolved; + KSTATUS s = KiResolvePathV(path, &resolved); + if (!KSUCCESS(s)) return s; + + IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; + if (!fs) return STATUS_NOT_FOUND; + if (!fs->Stat) return STATUS_NOT_SUPPORTED; + + memset(out, 0, sizeof(*out)); + out->Size = sizeof(*out); + return fs->Stat(fs, resolved.Path, out); +} + // Queries the filesystem identity for a file object. __declspec(dllexport) KSTATUS IoQueryFileIdentity(FILE_OBJECT* file, IO_FILE_IDENTITY* out) { if (!file || !out) @@ -1140,6 +1344,161 @@ __declspec(dllexport) KSTATUS IoQueryFileIdentity(FILE_OBJECT* file, IO_FILE_IDE return file->Fs->QueryIdentity(file->Fs, file->FileCtx, out); } +// Acquires an advisory process-owned byte-range lock for one file identity. +__declspec(dllexport) KSTATUS IoLockFile(FILE_OBJECT* file, PROCESS* owner, uint32_t mode, + uint64_t offset, uint64_t length, + uint64_t timeoutMilliseconds) { + if (!file || !owner) return STATUS_INVALID_PARAMETER; + if (mode != SYS_FILE_LOCK_SHARED && mode != SYS_FILE_LOCK_EXCLUSIVE) return STATUS_INVALID_PARAMETER; + if (length == 0) return STATUS_INVALID_PARAMETER; + if (timeoutMilliseconds != 0 && !KeCanWait()) return STATUS_INVALID_IRQL; + + IO_FILE_IDENTITY identity; + KSTATUS s = IoQueryFileIdentity(file, &identity); + if (!KSUCCESS(s)) return s; + + KiIoEnsureFileLockState(); + + uint64_t deadlineTick = 0; + if (timeoutMilliseconds != 0 && timeoutMilliseconds != SYS_WAIT_TIMEOUT_INFINITE) + deadlineTick = KiIoFileLockTimeoutDeadline(timeoutMilliseconds); + + EVENT* waitEvent = (EVENT*)0; + IO_FILE_LOCK_WAITER waiter; + memset(&waiter, 0, sizeof(waiter)); + + for (;;) { + IO_FILE_LOCK_ENTRY* entry = (IO_FILE_LOCK_ENTRY*)MmAllocatePoolTag(sizeof(*entry), MM_POOL_TAG('f', 'l', 'c', 'k')); + if (!entry) { + if (waitEvent) ObDereferenceObject(&waitEvent->Header); + return STATUS_NO_MEMORY; + } + + uintptr_t flags = KeAcquireSpinlock(&gFileLockLock); + if (!KiIoFileLockHasConflictLocked(&identity, owner, mode, offset, length)) { + entry->Identity = identity; + entry->Owner = owner; + entry->Offset = offset; + entry->Length = length; + entry->Mode = mode; + LIST_INIT(&entry->Link); + ListInsertTail(&gFileLocks, &entry->Link); + KeReleaseSpinlock(&gFileLockLock, flags); + if (waitEvent) ObDereferenceObject(&waitEvent->Header); + return STATUS_SUCCESS; + } + KeReleaseSpinlock(&gFileLockLock, flags); + MmFreePool(entry); + + if (timeoutMilliseconds == 0) { + if (waitEvent) ObDereferenceObject(&waitEvent->Header); + return STATUS_WOULD_BLOCK; + } + + if (!waitEvent) { + s = KeEventCreate(EventAutoReset, 0, &waitEvent); + if (!KSUCCESS(s)) return s; + waiter.Identity = identity; + waiter.Owner = owner; + waiter.Offset = offset; + waiter.Length = length; + waiter.Mode = mode; + waiter.Event = waitEvent; + LIST_INIT(&waiter.Link); + } else { + KeClearEvent(waitEvent); + } + + flags = KeAcquireSpinlock(&gFileLockLock); + if (!KiIoFileLockHasConflictLocked(&identity, owner, mode, offset, length)) { + KeReleaseSpinlock(&gFileLockLock, flags); + continue; + } + waiter.Armed = 1; + ListInsertTail(&gFileLockWaiters, &waiter.Link); + KeReleaseSpinlock(&gFileLockLock, flags); + + s = KiIoWaitForFileLockChange(waitEvent, timeoutMilliseconds, deadlineTick); + + flags = KeAcquireSpinlock(&gFileLockLock); + if (waiter.Armed) { + ListRemove(&waiter.Link); + waiter.Armed = 0; + } + KeReleaseSpinlock(&gFileLockLock, flags); + + if (!KSUCCESS(s)) { + ObDereferenceObject(&waitEvent->Header); + return s; + } + } +} + +// Releases this process's overlapping advisory locks for one file identity. +__declspec(dllexport) KSTATUS IoUnlockFile(FILE_OBJECT* file, PROCESS* owner, uint64_t offset, uint64_t length) { + if (!file || !owner || length == 0) return STATUS_INVALID_PARAMETER; + + IO_FILE_IDENTITY identity; + KSTATUS s = IoQueryFileIdentity(file, &identity); + if (!KSUCCESS(s)) return s; + + KiIoEnsureFileLockState(); + + bool released = false; + uintptr_t flags = KeAcquireSpinlock(&gFileLockLock); + LIST_ENTRY* head = &gFileLocks; + LIST_ENTRY* node = head->Fwd; + while (node != head) { + LIST_ENTRY* next = node->Fwd; + IO_FILE_LOCK_ENTRY* lock = CONTAINING_RECORD(node, IO_FILE_LOCK_ENTRY, Link); + if (lock->Owner == owner && KiIoSameFileIdentity(&lock->Identity, &identity) && + KiIoFileLockRangesOverlap(lock->Offset, lock->Length, offset, length)) { + ListRemove(&lock->Link); + MmFreePool(lock); + released = true; + } + node = next; + } + + if (released) + KiIoSignalFileLockWaitersLocked(&identity); + KeReleaseSpinlock(&gFileLockLock, flags); + + return released ? STATUS_SUCCESS : STATUS_NOT_FOUND; +} + +// Releases every advisory file lock owned by one exiting process. +__declspec(dllexport) void IoReleaseFileLocksForProcess(PROCESS* owner) { + if (!owner) return; + + KiIoEnsureFileLockState(); + + bool released = false; + uintptr_t flags = KeAcquireSpinlock(&gFileLockLock); + LIST_ENTRY* head = &gFileLocks; + LIST_ENTRY* node = head->Fwd; + while (node != head) { + LIST_ENTRY* next = node->Fwd; + IO_FILE_LOCK_ENTRY* lock = CONTAINING_RECORD(node, IO_FILE_LOCK_ENTRY, Link); + if (lock->Owner == owner) { + ListRemove(&lock->Link); + MmFreePool(lock); + released = true; + } + node = next; + } + + if (released) { + LIST_ENTRY* waiterHead = &gFileLockWaiters; + for (LIST_ENTRY* waiterNode = waiterHead->Fwd; waiterNode != waiterHead; waiterNode = waiterNode->Fwd) { + IO_FILE_LOCK_WAITER* waiter = CONTAINING_RECORD(waiterNode, IO_FILE_LOCK_WAITER, Link); + if (waiter->Armed && waiter->Event) + KeSignalEvent(waiter->Event); + } + } + KeReleaseSpinlock(&gFileLockLock, flags); +} + // Releases file. __declspec(dllexport) void IoCloseFile(FILE_OBJECT* file) { if (file) ObDereferenceObject(&file->Header); diff --git a/code/kernel/ke_apc.c b/code/kernel/ke_apc.c index 48f81d9..4387247 100644 --- a/code/kernel/ke_apc.c +++ b/code/kernel/ke_apc.c @@ -6,10 +6,55 @@ #include #include #include +#include #include #include #include +#define KI_USER_CS 0x1BULL +#define KI_USER_SS 0x23ULL +#define KI_RFLAGS_FIXED 0x2ULL +#define KI_RFLAGS_IOPL 0x3000ULL +#define KI_RFLAGS_NT 0x4000ULL +#define KI_RFLAGS_VM 0x20000ULL + +static volatile long long gUserApcReturnTokenSeed; + +// Returns whether an address is in the canonical user half accepted by IRET. +static bool KiUserAddressValid(uint64_t address) { + return address >= USER_BASE && address < USER_LIMIT; +} + +// Returns whether a saved user RFLAGS value can be restored safely. +static bool KiUserRflagsValid(uint64_t rflags) { + if ((rflags & KI_RFLAGS_FIXED) == 0) + return false; + if ((rflags & (KI_RFLAGS_IOPL | KI_RFLAGS_NT | KI_RFLAGS_VM)) != 0) + return false; + return true; +} + +// Validates the kernel-owned state that will be restored after a user APC. +static KSTATUS KiValidateUserApcResumeState(uint64_t rip, uint64_t rsp, uint64_t rflags, + uint64_t cs, uint64_t ss) { + if (!KiUserAddressValid(rip) || !KiUserAddressValid(rsp)) + return STATUS_ACCESS_DENIED; + if (cs != KI_USER_CS || ss != KI_USER_SS) + return STATUS_ACCESS_DENIED; + if (!KiUserRflagsValid(rflags)) + return STATUS_ACCESS_DENIED; + return STATUS_SUCCESS; +} + +// Allocates a nonzero token for one active user APC return frame. +static uint64_t KiNextUserApcReturnToken(THREAD* thread, uintptr_t frameAddress) { + uint64_t seed = (uint64_t)_InterlockedIncrement64(&gUserApcReturnTokenSeed); + uint64_t token = seed ^ (uint64_t)(uintptr_t)thread ^ (uint64_t)frameAddress ^ SYS_USER_APC_FRAME_MAGIC; + if (token == 0) + token = SYS_USER_APC_FRAME_MAGIC; + return token; +} + // Initializes apc State. void KiInitializeApcState(THREAD* thread) { if (!thread) return; @@ -25,6 +70,12 @@ void KiInitializeApcState(THREAD* thread) { thread->UserApcDispatcher = 0; thread->UserApcDeliverable = 0; thread->UserApcDeliveryActive = 0; + thread->UserApcFrameUserAddress = 0; + thread->UserApcReturnToken = 0; + thread->UserApcSavedRip = 0; + thread->UserApcSavedRsp = 0; + thread->UserApcSavedRflags = 0; + thread->UserApcSavedRax = 0; } __declspec(dllexport) void KeInitializeApc(KAPC* apc, @@ -184,12 +235,16 @@ void KiDeliverKernelApcs(void) { thread->ApcDeliveryActive = 0; } - // Updates user Apc Dispatcher. __declspec(dllexport) KSTATUS KeSetUserApcDispatcher(void* dispatcher) { THREAD* thread = KeGetCurrentThread(); if (!thread) return STATUS_FAILURE; if (!dispatcher) return STATUS_INVALID_PARAMETER; + if (!KiUserAddressValid((uint64_t)(uintptr_t)dispatcher)) return STATUS_ACCESS_DENIED; + + KSTATUS s = MmProbeUserRead(dispatcher, 1); + if (!KSUCCESS(s)) return s; + thread->UserApcDispatcher = (uintptr_t)dispatcher; return STATUS_SUCCESS; } @@ -201,6 +256,8 @@ __declspec(dllexport) KSTATUS KeQueueUserApc(THREAD* thread, void* systemArgument2) { if (!thread || !routine) return STATUS_INVALID_PARAMETER; + if (!KiUserAddressValid((uint64_t)(uintptr_t)routine)) + return STATUS_ACCESS_DENIED; if (thread->State == ThreadDead) return STATUS_OBJECT_DEAD; @@ -262,24 +319,33 @@ void KiCheckForUserApcDelivery(KTRAP_FRAME* frame) { if (!apc) return; + KSTATUS s = KiValidateUserApcResumeState(frame->Rip, frame->Rsp, frame->Rflags, + frame->Cs, frame->Ss); + if (!KSUCCESS(s)) { + DbgPrint("USER/APC: rejected saved return state rip=0x%016llX rsp=0x%016llX rflags=0x%016llX cs=0x%016llX ss=0x%016llX status=%u\n", + frame->Rip, frame->Rsp, frame->Rflags, frame->Cs, frame->Ss, s); + uintptr_t flags = KeAcquireSpinlock(&thread->UserApcLock); + ListInsertTail(&thread->UserApcQueue, &apc->Link); + thread->UserApcPending = 1; + KeReleaseSpinlock(&thread->UserApcLock, flags); + return; + } + uintptr_t oldRsp = (uintptr_t)frame->Rsp; uintptr_t newRsp = oldRsp - sizeof(SYS_USER_APC_FRAME_K); newRsp &= ~(uintptr_t)0xFULL; - newRsp -= 8; // make injected dispatcher entry look like a normal call target + newRsp -= 8; // Make injected dispatcher entry look like a normal call target. SYS_USER_APC_FRAME_K userFrame; memset(&userFrame, 0, sizeof(userFrame)); userFrame.Magic = SYS_USER_APC_FRAME_MAGIC; - userFrame.OriginalRip = frame->Rip; - userFrame.OriginalRsp = frame->Rsp; - userFrame.OriginalRflags = frame->Rflags; - userFrame.OriginalRax = STATUS_USER_APC; + userFrame.ReturnToken = KiNextUserApcReturnToken(thread, newRsp); userFrame.Routine = (uint64_t)(uintptr_t)apc->Routine; userFrame.NormalContext = (uint64_t)(uintptr_t)apc->NormalContext; userFrame.SystemArgument1 = (uint64_t)(uintptr_t)apc->SystemArgument1; userFrame.SystemArgument2 = (uint64_t)(uintptr_t)apc->SystemArgument2; - KSTATUS s = CopyToUser((void*)newRsp, &userFrame, sizeof(userFrame)); + s = CopyToUser((void*)newRsp, &userFrame, sizeof(userFrame)); if (!KSUCCESS(s)) { uintptr_t flags = KeAcquireSpinlock(&thread->UserApcLock); ListInsertTail(&thread->UserApcQueue, &apc->Link); @@ -288,6 +354,13 @@ void KiCheckForUserApcDelivery(KTRAP_FRAME* frame) { return; } + thread->UserApcFrameUserAddress = newRsp; + thread->UserApcReturnToken = userFrame.ReturnToken; + thread->UserApcSavedRip = frame->Rip; + thread->UserApcSavedRsp = frame->Rsp; + thread->UserApcSavedRflags = frame->Rflags; + thread->UserApcSavedRax = STATUS_USER_APC; + frame->Rip = thread->UserApcDispatcher; frame->Rsp = newRsp; frame->Rax = STATUS_USER_APC; @@ -331,32 +404,70 @@ void KiRundownApcState(THREAD* thread) { thread->UserApcDispatcher = 0; thread->UserApcDeliverable = 0; thread->UserApcDeliveryActive = 0; + thread->UserApcFrameUserAddress = 0; + thread->UserApcReturnToken = 0; + thread->UserApcSavedRip = 0; + thread->UserApcSavedRsp = 0; + thread->UserApcSavedRflags = 0; + thread->UserApcSavedRax = 0; } // Implements the user Apc Return operation. __declspec(dllexport) uint64_t KeUserApcReturn(const SYS_USER_APC_FRAME_K* userFrame) { + THREAD* thread = KeGetCurrentThread(); + if (!thread) + return STATUS_FAILURE; + + if (!thread->UserApcDeliveryActive || !userFrame) + return STATUS_INVALID_PARAMETER; + SYS_USER_APC_FRAME_K frameCopy; KSTATUS s = CopyFromUser(&frameCopy, userFrame, sizeof(frameCopy)); if (!KSUCCESS(s)) return s; - if (frameCopy.Magic != SYS_USER_APC_FRAME_MAGIC) + if (frameCopy.Magic != SYS_USER_APC_FRAME_MAGIC || + frameCopy.ReturnToken != thread->UserApcReturnToken || + (uintptr_t)userFrame != thread->UserApcFrameUserAddress) { + DbgPrint("USER/APC: rejected return frame user=0x%016llX active=0x%016llX token=0x%016llX expected=0x%016llX\n", + (uint64_t)(uintptr_t)userFrame, + (uint64_t)thread->UserApcFrameUserAddress, + frameCopy.ReturnToken, + thread->UserApcReturnToken); return STATUS_INVALID_PARAMETER; - - THREAD* thread = KeGetCurrentThread(); - if (!thread) - return STATUS_FAILURE; + } KTRAP_FRAME* frame = KeGetCurrentTrapFrame(); if (!frame || !KeTrapFrameFromUser(frame) || frame->Kind != KTRAP_KIND_SYSCALL) return STATUS_FAILURE; - frame->Rip = frameCopy.OriginalRip; - frame->Rsp = frameCopy.OriginalRsp; - frame->Rflags = frameCopy.OriginalRflags; - frame->Rax = frameCopy.OriginalRax; + s = KiValidateUserApcResumeState(thread->UserApcSavedRip, + thread->UserApcSavedRsp, + thread->UserApcSavedRflags, + frame->Cs, + frame->Ss); + if (!KSUCCESS(s)) { + DbgPrint("USER/APC: rejected kernel-owned resume state rip=0x%016llX rsp=0x%016llX rflags=0x%016llX status=%u\n", + thread->UserApcSavedRip, + thread->UserApcSavedRsp, + thread->UserApcSavedRflags, + s); + return s; + } + + uint64_t savedRax = thread->UserApcSavedRax; + frame->Rip = thread->UserApcSavedRip; + frame->Rsp = thread->UserApcSavedRsp; + frame->Rflags = thread->UserApcSavedRflags; + frame->Rax = savedRax; thread->UserApcDeliveryActive = 0; thread->UserApcDeliverable = 0; - return frameCopy.OriginalRax; + thread->UserApcFrameUserAddress = 0; + thread->UserApcReturnToken = 0; + thread->UserApcSavedRip = 0; + thread->UserApcSavedRsp = 0; + thread->UserApcSavedRflags = 0; + thread->UserApcSavedRax = 0; + return savedRax; } diff --git a/code/kernel/ke_debug.c b/code/kernel/ke_debug.c index 38b0c66..88f98a1 100644 --- a/code/kernel/ke_debug.c +++ b/code/kernel/ke_debug.c @@ -7,46 +7,35 @@ #include #include #include -#pragma intrinsic(_disable) -#pragma intrinsic(_enable) #include #include #include #include #include #include -#include #include +#include #include #include #include -int _fltused; // MSVC floating-point helper anchor +int _fltused; // MSVC floating-point helper anchor. int gDebugEnabled = 0; -static uint32_t gDebugFlags = 0; -void (*PrintChar)(char, uint32_t, uint32_t); +static uint32_t gDebugFlags; +static const HAL_API* gDebugHal; -#define KE_DEBUG_LOG_BUFFER_SIZE (50u * 1024u) -#define KE_DEBUG_SCREEN_BUFFER_SIZE (16u * 1024u) -#define KE_DEBUG_LOG_LINE_SIZE 1024u -#define KE_DEBUG_SCREEN_DRAIN_SIZE 512u +#define KE_DEBUG_LOG_BUFFER_SIZE (50u * 1024u) +#define KE_DEBUG_LOG_LINE_SIZE 1024u +#define KE_FATAL_OUTPUT_LINE_SIZE 2048u static SPINLOCK gDebugPrintLock = SPINLOCK_INIT; -static char* gDebugLogBuffer; -static size_t gDebugLogSize; -static size_t gDebugLogWrite; -static size_t gDebugLogUsed; -static char* gDebugScreenBuffer; -static size_t gDebugScreenSize; -static size_t gDebugScreenRead; -static size_t gDebugScreenWrite; -static size_t gDebugScreenUsed; +static char* gDebugLogBuffer; +static size_t gDebugLogSize; +static size_t gDebugLogWrite; +static size_t gDebugLogUsed; static volatile long gDebugLogFlushing; -static volatile long gDebugConsoleOwnsSerial; -static volatile long gDebugConsoleWorkReady; -static KWORK_ITEM gDebugConsoleWorkItem; -// Implements the debug Log Append Locked operation. +// Appends bytes to the in-memory debug log while its lock is held. static void KiDebugLogAppendLocked(const char* text, size_t len) { if (!gDebugLogBuffer || gDebugLogSize == 0 || !text || len == 0) return; @@ -57,7 +46,8 @@ static void KiDebugLogAppendLocked(const char* text, size_t len) { } size_t first = gDebugLogSize - gDebugLogWrite; - if (first > len) first = len; + if (first > len) + first = len; memcpy(gDebugLogBuffer + gDebugLogWrite, text, first); if (len > first) memcpy(gDebugLogBuffer, text + first, len - first); @@ -69,137 +59,52 @@ static void KiDebugLogAppendLocked(const char* text, size_t len) { gDebugLogUsed += len; } -// Implements the debug Screen Append Locked operation. -static void KiDebugScreenAppendLocked(const char* text, size_t len) { - if (!gDebugScreenBuffer || gDebugScreenSize == 0 || !text || len == 0) +// Writes one record through the HAL output router. +static void KiOutputWrite(uint32_t stream, uint32_t flags, + uint32_t foreground, uint32_t background, + const char* text, size_t length) { + if (!gDebugHal || !gDebugHal->Output.Write || !text || length == 0) return; - if (len >= gDebugScreenSize) { - text += len - (gDebugScreenSize - 1u); - len = gDebugScreenSize - 1u; - } - - while (gDebugScreenUsed + len > gDebugScreenSize) { - gDebugScreenRead = (gDebugScreenRead + 1u) % gDebugScreenSize; - gDebugScreenUsed--; - } - - size_t first = gDebugScreenSize - gDebugScreenWrite; - if (first > len) first = len; - memcpy(gDebugScreenBuffer + gDebugScreenWrite, text, first); - if (len > first) - memcpy(gDebugScreenBuffer, text + first, len - first); - - gDebugScreenWrite = (gDebugScreenWrite + len) % gDebugScreenSize; - gDebugScreenUsed += len; + HAL_OUTPUT_RECORD record; + record.Stream = stream; + record.Flags = flags; + record.Foreground = foreground; + record.Background = background; + record.Buffer = text; + record.Length = length; + gDebugHal->Output.Write(&record); } -// Implements the debug Screen Drain Locked operation. -static size_t KiDebugScreenDrainLocked(char* out, size_t max) { - if (!out || max == 0 || !gDebugScreenBuffer || gDebugScreenSize == 0 || gDebugScreenUsed == 0) - return 0; - - size_t len = gDebugScreenUsed; - if (len > max) len = max; - - size_t first = gDebugScreenSize - gDebugScreenRead; - if (first > len) first = len; - memcpy(out, gDebugScreenBuffer + gDebugScreenRead, first); - if (len > first) - memcpy(out + first, gDebugScreenBuffer, len - first); - - gDebugScreenRead = (gDebugScreenRead + len) % gDebugScreenSize; - gDebugScreenUsed -= len; - return len; -} - -// Ensures the asynchronous screen ring exists before console output starts using it. -static bool KiDebugEnsureScreenBuffer(void) { - if (gDebugScreenBuffer && gDebugScreenSize != 0) - return true; - - char* buffer = (char*)MmAllocatePoolTag(KE_DEBUG_SCREEN_BUFFER_SIZE, MM_POOL_TAG('d', 's', 'c', 'r')); - if (!buffer) - return false; - memset(buffer, 0, KE_DEBUG_SCREEN_BUFFER_SIZE); - - bool installed = false; - uintptr_t flags = KeAcquireSpinlock(&gDebugPrintLock); - if (!gDebugScreenBuffer) { - gDebugScreenBuffer = buffer; - gDebugScreenSize = KE_DEBUG_SCREEN_BUFFER_SIZE; - gDebugScreenRead = 0; - gDebugScreenWrite = 0; - gDebugScreenUsed = 0; - installed = true; - } - KeReleaseSpinlock(&gDebugPrintLock, flags); - - if (!installed) - MmFreePool(buffer); - return true; -} - -// Implements the debug Serial Write Char operation. -static void KiDebugSerialWriteChar(char c) { - while ((__inbyte(0x3FD) & 0x20) == 0) { } - if (c == '\n') { - __outbyte(0x3F8, '\r'); - while ((__inbyte(0x3FD) & 0x20) == 0) { } - } - __outbyte(0x3F8, (uint8_t)c); -} - -// Implements the debug Serial Write Locked operation. -static void KiDebugSerialWriteLocked(const char* text, size_t len) { - if (!text) +// Formats one bounded output record and routes it through the HAL. +static void KiOutputVPrintf(uint32_t stream, uint32_t flags, + uint32_t foreground, uint32_t background, + const char* format, va_list args) { + if (!format) return; - for (size_t i = 0; i < len; i++) - KiDebugSerialWriteChar(text[i]); -} -// Implements the debug Console Write operation. -static void KiDebugConsoleWrite(const char* text, size_t len) { - if (!PrintChar || !text) - return; - for (size_t i = 0; i < len; i++) - PrintChar(text[i], 0xCCCCCC, 0x012456); -} - -// Implements the debug Console Work operation. -static void KiDebugConsoleWork(void* context) { - (void)context; - char chunk[KE_DEBUG_SCREEN_DRAIN_SIZE]; - - for (;;) { - uintptr_t flags = KeAcquireSpinlock(&gDebugPrintLock); - size_t len = KiDebugScreenDrainLocked(chunk, sizeof(chunk)); - KeReleaseSpinlock(&gDebugPrintLock, flags); - - if (len == 0) - break; - KiDebugConsoleWrite(chunk, len); + char line[KE_FATAL_OUTPUT_LINE_SIZE]; + int count = vsnprintf_(line, sizeof(line), format, args); + size_t length = 0; + if (count > 0) { + length = (size_t)count; + if (length >= sizeof(line)) + length = sizeof(line) - 1u; } + KiOutputWrite(stream, flags, foreground, background, line, length); } -// Queues the debug Console Work operation if the passive work queue is ready. -static void KiDebugQueueConsoleWork(void) { - if (_InterlockedCompareExchange((volatile long*)&gDebugConsoleWorkReady, 1, 1) && KeIsWorkQueueStarted()) - (void)KeQueueWorkItem(&gDebugConsoleWorkItem); -} +static volatile long gPanicInProgress; +static volatile uintptr_t gPanicOwnerToken; +static volatile long gPanicStoppedCpuCount; -static volatile long gPanicInProgress = 0; -static volatile uintptr_t gPanicOwnerToken = 0; -static volatile long gPanicStoppedCpuCount = 0; - -// Implements the panic Secondary Halt operation. +// Permanently stops a secondary processor that entered an active panic. static _Noreturn void KiPanicSecondaryHalt(void) { - _disable(); _InterlockedIncrement(&gPanicStoppedCpuCount); - for (;;) __halt(); + HalStopProcessor(); } -// Implements the panic Current Token operation. +// Returns a stable token for the current execution context. static uintptr_t KiPanicCurrentToken(void) { THREAD* thread = KeGetCurrentThread(); if (thread) @@ -210,20 +115,20 @@ static uintptr_t KiPanicCurrentToken(void) { return 1u; } -// Implements the panic Is Active operation. +// Returns whether a fatal report is active. static int KiPanicIsActive(void) { return _InterlockedCompareExchange((volatile long*)&gPanicInProgress, 0, 0) != 0; } -// Implements the panic Current Cpu Owns Report operation. +// Returns whether the current processor owns the active fatal report. static int KiPanicCurrentCpuOwnsReport(void) { uintptr_t token = KiPanicCurrentToken(); return token != 0 && gPanicOwnerToken == token; } -// Implements the begin Fatal Report operation. +// Claims fatal-report ownership and masks interrupts on this processor. static void KiBeginFatalReport(void) { - _disable(); + (void)HalDisableInterrupts(); uintptr_t token = KiPanicCurrentToken(); if (_InterlockedCompareExchange((volatile long*)&gPanicInProgress, 1, 0) == 0) { gPanicOwnerToken = token; @@ -233,10 +138,7 @@ static void KiBeginFatalReport(void) { KiPanicSecondaryHalt(); } -// Returning NMI handler used for SMP control IPIs. Panic NMIs stop the -// target CPU permanently; TLB shootdown NMIs flush and return. A genuinely -// unexpected NMI is not handled here and will fall through to the normal fault -// path in the assembly stub. +// Handles NMI-based panic stops and TLB shootdowns. __declspec(dllexport) int KeHandleNmiIpi(void) { if (KiPanicIsActive()) KiPanicSecondaryHalt(); @@ -247,23 +149,11 @@ __declspec(dllexport) int KeHandleNmiIpi(void) { return 0; } -// Implements the kernel Put Char operation. -void KiKernelPutChar(char c) { - if (_InterlockedCompareExchange((volatile long*)&gDebugConsoleOwnsSerial, 1, 1)) - KiDebugSerialWriteChar(c); - if (PrintChar) - PrintChar(c, 0xCCCCCC, 0x012456); -} - -// Initializes debug. -void KeInitDebug(void (*PrintPtr)(char, uint32_t, uint32_t), uint32_t flags) { +// Initializes debug logging and initial output subscriptions. +void KeInitDebug(const HAL_API* hal, uint32_t flags) { + gDebugHal = hal; gDebugFlags = flags; gDebugEnabled = (flags & (KE_DEBUG_CONSOLE | KE_DEBUG_FILE_LOG)) != 0; - PrintChar = PrintPtr; - printf_set_putchar(KiKernelPutChar); - - if (!_InterlockedCompareExchange((volatile long*)&gDebugConsoleWorkReady, 1, 0)) - KeInitializeWorkItem(&gDebugConsoleWorkItem, KiDebugConsoleWork, (void*)0); if ((flags & (KE_DEBUG_CONSOLE | KE_DEBUG_FILE_LOG)) && !gDebugLogBuffer) { gDebugLogBuffer = (char*)MmAllocatePoolTag(KE_DEBUG_LOG_BUFFER_SIZE, MM_POOL_TAG('d', 'l', 'o', 'g')); @@ -275,81 +165,56 @@ void KeInitDebug(void (*PrintPtr)(char, uint32_t, uint32_t), uint32_t flags) { } } - if (flags & KE_DEBUG_CONSOLE) - (void)KiDebugEnsureScreenBuffer(); + if (hal && hal->Output.SetEndpointStreams && + hal->Output.BootFramebufferEndpointId != HAL_OUTPUT_ENDPOINT_INVALID) { + uint32_t streams = HAL_OUTPUT_STREAM_CONSOLE | + HAL_OUTPUT_STREAM_CRASH | + HAL_OUTPUT_STREAM_PANIC; + if (flags & KE_DEBUG_CONSOLE) + streams |= HAL_OUTPUT_STREAM_DEBUG; + (void)hal->Output.SetEndpointStreams(hal->Output.BootFramebufferEndpointId, streams); + } } -// Returns debug Flags. +// Returns the active debug flags. uint32_t KeGetDebugFlags(void) { return gDebugFlags; } -// Updates debug Console Writer. -void KeSetDebugConsoleWriter(void (*PrintPtr)(char, uint32_t, uint32_t)) { - if (!PrintPtr) - return; - PrintChar = PrintPtr; - printf_set_putchar(KiKernelPutChar); - - // After the display manager owns debug drawing, DbgPrint and user console - // writes mirror to COM1 themselves. The display writer is screen-only so - // asynchronous screen flushing does not duplicate serial bytes. - (void)KiDebugEnsureScreenBuffer(); - _InterlockedExchange((volatile long*)&gDebugConsoleOwnsSerial, 1); - KiDebugQueueConsoleWork(); -} - -// Implements the dbg Print operation. +// Writes one formatted kernel diagnostic. __declspec(dllexport) void DbgPrint(const char* fmt, ...) { - if (!gDebugEnabled || !fmt) return; + if (!gDebugEnabled || !fmt) + return; char line[KE_DEBUG_LOG_LINE_SIZE]; - va_list va; - va_start(va, fmt); - int n = vsnprintf_(line, sizeof(line), fmt, va); - va_end(va); + va_list args; + va_start(args, fmt); + int count = vsnprintf_(line, sizeof(line), fmt, args); + va_end(args); - size_t len = 0; - if (n > 0) { - len = (size_t)n; - if (len >= sizeof(line)) - len = sizeof(line) - 1u; + size_t length = 0; + if (count > 0) { + length = (size_t)count; + if (length >= sizeof(line)) + length = sizeof(line) - 1u; } - if (KiPanicIsActive()) { - if (!KiPanicCurrentCpuOwnsReport()) - KiPanicSecondaryHalt(); - if (gDebugFlags & KE_DEBUG_CONSOLE) { - if (_InterlockedCompareExchange((volatile long*)&gDebugConsoleOwnsSerial, 1, 1)) - KiDebugSerialWriteLocked(line, len); - KiDebugConsoleWrite(line, len); - } - return; + bool panic = KiPanicIsActive(); + if (panic && !KiPanicCurrentCpuOwnsReport()) + KiPanicSecondaryHalt(); + + if (!panic) { + uintptr_t lockFlags = KeAcquireSpinlock(&gDebugPrintLock); + if (!_InterlockedCompareExchange((volatile long*)&gDebugLogFlushing, 0, 0)) + KiDebugLogAppendLocked(line, length); + KeReleaseSpinlock(&gDebugPrintLock, lockFlags); } - bool queueConsole = false; - bool writeConsoleSync = false; - - uintptr_t flags = KeAcquireSpinlock(&gDebugPrintLock); - if (_InterlockedCompareExchange((volatile long*)&gDebugConsoleOwnsSerial, 1, 1)) - KiDebugSerialWriteLocked(line, len); - if (!_InterlockedCompareExchange((volatile long*)&gDebugLogFlushing, 0, 0)) - KiDebugLogAppendLocked(line, len); - - if (gDebugFlags & KE_DEBUG_CONSOLE) { - if (KeIsWorkQueueStarted() && gDebugScreenBuffer) { - KiDebugScreenAppendLocked(line, len); - queueConsole = true; - } else { - writeConsoleSync = true; - } - } - KeReleaseSpinlock(&gDebugPrintLock, flags); - - if (queueConsole) - KiDebugQueueConsoleWork(); - else if (writeConsoleSync) - KiDebugConsoleWrite(line, len); + KiOutputWrite(panic ? HAL_OUTPUT_STREAM_PANIC : HAL_OUTPUT_STREAM_DEBUG, + panic ? HAL_OUTPUT_WRITE_EMERGENCY : 0, + panic ? 0x00FF2222u : 0x00CCCCCCu, + panic ? 0x00000000u : 0x00012456u, + line, length); } // Flushes debug Log To File. @@ -402,7 +267,6 @@ __declspec(dllexport) KSTATUS KeFlushDebugLogToFile(SYS_STRING_VIEW path) { return (KSUCCESS(st) && totalWritten == used) ? STATUS_SUCCESS : st; } - // Attempts to persist the debug log during a panic when the filesystem is usable. static void KiTryFlushPanicDebugLog(void) { if (!(gDebugFlags & KE_DEBUG_FILE_LOG)) @@ -419,76 +283,69 @@ static void KiTryFlushPanicDebugLog(void) { DbgPrint("debuglog: panic flush to %s failed st=0x%x\n", path, st); } -// KePanic. +// Writes one formatted panic record through panic-safe endpoints. +static void KiPanicPrintf(const char* format, ...) { + va_list args; + va_start(args, format); + KiOutputVPrintf(HAL_OUTPUT_STREAM_PANIC, HAL_OUTPUT_WRITE_EMERGENCY, + 0x00FF2222u, 0x00000000u, format, args); + va_end(args); +} + +// Stops the system after reporting a fatal kernel error. __declspec(dllexport) _Noreturn void KePanic(int code, const char* fmt, ...) { KiBeginFatalReport(); - // Stop every other CPU for the final panic banner. Kernel fault dispatch - // claims panic ownership before its detailed dump, so this does not steal - // ownership from the CPU that found the fatal error. + // Stop every other CPU before writing the final report. Panic-safe endpoints + // do not depend on scheduler locks held by processors stopped by this IPI. HalApicSendPanicIpi(); - if (PrintChar) { - // Print a bright-red banner so the framebuffer is unmistakable. - static const char banner[] = "\n*** KERNEL PANIC ***\n"; - const char* p; - KCPU* cpu = KeGetCurrentCpu(); - THREAD* thread = KeGetCurrentThread(); + static const char banner[] = "\n*** KERNEL PANIC ***\n"; + KiOutputWrite(HAL_OUTPUT_STREAM_PANIC, HAL_OUTPUT_WRITE_EMERGENCY, + 0x00FF2222u, 0x00000000u, banner, sizeof(banner) - 1u); + KiPanicPrintf("Panic code: %d\n", code); - for (p = banner; *p; p++) - PrintChar(*p, 0xFF2222, 0x000000); - - printf("Panic code: %d\n", code); - if (cpu) { - printf("Panic CPU: %u LAPIC=%u", - (unsigned)cpu->CpuIndex, - (unsigned)cpu->LapicId); - if (thread) - printf(" thread=0x%016llX tid=%llu", - thread, - (unsigned long long)thread->ThreadId); - printf("\n"); + KCPU* cpu = KeGetCurrentCpu(); + THREAD* thread = KeGetCurrentThread(); + if (cpu) { + KiPanicPrintf("Panic CPU: %u LAPIC=%u", + (unsigned)cpu->CpuIndex, + (unsigned)cpu->LapicId); + if (thread) { + KiPanicPrintf(" thread=0x%016llX tid=%llu", + thread, + (unsigned long long)thread->ThreadId); } - - va_list va; - va_start(va, fmt); - vprintf(fmt, va); - va_end(va); + KiPanicPrintf("\n"); } + va_list args; + va_start(args, fmt); + KiOutputVPrintf(HAL_OUTPUT_STREAM_PANIC, HAL_OUTPUT_WRITE_EMERGENCY, + 0x00FF2222u, 0x00000000u, fmt, args); + va_end(args); + KiTryFlushPanicDebugLog(); - - for (;;) __halt(); + HalStopProcessor(); } -// KePrintConsole - write user-visible console text using the default console palette. -// User text mirrors to COM1 immediately, then queues expensive screen drawing -// through the passive debug-console worker when that path is available. +// Writes user-visible console text through subscribed output endpoints. __declspec(dllexport) void KePrintConsole(const char* buf, size_t len) { - if (!buf || len == 0) return; + KiOutputWrite(HAL_OUTPUT_STREAM_CONSOLE, 0, + 0x00CCCCCCu, 0x00012456u, buf, len); +} - bool queueConsole = false; - bool writeConsoleSync = false; - bool canQueueScreen = PrintChar && KeIsWorkQueueStarted() && KiDebugEnsureScreenBuffer(); - - uintptr_t flags = KeAcquireSpinlock(&gDebugPrintLock); - if (_InterlockedCompareExchange((volatile long*)&gDebugConsoleOwnsSerial, 1, 1)) - KiDebugSerialWriteLocked(buf, len); - - if (PrintChar) { - if (!KiPanicIsActive() && canQueueScreen) { - KiDebugScreenAppendLocked(buf, len); - queueConsole = true; - } else { - writeConsoleSync = true; - } - } - KeReleaseSpinlock(&gDebugPrintLock, flags); - - if (queueConsole) - KiDebugQueueConsoleWork(); - else if (writeConsoleSync) - KiDebugConsoleWrite(buf, len); +// Writes one formatted process-fault or kernel-fault diagnostic. +static void KiFaultPrintf(const char* format, ...) { + bool panic = KiPanicIsActive(); + va_list args; + va_start(args, format); + KiOutputVPrintf(panic ? HAL_OUTPUT_STREAM_PANIC : HAL_OUTPUT_STREAM_CRASH, + panic ? HAL_OUTPUT_WRITE_EMERGENCY : 0, + panic ? 0x00FF2222u : 0x00FFAA00u, + 0x00000000u, + format, args); + va_end(args); } // KeBugCheckProcess - user-mode fault handler @@ -585,6 +442,15 @@ static const char* KiResolveModuleEx(PROCESS* proc, uintptr_t addr, } } + uintptr_t kernelBase = 0; + size_t kernelSize = 0; + const char* kernelModule = LdrFindKernelModuleByAddress(addr, &kernelBase, &kernelSize); + if (kernelBase) { + if (baseOut) *baseOut = kernelBase; + if (sizeOut) *sizeOut = kernelSize; + return kernelModule; + } + return "unknown"; } @@ -600,71 +466,96 @@ static void KiPrintAddress(PROCESS* proc, const char* label, uintptr_t addr) { const char* mod = KiResolveModuleEx(proc, addr, &base, &size); if (base) { - printf("%s%016llx <%s+0x%016llX>\n", + KiFaultPrintf("%s%016llx <%s+0x%016llX>\n", label, (unsigned long long)addr, mod, (unsigned long long)(addr - base)); } else { - printf("%s%016llx \n", + KiFaultPrintf("%s%016llx \n", label, (unsigned long long)addr); } } -// Implements the user Read U64 operation. -static int KiUserReadU64(uintptr_t addr, uint64_t* out) { - if (!out) return 0; - if (!addr) return 0; - if (!KSUCCESS(MmProbeUserRead((const void*)addr, sizeof(uint64_t)))) - return 0; - *out = *(const uint64_t*)addr; +// Checks whether a kernel virtual range is mapped before diagnostic reads. +static int KiKernelRangeReadable(uintptr_t addr, size_t size) { + if (!addr || size == 0) return 0; + if (size - 1u > (size_t)(UINTPTR_MAX - addr)) return 0; + + uintptr_t start = addr & PAGE_MASK; + uintptr_t last = (addr + size - 1u) & PAGE_MASK; + + for (uintptr_t page = start;; page += PAGE_SIZE) { + PHYSICAL_ADDRESS phys = 0; + uint64_t flags = 0; + if (!KSUCCESS(MmPteQueryPageFor(MmPteQueryDebug, page, &phys, &flags))) + return 0; + if ((flags & PAGE_PRESENT) == 0) + return 0; + if (page == last) + break; + if (page > UINTPTR_MAX - PAGE_SIZE) + return 0; + } return 1; } -// Implements the user Read U8 operation. -static int KiUserReadU8(uintptr_t addr, uint8_t* out) { - if (!out) return 0; - if (!addr) return 0; - if (!KSUCCESS(MmProbeUserRead((const void*)addr, sizeof(uint8_t)))) +// Reads diagnostic memory from either user space or a mapped kernel range. +static int KiReadDiagnosticMemory(uintptr_t addr, void* out, size_t size) { + if (!out || !addr || size == 0) return 0; + + if (addr >= KERNEL_BASE) { + if (!KiKernelRangeReadable(addr, size)) + return 0; + memcpy(out, (const void*)addr, size); + return 1; + } + + if (!KSUCCESS(MmProbeUserRead((const void*)addr, size))) return 0; - *out = *(const uint8_t*)addr; + memcpy(out, (const void*)addr, size); return 1; } +// Reads one diagnostic uint64_t. +static int KiUserReadU64(uintptr_t addr, uint64_t* out) { + return KiReadDiagnosticMemory(addr, out, sizeof(*out)); +} + +// Reads one diagnostic byte. +static int KiUserReadU8(uintptr_t addr, uint8_t* out) { + return KiReadDiagnosticMemory(addr, out, sizeof(*out)); +} + // Dumps bytes. static void KiDumpBytes(PROCESS* proc, const char* label, uintptr_t center) { if (!center) { - printf("%s: \n", label); + KiFaultPrintf("%s: \n", label); return; } - printf("%s around ", label); + KiFaultPrintf("%s around ", label); KiPrintAddress(proc, "", center); uintptr_t start = center >= 16 ? center - 16 : 0; for (int row = 0; row < 2; row++) { uintptr_t addr = start + (uintptr_t)row * 16u; - printf(" %016llx:", (unsigned long long)addr); + KiFaultPrintf(" %016llx:", (unsigned long long)addr); for (int i = 0; i < 16; i++) { uint8_t b = 0; if (KiUserReadU8(addr + (uintptr_t)i, &b)) - printf(" %02x", b); + KiFaultPrintf(" %02x", b); else - printf(" ??"); + KiFaultPrintf(" ??"); } - printf("\n"); + KiFaultPrintf("\n"); } } -// Implements the user Read I32 operation. +// Reads one diagnostic int32_t. static int KiUserReadI32(uintptr_t addr, int32_t* out) { - if (!out) return 0; - if (!addr) return 0; - if (!KSUCCESS(MmProbeUserRead((const void*)addr, sizeof(int32_t)))) - return 0; - *out = *(const int32_t*)addr; - return 1; + return KiReadDiagnosticMemory(addr, out, sizeof(*out)); } // Dumps indirect Call Slot. @@ -693,21 +584,21 @@ static void KiDumpIndirectCallSlot(PROCESS* proc, uintptr_t ret) { KiUserReadI32(ret - 4, &disp)) { insn = ret - 7; } else { - printf("No RIP-relative indirect call pattern found before return address.\n"); + KiFaultPrintf("No RIP-relative indirect call pattern found before return address.\n"); return; } uintptr_t slot = ret + (intptr_t)disp; uint64_t target = 0; - printf("Decoded null indirect call:\n"); + KiFaultPrintf("Decoded null indirect call:\n"); KiPrintAddress(proc, " insn=", insn); KiPrintAddress(proc, " slot=", slot); if (KiUserReadU64(slot, &target)) { KiPrintAddress(proc, " *slot=", (uintptr_t)target); } else { - printf(" *slot=\n"); + KiFaultPrintf(" *slot=\n"); } KiDumpBytes(proc, "Bytes near indirect slot", slot); @@ -717,9 +608,9 @@ static void KiDumpIndirectCallSlot(PROCESS* proc, uintptr_t ret) { static void KiDumpModules(PROCESS* proc) { if (!proc) return; - printf("Images:\n"); + KiFaultPrintf("Images:\n"); if (proc->ImageBase && proc->ImageSize) { - printf(" %-31s %016llx-%016llx size=%llx\n", + KiFaultPrintf(" %-31s %016llx-%016llx size=%llx\n", proc->Name, (unsigned long long)proc->ImageBase, (unsigned long long)(proc->ImageBase + proc->ImageSize), @@ -729,7 +620,7 @@ static void KiDumpModules(PROCESS* proc) { for (uint32_t i = 0; i < proc->ModuleCount; i++) { uintptr_t base = proc->Modules[i].Base; size_t size = proc->Modules[i].Size; - printf(" %-31s %016llx-%016llx size=%llx\n", + KiFaultPrintf(" %-31s %016llx-%016llx size=%llx\n", proc->Modules[i].Name, (unsigned long long)base, (unsigned long long)(base + size), @@ -739,9 +630,9 @@ static void KiDumpModules(PROCESS* proc) { // Dumps user Stack. static void KiDumpUserStack(PROCESS* proc, uintptr_t rsp) { - printf("User stack @ %016llx:\n", (unsigned long long)rsp); + KiFaultPrintf("User stack @ %016llx:\n", (unsigned long long)rsp); if (!rsp) { - printf(" \n"); + KiFaultPrintf(" \n"); return; } @@ -753,20 +644,20 @@ static void KiDumpUserStack(PROCESS* proc, uintptr_t rsp) { size_t size = 0; const char* mod = KiResolveModuleEx(proc, (uintptr_t)value, &base, &size); if (base) { - printf(" [%02d] %016llx : %016llx <%s+0x%016llX>\n", + KiFaultPrintf(" [%02d] %016llx : %016llx <%s+0x%016llX>\n", i, (unsigned long long)slot, (unsigned long long)value, mod, (unsigned long long)((uintptr_t)value - base)); } else { - printf(" [%02d] %016llx : %016llx\n", + KiFaultPrintf(" [%02d] %016llx : %016llx\n", i, (unsigned long long)slot, (unsigned long long)value); } } else { - printf(" [%02d] %016llx : \n", + KiFaultPrintf(" [%02d] %016llx : \n", i, (unsigned long long)slot); } @@ -777,20 +668,20 @@ static void KiDumpUserStack(PROCESS* proc, uintptr_t rsp) { static void KiDumpFaultFrame(PROCESS* proc, const KFAULT_FRAME* f) { if (!f) return; - printf("Regs:\n"); - printf(" RAX=%016llx RBX=%016llx RCX=%016llx RDX=%016llx\n", + KiFaultPrintf("Regs:\n"); + KiFaultPrintf(" RAX=%016llx RBX=%016llx RCX=%016llx RDX=%016llx\n", (unsigned long long)f->Rax, (unsigned long long)f->Rbx, (unsigned long long)f->Rcx, (unsigned long long)f->Rdx); - printf(" RSI=%016llx RDI=%016llx RBP=%016llx RSP=%016llx\n", + KiFaultPrintf(" RSI=%016llx RDI=%016llx RBP=%016llx RSP=%016llx\n", (unsigned long long)f->Rsi, (unsigned long long)f->Rdi, (unsigned long long)f->Rbp, (unsigned long long)f->Rsp); - printf(" R8 =%016llx R9 =%016llx R10=%016llx R11=%016llx\n", + KiFaultPrintf(" R8 =%016llx R9 =%016llx R10=%016llx R11=%016llx\n", (unsigned long long)f->R8, (unsigned long long)f->R9, (unsigned long long)f->R10, (unsigned long long)f->R11); - printf(" R12=%016llx R13=%016llx R14=%016llx R15=%016llx\n", + KiFaultPrintf(" R12=%016llx R13=%016llx R14=%016llx R15=%016llx\n", (unsigned long long)f->R12, (unsigned long long)f->R13, (unsigned long long)f->R14, (unsigned long long)f->R15); - printf(" CS=%04llx SS=%04llx RFLAGS=%016llx\n", + KiFaultPrintf(" CS=%04llx SS=%04llx RFLAGS=%016llx\n", (unsigned long long)f->Cs, (unsigned long long)f->Ss, (unsigned long long)f->Rflags); @@ -798,12 +689,12 @@ static void KiDumpFaultFrame(PROCESS* proc, const KFAULT_FRAME* f) { if (f->Rip == 0) { uint64_t ret = 0; if (KiUserReadU64((uintptr_t)f->Rsp, &ret)) { - printf("Likely null indirect call. Return address from [RSP]:\n"); + KiFaultPrintf("Likely null indirect call. Return address from [RSP]:\n"); KiPrintAddress(proc, " ret=", (uintptr_t)ret); KiDumpBytes(proc, "Bytes near return address", (uintptr_t)ret); KiDumpIndirectCallSlot(proc, (uintptr_t)ret); } else { - printf("Likely null indirect call, but [RSP] is unreadable.\n"); + KiFaultPrintf("Likely null indirect call, but [RSP] is unreadable.\n"); } } else { KiDumpBytes(proc, "Bytes near RIP", (uintptr_t)f->Rip); @@ -812,14 +703,6 @@ static void KiDumpFaultFrame(PROCESS* proc, const KFAULT_FRAME* f) { KiDumpUserStack(proc, (uintptr_t)f->Rsp); } -// Prints crash Char. -static void KiPrintCrashChar(char c) { - if (_InterlockedCompareExchange((volatile long*)&gDebugConsoleOwnsSerial, 1, 1)) - KiDebugSerialWriteChar(c); - if (PrintChar) - PrintChar(c, 0xFFAA00, 0x000000); -} - static KSTATUS KiFaultExitStatus(int vector) { switch (vector) { case 0: return STATUS_PROCESS_DIVIDE_BY_ZERO; @@ -840,13 +723,11 @@ static void KiPrintCrashHeader(PROCESS* proc, int vector, uintptr_t rip, const char* modName = proc ? KiResolveModule(proc, rip) : "unknown"; static const char banner[] = "\n*** APPLICATION CRASH ***\n"; - for (const char* p = banner; *p; p++) - KiPrintCrashChar(*p); - - printf_set_putchar(KiPrintCrashChar); + KiOutputWrite(HAL_OUTPUT_STREAM_CRASH, 0, + 0x00FFAA00u, 0x00000000u, banner, sizeof(banner) - 1u); if (vector == 14) - printf("Process: %s (PID %u) | Fault: %s\n" + KiFaultPrintf("Process: %s (PID %u) | Fault: %s\n" "Module: %s | RIP: %016llx | Code: %04llx | CR2: %016llx\n", procName, pid, faultName, modName, @@ -854,7 +735,7 @@ static void KiPrintCrashHeader(PROCESS* proc, int vector, uintptr_t rip, (unsigned long long)errCode, (unsigned long long)cr2); else - printf("Process: %s (PID %u) | Fault: %s\n" + KiFaultPrintf("Process: %s (PID %u) | Fault: %s\n" "Module: %s | RIP: %016llx | Code: %04llx\n", procName, pid, faultName, modName, @@ -864,21 +745,19 @@ static void KiPrintCrashHeader(PROCESS* proc, int vector, uintptr_t rip, // Implements the finish Faulted Process operation. static _Noreturn void KiFinishFaultedProcess(PROCESS* proc, THREAD* thread, KSTATUS exitStatus) { - printf_set_putchar(KiKernelPutChar); - int exitCode = (int)exitStatus; if (proc) (void)KeTerminateProcess(proc, exitCode); if (thread) KeExitThread(thread, exitCode); - // KeExitThread normally switches away and never resumes this context. If + // KeExitThread normally switches away and never resumes this context. If // a fault path re-enters here during scheduler bring-up or with no runnable // peer yet, do not fall back to a local CLI/HLT dead stop: keep offering the // dead thread to the scheduler with interrupts enabled so waiters such as // the foreground shell can observe the process exit event. while (true) { - _enable(); + HalEnableInterrupts(); KeYield(); - __halt(); + HalWaitForInterrupt(); } } @@ -932,6 +811,7 @@ __declspec(dllexport) KSTATUS KeFaultDispatchFrame(const KFAULT_FRAME* frame) { KiDumpFaultFrame(proc, frame); if (vector == 14) { MmDebugDescribeAddress((uintptr_t)frame->Cr2); + if (frame->Rip) MmDebugDescribeAddress((uintptr_t)frame->Rip); if (frame->Rsp) MmDebugDescribeAddress((uintptr_t)frame->Rsp); MmVadDebugDumpProcess(proc); } @@ -994,5 +874,5 @@ __declspec(dllexport) _Noreturn void KeFaultDispatch(int vector, uintptr_t rip, frame.Rip = rip; frame.Cs = cs; (void)KeFaultDispatchFrame(&frame); - while (1) __halt(); + HalStopProcessor(); } diff --git a/code/kernel/ke_display.c b/code/kernel/ke_display.c index bd211e6..931b74b 100644 --- a/code/kernel/ke_display.c +++ b/code/kernel/ke_display.c @@ -30,6 +30,14 @@ #define DISPLAY_CONSOLE_MAX_CELLS (DISPLAY_CONSOLE_MAX_COLS * DISPLAY_CONSOLE_MAX_ROWS) #define DISPLAY_CONSOLE_DEFAULT_ATTR 0x07u #define DISPLAY_CURSOR_POS_INVALID (-2147483647 - 1) +#define DISPLAY_OUTPUT_BUFFER_CAPACITY 4096u +#define DISPLAY_OUTPUT_DRAIN_COUNT 128u + +typedef struct DISPLAY_OUTPUT_GLYPH { + char Character; + uint32_t Foreground; + uint32_t Background; +} DISPLAY_OUTPUT_GLYPH; typedef struct BOOTFB_CONTEXT { const HAL_API* Hal; @@ -78,7 +86,15 @@ static uint16_t gConsoleAttribute; static char gConsoleCells[DISPLAY_CONSOLE_MAX_CELLS]; static uint8_t gConsoleAttrs[DISPLAY_CONSOLE_MAX_CELLS]; static volatile long gDisplayConsoleSuspended; +static SPINLOCK gDisplayOutputLock = SPINLOCK_INIT; +static DISPLAY_OUTPUT_GLYPH* gDisplayOutputBuffer; +static size_t gDisplayOutputRead; +static size_t gDisplayOutputWrite; +static size_t gDisplayOutputUsed; +static KWORK_ITEM gDisplayOutputWorkItem; +static volatile long gDisplayOutputWorkInitialized; +static KSTATUS KiDisplayPrintCharInternal(char c, uint32_t fg, uint32_t bg); static KSTATUS BootFbGetMode(DISPLAY_DEVICE* device, DISPLAY_MODE* outMode); static KSTATUS BootFbBlit(DISPLAY_DEVICE* device, int x, int y, int w, int h, const uint32_t* pixels); static KSTATUS BootFbFillRect(DISPLAY_DEVICE* device, int x, int y, int w, int h, uint32_t color); @@ -105,7 +121,6 @@ static const DISPLAY_DEVICE_OPS gBootFbOps = { .QueryStats = BootFbQueryStats, }; - static bool KiDisplayOutputSuspended(void); static void KiDisplayCursorWorker(void* context); static void KiDisplayDirtyAll(void); @@ -161,14 +176,13 @@ static void KiDisplayQueueCursorWork(void) { return; } - // During single-threaded early boot there is no worker thread yet. The + // During single-threaded early boot there is no worker thread yet. The // boot framebuffer console still needs synchronous cursor setup; once the // scheduler work queue exists all cursor commits are asynchronous. if (!KiDisplayProviderCallbacksRequirePassive()) KiDisplayCursorWorker(0); } - // Checks whether a primary display provider is available. static bool KiDisplayHasDevice(void) { return gPrimaryDisplay && gPrimaryDisplay->Ops; @@ -195,7 +209,6 @@ static bool KiDisplayHasCursorOps(void) { gPrimaryDisplay->Ops->ShowCursor; } - static void KiDisplayCursorWorker(void* context) { (void)context; @@ -263,9 +276,9 @@ static void KiDisplayCursorWorker(void* context) { newCommittedY = DISPLAY_CURSOR_POS_INVALID; } } else if (hardwareCursor) { - // Hardware cursors are independent of framebuffer drawing. Commit + // Hardware cursors are independent of framebuffer drawing. Commit // position only when the logical cursor request changed or the - // provider has not yet been synchronized. This prevents stale or + // provider has not yet been synchronized. This prevents stale or // duplicate work items from hammering device cursor registers. bool needPosition = !drawn || x != committedX || y != committedY; if (needPosition && ops->SetCursorPosition) { @@ -308,7 +321,7 @@ static void KiDisplayCursorWorker(void* context) { gCursorCommittedX = newCommittedX; gCursorCommittedY = newCommittedY; } - // Do not spin forever on provider failure. A later explicit cursor + // Do not spin forever on provider failure. A later explicit cursor // request, show/hide request, or display-generation change will retry. gCursorAppliedGeneration = requestGeneration; if (gCursorRequestGeneration != requestGeneration) @@ -349,8 +362,7 @@ static bool KiDisplayClipRect(const DISPLAY_MODE* mode, DISPLAY_RECT* rect) { return true; } - -// Snapshots the current primary under the manager spinlock. The caller must +// Snapshots the current primary under the manager spinlock. The caller must // hold the provider mutex before invoking callbacks through the returned ops; // KeDisplaySetPrimary takes the same mutex, so the provider object cannot be // switched while the callback sequence is in progress. @@ -966,12 +978,109 @@ static KSTATUS KiDisplayDrawConsoleCellLocked(int col, int row, char c, uint16_t KiDisplayConsoleBg(attr)); } -// Implements the display Serial Write Char operation. -static void KiDisplaySerialWriteChar(char c) { - while ((__inbyte(0x3FD) & 0x20) == 0) { } - if (c == '\n') - __outbyte(0x3F8, '\r'); - __outbyte(0x3F8, (uint8_t)c); +// Appends one glyph to the runtime display output ring while its lock is held. +static void KiDisplayOutputAppendLocked(char character, uint32_t foreground, uint32_t background) { + if (!gDisplayOutputBuffer) + return; + + if (gDisplayOutputUsed == DISPLAY_OUTPUT_BUFFER_CAPACITY) { + gDisplayOutputRead = (gDisplayOutputRead + 1u) % DISPLAY_OUTPUT_BUFFER_CAPACITY; + gDisplayOutputUsed--; + } + + DISPLAY_OUTPUT_GLYPH* glyph = &gDisplayOutputBuffer[gDisplayOutputWrite]; + glyph->Character = character; + glyph->Foreground = foreground; + glyph->Background = background; + gDisplayOutputWrite = (gDisplayOutputWrite + 1u) % DISPLAY_OUTPUT_BUFFER_CAPACITY; + gDisplayOutputUsed++; +} + +// Drains buffered output through the active display provider. +static void KiDisplayOutputWorker(void* context) { + (void)context; + DISPLAY_OUTPUT_GLYPH glyphs[DISPLAY_OUTPUT_DRAIN_COUNT]; + + for (;;) { + size_t count = 0; + uintptr_t flags = KeAcquireSpinlock(&gDisplayOutputLock); + while (count < DISPLAY_OUTPUT_DRAIN_COUNT && gDisplayOutputUsed != 0) { + glyphs[count++] = gDisplayOutputBuffer[gDisplayOutputRead]; + gDisplayOutputRead = (gDisplayOutputRead + 1u) % DISPLAY_OUTPUT_BUFFER_CAPACITY; + gDisplayOutputUsed--; + } + KeReleaseSpinlock(&gDisplayOutputLock, flags); + + if (count == 0) + break; + for (size_t i = 0; i < count; i++) { + (void)KiDisplayPrintCharInternal(glyphs[i].Character, + glyphs[i].Foreground, + glyphs[i].Background); + } + } +} + +// Queues the runtime display output worker when passive work is available. +static void KiDisplayQueueOutputWorker(void) { + if (_InterlockedCompareExchange(&gDisplayOutputWorkInitialized, 1, 1) && + KeIsWorkQueueStarted()) { + (void)KeQueueWorkItem(&gDisplayOutputWorkItem); + } +} + +// Receives one HAL output record for the runtime display endpoint. +static void KiDisplayWriteOutputRecord(void* context, const HAL_OUTPUT_RECORD* record) { + (void)context; + if (!record || !record->Buffer || record->Length == 0) + return; + if (KiDisplayOutputSuspended()) + return; + + if (KeIsWorkQueueStarted() && gDisplayOutputBuffer) { + uintptr_t flags = KeAcquireSpinlock(&gDisplayOutputLock); + for (size_t i = 0; i < record->Length; i++) { + KiDisplayOutputAppendLocked(record->Buffer[i], + record->Foreground, + record->Background); + } + KeReleaseSpinlock(&gDisplayOutputLock, flags); + KiDisplayQueueOutputWorker(); + return; + } + + for (size_t i = 0; i < record->Length; i++) { + (void)KiDisplayPrintCharInternal(record->Buffer[i], + record->Foreground, + record->Background); + } +} + +// Registers the runtime display as a HAL output endpoint. +static void KiDisplayRegisterOutputEndpoint(void) { + if (!gDisplayHal || !gDisplayHal->Output.RegisterEndpoint) + return; + + HAL_OUTPUT_ENDPOINT endpoint; + endpoint.Name = "runtime-display"; + endpoint.StreamMask = HAL_OUTPUT_STREAM_CONSOLE | HAL_OUTPUT_STREAM_CRASH; + if (KeGetDebugFlags() & KE_DEBUG_CONSOLE) + endpoint.StreamMask |= HAL_OUTPUT_STREAM_DEBUG; + endpoint.Capabilities = 0; + endpoint.Write = KiDisplayWriteOutputRecord; + endpoint.Context = 0; + + uint32_t endpointId = HAL_OUTPUT_ENDPOINT_INVALID; + if (!KSUCCESS(gDisplayHal->Output.RegisterEndpoint(&endpoint, &endpointId))) + return; + + (void)endpointId; + if (gDisplayHal->Output.SetEndpointStreams && + gDisplayHal->Output.BootFramebufferEndpointId != HAL_OUTPUT_ENDPOINT_INVALID) { + (void)gDisplayHal->Output.SetEndpointStreams( + gDisplayHal->Output.BootFramebufferEndpointId, + HAL_OUTPUT_STREAM_PANIC); + } } // Implements the display Initialize operation. @@ -1000,6 +1109,16 @@ void KeDisplayInitialize(const HAL_API* hal) { (void)KeMutexCreate(&gDisplayProviderMutex); KeInitializeWorkItem(&gDisplayCursorWorkItem, KiDisplayCursorWorker, 0); _InterlockedExchange(&gDisplayCursorWorkInitialized, 1); + KeInitializeWorkItem(&gDisplayOutputWorkItem, KiDisplayOutputWorker, 0); + _InterlockedExchange(&gDisplayOutputWorkInitialized, 1); + gDisplayOutputRead = 0; + gDisplayOutputWrite = 0; + gDisplayOutputUsed = 0; + if (!gDisplayOutputBuffer) { + gDisplayOutputBuffer = (DISPLAY_OUTPUT_GLYPH*)MmAllocatePoolTag( + DISPLAY_OUTPUT_BUFFER_CAPACITY * sizeof(DISPLAY_OUTPUT_GLYPH), + MM_POOL_TAG('d', 'o', 'u', 't')); + } gConsoleX = 0; gConsoleY = 0; KiDisplayConsoleResetCellsLocked(); @@ -1047,15 +1166,16 @@ void KeDisplayInitialize(const HAL_API* hal) { mode.Width, mode.Height, mode.Stride); } - // KeDisplayInitialize runs while the debug console still points at the - // HAL early console, so the registration messages above are written - // directly through HAL_SCREEN. Resync the bootfb backbuffer once before - // kmain switches DbgPrint to KeDisplayDebugPrintChar. + // The registration messages above were routed through the HAL boot + // framebuffer endpoint. Resync the display-manager backbuffer before + // the runtime display endpoint becomes the normal screen subscriber. if (gBootFb.Backbuffer) { size_t pixels2 = (size_t)gBootFb.Stride * (size_t)gBootFb.Height; memcpy(gBootFb.Backbuffer, gBootFb.Framebuffer, pixels2 * sizeof(uint32_t)); } } + + KiDisplayRegisterOutputEndpoint(); } DISPLAY_API KSTATUS KeDisplayRegisterDevice(DISPLAY_DEVICE* device) { @@ -1120,7 +1240,6 @@ static KSTATUS KiDisplaySeedNewPrimary(DISPLAY_DEVICE* oldDevice, DISPLAY_DEVICE return s; } - DISPLAY_API void KeDisplaySuspendConsole(bool suspend) { _InterlockedExchange((volatile long*)&gDisplayConsoleSuspended, suspend ? 1 : 0); } @@ -1171,7 +1290,7 @@ DISPLAY_API KSTATUS KeDisplaySetPrimary(DISPLAY_DEVICE* device) { (void)oldDevice->Ops->ShowCursor(oldDevice, false); // Clean architecture policy: handoff does not read back/copy the old boot - // framebuffer. The new primary is cleared to the kernel console blue and + // framebuffer. The new primary is cleared to the kernel console blue and // user/session code redraws state above the provider boundary. if (device->Ops->GetMode && device->Ops->FillRect) { DISPLAY_MODE mode; @@ -1523,10 +1642,7 @@ DISPLAY_API KSTATUS KeDisplayPutChar(char c, int x, int y, uint32_t fg, uint32_t return s; } -static KSTATUS KiDisplayPrintCharInternal(char c, uint32_t fg, uint32_t bg, bool mirrorSerial) { - if (mirrorSerial) - KiDisplaySerialWriteChar(c); - +static KSTATUS KiDisplayPrintCharInternal(char c, uint32_t fg, uint32_t bg) { if (KiDisplayOutputSuspended()) { if (!KeCanWait()) return KiDisplayFallbackPrintChar(c, fg, bg); @@ -1534,11 +1650,8 @@ static KSTATUS KiDisplayPrintCharInternal(char c, uint32_t fg, uint32_t bg, bool } // Once the scheduler/work-queue era begins, display provider callbacks are - // PASSIVE_LEVEL-only. DbgPrint queues screen output through the work queue; - // panic and explicit console paths may still use the serial mirroring entry. - // A panic has interrupts disabled and cannot take the provider mutex, so it - // falls back to the boot framebuffer writer instead of disappearing from - // the screen while still printing to COM1. + // PASSIVE_LEVEL-only. The HAL output endpoint buffers normal text so this + // fallback is reserved for explicit display calls made at elevated IRQL. if (KiDisplayProviderCallbacksRequirePassive() && !KeCanWait()) return KiDisplayFallbackPrintChar(c, fg, bg); @@ -1642,12 +1755,7 @@ static KSTATUS KiDisplayPrintCharInternal(char c, uint32_t fg, uint32_t bg, bool } DISPLAY_API KSTATUS KeDisplayPrintChar(char c, uint32_t fg, uint32_t bg) { - return KiDisplayPrintCharInternal(c, fg, bg, true); -} - -// Implements the display Debug Print Char operation. -void KeDisplayDebugPrintChar(char c, uint32_t fg, uint32_t bg) { - (void)KiDisplayPrintCharInternal(c, fg, bg, false); + return KiDisplayPrintCharInternal(c, fg, bg); } DISPLAY_API KSTATUS KeDisplayGetConsoleInfo(KE_CONSOLE_INFO* outInfo) { diff --git a/code/kernel/ke_gdt_reload.asm b/code/kernel/ke_gdt_reload.asm index 0b68c2a..3c704c3 100644 --- a/code/kernel/ke_gdt_reload.asm +++ b/code/kernel/ke_gdt_reload.asm @@ -5,8 +5,8 @@ KeFlushGdt PROC ; RCX = pGdtr (pointer to limit/base struct) ; RDX = codeSel - ; R8 = dataSel - ; R9 = tssSel + ; R8 = dataSel + ; R9 = tssSel ; 1. Load the GDT lgdt fword ptr [rcx] diff --git a/code/kernel/ke_idt.c b/code/kernel/ke_idt.c index fbb58ac..4f57451 100644 --- a/code/kernel/ke_idt.c +++ b/code/kernel/ke_idt.c @@ -5,7 +5,7 @@ // 0x08 ring-0 code (64-bit) // 0x10 ring-0 data // 0x18 ring-3 code (64-bit, DPL=3) -// 0x20 ring-3 data (DPL=3) - note: 0x1B/0x23 = 0x18|3 / 0x20|3 +// 0x20 ring-3 data (DPL=3) - note: 0x1B/0x23 = 0x18|3 / 0x20|3 // 0x28 TSS low // 0x30 TSS high. #include diff --git a/code/kernel/ke_input.c b/code/kernel/ke_input.c index 9201faf..ba60eb9 100644 --- a/code/kernel/ke_input.c +++ b/code/kernel/ke_input.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #define KEY_QUEUE_SIZE 256u @@ -42,6 +43,7 @@ static volatile int gKeyHead; static volatile int gKeyTail; static volatile INPUT_KEY gRepeatKey; static volatile uint64_t gRepeatNextTick; +static KDPC gRepeatDpc; static INPUT_MOUSE_EVENT gMouseQueue[MOUSE_QUEUE_SIZE]; static volatile int gMouseHead; @@ -54,9 +56,9 @@ static volatile uint8_t gMouseButtons; static volatile uint32_t gNextInputDeviceId = 1; static EVENT* gKeyboardEvent; -static EVENT* gInputStateEvent; static char KiInputKeyToChar(INPUT_KEY key); +static void KiInputServiceRepeat(void); static const KEY_CHAR_PAIR gCharMap[] = { { INPUT_KEY_1, '1', '!' }, { INPUT_KEY_2, '2', '@' }, @@ -82,7 +84,14 @@ static const KEY_CHAR_PAIR gCharMap[] = { { INPUT_KEY_KP_0, '0', '0' }, { INPUT_KEY_KP_PERIOD, '.', '.' }, }; -// Queues a keyboard event and wakes one blocked reader when the event was accepted. +// Updates the waitable keyboard event to match queued keyboard input. +static void KiInputRefreshKeyboardEvent(void) { + if (!gKeyboardEvent) return; + if (gKeyHead != gKeyTail) KeSignalEvent(gKeyboardEvent); + else KeClearEvent(gKeyboardEvent); +} + +// Queues a keyboard event and wakes waiters when the event was accepted. static void KiInputQueueKey(INPUT_KEY key, char ch) { if (key == INPUT_KEY_NONE) return; int next = (gKeyTail + 1) & (int)KEY_QUEUE_MASK; @@ -91,9 +100,7 @@ static void KiInputQueueKey(INPUT_KEY key, char ch) { gKeyQueue[gKeyTail].ch = ch; gKeyQueue[gKeyTail].modifiers = gModifiers; gKeyTail = next; - - if (gKeyboardEvent) - KeSignalEvent(gKeyboardEvent); + KiInputRefreshKeyboardEvent(); } // Implements the input Queue Has Space operation. @@ -167,6 +174,16 @@ static void KiInputServiceRepeat(void) { gRepeatNextTick = now + KEY_REPEAT_PERIOD_US; } +// Implements the input Repeat Dpc Routine operation. +static void KiInputRepeatDpcRoutine(KDPC* dpc, void* context, void* systemArgument1, void* systemArgument2) { + (void)dpc; + (void)context; + (void)systemArgument1; + (void)systemArgument2; + + KiInputServiceRepeat(); +} + // Implements the input Set Modifier operation. static void KiInputSetModifier(INPUT_KEY key, bool pressed) { uint32_t bit = 0; @@ -219,17 +236,14 @@ void KeInputInitialize(const HAL_API* hal) { gRepeatNextTick = 0; gMouseHead = gMouseTail = 0; gMouseButtons = 0; + KeInitializeDpc(&gRepeatDpc, KiInputRepeatDpcRoutine, (void*)0); if (!gKeyboardEvent) { - KSTATUS s = KeEventCreate(EventAutoReset, 0, &gKeyboardEvent); + KSTATUS s = KeEventCreate(EventManualReset, 0, &gKeyboardEvent); if (!KSUCCESS(s)) DbgPrint("Input: keyboard wait event create failed st=0x%x\n", s); } - if (!gInputStateEvent) { - KSTATUS s = KeEventCreate(EventAutoReset, 0, &gInputStateEvent); - if (!KSUCCESS(s)) - DbgPrint("Input: state wait event create failed st=0x%x\n", s); - } + KiInputRefreshKeyboardEvent(); int w = 1920, h = 1080; DISPLAY_MODE mode; @@ -272,7 +286,6 @@ __declspec(dllexport) void KeInputSubmitKey(const INPUT_KEY_EVENT* event) { gKeyDown[key] = 1u; KiInputSetModifier(key, true); - if (gInputStateEvent) KeSignalEvent(gInputStateEvent); KiInputQueueKey(key, KiInputKeyToChar(key)); KiInputStartRepeat(key, KiInputNowTicks()); } else { @@ -281,7 +294,6 @@ __declspec(dllexport) void KeInputSubmitKey(const INPUT_KEY_EVENT* event) { gKeyDown[key] = 0u; KiInputSetModifier(key, false); - if (gInputStateEvent) KeSignalEvent(gInputStateEvent); KiInputStopRepeat(key); } } @@ -363,16 +375,17 @@ INPUT_KEY KeInputKbPoll(void) { return (gKeyHead == gKeyTail) ? INPUT_KEY_NONE : gKeyQueue[gKeyHead].key; } - -// Waits until at least one keyboard event is available. -KSTATUS KeInputWaitKey(uint8_t alertable) { +// Waits until at least one keyboard event is available or the timeout expires. +KSTATUS KeInputWaitKey(uint8_t alertable, uint64_t timeoutMs) { if (KeInputKbPoll() != INPUT_KEY_NONE) return STATUS_SUCCESS; if (!gKeyboardEvent) return STATUS_FAILURE; for (;;) { - KSTATUS s = KeWaitForEvent(gKeyboardEvent, alertable); + EVENT* event = gKeyboardEvent; + uint32_t selected = 0; + KSTATUS s = KeWaitForMultipleEvents(&event, 1, alertable, timeoutMs, &selected); if (!KSUCCESS(s)) return s; if (KeInputKbPoll() != INPUT_KEY_NONE) @@ -380,6 +393,17 @@ KSTATUS KeInputWaitKey(uint8_t alertable) { } } +// Services typematic repeat from the scheduler timer bottom half. +void KeInputTimerTick(void) { + INPUT_KEY key = gRepeatKey; + if (!KiInputKeyIsRepeatable(key) || key >= MAX_INPUT_KEYS || !gKeyDown[key]) + return; + + uint64_t now = KiInputNowTicks(); + if (now != 0 && now >= gRepeatNextTick) + KeInsertQueueDpc(&gRepeatDpc, (void*)0, (void*)0); +} + // Implements the input Is Key Down operation. bool KeInputIsKeyDown(INPUT_KEY key) { if (key == INPUT_KEY_NONE || key >= MAX_INPUT_KEYS) return false; @@ -395,7 +419,6 @@ bool KeInputPeekEvent(INPUT_KEY_RECORD* record) { INPUT_KEY_QUEUE_ENTRY entry = gKeyQueue[gKeyHead]; record->Key = entry.key; record->Char = entry.ch; - record->Reserved = 0; record->Modifiers = entry.modifiers; return true; } @@ -404,6 +427,7 @@ bool KeInputPeekEvent(INPUT_KEY_RECORD* record) { bool KeInputReadEvent(INPUT_KEY_RECORD* record) { if (!KeInputPeekEvent(record)) return false; gKeyHead = (gKeyHead + 1) & (int)KEY_QUEUE_MASK; + KiInputRefreshKeyboardEvent(); return true; } @@ -424,21 +448,19 @@ uint32_t KeInputGetModifiers(void) { return gModifiers; } -// Implements the input Mouse Poll operation. -bool KeInputMousePoll(void) { - return gMouseHead != gMouseTail; +// Implements the input Mouse Peek operation. +bool KeInputMousePeek(INPUT_MOUSE_EVENT* event) { + if (!event) return false; + if (gMouseHead == gMouseTail) return false; + + *event = gMouseQueue[gMouseHead]; + return true; } // Implements the input Mouse Read operation. -bool KeInputMouseRead(int* dx, int* dy, uint8_t* buttons) { - if (!dx || !dy || !buttons) return false; - if (gMouseHead == gMouseTail) return false; - - INPUT_MOUSE_EVENT ev = gMouseQueue[gMouseHead]; +bool KeInputMouseRead(INPUT_MOUSE_EVENT* event) { + if (!KeInputMousePeek(event)) return false; gMouseHead = (gMouseHead + 1) & (int)MOUSE_QUEUE_MASK; - *dx = ev.dx; - *dy = ev.dy; - *buttons = ev.buttons; return true; } @@ -485,18 +507,18 @@ static void KiInputQueueDestroy(OBJECT* object) { KSTATUS KeInputOpenQueue(INPUT_QUEUE** out) { if (!out) return STATUS_INVALID_PARAMETER; *out = (INPUT_QUEUE*)0; - if (!gInputStateEvent) return STATUS_FAILURE; + if (!gKeyboardEvent) return STATUS_FAILURE; INPUT_QUEUE* queue = (INPUT_QUEUE*)ObAllocateObject(ObjectTypeInputQueue, sizeof(INPUT_QUEUE)); if (!queue) return STATUS_NO_MEMORY; queue->Header.Destroy = KiInputQueueDestroy; - ObReferenceObject(&gInputStateEvent->Header); - queue->WaitEvent = gInputStateEvent; + ObReferenceObject(&gKeyboardEvent->Header); + queue->WaitEvent = gKeyboardEvent; *out = queue; return STATUS_SUCCESS; } -// Returns the event that wakes when keyboard state changes. +// Returns the event that wakes when keyboard input can be read. EVENT* KeInputQueueWaitEvent(INPUT_QUEUE* queue) { return queue ? queue->WaitEvent : (EVENT*)0; } diff --git a/code/kernel/ke_process.c b/code/kernel/ke_process.c index 4bfa1cc..a8ae98f 100644 --- a/code/kernel/ke_process.c +++ b/code/kernel/ke_process.c @@ -66,6 +66,7 @@ KSTATUS KeCreateSystemProcess(PROCESS** out) { return s; } + proc->Flags |= KE_PROCESS_FLAG_SYSTEM | KE_PROCESS_FLAG_TERMINATION_PROTECTED; gSystemProcess = proc; *out = proc; return STATUS_SUCCESS; @@ -100,8 +101,6 @@ size_t KeCopyProcessInfo(struct SYS_PROCESS_INFO* infoOut, size_t maxCount) { KiProcessInit(); PROCESS* current = KeGetCurrentProcess(); - PROCESS* system = KeGetSystemProcess(); - uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); size_t count = 0; LIST_ENTRY* head = &gProcessList; @@ -120,7 +119,7 @@ size_t KeCopyProcessInfo(struct SYS_PROCESS_INFO* infoOut, size_t maxCount) { uintptr_t handleFlags = KeAcquireSpinlock(&p->HandleLock); uint32_t threadCount = p->ThreadCount; uint32_t handleCount = KiCountProcessHandlesLocked(p); - int exitCode = p->ExitCode; + int32_t exitCode = p->ExitCode; long terminating = p->Terminating; long exited = p->Exited; KeReleaseSpinlock(&p->HandleLock, handleFlags); @@ -139,7 +138,10 @@ size_t KeCopyProcessInfo(struct SYS_PROCESS_INFO* infoOut, size_t maxCount) { info->State = SYS_PROCESS_STATE_RUNNING; } - if (p == system) info->Flags |= SYS_PROCESS_FLAG_SYSTEM; + if (p->Flags & KE_PROCESS_FLAG_SYSTEM) info->Flags |= SYS_PROCESS_FLAG_SYSTEM; + if (p->Flags & KE_PROCESS_FLAG_IDLE) info->Flags |= SYS_PROCESS_FLAG_IDLE; + if (p->Flags & KE_PROCESS_FLAG_TERMINATION_PROTECTED) + info->Flags |= SYS_PROCESS_FLAG_TERMINATION_PROTECTED; if (p == current) info->Flags |= SYS_PROCESS_FLAG_CURRENT; MmVadQueryProcessMemory(p, &info->VadCount, @@ -190,6 +192,7 @@ static uint32_t KiWaitObjectTypeToSys(OBJECT_TYPE type) { case ObjectTypeUsbPipe: return SYS_THREAD_WAIT_USB_PIPE; case ObjectTypeFileMapping: return SYS_THREAD_WAIT_FILE_MAPPING; case ObjectTypeSurface: return SYS_THREAD_WAIT_SURFACE; + case ObjectTypeTimer: return SYS_THREAD_WAIT_TIMER; default: return SYS_THREAD_WAIT_UNKNOWN; } } @@ -276,6 +279,7 @@ static void KiProcessRundownResources(PROCESS* proc) { if (_InterlockedExchange(&proc->ResourcesReleased, 1) != 0) return; + IoReleaseFileLocksForProcess(proc); KiProcessReleaseAllHandles(proc); // Release every user VA region owned by this process. This includes the @@ -334,6 +338,7 @@ KSTATUS KeCreateProcess(const char* name, PROCESS **out) { proc->Header.Destroy = KiProcessDestroy; proc->ProcessId = _InterlockedExchangeAdd((volatile long *)&gNextProcessId, 1); + proc->Flags = 0; strncpy(proc->Name, name, PROCESS_NAME_MAX - 1); proc->Name[PROCESS_NAME_MAX - 1] = '\0'; @@ -381,7 +386,6 @@ KSTATUS KeCreateProcess(const char* name, PROCESS **out) { return STATUS_SUCCESS; } - // KeSetupUserProcessEnvironment. static KSTATUS KiCopyEnvironmentStringView(PROCESS* proc, SYS_STRING_VIEW src, @@ -558,8 +562,10 @@ void KeCompleteProcessExitIfLastThread(PROCESS* process) { } // Implements the exit Process operation. -void KeExitProcess(PROCESS* process, int exitCode) { - if (!process) return; +KSTATUS KeExitProcess(PROCESS* process, int32_t exitCode) { + if (!process) return STATUS_INVALID_PARAMETER; + if (KeIsProcessTerminationProtected(process)) return STATUS_ACCESS_DENIED; + process->ExitCode = exitCode; _InterlockedExchange(&process->Terminating, 1); @@ -567,11 +573,13 @@ void KeExitProcess(PROCESS* process, int exitCode) { // not drop the process object here: each thread owns one process reference, // and KiThreadDestroy releases that reference after unlinking the thread. KeCompleteProcessExitIfLastThread(process); + return STATUS_SUCCESS; } // Implements the terminate Process operation. -KSTATUS KeTerminateProcess(PROCESS* process, int exitCode) { +KSTATUS KeTerminateProcess(PROCESS* process, int32_t exitCode) { if (!process) return STATUS_INVALID_PARAMETER; + if (KeIsProcessTerminationProtected(process)) return STATUS_ACCESS_DENIED; process->ExitCode = exitCode; _InterlockedExchange(&process->Terminating, 1); @@ -588,7 +596,7 @@ KSTATUS KeTerminateProcess(PROCESS* process, int exitCode) { continue; // A terminating process must not be held open by worker threads that - // are blocked in non-alertable waits. User-mode DLLs such as Bink can + // are blocked in non-alertable waits. User-mode DLLs such as Bink can // leave IO/submit workers parked on semaphores; after the foreground // thread crashes, those waiters would otherwise keep ThreadCount > 0 // and prevent the process ExitEvent from waking the shell. @@ -616,8 +624,13 @@ int KeIsProcessTerminating(PROCESS* process) { return process && process->Terminating != 0; } -// KeGetProcessExitCode. -KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut) { +// Returns whether process termination is forbidden. +int KeIsProcessTerminationProtected(PROCESS* process) { + return process && (process->Flags & KE_PROCESS_FLAG_TERMINATION_PROTECTED) != 0; +} + +// Copies the recorded process exit code. +KSTATUS KeGetProcessExitCode(PROCESS* process, int32_t* exitCodeOut) { if (!process || !exitCodeOut) return STATUS_INVALID_PARAMETER; *exitCodeOut = process->ExitCode; return STATUS_SUCCESS; @@ -699,6 +712,55 @@ uint32_t KeGetHandleRights(PROCESS* process, uint32_t handle) { return rights; } +// Duplicates a handle from one process table into another. +KSTATUS KeDuplicateHandleToProcess(PROCESS* sourceProcess, uint32_t sourceHandle, PROCESS* targetProcess, uint32_t desiredRights, uint32_t duplicateFlags, uint32_t* targetHandleOut) { + if (!sourceProcess || !targetProcess || !targetHandleOut) return STATUS_INVALID_PARAMETER; + if (sourceHandle == 0 || sourceHandle >= HANDLE_TABLE_SIZE) return STATUS_INVALID_HANDLE; + + const uint32_t validDuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS; + const uint32_t publicHandleRights = SYS_HANDLE_RIGHT_ALL; + + if ((duplicateFlags & ~validDuplicateFlags) != 0) return STATUS_INVALID_PARAMETER; + if ((duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) != 0 && desiredRights != 0) + return STATUS_INVALID_PARAMETER; + if ((duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) == 0) { + if (desiredRights == 0) return STATUS_INVALID_PARAMETER; + if ((desiredRights & ~publicHandleRights) != 0) return STATUS_INVALID_PARAMETER; + } + + OBJECT* obj = NULL; + uint32_t sourceRights = 0; + + uintptr_t savedFlags = KeAcquireSpinlock(&sourceProcess->HandleLock); + HANDLE_ENTRY* entry = &sourceProcess->Handles[sourceHandle]; + if (!entry->Object) { + KeReleaseSpinlock(&sourceProcess->HandleLock, savedFlags); + return STATUS_INVALID_HANDLE; + } + if ((entry->Rights & HANDLE_RIGHT_DUPLICATE) == 0) { + KeReleaseSpinlock(&sourceProcess->HandleLock, savedFlags); + return STATUS_ACCESS_DENIED; + } + + obj = entry->Object; + sourceRights = entry->Rights; + ObReferenceObject(obj); + KeReleaseSpinlock(&sourceProcess->HandleLock, savedFlags); + + uint32_t targetRights = sourceRights; + if ((duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) == 0) { + if ((desiredRights & ~sourceRights) != 0) { + ObDereferenceObject(obj); + return STATUS_ACCESS_DENIED; + } + targetRights = desiredRights; + } + + KSTATUS s = KeAllocateHandleWithRights(targetProcess, obj, targetRights, targetHandleOut); + ObDereferenceObject(obj); + return s; +} + // Returns process By Id. PROCESS* KeGetProcessById(uint32_t pid) { KiProcessInit(); diff --git a/code/kernel/ke_queue.c b/code/kernel/ke_queue.c index 3b3922f..bab2eb9 100644 --- a/code/kernel/ke_queue.c +++ b/code/kernel/ke_queue.c @@ -1,97 +1,189 @@ -// Kernel message queue primitive. This file owns bounded queue storage and blocking send/receive behavior. +// Kernel message queue primitive. This file owns bounded queue storage and queue timeout behavior. +#include #include #include -#include #include #include #include -// Implements the queue Destroy operation. -static void KiQueueDestroy(OBJECT* obj) { (void)obj; } +#define QUEUE_TIMEOUT_INFINITE UINT64_MAX -// Implements the queue Create operation. +// Destroys a queue and releases its private state events. +static void KiQueueDestroy(OBJECT* obj) { + QUEUE* queue = (QUEUE*)obj; + if (!queue) + return; + if (queue->NotEmptyEvent) { + ObDereferenceObject(&queue->NotEmptyEvent->Header); + queue->NotEmptyEvent = (EVENT*)0; + } + if (queue->NotFullEvent) { + ObDereferenceObject(&queue->NotFullEvent->Header); + queue->NotFullEvent = (EVENT*)0; + } +} + +// Updates waitable state after one message has been inserted. +static void KiQueueNoteInsertedLocked(QUEUE* queue, uint32_t previousCount) { + if (!queue || !queue->NotEmptyEvent || !queue->NotFullEvent) + return; + + if (previousCount == 0) + KeSignalEvent(queue->NotEmptyEvent); + if (queue->Count == QUEUE_DEPTH) + KeClearEvent(queue->NotFullEvent); +} + +// Updates waitable state after one message has been removed. +static void KiQueueNoteRemovedLocked(QUEUE* queue, uint32_t previousCount) { + if (!queue || !queue->NotEmptyEvent || !queue->NotFullEvent) + return; + + if (previousCount == QUEUE_DEPTH) + KeSignalEvent(queue->NotFullEvent); + if (queue->Count == 0) + KeClearEvent(queue->NotEmptyEvent); +} + +// Returns the scheduler tick value used by queue finite-timeout accounting. +static uint64_t KiQueueReadSchedulerTick(void) { + KE_SCHED_STATS stats; + KeGetSchedulerStats(&stats); + return stats.Ticks; +} + +// Converts a millisecond timeout to at least one scheduler tick. +static uint64_t KiQueueTimeoutTicks(uint64_t timeoutMilliseconds) { + uint64_t ticks = timeoutMilliseconds / SCHED_TICK_MS; + if ((timeoutMilliseconds % SCHED_TICK_MS) != 0) + ticks++; + return ticks ? ticks : 1; +} + +// Computes an absolute scheduler-tick deadline for a finite timeout. +static uint64_t KiQueueTimeoutDeadline(uint64_t timeoutMilliseconds) { + uint64_t now = KiQueueReadSchedulerTick(); + uint64_t ticks = KiQueueTimeoutTicks(timeoutMilliseconds); + uint64_t deadline = now + ticks; + return deadline < now ? UINT64_MAX : deadline; +} + +// Computes the remaining wait time for a finite timeout deadline. +static uint64_t KiQueueRemainingMilliseconds(uint64_t deadlineTick) { + uint64_t now = KiQueueReadSchedulerTick(); + if (deadlineTick <= now) + return 0; + + uint64_t ticks = deadlineTick - now; + if (ticks > UINT64_MAX / SCHED_TICK_MS) + return UINT64_MAX; + return ticks * SCHED_TICK_MS; +} + +// Waits for a queue state event using either an infinite or finite timeout. +static KSTATUS KiQueueWaitForEvent(QUEUE* queue, EVENT* event, uint64_t timeoutMilliseconds, uint64_t deadlineTick) { + if (!queue || !event) + return STATUS_INVALID_PARAMETER; + + uint64_t waitMilliseconds = timeoutMilliseconds; + if (timeoutMilliseconds != QUEUE_TIMEOUT_INFINITE) { + waitMilliseconds = KiQueueRemainingMilliseconds(deadlineTick); + if (waitMilliseconds == 0) + return STATUS_TIMEOUT; + } + + EVENT* events[1] = { event }; + return KeWaitForMultipleEvents(events, 1, 0, waitMilliseconds, (uint32_t*)0); +} + +// Creates a fixed-depth queue with waitable occupancy and capacity events. KSTATUS KeQueueCreate(QUEUE **out) { if (!out) return STATUS_INVALID_PARAMETER; + *out = (QUEUE*)0; + QUEUE* q = (QUEUE *)ObAllocateObject(ObjectTypeQueue, sizeof(QUEUE)); if (!q) return STATUS_NO_MEMORY; q->Header.Destroy = KiQueueDestroy; q->Lock = (SPINLOCK)SPINLOCK_INIT; q->Head = q->Tail = q->Count = 0; - LIST_INIT(&q->SendWaiters); - LIST_INIT(&q->RecvWaiters); + + KSTATUS s = KeEventCreate(EventManualReset, 0, &q->NotEmptyEvent); + if (!KSUCCESS(s)) { + ObDereferenceObject(&q->Header); + return s; + } + + s = KeEventCreate(EventManualReset, 1, &q->NotFullEvent); + if (!KSUCCESS(s)) { + ObDereferenceObject(&q->Header); + return s; + } + *out = q; return STATUS_SUCCESS; } -// Implements the send Message operation. -KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, bool blocking) { +// Sends one fixed-size queue message, waiting according to the timeout contract. +KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, uint64_t timeoutMilliseconds) { if (!queue || !msg) return STATUS_INVALID_PARAMETER; - if (blocking && !KeCanWait()) return STATUS_INVALID_IRQL; - THREAD* self = KeGetCurrentThread(); + if (timeoutMilliseconds != 0 && !KeCanWait()) return STATUS_INVALID_IRQL; - while (true) { + uint64_t deadlineTick = 0; + if (timeoutMilliseconds != 0 && timeoutMilliseconds != QUEUE_TIMEOUT_INFINITE) + deadlineTick = KiQueueTimeoutDeadline(timeoutMilliseconds); + + for (;;) { uintptr_t flags = KeAcquireSpinlock(&queue->Lock); if (queue->Count < QUEUE_DEPTH) { + uint32_t previousCount = queue->Count; queue->Ring[queue->Tail] = *msg; queue->Tail = (queue->Tail + 1) % QUEUE_DEPTH; queue->Count++; - - // Wake a receiver if one is waiting. - if (!LIST_IS_EMPTY(&queue->RecvWaiters)) { - LIST_ENTRY* node = ListPopHead(&queue->RecvWaiters); - THREAD* t = CONTAINING_RECORD(node, THREAD, SchedulerLink); - KeReleaseSpinlock(&queue->Lock, flags); - KeUnblockThread(t); - } else { - KeReleaseSpinlock(&queue->Lock, flags); - } + KiQueueNoteInsertedLocked(queue, previousCount); + KeReleaseSpinlock(&queue->Lock, flags); return STATUS_SUCCESS; } - if (!blocking) { - KeReleaseSpinlock(&queue->Lock, flags); - return STATUS_QUEUE_FULL; - } - KePrepareBlockThread(self, &queue->Header, 0); - ListInsertTail(&queue->SendWaiters, &self->SchedulerLink); - KeSetThreadWaitList(self, &queue->SendWaiters); + KeClearEvent(queue->NotFullEvent); KeReleaseSpinlock(&queue->Lock, flags); - KeCommitBlockThread(self, 0); + + if (timeoutMilliseconds == 0) + return STATUS_WOULD_BLOCK; + + KSTATUS s = KiQueueWaitForEvent(queue, queue->NotFullEvent, timeoutMilliseconds, deadlineTick); + if (s == STATUS_TIMEOUT || KFAILED(s)) + return s; } } -// Implements the receive Message operation. -KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, bool blocking) { +// Receives one fixed-size queue message, waiting according to the timeout contract. +KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, uint64_t timeoutMilliseconds) { if (!queue || !msg) return STATUS_INVALID_PARAMETER; - if (blocking && !KeCanWait()) return STATUS_INVALID_IRQL; - THREAD* self = KeGetCurrentThread(); + if (timeoutMilliseconds != 0 && !KeCanWait()) return STATUS_INVALID_IRQL; - while (true) { + uint64_t deadlineTick = 0; + if (timeoutMilliseconds != 0 && timeoutMilliseconds != QUEUE_TIMEOUT_INFINITE) + deadlineTick = KiQueueTimeoutDeadline(timeoutMilliseconds); + + for (;;) { uintptr_t flags = KeAcquireSpinlock(&queue->Lock); if (queue->Count > 0) { + uint32_t previousCount = queue->Count; *msg = queue->Ring[queue->Head]; queue->Head = (queue->Head + 1) % QUEUE_DEPTH; queue->Count--; - - // Wake a sender if one is waiting. - if (!LIST_IS_EMPTY(&queue->SendWaiters)) { - LIST_ENTRY* node = ListPopHead(&queue->SendWaiters); - THREAD* t = CONTAINING_RECORD(node, THREAD, SchedulerLink); - KeReleaseSpinlock(&queue->Lock, flags); - KeUnblockThread(t); - } else { - KeReleaseSpinlock(&queue->Lock, flags); - } + KiQueueNoteRemovedLocked(queue, previousCount); + KeReleaseSpinlock(&queue->Lock, flags); return STATUS_SUCCESS; } - if (!blocking) { - KeReleaseSpinlock(&queue->Lock, flags); - return STATUS_QUEUE_EMPTY; - } - KePrepareBlockThread(self, &queue->Header, 0); - ListInsertTail(&queue->RecvWaiters, &self->SchedulerLink); - KeSetThreadWaitList(self, &queue->RecvWaiters); + KeClearEvent(queue->NotEmptyEvent); KeReleaseSpinlock(&queue->Lock, flags); - KeCommitBlockThread(self, 0); + + if (timeoutMilliseconds == 0) + return STATUS_WOULD_BLOCK; + + KSTATUS s = KiQueueWaitForEvent(queue, queue->NotEmptyEvent, timeoutMilliseconds, deadlineTick); + if (s == STATUS_TIMEOUT || KFAILED(s)) + return s; } } diff --git a/code/kernel/ke_sched.c b/code/kernel/ke_sched.c index 67a64b4..3e1be18 100644 --- a/code/kernel/ke_sched.c +++ b/code/kernel/ke_sched.c @@ -3,7 +3,6 @@ #include #include #include -#pragma intrinsic(_enable) #include #include #include @@ -12,6 +11,8 @@ #include #include #include +#include +#include #include #include #include @@ -196,7 +197,6 @@ void KeGetSchedulerStats(KE_SCHED_STATS* out) { KeReleaseSpinlock(&gSchedLock, flags); } - // Implements the arm Post Switch Unlock operation. static void KiArmPostSwitchUnlock(THREAD* thread, uintptr_t cookie) { // The scheduler lock is intentionally held across KeSwitchContext(). The @@ -213,7 +213,6 @@ static uintptr_t KiPostSwitchUnlockCookie(THREAD* thread) { return thread ? thread->SwitchLockCookie : 0x202ULL; } - // Implements the scheduler Bug operation. static void KiSchedulerBug(const char* where, THREAD* thread, const char* why) { KePanic(KE_PANIC_CODE_SCHEDULER, @@ -298,8 +297,7 @@ static void KiIdleLoop(void) { // the idle thread through an unusual switch path must not sleep with // interrupts disabled, otherwise reschedule/TLB shootdown IPIs can // be delayed forever. - _enable(); - __halt(); + HalWaitForInterrupt(); } } @@ -312,6 +310,7 @@ void KiInitializeSchedulerGlobal(void) { KSTATUS s = KeCreateProcess("idle", &gIdleProcess); if (!KSUCCESS(s)) KePanic(KE_PANIC_CODE_INIT, "scheduler: failed to create idle process, status=%u\n", s); + gIdleProcess->Flags |= KE_PROCESS_FLAG_IDLE | KE_PROCESS_FLAG_TERMINATION_PROTECTED; PROCESS* systemProc; s = KeCreateSystemProcess(&systemProc); @@ -463,7 +462,6 @@ void KeUnreadyThread(THREAD* thread) { KeReleaseSpinlock(&gSchedLock, flags); } - // Implements the any Runnable For Cpu Locked operation. static int KiAnyRunnableForCpuLocked(KCPU* cpu) { if (!cpu) cpu = KeGetCurrentCpu(); @@ -507,7 +505,6 @@ static THREAD* KiPickNextForCpu(KCPU* cpu) { return cpu->IdleThread; } - // Implements the request Reschedule operation. void KiRequestReschedule(void) { KCPU* cpu = KeGetCurrentCpu(); @@ -592,7 +589,6 @@ void KiScheduleFromInterrupt(void) { KiSchedule(); } - // Implements the scheduler Mark Thread Waiting operation. void KiSchedulerMarkThreadWaiting(THREAD* thread) { if (!thread) return; @@ -624,7 +620,6 @@ void KiSchedulerNoteVoluntaryBlock(THREAD* thread) { KeReleaseSpinlock(&gSchedLock, flags); } - // Completes a wait while the scheduler lock is held. static void KiWakeThreadLocked(THREAD* thread, KSTATUS status) { if (!thread) return; @@ -666,7 +661,6 @@ static void KiWakeThreadLocked(THREAD* thread, KSTATUS status) { KiPokeRunnableCpusLocked(thread); } - // Wakes a thread after first removing its scheduler-link wait membership. void KeUnblockThreadFromWaitListWithStatus(THREAD* thread, KSTATUS status) { if (!thread) return; @@ -719,6 +713,7 @@ void KeSchedulerTick(void) { gSchedStats.Ticks++; KiWakeExpiredSleepersLocked(gSchedStats.Ticks); + KeInputTimerTick(); int requestReschedule = 0; @@ -783,11 +778,12 @@ void KeSchedulerTick(void) { KeReleaseSpinlock(&gSchedLock, flags); + KeTimerTick(); + if (requestReschedule) KiRequestReschedule(); } - // Arms a timeout for the current thread after a wait primitive has prepared it to block. KSTATUS KeArmCurrentThreadTimeout(uint64_t milliseconds, KSTATUS timeoutStatus) { if (milliseconds == 0) diff --git a/code/kernel/ke_selftest.c b/code/kernel/ke_selftest.c index 49061e3..3004636 100644 --- a/code/kernel/ke_selftest.c +++ b/code/kernel/ke_selftest.c @@ -478,10 +478,6 @@ static void KiRunIrpKernelApcSelfTest(const HAL_API* hal) { DbgPrint("IRP/APC selftest: PASS\n"); } - - - - // Verifies that the PFN database tracks ownership, references, pins, and contiguous runs. static void KiRunPfnDatabaseSelfTest(void) { MM_PFN_STATS before; @@ -654,7 +650,6 @@ static void KiRunMemoryMappingSelfTest(void) { DbgPrint("MM selftest: PTE map/query/protect/unmap PASS\n"); } - // Verifies sparse VAD commitment and NOACCESS protection metadata. static void KiRunVadSparseSelfTest(void) { PROCESS* process = KeGetCurrentProcess(); @@ -756,7 +751,6 @@ static void KiRunVadSparseSelfTest(void) { DbgPrint("VAD selftest: sparse commit/protect/query/demand-zero PASS\n"); } - // Verifies that writable copy-on-write views split before either alias can modify shared state. static void KiRunVadCopyOnWriteSelfTest(void) { PROCESS* process = KeGetCurrentProcess(); @@ -872,7 +866,6 @@ static void KiRunVadCopyOnWriteSelfTest(void) { DbgPrint("VAD COW selftest: shared views and zero-page split PASS\n"); } - // Verifies that a private resident VAD page can move to swap and fault back in. static void KiRunVadSwapSelfTest(void) { if (!MmSwapIsEnabled()) { @@ -1098,6 +1091,7 @@ static void KiRunAsyncFileIoSelfTest(void) { wiosb.Status = STATUS_FAILURE; wiosb.Information = 0; + (void)IoDeleteFile(SYS_SV_LITERAL("asyncio.tmp")); s = IoCreateFile(SYS_SV_LITERAL("asyncio.tmp"), &wf); if (!KSUCCESS(s)) KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: create write temp failed %u\n", s); @@ -1161,7 +1155,6 @@ static void KiRunAsyncFileIoSelfTest(void) { DbgPrint("FILE/ASYNC selftest: PASS\n"); } - // Runs trap Frame Self Test. static void KiRunTrapFrameSelfTest(void) { DbgPrint("TRAP selftest: begin\n"); @@ -1180,8 +1173,6 @@ static void KiRunTrapFrameSelfTest(void) { DbgPrint("TRAP selftest: PASS\n"); } - - // Runs core Hardening Self Tests. void KiRunCoreHardeningSelfTests(void) { DbgPrint("CORE selftest: begin\n"); @@ -1253,7 +1244,6 @@ void KiRunCoreHardeningSelfTests(void) { DbgPrint("CORE selftest: PASS\n"); } - // Runs early Kernel Self Tests. void KiRunEarlyKernelSelfTests(void) { KSTATUS s = KeRunApplicationProcessorSchedulerSelfTests(); diff --git a/code/kernel/ke_smp.c b/code/kernel/ke_smp.c index a49898f..ceaf151 100644 --- a/code/kernel/ke_smp.c +++ b/code/kernel/ke_smp.c @@ -8,8 +8,6 @@ #include #include #include -#pragma intrinsic(_disable) -#pragma intrinsic(_enable) #include #include #include @@ -254,11 +252,10 @@ static KSTATUS KiBuildApTrampoline(void) { } _Noreturn void KiApKernelEntry(KCPU* cpu) { - _disable(); + (void)HalDisableInterrupts(); - if (!cpu) { - while (1) __halt(); - } + if (!cpu) + HalStopProcessor(); // APs start from the SIPI reset state, so do not assume the firmware/BSP // CR4 feature bits are present. The scheduler and context-switch paths @@ -313,27 +310,26 @@ _Noreturn void KiApKernelEntry(KCPU* cpu) { cpu->Parked = 0; // Do not arm the AP's periodic LAPIC timer while it is still in - // the raw park loop. Timer IRQs are scheduler-owned events; + // the raw park loop. Timer IRQs are scheduler-owned events; // the AP may take them only after it has a current idle thread. HalTimerConfigureCurrentCpu(); _InterlockedExchange(&cpu->SchedulerActive, 1); - _enable(); + HalEnableInterrupts(); KeStartInitialThread(idle); } } // The BSP admits parked APs by storing SchedulerCommand and sending a - // wake IPI. Avoid the classic lost wakeup where the command appears + // wake IPI. Avoid the classic lost wakeup where the command appears // between the poll and HLT: check once with interrupts disabled, then // STI;HLT as a single sleep transition. - _disable(); + uintptr_t interruptFlags = HalDisableInterrupts(); if (_InterlockedCompareExchange(&cpu->SchedulerCommand, 0, 0) != 0) { - _enable(); + HalRestoreInterrupts(interruptFlags); continue; } - _enable(); - __halt(); + HalWaitForInterrupt(); } } @@ -367,7 +363,6 @@ static void KiPrepareCpuRecord(uint32_t cpuIndex, const HAL_CPU_INFO* info) { cpu->SchedulerActive = 0; } - // Implements the ceil Log2 U32 operation. static uint32_t KiCeilLog2U32(uint32_t value) { if (value <= 1u) @@ -591,7 +586,7 @@ void KeBuildProcessorTopology(void) { // Creates ap Stack. static KSTATUS KiAllocateApStack(KCPU* cpu) { - // AP kernel stacks do not need physically contiguous memory. Requiring + // AP kernel stacks do not need physically contiguous memory. Requiring // MmPfnAllocateContiguous(8) made SMP startup depend on a DMA-style run being // available, which is fragile on real machines and unrelated to AP startup. size_t stackBytes = (size_t)AP_STACK_PAGES * PAGE_SIZE; @@ -612,7 +607,6 @@ static KSTATUS KiAllocateApStack(KCPU* cpu) { return STATUS_SUCCESS; } - // Implements the ap Stage Name operation. static const char* KiApStageName(uint32_t stage) { switch (stage) { @@ -806,9 +800,9 @@ KSTATUS KeAdmitApplicationProcessorsToScheduler(void) { _ReadWriteBarrier(); _InterlockedExchange(&cpu->SchedulerCommand, 1); - // Parked APs sleep with HLT and have no LAPIC timer armed yet. The + // Parked APs sleep with HLT and have no LAPIC timer armed yet. The // command store alone is not sufficient; send a fixed-vector IPI only - // to wake the parked loop. ReschedulePending is intentionally left + // to wake the parked loop. ReschedulePending is intentionally left // clear, so the interrupt exit path will not try to schedule before // the AP has installed its idle thread. HalApicSendRescheduleIpi(cpu->LapicId); diff --git a/code/kernel/ke_switch.asm b/code/kernel/ke_switch.asm index c611d9c..32a6bf5 100644 --- a/code/kernel/ke_switch.asm +++ b/code/kernel/ke_switch.asm @@ -20,7 +20,7 @@ EXTERN KeSetCurrentTssRsp0 : PROC ; ------------------------------------------------------------------------- ; void KeSwitchContext(THREAD* from, THREAD* to) -; RCX = from, RDX = to (Microsoft x64) +; RCX = from, RDX = to (Microsoft x64) ; ------------------------------------------------------------------------- KeSwitchContext PROC mov rax, [rcx + THREAD_FpuContext] @@ -75,7 +75,6 @@ KeSwitchContext PROC ret KeSwitchContext ENDP - ; ------------------------------------------------------------------------- ; void KeStartInitialThread(THREAD* to) ; RCX = to diff --git a/code/kernel/ke_syscall.asm b/code/kernel/ke_syscall.asm index 6c01689..f119815 100644 --- a/code/kernel/ke_syscall.asm +++ b/code/kernel/ke_syscall.asm @@ -152,7 +152,7 @@ KeSyscallEntry ENDP ; also shows which register held the null target. ; ; KeFaultDispatchFrame signature (MS x64): -; KSTATUS KeFaultDispatchFrame(const KFAULT_FRAME* frame) ; rcx +; KSTATUS KeFaultDispatchFrame(const KFAULT_FRAME* frame) ; rcx ; --------------------------------------------------------------------------- KFAULT_MAGIC EQU 3146544C5541464Bh ; "KFAULTF1" little-ish @@ -420,7 +420,6 @@ pf_unhandled_halt: jmp pf_unhandled_halt KeFaultPF ENDP - ; --------------------------------------------------------- ; Generic CPU exception stubs. ; Each vector gets its own IDT entry and vector number so faults like #UD diff --git a/code/kernel/ke_syscall.c b/code/kernel/ke_syscall.c index 530bcdc..cee3e7b 100644 --- a/code/kernel/ke_syscall.c +++ b/code/kernel/ke_syscall.c @@ -21,7 +21,7 @@ // KeCloseHandle(proc, handle) // -> ObDereferenceObject(obj) -// -> obj->Destroy(obj) [type-specific cleanup, e.g. KiFileDestroy] +// -> obj->Destroy(obj) [type-specific cleanup, e.g. KiFileDestroy] // -> MmFreePool(obj) // There is no separate SyscallCloseFile. Removing that stub was the right @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -101,7 +102,6 @@ typedef struct SYS_USER_ASYNC_APC_CONTEXT { void* UserContext; } SYS_USER_ASYNC_APC_CONTEXT; - typedef struct FILE_MAPPING_OBJECT { OBJECT Header; FILE_OBJECT* File; @@ -216,6 +216,8 @@ static void KiSurfaceStatsQuery(SYS_SURFACE_STATS* out) { uintptr_t flags = KeAcquireSpinlock(&gSurfaceStatsLock); *out = gSurfaceStats; + out->Size = sizeof(*out); + out->Version = SYS_STATS_VERSION; KeReleaseSpinlock(&gSurfaceStatsLock, flags); } @@ -336,7 +338,6 @@ static KSTATUS KiAllocHandleForCurrent(OBJECT* obj, uint32_t* handleOut) { return KiAllocHandleForCurrentWithRights(obj, HANDLE_RIGHT_ALL, handleOut); } - #define SYSCALL_STRING_MAX SYS_PATH_MAX #define SYSCALL_TEXT_MAX_BYTES (64ULL * 1024ULL) #define SYSCALL_IO_MAX_BYTES (16ULL * 1024ULL * 1024ULL) @@ -408,12 +409,24 @@ static KSTATUS KiCopySizeToUser(uint64_t arg, size_t value) { return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value)); } +// Copies u64 To User. +static KSTATUS KiCopyU64ToUser(uint64_t arg, uint64_t value) { + if (!arg) return STATUS_INVALID_PARAMETER; + return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value)); +} + // Copies int To User. static KSTATUS KiCopyIntToUser(uint64_t arg, int value) { if (!arg) return STATUS_INVALID_PARAMETER; return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value)); } +// Copies one signed 32-bit integer to user memory. +static KSTATUS KiCopyInt32ToUser(uint64_t arg, int32_t value) { + if (!arg) return STATUS_INVALID_PARAMETER; + return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value)); +} + // Implements the mul Size operation. static int KiMulSize(size_t a, size_t b, size_t* out) { if (!out) return 0; @@ -431,18 +444,23 @@ static size_t KiAlignUpSize(size_t value, size_t alignment) { // Individual syscall handlers. -// SYS_EXIT_PROCESS (0) a0 = exitCode. +// SYS_EXIT_PROCESS (0) a0 = exitCode. static uint64_t SyscallExitProcess(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; THREAD* t = KeGetCurrentThread(); PROCESS* proc = t ? t->Owner : NULL; - if (proc) KeExitProcess(proc, (int)a0); - if (t) KeExitThread(t, (int)a0); + int32_t exitCode = (int32_t)a0; + if (!proc || !t) return STATUS_FAILURE; + + KSTATUS s = KeExitProcess(proc, exitCode); + if (!KSUCCESS(s)) return s; + + KeExitThread(t, exitCode); while (true) ; // unreachable - KeExitThread does not return } -// SYS_EXIT_THREAD (1) a0 = exitCode. +// SYS_EXIT_THREAD (1) a0 = exitCode. static uint64_t SyscallExitThread(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; @@ -451,7 +469,7 @@ static uint64_t SyscallExitThread(uint64_t a0, uint64_t a1, uint64_t a2, while (true); } -// SYS_CREATE_THREAD a0=*SYS_CREATE_THREAD_REQUEST. +// SYS_CREATE_THREAD a0=*SYS_CREATE_THREAD_REQUEST. // Creates a runnable user thread and optionally returns both its waitable // handle and diagnostic TID. The handle is the synchronization identity. static uint64_t SyscallCreateThread(uint64_t a0, uint64_t a1, uint64_t a2, @@ -552,6 +570,9 @@ static KSTATUS KiHandleToWaitEvent(PROCESS* proc, uint32_t handle, EVENT** event case ObjectTypeInputQueue: event = KeInputQueueWaitEvent(CONTAINING_RECORD(obj, INPUT_QUEUE, Header)); break; + case ObjectTypeTimer: + event = KeTimerWaitEvent(CONTAINING_RECORD(obj, KTIMER, Header)); + break; default: ObDereferenceObject(obj); return STATUS_NOT_SUPPORTED; @@ -697,7 +718,7 @@ static uint64_t SyscallOpenInputQueue(uint64_t a0, uint64_t a1, uint64_t a2, if (!KSUCCESS(s)) return s; uint32_t handle = 0; - s = KiAllocHandleForCurrentWithRights(&queue->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY, &handle); + s = KiAllocHandleForCurrentWithRights(&queue->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY, &handle); if (!KSUCCESS(s)) return s; s = KiCopyHandleToUser(a1, handle); @@ -732,14 +753,89 @@ static uint64_t SyscallYield(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } -// SYS_SLEEP (6) a0 = milliseconds. +// SYS_CREATE_TIMER a0=*handleOut. +static uint64_t SyscallCreateTimer(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; + if (!a0) return STATUS_INVALID_PARAMETER; + + KTIMER* timer = (KTIMER*)0; + KSTATUS s = KeTimerCreate(&timer); + if (!KSUCCESS(s)) return s; + + uint32_t handle = 0; + s = KiAllocHandleForCurrentWithRights(&timer->Header, + HANDLE_RIGHT_WAIT | HANDLE_RIGHT_SIGNAL | HANDLE_RIGHT_QUERY, + &handle); + if (!KSUCCESS(s)) + return s; + + s = KiCopyHandleToUser(a0, handle); + if (!KSUCCESS(s)) { + PROCESS* proc = KeGetCurrentProcess(); + if (proc) KeCloseHandle(proc, handle); + } + return s; +} + +// SYS_SET_TIMER a0=*SYS_SET_TIMER_REQUEST. +static uint64_t SyscallSetTimer(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; + + SYS_SET_TIMER_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (req.Size != sizeof(req) || req.Flags != SYS_TIMER_FLAG_NONE) + return STATUS_INVALID_PARAMETER; + + KTIMER* timer = (KTIMER*)KiHandleToObject(req.TimerHandle, ObjectTypeTimer, HANDLE_RIGHT_SIGNAL); + if (!timer) return STATUS_INVALID_HANDLE; + s = KeTimerSet(timer, req.DueTimeMilliseconds, req.PeriodMilliseconds); + ObDereferenceObject(&timer->Header); + return s; +} + +// SYS_CANCEL_TIMER a0=timerHandle. +static uint64_t SyscallCancelTimer(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; + + KTIMER* timer = (KTIMER*)KiHandleToObject((uint32_t)a0, ObjectTypeTimer, HANDLE_RIGHT_SIGNAL); + if (!timer) return STATUS_INVALID_HANDLE; + KSTATUS s = KeTimerCancel(timer); + ObDereferenceObject(&timer->Header); + return s; +} + +// SYS_SLEEP a0=milliseconds. +// Sleep is a convenience wrapper over the same waitable timer backend exposed +// by timer handles. A zero-length sleep remains a yield. static uint64_t SyscallSleep(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; - return KeDelayThread(a0); + + if (a0 == 0) { + KeYield(); + return STATUS_SUCCESS; + } + + KTIMER* timer = (KTIMER*)0; + KSTATUS s = KeTimerCreate(&timer); + if (!KSUCCESS(s)) return s; + + s = KeTimerSet(timer, a0, 0); + if (KSUCCESS(s)) { + EVENT* event = KeTimerWaitEvent(timer); + uint32_t selected = 0; + s = KeWaitForMultipleEvents(&event, 1, 0, SYS_WAIT_TIMEOUT_INFINITE, &selected); + } + + ObDereferenceObject(&timer->Header); + return s; } -// SYS_SET_USER_APC_DISPATCHER a0 = dispatcher entry in syscall.dll/user image. +// SYS_SET_USER_APC_DISPATCHER a0 = dispatcher entry in syscall.dll/user image. static uint64_t SyscallSetUserApcDispatcher(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; @@ -749,14 +845,14 @@ static uint64_t SyscallSetUserApcDispatcher(uint64_t a0, uint64_t a1, uint64_t a return KeSetUserApcDispatcher((void*)(uintptr_t)a0); } -// SYS_APC_RETURN a0 = SYS_USER_APC_FRAME* on the user stack. +// SYS_APC_RETURN a0 = SYS_USER_APC_FRAME* on the user stack. static uint64_t SyscallApcReturn(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; return KeUserApcReturn((const SYS_USER_APC_FRAME_K*)(uintptr_t)a0); } -// SYS_SET_THREAD_PRIORITY (7) a0 = priority. +// SYS_SET_THREAD_PRIORITY (7) a0 = priority. static uint64_t SyscallSetThreadPriority(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; @@ -902,7 +998,7 @@ static uint64_t SyscallVmQuery(uint64_t a0, uint64_t a1, uint64_t a2, return CopyToUser((void*)(uintptr_t)a1, &info, sizeof(info)); } -// SYS_CREATE_MUTEX (11) a0 = *handleOut. +// SYS_CREATE_MUTEX (11) a0 = *handleOut. static uint64_t SyscallMutexCreate(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; @@ -921,7 +1017,7 @@ static uint64_t SyscallMutexCreate(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_TRY_ACQUIRE_MUTEX (13) a0 = handle. +// SYS_TRY_ACQUIRE_MUTEX (13) a0 = handle. static uint64_t SyscallMutexTryAcquire(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; @@ -932,7 +1028,7 @@ static uint64_t SyscallMutexTryAcquire(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_RELEASE_MUTEX (14) a0 = handle. +// SYS_RELEASE_MUTEX (14) a0 = handle. static uint64_t SyscallMutexRelease(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; @@ -943,7 +1039,7 @@ static uint64_t SyscallMutexRelease(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } -// SYS_CREATE_SEM (15) a0=initial a1=max a2=*handleOut. +// SYS_CREATE_SEM (15) a0=initial a1=max a2=*handleOut. static uint64_t SyscallSemCreate(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a3; (void)a4; (void)a5; @@ -962,7 +1058,7 @@ static uint64_t SyscallSemCreate(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_TRY_WAIT_SEM (17) a0 = handle. +// SYS_TRY_WAIT_SEM (17) a0 = handle. static uint64_t SyscallSemTryWait(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; @@ -973,7 +1069,7 @@ static uint64_t SyscallSemTryWait(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_SIGNAL_SEM (18) a0 = handle. +// SYS_SIGNAL_SEM (18) a0 = handle. static uint64_t SyscallSemSignal(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; @@ -984,7 +1080,7 @@ static uint64_t SyscallSemSignal(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } -// SYS_CREATE_EVENT (19) a0=type a1=initialState a2=*handleOut. +// SYS_CREATE_EVENT (19) a0=type a1=initialState a2=*handleOut. static uint64_t SyscallEventCreate(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a3; (void)a4; (void)a5; @@ -1003,7 +1099,7 @@ static uint64_t SyscallEventCreate(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_SIGNAL_EVENT (20) a0 = handle. +// SYS_SIGNAL_EVENT (20) a0 = handle. static uint64_t SyscallEventSignal(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; @@ -1014,7 +1110,7 @@ static uint64_t SyscallEventSignal(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } -// SYS_CLEAR_EVENT (21) a0 = handle. +// SYS_CLEAR_EVENT (21) a0 = handle. static uint64_t SyscallEventClear(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; @@ -1025,7 +1121,7 @@ static uint64_t SyscallEventClear(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } -// SYS_CREATE_QUEUE (23) a0 = *handleOut. +// SYS_CREATE_QUEUE (23) a0 = *handleOut. static uint64_t SyscallQueueCreate(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; @@ -1044,41 +1140,51 @@ static uint64_t SyscallQueueCreate(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_SEND_QUEUE (24) a0=handle a1=*msg a2=blocking. +// SYS_SEND_QUEUE a0=*SYS_QUEUE_SEND_REQUEST. static uint64_t SyscallQueueSend(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!a1) return STATUS_INVALID_PARAMETER; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + + SYS_QUEUE_SEND_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (req.Size != sizeof(req) || req.Flags != SYS_QUEUE_FLAG_NONE || !req.Message) + return STATUS_INVALID_PARAMETER; QUEUE_MESSAGE msg; - KSTATUS s = CopyFromUser(&msg, (const void*)(uintptr_t)a1, sizeof(msg)); + s = CopyFromUser(&msg, req.Message, sizeof(msg)); if (!KSUCCESS(s)) return s; - QUEUE* q = (QUEUE*)KiHandleToObject((uint32_t)a0, ObjectTypeQueue, HANDLE_RIGHT_WRITE); + QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, HANDLE_RIGHT_WRITE); if (!q) return STATUS_NOT_FOUND; - s = KeSendMessage(q, &msg, (bool)a2); + s = KeSendMessage(q, &msg, req.TimeoutMilliseconds); ObDereferenceObject(&q->Header); return s; } -// SYS_RECV_QUEUE (25) a0=handle a1=*msg a2=blocking. +// SYS_RECV_QUEUE a0=*SYS_QUEUE_RECV_REQUEST. static uint64_t SyscallQueueRecv(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!a1) return STATUS_INVALID_PARAMETER; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - QUEUE* q = (QUEUE*)KiHandleToObject((uint32_t)a0, ObjectTypeQueue, HANDLE_RIGHT_READ); + SYS_QUEUE_RECV_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (req.Size != sizeof(req) || req.Flags != SYS_QUEUE_FLAG_NONE || !req.MessageOut) + return STATUS_INVALID_PARAMETER; + + QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, HANDLE_RIGHT_READ); if (!q) return STATUS_NOT_FOUND; QUEUE_MESSAGE msg; - KSTATUS s = KeReceiveMessage(q, &msg, (bool)a2); + s = KeReceiveMessage(q, &msg, req.TimeoutMilliseconds); ObDereferenceObject(&q->Header); if (!KSUCCESS(s)) return s; - return CopyToUser((void*)(uintptr_t)a1, &msg, sizeof(msg)); + return CopyToUser(req.MessageOut, &msg, sizeof(msg)); } -// SYS_CLOSE_HANDLE (26) a0 = handle +// SYS_CLOSE_HANDLE (26) a0 = handle // The one and only close path. KeCloseHandle drops the handle-table reference // on 'obj'. If that was the last reference, ObDereferenceObject runs the @@ -1275,15 +1381,120 @@ static uint64_t SyscallCloseHandle(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } -// SYS_OPEN_FILE (27) a0=*path a1=*handleOut. +// Converts public file access flags to handle rights and file object flags. +static KSTATUS KiFileAccessToHandleRights(uint32_t access, uint32_t* rightsOut, uint32_t* fileFlagsOut) { + if (!rightsOut || !fileFlagsOut) + return STATUS_INVALID_PARAMETER; + + uint32_t valid = SYS_FILE_ACCESS_READ | + SYS_FILE_ACCESS_WRITE | + SYS_FILE_ACCESS_APPEND | + SYS_FILE_ACCESS_QUERY | + SYS_FILE_ACCESS_LOCK; + if ((access & ~valid) != 0 || access == 0) + return STATUS_INVALID_PARAMETER; + + uint32_t rights = 0; + uint32_t fileFlags = 0; + if (access & SYS_FILE_ACCESS_READ) + rights |= HANDLE_RIGHT_READ; + if (access & SYS_FILE_ACCESS_WRITE) + rights |= HANDLE_RIGHT_WRITE; + if (access & SYS_FILE_ACCESS_APPEND) { + rights |= HANDLE_RIGHT_WRITE; + fileFlags |= IO_FILE_FLAG_APPEND; + } + if (access & SYS_FILE_ACCESS_QUERY) + rights |= HANDLE_RIGHT_QUERY; + if (access & SYS_FILE_ACCESS_LOCK) + rights |= HANDLE_RIGHT_LOCK; + + *rightsOut = rights; + *fileFlagsOut = fileFlags; + return STATUS_SUCCESS; +} + +// Opens a file using the public disposition contract. +static KSTATUS KiOpenFileByDisposition(SYS_STRING_VIEW path, + uint32_t disposition, + uint32_t access, + FILE_OBJECT** fileOut) { + if (!fileOut) + return STATUS_INVALID_PARAMETER; + *fileOut = NULL; + + bool writes = (access & (SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_APPEND)) != 0; + KSTATUS s; + + switch (disposition) { + case SYS_FILE_OPEN_EXISTING: + return IoOpenFile(path, fileOut); + + case SYS_FILE_CREATE_NEW: + if (!writes) + return STATUS_ACCESS_DENIED; + return IoCreateFile(path, fileOut); + + case SYS_FILE_CREATE_ALWAYS: + if (!writes) + return STATUS_ACCESS_DENIED; + s = IoCreateFile(path, fileOut); + if (s == STATUS_OBJECT_NAME_COLLISION) { + s = IoOpenFile(path, fileOut); + if (KSUCCESS(s)) { + KSTATUS ts = IoTruncateFile(*fileOut, 0); + if (!KSUCCESS(ts)) { + IoCloseFile(*fileOut); + *fileOut = NULL; + return ts; + } + } + } + return s; + + case SYS_FILE_OPEN_ALWAYS: + s = IoOpenFile(path, fileOut); + if (s == STATUS_NOT_FOUND) { + if (!writes) + return STATUS_ACCESS_DENIED; + s = IoCreateFile(path, fileOut); + } + return s; + + case SYS_FILE_TRUNCATE_EXISTING: + if (!writes) + return STATUS_ACCESS_DENIED; + s = IoOpenFile(path, fileOut); + if (KSUCCESS(s)) { + KSTATUS ts = IoTruncateFile(*fileOut, 0); + if (!KSUCCESS(ts)) { + IoCloseFile(*fileOut); + *fileOut = NULL; + return ts; + } + } + return s; + + default: + return STATUS_INVALID_PARAMETER; + } +} + +// Implements SYS_OPEN_FILE. static uint64_t SyscallOpenFile(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; - SYS_PATH_HANDLE_REQUEST req; + SYS_OPEN_FILE_REQUEST req; KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); if (!KSUCCESS(s)) return s; if (!req.HandleOut) return STATUS_INVALID_PARAMETER; + if (req.Flags != SYS_FILE_FLAG_NONE) return STATUS_INVALID_PARAMETER; + + uint32_t rights = 0; + uint32_t fileFlags = 0; + s = KiFileAccessToHandleRights(req.Access, &rights, &fileFlags); + if (!KSUCCESS(s)) return s; char pathBuf[SYSCALL_STRING_MAX]; SYS_STRING_VIEW path; @@ -1292,12 +1503,21 @@ static uint64_t SyscallOpenFile(uint64_t a0, uint64_t a1, uint64_t a2, if (path.Length == 0) return STATUS_INVALID_PARAMETER; FILE_OBJECT* file = NULL; - s = IoOpenFile(path, &file); + s = KiOpenFileByDisposition(path, req.Disposition, req.Access, &file); if (!KSUCCESS(s)) return s; + if (fileFlags != 0) { + uintptr_t flags = KeAcquireSpinlock(&file->Lock); + file->Flags |= fileFlags; + KeReleaseSpinlock(&file->Lock, flags); + } + uint32_t h = 0; - s = KiAllocHandleForCurrentWithRights(&file->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_QUERY, &h); - if (!KSUCCESS(s)) return s; + s = KiAllocHandleForCurrentWithRights(&file->Header, rights, &h); + if (!KSUCCESS(s)) { + IoCloseFile(file); + return s; + } s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, h); if (!KSUCCESS(s)) { @@ -1683,21 +1903,21 @@ static uint64_t SyscallAsyncFileCommon(uint64_t a0, bool write) { return s; } -// SYS_READ_FILE_ASYNC (43) a0=*SYS_ASYNC_FILE_REQUEST. +// SYS_READ_FILE_ASYNC (43) a0=*SYS_ASYNC_FILE_REQUEST. static uint64_t SyscallReadFileAsync(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; return SyscallAsyncFileCommon(a0, false); } -// SYS_WRITE_FILE_ASYNC (44) a0=*SYS_ASYNC_FILE_REQUEST. +// SYS_WRITE_FILE_ASYNC (44) a0=*SYS_ASYNC_FILE_REQUEST. static uint64_t SyscallWriteFileAsync(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; return SyscallAsyncFileCommon(a0, true); } -// SYS_CANCEL_IO a0 = async request handle. +// SYS_CANCEL_IO a0 = async request handle. static uint64_t SyscallCancelIo(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; @@ -1710,39 +1930,7 @@ static uint64_t SyscallCancelIo(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_CREATE_FILE (30) a0=*path a1=*handleOut. -static uint64_t SyscallCreateFile(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; - - SYS_PATH_HANDLE_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); - if (!KSUCCESS(s)) return s; - if (!req.HandleOut) return STATUS_INVALID_PARAMETER; - - char pathBuf[SYSCALL_STRING_MAX]; - SYS_STRING_VIEW path; - s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true); - if (!KSUCCESS(s)) return s; - if (path.Length == 0) return STATUS_INVALID_PARAMETER; - - FILE_OBJECT* file = NULL; - s = IoCreateFile(path, &file); - if (!KSUCCESS(s)) return s; - - uint32_t h = 0; - s = KiAllocHandleForCurrentWithRights(&file->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_QUERY, &h); - if (!KSUCCESS(s)) return s; - - s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, h); - if (!KSUCCESS(s)) { - PROCESS* proc = KeGetCurrentProcess(); - if (proc) KeCloseHandle(proc, h); - } - return s; -} - -// SYS_DELETE_FILE (31) a0=*path. +// SYS_DELETE_FILE (31) a0=*path. static uint64_t SyscallDeleteFile(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; @@ -1760,7 +1948,7 @@ static uint64_t SyscallDeleteFile(uint64_t a0, uint64_t a1, uint64_t a2, return IoDeleteFile(path); } -// SYS_TRUNCATE_FILE (32) a0=handle a1=newSize. +// SYS_TRUNCATE_FILE (32) a0=handle a1=newSize. static uint64_t SyscallTruncateFile(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; @@ -1782,20 +1970,24 @@ static uint64_t SyscallFlushFile(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_GET_FILE_POS (33) a0=handle -> current seek offset. +// SYS_GET_FILE_POS (33) a0=handle a1=*positionOut. static uint64_t SyscallGetFilePos(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; + (void)a2; (void)a3; (void)a4; (void)a5; + if (!a1) return STATUS_INVALID_PARAMETER; + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_QUERY); - if (!file) return (uint64_t)-1; + if (!file) return STATUS_INVALID_HANDLE; + uintptr_t flags = KeAcquireSpinlock(&file->Lock); uint64_t pos = file->Offset; KeReleaseSpinlock(&file->Lock, flags); + ObDereferenceObject(&file->Header); - return pos; + return KiCopyU64ToUser(a1, pos); } -// SYS_SEEK_FILE (34) a0=handle a1=offset. +// SYS_SEEK_FILE (34) a0=handle a1=offset. static uint64_t SyscallSeekFile(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; @@ -1806,18 +1998,61 @@ static uint64_t SyscallSeekFile(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_GET_FILE_SIZE (35) a0=handle -> uint64_t. +// SYS_GET_FILE_SIZE (35) a0=handle a1=*sizeOut. static uint64_t SyscallGetFileSize(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; + (void)a2; (void)a3; (void)a4; (void)a5; + if (!a1) return STATUS_INVALID_PARAMETER; + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_QUERY); - if (!file) return 0; + if (!file) return STATUS_INVALID_HANDLE; + uint64_t sz = IoGetFileSize(file); ObDereferenceObject(&file->Header); - return sz; + return KiCopyU64ToUser(a1, sz); } -// SYS_CREATE_SURFACE (99) a0=width a1=height a2=format a3=flags a4=*handleOut. +// SYS_GET_FILE_STAT a0=handle a1=*SYS_FILE_STAT. +static uint64_t SyscallGetFileStat(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + if (!a1) return STATUS_INVALID_PARAMETER; + + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_QUERY); + if (!file) return STATUS_INVALID_HANDLE; + + SYS_FILE_STAT stat; + KSTATUS s = IoGetFileStat(file, &stat); + ObDereferenceObject(&file->Header); + if (!KSUCCESS(s)) return s; + + return CopyToUser((void*)(uintptr_t)a1, &stat, sizeof(stat)); +} + +// SYS_STAT_FILE a0=*SYS_STAT_FILE_REQUEST. +static uint64_t SyscallStatFile(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; + + SYS_STAT_FILE_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (!req.StatOut) return STATUS_INVALID_PARAMETER; + + char pathBuf[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW path; + s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true); + if (!KSUCCESS(s)) return s; + if (path.Length == 0) return STATUS_INVALID_PARAMETER; + + SYS_FILE_STAT stat; + s = IoStatFile(path, &stat); + if (!KSUCCESS(s)) return s; + + return CopyToUser(req.StatOut, &stat, sizeof(stat)); +} + +// SYS_CREATE_SURFACE (99) a0=width a1=height a2=format a3=flags a4=*handleOut. static uint64_t SyscallCreateSurface(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a5; @@ -1900,7 +2135,7 @@ static uint64_t SyscallCreateSurface(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } -// SYS_MAP_SURFACE (100) a0=surfaceHandle a1=*baseOut a2=*pitchBytesOut. +// SYS_MAP_SURFACE (100) a0=surfaceHandle a1=*baseOut a2=*pitchBytesOut. static uint64_t SyscallMapSurface(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a3; (void)a4; (void)a5; @@ -1945,7 +2180,7 @@ static uint64_t SyscallMapSurface(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_QUERY_SURFACE (101) a0=surfaceHandle a1=*SYS_SURFACE_DESC. +// SYS_QUERY_SURFACE (101) a0=surfaceHandle a1=*SYS_SURFACE_DESC. static uint64_t SyscallQuerySurface(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; @@ -2025,7 +2260,7 @@ static int KiClipSurfaceRect(const SURFACE_OBJECT* surface, const DISPLAY_RECT* return 1; } -// SYS_PRESENT_SURFACE (102) a0=surfaceHandle a1=*DISPLAY_RECT[] a2=rectCount. +// SYS_PRESENT_SURFACE (102) a0=surfaceHandle a1=*DISPLAY_RECT[] a2=rectCount. static uint64_t SyscallPresentSurface(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a3; (void)a4; (void)a5; @@ -2094,14 +2329,14 @@ static uint64_t SyscallPresentSurface(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_CLEAR_SCREEN (67) a0=color. +// SYS_CLEAR_SCREEN (67) a0=color. static uint64_t SyscallClearScreen(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; return KeDisplayClear((uint32_t)a0); } -// SYS_GET_SCREEN_SIZE (68) a0=*widthOut a1=*heightOut. +// SYS_GET_SCREEN_SIZE (68) a0=*widthOut a1=*heightOut. static uint64_t SyscallGetScreenSize(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; @@ -2119,7 +2354,7 @@ static uint64_t SyscallGetScreenSize(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } -// SYS_PUT_SCREEN_CHAR (65) a0=c a1=x a2=y a3=fg a4=bg. +// SYS_PUT_SCREEN_CHAR (65) a0=c a1=x a2=y a3=fg a4=bg. static uint64_t SyscallPutScreenChar(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a5; @@ -2127,7 +2362,7 @@ static uint64_t SyscallPutScreenChar(uint64_t a0, uint64_t a1, uint64_t a2, (uint32_t)a3, (uint32_t)a4); } -// SYS_GET_CONSOLE_INFO (61) a0=*SYS_CONSOLE_INFO. +// SYS_GET_CONSOLE_INFO (61) a0=*SYS_CONSOLE_INFO. static uint64_t SyscallGetConsoleInfo(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; @@ -2147,7 +2382,7 @@ static uint64_t SyscallGetConsoleInfo(uint64_t a0, uint64_t a1, uint64_t a2, return CopyToUser((void*)(uintptr_t)a0, &out, sizeof(out)); } -// SYS_SET_CONSOLE_CURSOR (62) a0=column a1=row. +// SYS_SET_CONSOLE_CURSOR (62) a0=column a1=row. static uint64_t SyscallSetConsoleCursor(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; @@ -2157,7 +2392,7 @@ static uint64_t SyscallSetConsoleCursor(uint64_t a0, uint64_t a1, uint64_t a2, return KeDisplaySetConsoleCursor(col, row); } -// SYS_FILL_CONSOLE_CHARACTER (63) a0=char a1=column a2=row a3=count a4=*writtenOut. +// SYS_FILL_CONSOLE_CHARACTER (63) a0=char a1=column a2=row a3=count a4=*writtenOut. static uint64_t SyscallFillConsoleCharacter(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a5; @@ -2174,7 +2409,7 @@ static uint64_t SyscallFillConsoleCharacter(uint64_t a0, uint64_t a1, uint64_t a return s; } -// SYS_FILL_CONSOLE_ATTRIBUTE (64) a0=attribute a1=column a2=row a3=count a4=*writtenOut. +// SYS_FILL_CONSOLE_ATTRIBUTE (64) a0=attribute a1=column a2=row a3=count a4=*writtenOut. static uint64_t SyscallFillConsoleAttribute(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a5; @@ -2191,113 +2426,164 @@ static uint64_t SyscallFillConsoleAttribute(uint64_t a0, uint64_t a1, uint64_t a return s; } -// SYS_POLL_KEYBOARD (70) -> next INPUT_KEY or INPUT_KEY_NONE. +// Validates a user input queue handle. Handle zero means the default queue. +static KSTATUS KiValidateInputQueueHandle(uint32_t handle) { + if (handle == 0) return STATUS_SUCCESS; + + OBJECT* obj = KiHandleToObject(handle, ObjectTypeInputQueue, HANDLE_RIGHT_READ); + if (!obj) return STATUS_INVALID_HANDLE; + ObDereferenceObject(obj); + return STATUS_SUCCESS; +} + +// Copies one keyboard input record to user mode. +static KSTATUS KiCopyInputRecordToUser(INPUT_KEY_RECORD* userRecord, const INPUT_KEY_RECORD* record) { + if (!userRecord || !record) return STATUS_INVALID_PARAMETER; + return CopyToUser(userRecord, record, sizeof(*record)); +} + +// Imports and validates a keyboard input event request. +static KSTATUS KiCopyInputEventRequest(uint64_t arg, SYS_INPUT_EVENT_REQUEST* req) { + KSTATUS s = KiCopyUserRequest(arg, req, sizeof(*req)); + if (!KSUCCESS(s)) return s; + if (req->Size != sizeof(*req)) return STATUS_INVALID_PARAMETER; + if (req->Flags != SYS_INPUT_EVENT_FLAG_NONE) return STATUS_INVALID_PARAMETER; + if (!req->RecordOut) return STATUS_INVALID_PARAMETER; + return KiValidateInputQueueHandle(req->QueueHandle); +} + +// SYS_POLL_KEYBOARD a0=*INPUT_KEY -> KSTATUS. static uint64_t SyscallKbPoll(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; - return (uint64_t)KeInputKbPoll(); + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!a0) return STATUS_INVALID_PARAMETER; + + INPUT_KEY key = KeInputKbPoll(); + KSTATUS s = CopyToUser((void*)(uintptr_t)a0, &key, sizeof(key)); + if (!KSUCCESS(s)) return s; + return (key == INPUT_KEY_NONE) ? STATUS_WOULD_BLOCK : STATUS_SUCCESS; } -// SYS_IS_KEY_DOWN (71) a0=INPUT_KEY -> 1 or 0. +// SYS_IS_KEY_DOWN a0=INPUT_KEY a1=*bool -> KSTATUS. static uint64_t SyscallKbIsKeyDown(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; - return KeInputIsKeyDown((INPUT_KEY)a0) ? 1u : 0u; + (void)a2; (void)a3; (void)a4; (void)a5; + if (!a1) return STATUS_INVALID_PARAMETER; + + bool down = KeInputIsKeyDown((INPUT_KEY)a0); + return CopyToUser((void*)(uintptr_t)a1, &down, sizeof(down)); } -// SYS_READ_KEYBOARD_CHAR (72) -> char. +// SYS_READ_KEYBOARD_CHAR a0=*char -> KSTATUS. static uint64_t SyscallKbReadChar(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; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!a0) return STATUS_INVALID_PARAMETER; for (;;) { char ch = KeInputReadChar(); if (ch) - return (uint64_t)(uint8_t)ch; + return CopyToUser((void*)(uintptr_t)a0, &ch, sizeof(ch)); - KSTATUS s = KeInputWaitKey(0); + KSTATUS s = KeInputWaitKey(0, SYS_WAIT_TIMEOUT_INFINITE); if (!KSUCCESS(s)) - return 0; + return s; } } -// SYS_READ_KEY (73) -> INPUT_KEY. +// SYS_READ_KEY a0=*INPUT_KEY -> KSTATUS. static uint64_t SyscallKbReadKey(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; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!a0) return STATUS_INVALID_PARAMETER; for (;;) { INPUT_KEY key = KeInputReadKey(); if (key != INPUT_KEY_NONE) - return (uint64_t)key; + return CopyToUser((void*)(uintptr_t)a0, &key, sizeof(key)); - KSTATUS s = KeInputWaitKey(0); + KSTATUS s = KeInputWaitKey(0, SYS_WAIT_TIMEOUT_INFINITE); if (!KSUCCESS(s)) - return (uint64_t)INPUT_KEY_NONE; + return s; } } -// SYS_GET_KEY_MODIFIERS (74) -> uint32_t INPUT_MOD_* flags. +// SYS_GET_KEY_MODIFIERS -> uint32_t INPUT_MOD_* flags. static uint64_t SyscallKbGetModifiers(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; return (uint64_t)KeInputGetModifiers(); } -// SYS_READ_INPUT_EVENT (104) a0=*INPUT_KEY_RECORD -> KSTATUS. +// SYS_READ_INPUT_EVENT a0=*SYS_INPUT_EVENT_REQUEST -> KSTATUS. static uint64_t SyscallKbReadInputEvent(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; - if (!a0) return STATUS_INVALID_PARAMETER; + + SYS_INPUT_EVENT_REQUEST req; + KSTATUS s = KiCopyInputEventRequest(a0, &req); + if (!KSUCCESS(s)) return s; INPUT_KEY_RECORD record; for (;;) { if (KeInputReadEvent(&record)) - return CopyToUser((void*)(uintptr_t)a0, &record, sizeof(record)); + return KiCopyInputRecordToUser(req.RecordOut, &record); - KSTATUS s = KeInputWaitKey(0); + if (req.TimeoutMilliseconds == 0) + return STATUS_WOULD_BLOCK; + + s = KeInputWaitKey(0, req.TimeoutMilliseconds); if (!KSUCCESS(s)) return s; } } -// SYS_PEEK_INPUT_EVENT (105) a0=*INPUT_KEY_RECORD -> KSTATUS. +// SYS_PEEK_INPUT_EVENT a0=*SYS_INPUT_EVENT_REQUEST -> KSTATUS. static uint64_t SyscallKbPeekInputEvent(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; - if (!a0) return STATUS_INVALID_PARAMETER; + + SYS_INPUT_EVENT_REQUEST req; + KSTATUS s = KiCopyInputEventRequest(a0, &req); + if (!KSUCCESS(s)) return s; INPUT_KEY_RECORD record; - if (!KeInputPeekEvent(&record)) - return STATUS_TIMEOUT; + for (;;) { + if (KeInputPeekEvent(&record)) + return KiCopyInputRecordToUser(req.RecordOut, &record); - return CopyToUser((void*)(uintptr_t)a0, &record, sizeof(record)); + if (req.TimeoutMilliseconds == 0) + return STATUS_WOULD_BLOCK; + + s = KeInputWaitKey(0, req.TimeoutMilliseconds); + if (!KSUCCESS(s)) + return s; + } } -// SYS_POLL_MOUSE (75) -> 1 or 0. -static uint64_t SyscallMousePoll(uint64_t a0, uint64_t a1, uint64_t a2, +// SYS_PEEK_MOUSE a0=*INPUT_MOUSE_EVENT -> KSTATUS. +static uint64_t SyscallMousePeek(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; - return KeInputMousePoll() ? 1u : 0u; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!a0) return STATUS_INVALID_PARAMETER; + + INPUT_MOUSE_EVENT event; + if (!KeInputMousePeek(&event)) return STATUS_WOULD_BLOCK; + return CopyToUser((void*)(uintptr_t)a0, &event, sizeof(event)); } -// SYS_READ_MOUSE (76) a0=*dx a1=*dy a2=*buttons -> 1 or 0. +// SYS_READ_MOUSE a0=*INPUT_MOUSE_EVENT -> KSTATUS. static uint64_t SyscallMouseRead(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!a0 || !a1 || !a2) return 0; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!a0) return STATUS_INVALID_PARAMETER; - int dx = 0, dy = 0; - uint8_t buttons = 0; - if (!KeInputMouseRead(&dx, &dy, &buttons)) return 0; - - if (!KSUCCESS(CopyToUser((void*)(uintptr_t)a0, &dx, sizeof(dx)))) return 0; - if (!KSUCCESS(CopyToUser((void*)(uintptr_t)a1, &dy, sizeof(dy)))) return 0; - if (!KSUCCESS(CopyToUser((void*)(uintptr_t)a2, &buttons, sizeof(buttons)))) return 0; - return 1u; + INPUT_MOUSE_EVENT event; + if (!KeInputMouseRead(&event)) return STATUS_WOULD_BLOCK; + return CopyToUser((void*)(uintptr_t)a0, &event, sizeof(event)); } -// SYS_GET_MOUSE_POS (77) a0=*x a1=*y. +// SYS_GET_MOUSE_POS a0=*x a1=*y -> KSTATUS. static uint64_t SyscallMouseGetPos(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; @@ -2310,14 +2596,25 @@ static uint64_t SyscallMouseGetPos(uint64_t a0, uint64_t a1, uint64_t a2, return KiCopyIntToUser(a1, y); } -// SYS_GET_TICK (78) -> uint64_t microseconds. +// SYS_GET_TICK (75) returns monotonic ticks since boot. static uint64_t SyscallGetTick(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; return gHal ? gHal->Timer.QueryTicks() : 0; } -// SYS_GET_SYSTEM_TIME a0=*SYS_SYSTEM_TIME. +// SYS_GET_TICK_FREQUENCY (110) copies SysGetTick ticks per second. +static uint64_t SyscallGetTickFrequency(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; + if (!gHal || !gHal->Timer.QueryTickFrequency) return STATUS_DEVICE_ERROR; + + uint64_t frequency = gHal->Timer.QueryTickFrequency(); + if (!frequency) return STATUS_DEVICE_ERROR; + return KiCopyU64ToUser(a0, frequency); +} + +// SYS_GET_SYSTEM_TIME a0=*SYS_SYSTEM_TIME. static uint64_t SyscallGetSystemTime(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; @@ -2329,7 +2626,7 @@ static uint64_t SyscallGetSystemTime(uint64_t a0, uint64_t a1, uint64_t a2, return CopyToUser((void*)(uintptr_t)a0, &t, sizeof(t)); } -// SYS_PRINT_DEBUG (79) a0=*str. +// SYS_PRINT_DEBUG (79) a0=*str. static uint64_t SyscallDebugPrint(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; @@ -2354,7 +2651,7 @@ static uint64_t SyscallDebugPrint(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } -// SYS_QUERY_TRAP_FRAME a0=*SYS_TRAP_FRAME_INFO. +// SYS_QUERY_TRAP_FRAME a0=*SYS_TRAP_FRAME_INFO. static uint64_t SyscallQueryTrapFrame(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; @@ -2375,7 +2672,7 @@ static uint64_t SyscallQueryTrapFrame(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_CREATE_PROCESS (50) a0=*path a1=*handleOut a2=*cmdline (optional). +// SYS_CREATE_PROCESS (50) a0=*path a1=*handleOut a2=*cmdline (optional). static uint64_t SyscallCreateProcess(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; @@ -2467,7 +2764,7 @@ static uint64_t SyscallCreateProcess(uint64_t a0, uint64_t a1, uint64_t a2, } uint32_t handle = 0; - s = KeAllocateHandleWithRights(caller, &proc->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE, &handle); + s = KeAllocateHandleWithRights(caller, &proc->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE | HANDLE_RIGHT_DUPLICATE, &handle); if (!KSUCCESS(s)) { proc->ImageBase = 0; proc->ImageSize = 0; @@ -2502,7 +2799,40 @@ static uint64_t SyscallCreateProcess(uint64_t a0, uint64_t a1, uint64_t a2, ObDereferenceObject(&proc->Header); return STATUS_SUCCESS; } -// SYS_GET_PROCESS_EXIT_CODE (52) a0=handle a1=*exitCodeOut. +// SYS_OPEN_PROCESS a0=pid a1=*handleOut. +static uint64_t SyscallOpenProcess(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + if (!a1) return STATUS_INVALID_PARAMETER; + + KSTATUS s = MmProbeUserWrite((void*)(uintptr_t)a1, sizeof(uint32_t)); + if (!KSUCCESS(s)) return s; + + PROCESS* proc = KeGetProcessById((uint32_t)a0); + if (!proc) return STATUS_NOT_FOUND; + + PROCESS* caller = KeGetCurrentProcess(); + if (!caller) { + ObDereferenceObject(&proc->Header); + return STATUS_FAILURE; + } + + uint32_t handle = 0; + s = KeAllocateHandleWithRights(caller, &proc->Header, + HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE | HANDLE_RIGHT_DUPLICATE, + &handle); + ObDereferenceObject(&proc->Header); + if (!KSUCCESS(s)) return s; + + s = KiCopyHandleToUser(a1, handle); + if (!KSUCCESS(s)) { + KeCloseHandle(caller, handle); + return s; + } + return STATUS_SUCCESS; +} + +// SYS_GET_PROCESS_EXIT_CODE (4) copies the process exit code. static uint64_t SyscallGetProcessExitCode(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; @@ -2511,14 +2841,14 @@ static uint64_t SyscallGetProcessExitCode(uint64_t a0, uint64_t a1, uint64_t a2, PROCESS* proc = (PROCESS*)KiHandleToObject((uint32_t)a0, ObjectTypeProcess, HANDLE_RIGHT_QUERY); if (!proc) return STATUS_INVALID_HANDLE; - int exitCode = 0; + int32_t exitCode = 0; KSTATUS s = KeGetProcessExitCode(proc, &exitCode); ObDereferenceObject(&proc->Header); if (!KSUCCESS(s)) return s; - return KiCopyIntToUser(a1, exitCode); + return KiCopyInt32ToUser(a1, exitCode); } -// SYS_WRITE_CONSOLE (60) a0=*buf a1=len +// SYS_WRITE_CONSOLE (60) a0=*buf a1=len // Writes user console text in white; distinct from green SYS_PRINT_DEBUG. static uint64_t SyscallWriteConsole(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { @@ -2544,7 +2874,7 @@ static uint64_t SyscallWriteConsole(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } -// SYS_OPEN_DIR (54) a0=*dirPath a1=*handleOut. +// SYS_OPEN_DIR (54) a0=*dirPath a1=*handleOut. static uint64_t SyscallOpenDir(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; @@ -2582,7 +2912,7 @@ static uint64_t SyscallOpenDir(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } -// SYS_READ_DIR (55) a0=handle a1=*KDIR_ENTRY +// SYS_READ_DIR (55) a0=handle a1=*KDIR_ENTRY // Returns STATUS_NO_MORE_FILES when the directory is exhausted. static uint64_t SyscallReadDir(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { @@ -2600,7 +2930,7 @@ static uint64_t SyscallReadDir(uint64_t a0, uint64_t a1, uint64_t a2, return CopyToUser((void*)(uintptr_t)a1, &entry, sizeof(entry)); } -// SYS_SET_CWD (56) a0=*path. +// SYS_SET_CWD (56) a0=*path. static uint64_t SyscallSetCwd(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; @@ -2618,31 +2948,49 @@ static uint64_t SyscallSetCwd(uint64_t a0, uint64_t a1, uint64_t a2, return IoSetCwd(path); } -// SYS_GET_CWD (57) a0=*buf a1=size. -static uint64_t SyscallGetCwd(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { +// SYS_REAL_PATH a0=*SYS_REAL_PATH_REQUEST. +static uint64_t SyscallRealPath(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; - SYS_GET_CWD_REQUEST req; + SYS_REAL_PATH_REQUEST req; KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - if (!req.Buffer && req.Capacity) return STATUS_INVALID_PARAMETER; + if (req.Size != sizeof(req) || req.Flags != SYS_REAL_PATH_FLAG_NONE) + return STATUS_INVALID_PARAMETER; + if (!req.Buffer && req.BufferSize != 0) return STATUS_INVALID_PARAMETER; + if (!req.Buffer && !req.RequiredSizeOut) return STATUS_INVALID_PARAMETER; - char cwd[SYSCALL_STRING_MAX]; - s = IoGetCwd(cwd, sizeof(cwd)); - if (!KSUCCESS(s)) return s; - - size_t len = strlen(cwd); - if (req.LengthOut) { - s = CopyToUser(req.LengthOut, &len, sizeof(len)); + if (req.RequiredSizeOut) { + s = MmProbeUserWrite(req.RequiredSizeOut, sizeof(size_t)); if (!KSUCCESS(s)) return s; } - if (len > req.Capacity) return STATUS_BUFFER_TOO_SMALL; - if (len) return CopyToUser(req.Buffer, cwd, len); - return STATUS_SUCCESS; + + char pathBuf[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW path; + s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true); + if (!KSUCCESS(s)) return s; + if (path.Length == 0) return STATUS_INVALID_PARAMETER; + + char resolved[SYS_CWD_MAX]; + size_t required = 0; + s = IoRealPath(path, resolved, sizeof(resolved), &required); + if (!KSUCCESS(s)) return s; + + if (req.RequiredSizeOut) { + KSTATUS cs = KiCopySizeToUser((uint64_t)(uintptr_t)req.RequiredSizeOut, required); + if (!KSUCCESS(cs)) return cs; + } + + if (!req.Buffer) + return STATUS_SUCCESS; + if (req.BufferSize < required) + return STATUS_BUFFER_TOO_SMALL; + + return CopyToUser(req.Buffer, resolved, required); } -// SYS_GET_PROCESS_PID (59) a0=procHandle a1=*pidOut +// SYS_GET_PROCESS_PID (59) a0=procHandle a1=*pidOut // Returns the numeric PID of a process identified by handle. // Used by posix_spawn to map the kernel handle back to a pid_t for waitpid. static uint64_t SyscallGetProcessPid(uint64_t a0, uint64_t a1, uint64_t a2, @@ -2687,7 +3035,7 @@ static int KiNamedQueueNameEquals(const NAMED_QUEUE_ENTRY* entry, return SysSvEqual(SysSvFromParts(entry->Name, entry->NameLength), name); } -// SYS_PUBLISH_QUEUE (60) a0=*name a1=queueHandle +// SYS_PUBLISH_QUEUE (60) a0=*name a1=queueHandle // Registers the queue identified by 'queueHandle' under 'name'. // Fails with STATUS_ALREADY_EXISTS if the name is already taken. @@ -2735,7 +3083,7 @@ static uint64_t SyscallPublishQueue(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_NO_MEMORY; } -// SYS_OPEN_NAMED_QUEUE (61) a0=*name a1=*handleOut +// SYS_OPEN_NAMED_QUEUE (61) a0=*name a1=*handleOut // Looks up 'name' in the registry and allocates a handle to the queue in the // calling process's handle table. Returns STATUS_NOT_FOUND if no match. @@ -2786,54 +3134,57 @@ static uint64_t SyscallOpenNamedQueue(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } -// SYS_DUPLICATE_QUEUE (62) a0=handle a1=targetPid a2=*handleOut +// SYS_DUPLICATE_HANDLE a0=*request. -// Looks up the QUEUE behind 'handle' in the CALLER's table, then allocates -// a new handle for the same object in the TARGET process's table. -// Both processes share the same underlying QUEUE (refcount bumped). -// The caller's own handle is unchanged. -static uint64_t SyscallQueueDuplicate(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!a2) return STATUS_INVALID_PARAMETER; +// Duplicates a handle from the caller into the target process table. +static uint64_t SyscallDuplicateHandle(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; - QUEUE* q = (QUEUE*)KiHandleToObject((uint32_t)a0, ObjectTypeQueue, HANDLE_RIGHT_DUPLICATE); - if (!q) return STATUS_NOT_FOUND; + SYS_DUPLICATE_HANDLE_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (!req.SourceHandle || !req.TargetProcessHandle || !req.TargetHandleOut) + return STATUS_INVALID_PARAMETER; - PROCESS* target = KeGetProcessById((uint32_t)a1); - if (!target) { - ObDereferenceObject(&q->Header); - return STATUS_NOT_FOUND; + s = MmProbeUserWrite(req.TargetHandleOut, sizeof(uint32_t)); + if (!KSUCCESS(s)) return s; + + PROCESS* caller = KeGetCurrentProcess(); + if (!caller) return STATUS_FAILURE; + + PROCESS* target = (PROCESS*)KiHandleToObject(req.TargetProcessHandle, ObjectTypeProcess, HANDLE_RIGHT_DUPLICATE); + if (!target) return STATUS_INVALID_HANDLE; + + uint32_t targetHandle = 0; + s = KeDuplicateHandleToProcess(caller, req.SourceHandle, target, req.DesiredRights, req.Flags, &targetHandle); + if (!KSUCCESS(s)) { + ObDereferenceObject(&target->Header); + return s; } - uint32_t newHandle = 0; - KSTATUS s = KeAllocateHandleWithRights(target, &q->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_DUPLICATE | HANDLE_RIGHT_QUERY, &newHandle); - if (KSUCCESS(s)) { - KSTATUS cs = KiCopyHandleToUser(a2, newHandle); - if (!KSUCCESS(cs)) { - KeCloseHandle(target, newHandle); - s = cs; - } + s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.TargetHandleOut, targetHandle); + if (!KSUCCESS(s)) { + KeCloseHandle(target, targetHandle); + ObDereferenceObject(&target->Header); + return s; } ObDereferenceObject(&target->Header); - ObDereferenceObject(&q->Header); - return s; + return STATUS_SUCCESS; } -// SYS_GET_NAMED_QUEUE_OWNER_PID (63) a0=*name a1=*pidOut +// SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS a0=*request. -// Returns the ProcessId of whoever published the named queue. -// Lets a client get the publisher's PID so it can duplicate one of its -// own handles into that process via SYS_DUPLICATE_QUEUE. -static uint64_t SyscallNamedQueueOwnerPid(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { +// Opens a process handle for the process that published a named queue. +static uint64_t SyscallOpenNamedQueueOwnerProcess(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; SYS_NAMED_QUEUE_OWNER_REQUEST req; KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - if (!req.PidOut) return STATUS_INVALID_PARAMETER; + if (!req.ProcessHandleOut) return STATUS_INVALID_PARAMETER; char name[NAMED_QUEUE_NAME_MAX]; SYS_STRING_VIEW nameView; @@ -2841,38 +3192,66 @@ static uint64_t SyscallNamedQueueOwnerPid(uint64_t a0, uint64_t a1, uint64_t a2, if (!KSUCCESS(s)) return s; if (nameView.Length == 0) return STATUS_INVALID_PARAMETER; + s = MmProbeUserWrite(req.ProcessHandleOut, sizeof(uint32_t)); + if (!KSUCCESS(s)) return s; + uint32_t pid = 0; uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock); for (int i = 0; i < NAMED_QUEUE_MAX; i++) { if (KiNamedQueueNameEquals(&gNamedQueues[i], nameView)) { pid = gNamedQueues[i].PublisherPid; - KeReleaseSpinlock(&gNamedQueueLock, flags); - return KiCopyU32ToUser((uint64_t)(uintptr_t)req.PidOut, pid); + break; } } KeReleaseSpinlock(&gNamedQueueLock, flags); - return STATUS_NOT_FOUND; + if (!pid) return STATUS_NOT_FOUND; + + PROCESS* owner = KeGetProcessById(pid); + if (!owner) return STATUS_NOT_FOUND; + + PROCESS* caller = KeGetCurrentProcess(); + if (!caller) { + ObDereferenceObject(&owner->Header); + return STATUS_FAILURE; + } + + uint32_t handle = 0; + s = KeAllocateHandleWithRights(caller, &owner->Header, + HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_DUPLICATE, + &handle); + ObDereferenceObject(&owner->Header); + if (!KSUCCESS(s)) return s; + + s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.ProcessHandleOut, handle); + if (!KSUCCESS(s)) { + KeCloseHandle(caller, handle); + return s; + } + return STATUS_SUCCESS; } static uint64_t SyscallKillProcess(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; - PROCESS* proc = KeGetProcessById((uint32_t)a0); - if (!proc) return STATUS_NOT_FOUND; + PROCESS* proc = (PROCESS*)KiHandleToObject((uint32_t)a0, ObjectTypeProcess, HANDLE_RIGHT_TERMINATE); + if (!proc) return STATUS_INVALID_HANDLE; + THREAD* self = KeGetCurrentThread(); - int exitCode = (int)a1; + int32_t exitCode = (int32_t)a1; if (self && self->Owner == proc) { - KeExitProcess(proc, exitCode); + KSTATUS s = KeExitProcess(proc, exitCode); ObDereferenceObject(&proc->Header); + if (!KSUCCESS(s)) return s; KeExitThread(self, exitCode); while (true) ; } + KSTATUS s = KeTerminateProcess(proc, exitCode); ObDereferenceObject(&proc->Header); return s; } -// SYS_GET_PROCESS_LIST (6) a0=*SYS_PROCESS_INFO[] a1=maxCount a2=*countOut. +// SYS_GET_PROCESS_LIST (6) a0=*SYS_PROCESS_INFO[] a1=maxCount a2=*countOut. static uint64_t SyscallGetProcessList(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a3; (void)a4; (void)a5; @@ -2906,7 +3285,7 @@ static uint64_t SyscallGetProcessList(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } -// SYS_GET_THREAD_LIST (103) a0=pid a1=*SYS_THREAD_INFO[] a2=maxCount a3=*countOut. +// SYS_GET_THREAD_LIST (103) a0=pid a1=*SYS_THREAD_INFO[] a2=maxCount a3=*countOut. static uint64_t SyscallGetThreadList(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a4; (void)a5; @@ -2945,7 +3324,7 @@ static uint64_t SyscallGetThreadList(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } -// SYS_RENAME_FILE (66) a0=*oldPath a1=*newPath. +// SYS_RENAME_FILE (66) a0=*oldPath a1=*newPath. static uint64_t SyscallRenameFile(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; @@ -2966,7 +3345,54 @@ static uint64_t SyscallRenameFile(uint64_t a0, uint64_t a1, uint64_t a2, return IoRenameFile(oldPath, newPath); } -// SYS_CREATE_DIR (67) a0=*path. +// SYS_LOCK_FILE a0=*SYS_LOCK_FILE_REQUEST. +static uint64_t SyscallLockFile(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; + + SYS_LOCK_FILE_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (req.Size != sizeof(req)) return STATUS_INVALID_PARAMETER; + if (req.Flags != SYS_FILE_LOCK_FLAG_NONE) return STATUS_INVALID_PARAMETER; + if (req.Mode != SYS_FILE_LOCK_SHARED && req.Mode != SYS_FILE_LOCK_EXCLUSIVE) return STATUS_INVALID_PARAMETER; + if (req.Length == 0) return STATUS_INVALID_PARAMETER; + + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) return STATUS_FAILURE; + + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, HANDLE_RIGHT_LOCK); + if (!file) return STATUS_INVALID_HANDLE; + + s = IoLockFile(file, proc, req.Mode, req.Offset, req.Length, req.TimeoutMilliseconds); + ObDereferenceObject(&file->Header); + return s; +} + +// SYS_UNLOCK_FILE a0=*SYS_UNLOCK_FILE_REQUEST. +static uint64_t SyscallUnlockFile(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; + + SYS_UNLOCK_FILE_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (req.Size != sizeof(req)) return STATUS_INVALID_PARAMETER; + if (req.Flags != SYS_FILE_LOCK_FLAG_NONE) return STATUS_INVALID_PARAMETER; + if (req.Length == 0) return STATUS_INVALID_PARAMETER; + + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) return STATUS_FAILURE; + + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, HANDLE_RIGHT_LOCK); + if (!file) return STATUS_INVALID_HANDLE; + + s = IoUnlockFile(file, proc, req.Offset, req.Length); + ObDereferenceObject(&file->Header); + return s; +} + +// SYS_CREATE_DIR a0=*path. static uint64_t SyscallCreateDir(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; @@ -2983,7 +3409,7 @@ static uint64_t SyscallCreateDir(uint64_t a0, uint64_t a1, uint64_t a2, return IoCreateDir(path); } -// SYS_REMOVE_DIR (68) a0=*path. +// SYS_REMOVE_DIR (68) a0=*path. static uint64_t SyscallRemoveDir(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; @@ -3000,12 +3426,11 @@ static uint64_t SyscallRemoveDir(uint64_t a0, uint64_t a1, uint64_t a2, return IoRemoveDir(path); } - typedef struct SYS_SOCKET_SEND_TO_REQUEST_K { const void* Buffer; size_t Length; uint32_t Flags; - const SYS_SOCKET_ADDRESS_IPV4_K* RemoteAddress; + const SYS_SOCKET_ADDRESS_K* RemoteAddress; size_t RemoteAddressSize; size_t* SentOut; } SYS_SOCKET_SEND_TO_REQUEST_K; @@ -3014,7 +3439,7 @@ typedef struct SYS_SOCKET_RECV_FROM_REQUEST_K { void* Buffer; size_t Length; uint32_t Flags; - SYS_SOCKET_ADDRESS_IPV4_K* RemoteAddressOut; + SYS_SOCKET_ADDRESS_K* RemoteAddressOut; size_t* RemoteAddressSizeInOut; size_t* ReceivedOut; } SYS_SOCKET_RECV_FROM_REQUEST_K; @@ -3023,7 +3448,7 @@ typedef struct SYS_RESOLVE_HOST_REQUEST_K { SYS_STRING_VIEW Name; uint32_t Family; uint32_t Flags; - SYS_SOCKET_ADDRESS_IPV4_K* AddressOut; + SYS_SOCKET_ADDRESS_K* AddressOut; size_t* AddressSizeInOut; } SYS_RESOLVE_HOST_REQUEST_K; @@ -3048,7 +3473,7 @@ typedef struct SYS_MEMORY_STATUS_K { #define SYS_POLL_MAX_FDS 64u -// SYS_CREATE_SOCKET a0=domain a1=type a2=protocol a3=*handleOut. +// SYS_CREATE_SOCKET a0=domain a1=type a2=protocol a3=*handleOut. static uint64_t SyscallSocket(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a4; (void)a5; @@ -3073,14 +3498,14 @@ static uint64_t SyscallSocket(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_BIND_SOCKET a0=socketHandle a1=*SYS_SOCKET_ADDRESS_IPV4 a2=addressSize. +// SYS_BIND_SOCKET a0=socketHandle a1=*SYS_SOCKET_ADDRESS a2=addressSize. static uint64_t SyscallBind(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a3; (void)a4; (void)a5; if (!a1) return STATUS_INVALID_PARAMETER; - if ((size_t)a2 < sizeof(SYS_SOCKET_ADDRESS_IPV4_K)) return STATUS_BUFFER_TOO_SMALL; + if ((size_t)a2 < sizeof(SYS_SOCKET_ADDRESS_K)) return STATUS_BUFFER_TOO_SMALL; - SYS_SOCKET_ADDRESS_IPV4_K address; + SYS_SOCKET_ADDRESS_K address; KSTATUS s = CopyFromUser(&address, (const void*)(uintptr_t)a1, sizeof(address)); if (!KSUCCESS(s)) return s; @@ -3092,7 +3517,7 @@ static uint64_t SyscallBind(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_LISTEN_SOCKET a0=socketHandle a1=backlog. +// SYS_LISTEN_SOCKET a0=socketHandle a1=backlog. static uint64_t SyscallListen(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; @@ -3105,7 +3530,7 @@ static uint64_t SyscallListen(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_ACCEPT_SOCKET a0=listenSocket a1=*remoteAddressOut(optional) +// SYS_ACCEPT_SOCKET a0=listenSocket a1=*remoteAddressOut(optional) // a2=*remoteAddressSizeInOut(optional) a3=*acceptedHandleOut. static uint64_t SyscallAccept(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { @@ -3118,12 +3543,12 @@ static uint64_t SyscallAccept(uint64_t a0, uint64_t a1, uint64_t a2, if (!a2) return STATUS_INVALID_PARAMETER; KSTATUS cs = CopyFromUser(&remoteCapacity, (const void*)(uintptr_t)a2, sizeof(remoteCapacity)); if (!KSUCCESS(cs)) return cs; - if (remoteCapacity < sizeof(SYS_SOCKET_ADDRESS_IPV4_K)) { - size_t required = sizeof(SYS_SOCKET_ADDRESS_IPV4_K); + if (remoteCapacity < sizeof(SYS_SOCKET_ADDRESS_K)) { + size_t required = sizeof(SYS_SOCKET_ADDRESS_K); (void)CopyToUser((void*)(uintptr_t)a2, &required, sizeof(required)); return STATUS_BUFFER_TOO_SMALL; } - cs = MmProbeUserWrite((void*)(uintptr_t)a1, sizeof(SYS_SOCKET_ADDRESS_IPV4_K)); + cs = MmProbeUserWrite((void*)(uintptr_t)a1, sizeof(SYS_SOCKET_ADDRESS_K)); if (!KSUCCESS(cs)) return cs; } @@ -3131,7 +3556,7 @@ static uint64_t SyscallAccept(uint64_t a0, uint64_t a1, uint64_t a2, if (!listener) return STATUS_NOT_FOUND; SOCKET_OBJECT* accepted = NULL; - SYS_SOCKET_ADDRESS_IPV4_K remote; + SYS_SOCKET_ADDRESS_K remote; KSTATUS s = NetSocketAccept(listener, &accepted, wantRemote ? &remote : NULL); ObDereferenceObject(&listener->Header); if (!KSUCCESS(s)) return s; @@ -3144,7 +3569,7 @@ static uint64_t SyscallAccept(uint64_t a0, uint64_t a1, uint64_t a2, // the returned process handle owns the accepted socket from this point. if (wantRemote) { - size_t actual = sizeof(SYS_SOCKET_ADDRESS_IPV4_K); + size_t actual = sizeof(SYS_SOCKET_ADDRESS_K); s = CopyToUser((void*)(uintptr_t)a1, &remote, sizeof(remote)); if (KSUCCESS(s)) s = CopyToUser((void*)(uintptr_t)a2, &actual, sizeof(actual)); @@ -3160,14 +3585,14 @@ static uint64_t SyscallAccept(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_CONNECT_SOCKET a0=socketHandle a1=*SYS_SOCKET_ADDRESS_IPV4 a2=addressSize. +// SYS_CONNECT_SOCKET a0=socketHandle a1=*SYS_SOCKET_ADDRESS a2=addressSize. static uint64_t SyscallConnect(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a3; (void)a4; (void)a5; if (!a1) return STATUS_INVALID_PARAMETER; - if ((size_t)a2 < sizeof(SYS_SOCKET_ADDRESS_IPV4_K)) return STATUS_BUFFER_TOO_SMALL; + if ((size_t)a2 < sizeof(SYS_SOCKET_ADDRESS_K)) return STATUS_BUFFER_TOO_SMALL; - SYS_SOCKET_ADDRESS_IPV4_K address; + SYS_SOCKET_ADDRESS_K address; KSTATUS s = CopyFromUser(&address, (const void*)(uintptr_t)a1, sizeof(address)); if (!KSUCCESS(s)) return s; @@ -3179,7 +3604,7 @@ static uint64_t SyscallConnect(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_SEND_SOCKET a0=socketHandle a1=*buffer a2=length a3=flags a4=*sentOut(optional). +// SYS_SEND_SOCKET a0=socketHandle a1=*buffer a2=length a3=flags a4=*sentOut(optional). static uint64_t SyscallSend(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a5; @@ -3213,7 +3638,7 @@ static uint64_t SyscallSend(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_RECV_SOCKET a0=socketHandle a1=*buffer a2=length a3=flags a4=*receivedOut(optional). +// SYS_RECV_SOCKET a0=socketHandle a1=*buffer a2=length a3=flags a4=*receivedOut(optional). static uint64_t SyscallRecv(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a5; @@ -3248,7 +3673,7 @@ static uint64_t SyscallRecv(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_SHUTDOWN_SOCKET a0=socketHandle a1=SYS_SHUTDOWN_SOCKET_*. +// SYS_SHUTDOWN_SOCKET a0=socketHandle a1=SYS_SHUTDOWN_SOCKET_*. static uint64_t SyscallShutdown(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; @@ -3261,7 +3686,7 @@ static uint64_t SyscallShutdown(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_SEND_TO_SOCKET a0=socketHandle a1=*SYS_SOCKET_SEND_TO_REQUEST. +// SYS_SEND_TO_SOCKET a0=socketHandle a1=*SYS_SOCKET_SEND_TO_REQUEST. static uint64_t SyscallSendTo(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; @@ -3272,10 +3697,10 @@ static uint64_t SyscallSendTo(uint64_t a0, uint64_t a1, uint64_t a2, if (!KSUCCESS(s)) return s; if (req.Length && !req.Buffer) return STATUS_INVALID_PARAMETER; if (!req.RemoteAddress) return STATUS_INVALID_PARAMETER; - if (req.RemoteAddressSize < sizeof(SYS_SOCKET_ADDRESS_IPV4_K)) return STATUS_BUFFER_TOO_SMALL; + if (req.RemoteAddressSize < sizeof(SYS_SOCKET_ADDRESS_K)) return STATUS_BUFFER_TOO_SMALL; if (req.Length > SYSCALL_IO_MAX_BYTES) return STATUS_INVALID_PARAMETER; - SYS_SOCKET_ADDRESS_IPV4_K remote; + SYS_SOCKET_ADDRESS_K remote; s = CopyFromUser(&remote, req.RemoteAddress, sizeof(remote)); if (!KSUCCESS(s)) return s; @@ -3302,7 +3727,7 @@ static uint64_t SyscallSendTo(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_RECV_FROM_SOCKET a0=socketHandle a1=*SYS_SOCKET_RECV_FROM_REQUEST. +// SYS_RECV_FROM_SOCKET a0=socketHandle a1=*SYS_SOCKET_RECV_FROM_REQUEST. static uint64_t SyscallRecvFrom(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; @@ -3320,12 +3745,12 @@ static uint64_t SyscallRecvFrom(uint64_t a0, uint64_t a1, uint64_t a2, if (!req.RemoteAddressSizeInOut) return STATUS_INVALID_PARAMETER; s = CopyFromUser(&remoteCapacity, req.RemoteAddressSizeInOut, sizeof(remoteCapacity)); if (!KSUCCESS(s)) return s; - if (remoteCapacity < sizeof(SYS_SOCKET_ADDRESS_IPV4_K)) { - size_t required = sizeof(SYS_SOCKET_ADDRESS_IPV4_K); + if (remoteCapacity < sizeof(SYS_SOCKET_ADDRESS_K)) { + size_t required = sizeof(SYS_SOCKET_ADDRESS_K); (void)CopyToUser(req.RemoteAddressSizeInOut, &required, sizeof(required)); return STATUS_BUFFER_TOO_SMALL; } - s = MmProbeUserWrite(req.RemoteAddressOut, sizeof(SYS_SOCKET_ADDRESS_IPV4_K)); + s = MmProbeUserWrite(req.RemoteAddressOut, sizeof(SYS_SOCKET_ADDRESS_K)); if (!KSUCCESS(s)) return s; } @@ -3344,7 +3769,7 @@ static uint64_t SyscallRecvFrom(uint64_t a0, uint64_t a1, uint64_t a2, SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); if (!socket) { if (kbuf) MmFreePool(kbuf); return STATUS_NOT_FOUND; } - SYS_SOCKET_ADDRESS_IPV4_K remote; + SYS_SOCKET_ADDRESS_K remote; memset(&remote, 0, sizeof(remote)); size_t received = 0; s = NetSocketReceiveFrom(socket, kbuf, req.Length, req.Flags, wantRemote ? &remote : NULL, &received); @@ -3355,7 +3780,7 @@ static uint64_t SyscallRecvFrom(uint64_t a0, uint64_t a1, uint64_t a2, if (kbuf) MmFreePool(kbuf); if (KSUCCESS(s) && wantRemote) { - size_t actual = sizeof(SYS_SOCKET_ADDRESS_IPV4_K); + size_t actual = sizeof(SYS_SOCKET_ADDRESS_K); s = CopyToUser(req.RemoteAddressOut, &remote, sizeof(remote)); if (KSUCCESS(s)) s = CopyToUser(req.RemoteAddressSizeInOut, &actual, sizeof(actual)); @@ -3366,8 +3791,7 @@ static uint64_t SyscallRecvFrom(uint64_t a0, uint64_t a1, uint64_t a2, return s; } - -// SYS_RESOLVE_HOST a0=*SYS_RESOLVE_HOST_REQUEST. +// SYS_RESOLVE_HOST a0=*SYS_RESOLVE_HOST_REQUEST. static uint64_t SyscallResolveHost(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; @@ -3379,7 +3803,7 @@ static uint64_t SyscallResolveHost(uint64_t a0, uint64_t a1, uint64_t a2, if (!req.Name.Data || req.Name.Length == 0 || req.Name.Length > SYS_RESOLVE_HOST_NAME_MAX) return STATUS_INVALID_PARAMETER; - if (req.Family != SYS_AF_INET) + if (req.Family != SYS_AF_INET && req.Family != SYS_AF_INET6) return STATUS_NOT_SUPPORTED; if (req.Flags != 0) return STATUS_NOT_SUPPORTED; @@ -3389,8 +3813,8 @@ static uint64_t SyscallResolveHost(uint64_t a0, uint64_t a1, uint64_t a2, size_t capacity = 0; s = CopyFromUser(&capacity, req.AddressSizeInOut, sizeof(capacity)); if (!KSUCCESS(s)) return s; - if (capacity < sizeof(SYS_SOCKET_ADDRESS_IPV4_K)) { - size_t required = sizeof(SYS_SOCKET_ADDRESS_IPV4_K); + if (capacity < sizeof(SYS_SOCKET_ADDRESS_K)) { + size_t required = sizeof(SYS_SOCKET_ADDRESS_K); (void)CopyToUser(req.AddressSizeInOut, &required, sizeof(required)); return STATUS_BUFFER_TOO_SMALL; } @@ -3400,18 +3824,17 @@ static uint64_t SyscallResolveHost(uint64_t a0, uint64_t a1, uint64_t a2, s = KiImportUserTextView(req.Name, name, sizeof(name), &nameView, true); if (!KSUCCESS(s)) return s; - SYS_SOCKET_ADDRESS_IPV4_K address; - s = NetResolveHostIpv4(name, &address); + SYS_SOCKET_ADDRESS_K address; + s = NetResolveHost(name, req.Family, &address); if (!KSUCCESS(s)) return s; - size_t actual = sizeof(SYS_SOCKET_ADDRESS_IPV4_K); + size_t actual = sizeof(SYS_SOCKET_ADDRESS_K); s = CopyToUser(req.AddressOut, &address, sizeof(address)); if (!KSUCCESS(s)) return s; return CopyToUser(req.AddressSizeInOut, &actual, sizeof(actual)); } - -// SYS_SET_SOCKET_OPTION a0=socketHandle a1=*SYS_SOCKET_OPTION_REQUEST. +// SYS_SET_SOCKET_OPTION a0=socketHandle a1=*SYS_SOCKET_OPTION_REQUEST. static uint64_t SyscallSetSocketOption(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; @@ -3435,7 +3858,7 @@ static uint64_t SyscallSetSocketOption(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_GET_SOCKET_OPTION a0=socketHandle a1=*SYS_SOCKET_OPTION_REQUEST. +// SYS_GET_SOCKET_OPTION a0=socketHandle a1=*SYS_SOCKET_OPTION_REQUEST. static uint64_t SyscallGetSocketOption(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; @@ -3478,7 +3901,7 @@ static int KiPollDeadlineExpired(uint64_t start, uint32_t timeoutMs) { return (gHal->Timer.QueryTicks() - start) >= ((uint64_t)timeoutMs * 1000u); } -// SYS_POLL a0=*SYS_POLLFD array a1=count a2=timeoutMs a3=*readyOut(optional). +// SYS_POLL a0=*SYS_POLLFD array a1=count a2=timeoutMs a3=*readyOut(optional). static uint64_t SyscallPoll(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a4; (void)a5; @@ -3535,7 +3958,7 @@ static uint64_t SyscallPoll(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_GET_MEMORY_STATUS a0=*SYS_MEMORY_STATUS. +// SYS_GET_MEMORY_STATUS a0=*SYS_MEMORY_STATUS. static uint64_t SyscallGetMemoryStatus(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; @@ -3559,8 +3982,7 @@ static uint64_t SyscallGetMemoryStatus(uint64_t a0, uint64_t a1, uint64_t a2, return CopyToUser((void*)(uintptr_t)a0, &status, sizeof(status)); } - -// SYS_QUERY_KERNEL_STATS a0=classId a1=buffer a2=bufferSize a3=*requiredSizeOut. +// SYS_QUERY_KERNEL_STATS a0=classId a1=buffer a2=bufferSize a3=*requiredSizeOut. static KSTATUS KiReturnKernelStats(uint64_t bufferArg, size_t bufferSize, uint64_t requiredArg, const void* src, size_t size) { @@ -3609,6 +4031,8 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, SYS_SCHEDULER_STATS us; KeGetSchedulerStats(&ks); memset(&us, 0, sizeof(us)); + us.Size = sizeof(us); + us.Version = SYS_STATS_VERSION; us.Ticks = ks.Ticks; us.ContextSwitches = ks.ContextSwitches; us.KeepCurrentDecisions = ks.KeepCurrentDecisions; @@ -3629,6 +4053,8 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, SYS_PMM_STATS um; MmQueryPhysicalStats(&km); memset(&um, 0, sizeof(um)); + um.Size = sizeof(um); + um.Version = SYS_STATS_VERSION; um.TotalFrames = km.TotalFrames; um.FreeFrames = km.FreeFrames; um.UsedFrames = km.UsedFrames; @@ -3639,12 +4065,13 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um)); } - case SYS_KERNEL_STATS_PFN: { MM_PFN_STATS km; SYS_PFN_STATS um; MmPfnQueryStats(&km); memset(&um, 0, sizeof(um)); + um.Size = sizeof(um); + um.Version = SYS_STATS_VERSION; um.TotalFrames = km.TotalFrames; um.FreeFrames = km.FreeFrames; um.UsedFrames = km.UsedFrames; @@ -3705,6 +4132,8 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, SYS_PTE_STATS um; MmPteQueryStats(&km); memset(&um, 0, sizeof(um)); + um.Size = sizeof(um); + um.Version = SYS_STATS_VERSION; um.MapCalls = km.MapCalls; um.MapFailures = km.MapFailures; um.UnmapCalls = km.UnmapCalls; @@ -3713,7 +4142,7 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, um.ProtectFailures = km.ProtectFailures; um.QueryCalls = km.QueryCalls; um.QueryFailures = km.QueryFailures; - for (size_t i = 0; i < 6 && i < MmPteQueryPurposeCount; i++) { + for (size_t i = 0; i < SYS_PTE_QUERY_PURPOSE_COUNT && i < MmPteQueryPurposeCount; i++) { um.QueryByPurpose[i] = km.QueryByPurpose[i]; um.QueryFailuresByPurpose[i] = km.QueryFailuresByPurpose[i]; } @@ -3737,6 +4166,8 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, SYS_VAD_STATS um; MmVadQueryStats(&km); memset(&um, 0, sizeof(um)); + um.Size = sizeof(um); + um.Version = SYS_STATS_VERSION; um.ResolveFaultCalls = km.ResolveFaultCalls; um.ResolvedFaults = km.ResolvedFaults; um.FailedFaults = km.FailedFaults; @@ -3771,12 +4202,13 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um)); } - case SYS_KERNEL_STATS_FILE_CACHE: { MM_FILE_CACHE_STATS km; SYS_FILE_CACHE_STATS um; MmFileCacheQueryStats(&km); memset(&um, 0, sizeof(um)); + um.Size = sizeof(um); + um.Version = SYS_STATS_VERSION; um.Lookups = km.Lookups; um.Hits = km.Hits; um.Misses = km.Misses; @@ -3832,6 +4264,8 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, SYS_SWAP_STATS um; MmSwapQueryStats(&km); memset(&um, 0, sizeof(um)); + um.Size = sizeof(um); + um.Version = SYS_STATS_VERSION; um.Enabled = km.Enabled; um.TotalSlots = km.TotalSlots; um.UsedSlots = km.UsedSlots; @@ -3852,6 +4286,8 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, SYS_FILESYSTEM_STATS um; IoQueryFilesystemStats(&km); memset(&um, 0, sizeof(um)); + um.Size = sizeof(um); + um.Version = SYS_STATS_VERSION; um.PathLookups = km.PathLookups; um.PathCacheHits = km.PathCacheHits; um.PathCacheMisses = km.PathCacheMisses; @@ -3859,14 +4295,8 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, um.PathCacheInvalidations = km.PathCacheInvalidations; um.PathCacheGeneration = km.PathCacheGeneration; um.DirectorySearches = km.DirectorySearches; - um.DirectoryClustersRead = km.DirectoryClustersRead; + um.DirectoryBlocksRead = km.DirectoryBlocksRead; um.DirectoryEntriesScanned = km.DirectoryEntriesScanned; - um.FatChainCacheLookups = km.FatChainCacheLookups; - um.FatChainCacheHits = km.FatChainCacheHits; - um.FatChainCacheMisses = km.FatChainCacheMisses; - um.FatChainCacheStores = km.FatChainCacheStores; - um.FatChainCacheInvalidations = km.FatChainCacheInvalidations; - um.FatChainWalkSteps = km.FatChainWalkSteps; um.DataReadRuns = km.DataReadRuns; um.DataReadSectors = km.DataReadSectors; um.DataReadRunMerges = km.DataReadRunMerges; @@ -3879,6 +4309,8 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, SYS_DISK_STATS um; IoQueryDiskStats(&km); memset(&um, 0, sizeof(um)); + um.Size = sizeof(um); + um.Version = SYS_STATS_VERSION; um.RegisteredDisks = km.RegisteredDisks; um.ReadRequests = km.ReadRequests; um.ReadSectors = km.ReadSectors; @@ -3901,16 +4333,27 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, } case SYS_KERNEL_STATS_CPU: { - SYS_CPU_STATS stats; + struct { + SYS_CPU_STATS Header; + SYS_CPU_STAT Records[KE_MAX_CPUS]; + } stats; memset(&stats, 0, sizeof(stats)); - stats.CpuCount = gCpuCount; + uint32_t n = gCpuCount; - if (n > SYS_KERNEL_STATS_MAX_CPUS) - n = SYS_KERNEL_STATS_MAX_CPUS; + if (n > KE_MAX_CPUS) + n = KE_MAX_CPUS; + + size_t size = sizeof(SYS_CPU_STATS) + (size_t)n * sizeof(SYS_CPU_STAT); + stats.Header.Size = (uint32_t)size; + stats.Header.Version = SYS_STATS_VERSION; + stats.Header.HeaderSize = sizeof(SYS_CPU_STATS); + stats.Header.CpuCount = gCpuCount; + stats.Header.RecordSize = sizeof(SYS_CPU_STAT); + stats.Header.RecordCount = n; for (uint32_t i = 0; i < n; i++) { KCPU* cpu = &gCpus[i]; - SYS_CPU_STAT* out = &stats.Cpu[i]; + SYS_CPU_STAT* out = &stats.Records[i]; out->CpuIndex = cpu->CpuIndex; out->LapicId = cpu->LapicId; if (cpu->Present) out->Flags |= SYS_CPU_FLAG_PRESENT; @@ -3925,7 +4368,7 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, out->ContextSwitches = cpu->ContextSwitches; } - return KiReturnKernelStats(a1, (size_t)a2, a3, &stats, sizeof(stats)); + return KiReturnKernelStats(a1, (size_t)a2, a3, &stats, size); } default: @@ -3933,120 +4376,141 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, } } -// Dispatch table - index must match SYS_* in syscall_nr.h exactly. +// Dispatch table keyed by SYS_* constants. typedef uint64_t (*SYSCALL_HANDLER)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t); static const SYSCALL_HANDLER gSyscallTable[SYS_MAX] = { - SyscallExitProcess, // 00 SYS_EXIT_PROCESS. - SyscallExitThread, // 01 SYS_EXIT_THREAD. - SyscallCreateProcess, // 02 SYS_CREATE_PROCESS. - SyscallWaitSingle, // 03 SYS_WAIT_SINGLE. - SyscallGetProcessExitCode, // 04 SYS_GET_PROCESS_EXIT_CODE. - SyscallKillProcess, // 05 SYS_KILL_PROCESS. - SyscallGetProcessList, // 06 SYS_GET_PROCESS_LIST. - SyscallGetProcessPid, // 07 SYS_GET_PROCESS_PID. - SyscallCreateThread, // 08 SYS_CREATE_THREAD. - 0, // 09 retired SYS_WAIT_THREAD. - SyscallGetCurrentTid, // 10 SYS_GET_CURRENT_TID. - SyscallGetCurrentPid, // 11 SYS_GET_CURRENT_PID. - SyscallSetThreadPriority, // 12 SYS_SET_THREAD_PRIORITY. - SyscallYield, // 13 SYS_YIELD. - SyscallSleep, // 14 SYS_SLEEP. - SyscallSetUserApcDispatcher, // 15 SYS_SET_USER_APC_DISPATCHER. - SyscallApcReturn, // 16 SYS_APC_RETURN. - SyscallVmAlloc, // 17 SYS_ALLOC_VM. - SyscallVmFree, // 18 SYS_FREE_VM. - SyscallVmProtect, // 19 SYS_PROTECT_VM. - SyscallVmQuery, // 20 SYS_QUERY_VM. - SyscallCloseHandle, // 21 SYS_CLOSE_HANDLE. - SyscallMutexCreate, // 22 SYS_CREATE_MUTEX. - 0, // 23 retired SYS_ACQUIRE_MUTEX. - SyscallMutexTryAcquire, // 24 SYS_TRY_ACQUIRE_MUTEX. - SyscallMutexRelease, // 25 SYS_RELEASE_MUTEX. - SyscallSemCreate, // 26 SYS_CREATE_SEM. - 0, // 27 retired SYS_WAIT_SEM. - SyscallSemTryWait, // 28 SYS_TRY_WAIT_SEM. - SyscallSemSignal, // 29 SYS_SIGNAL_SEM. - SyscallEventCreate, // 30 SYS_CREATE_EVENT. - SyscallEventSignal, // 31 SYS_SIGNAL_EVENT. - SyscallEventClear, // 32 SYS_CLEAR_EVENT. - 0, // 33 retired SYS_WAIT_EVENT. - SyscallQueueCreate, // 34 SYS_CREATE_QUEUE. - SyscallQueueSend, // 35 SYS_SEND_QUEUE. - SyscallQueueRecv, // 36 SYS_RECV_QUEUE. - SyscallPublishQueue, // 37 SYS_PUBLISH_QUEUE. - SyscallOpenNamedQueue, // 38 SYS_OPEN_NAMED_QUEUE. - SyscallQueueDuplicate, // 39 SYS_DUPLICATE_QUEUE. - SyscallNamedQueueOwnerPid, // 40 SYS_GET_NAMED_QUEUE_OWNER_PID. - SyscallOpenFile, // 41 SYS_OPEN_FILE. - SyscallCreateFile, // 42 SYS_CREATE_FILE. - SyscallReadFile, // 43 SYS_READ_FILE. - SyscallWriteFile, // 44 SYS_WRITE_FILE. - SyscallReadFileAsync, // 45 SYS_READ_FILE_ASYNC. - SyscallWriteFileAsync, // 46 SYS_WRITE_FILE_ASYNC. - SyscallCancelIo, // 47 SYS_CANCEL_IO. - SyscallSeekFile, // 48 SYS_SEEK_FILE. - SyscallGetFilePos, // 49 SYS_GET_FILE_POS. - SyscallGetFileSize, // 50 SYS_GET_FILE_SIZE. - SyscallTruncateFile, // 51 SYS_TRUNCATE_FILE. - SyscallDeleteFile, // 52 SYS_DELETE_FILE. - SyscallRenameFile, // 53 SYS_RENAME_FILE. - SyscallOpenDir, // 54 SYS_OPEN_DIR. - SyscallReadDir, // 55 SYS_READ_DIR. - SyscallCreateDir, // 56 SYS_CREATE_DIR. - SyscallRemoveDir, // 57 SYS_REMOVE_DIR. - SyscallSetCwd, // 58 SYS_SET_CWD. - SyscallGetCwd, // 59 SYS_GET_CWD. - SyscallWriteConsole, // 60 SYS_WRITE_CONSOLE. - SyscallGetConsoleInfo, // 61 SYS_GET_CONSOLE_INFO. - SyscallSetConsoleCursor, // 62 SYS_SET_CONSOLE_CURSOR. - SyscallFillConsoleCharacter, // 63 SYS_FILL_CONSOLE_CHARACTER. - SyscallFillConsoleAttribute, // 64 SYS_FILL_CONSOLE_ATTRIBUTE. - SyscallPutScreenChar, // 65 SYS_PUT_SCREEN_CHAR. - SyscallClearScreen, // 66 SYS_CLEAR_SCREEN. - SyscallGetScreenSize, // 67 SYS_GET_SCREEN_SIZE. - SyscallQueryDisplayInfo, // 68 SYS_QUERY_DISPLAY_INFO. - SyscallKbPoll, // 69 SYS_POLL_KEYBOARD. - SyscallKbIsKeyDown, // 70 SYS_IS_KEY_DOWN. - SyscallKbReadChar, // 71 SYS_READ_KEYBOARD_CHAR. - SyscallKbReadKey, // 72 SYS_READ_KEY. - SyscallKbGetModifiers, // 73 SYS_GET_KEY_MODIFIERS. - SyscallMousePoll, // 74 SYS_POLL_MOUSE. - SyscallMouseRead, // 75 SYS_READ_MOUSE. - SyscallMouseGetPos, // 76 SYS_GET_MOUSE_POS. - SyscallGetTick, // 77 SYS_GET_TICK. - SyscallDebugPrint, // 78 SYS_PRINT_DEBUG. - SyscallQueryTrapFrame, // 79 SYS_QUERY_TRAP_FRAME. - SyscallSocket, // 80 SYS_CREATE_SOCKET. - SyscallBind, // 81 SYS_BIND_SOCKET. - SyscallListen, // 82 SYS_LISTEN_SOCKET. - SyscallAccept, // 83 SYS_ACCEPT_SOCKET. - SyscallConnect, // 84 SYS_CONNECT_SOCKET. - SyscallSend, // 85 SYS_SEND_SOCKET. - SyscallRecv, // 86 SYS_RECV_SOCKET. - SyscallShutdown, // 87 SYS_SHUTDOWN_SOCKET. - SyscallSendTo, // 88 SYS_SEND_TO_SOCKET. - SyscallRecvFrom, // 89 SYS_RECV_FROM_SOCKET. - SyscallResolveHost, // 90 SYS_RESOLVE_HOST. - SyscallSetSocketOption, // 91 SYS_SET_SOCKET_OPTION. - SyscallGetSocketOption, // 92 SYS_GET_SOCKET_OPTION. - SyscallPoll, // 93 SYS_POLL. - SyscallGetMemoryStatus, // 94 SYS_GET_MEMORY_STATUS. - SyscallQueryKernelStats, // 95 SYS_QUERY_KERNEL_STATS. - SyscallGetSystemTime, // 96 SYS_GET_SYSTEM_TIME. - SyscallCreateFileMapping, // 97 SYS_CREATE_FILE_MAPPING. - SyscallMapFile, // 98 SYS_MAP_FILE. - SyscallCreateSurface, // 99 SYS_CREATE_SURFACE. - SyscallMapSurface, // 100 SYS_MAP_SURFACE. - SyscallQuerySurface, // 101 SYS_QUERY_SURFACE. - SyscallPresentSurface, // 102 SYS_PRESENT_SURFACE. - SyscallGetThreadList, // 103 SYS_GET_THREAD_LIST. - SyscallKbReadInputEvent, // 104 SYS_READ_INPUT_EVENT. - SyscallKbPeekInputEvent, // 105 SYS_PEEK_INPUT_EVENT. - SyscallWaitMany, // 106 SYS_WAIT_MANY. - SyscallOpenInputQueue, // 107 SYS_OPEN_INPUT_QUEUE. - SyscallFlushFile, // 108 SYS_FLUSH_FILE. + // Process, threads, waits, and APC. + [SYS_EXIT_PROCESS] = SyscallExitProcess, + [SYS_EXIT_THREAD] = SyscallExitThread, + [SYS_CREATE_PROCESS] = SyscallCreateProcess, + [SYS_OPEN_PROCESS] = SyscallOpenProcess, + [SYS_GET_PROCESS_EXIT_CODE] = SyscallGetProcessExitCode, + [SYS_KILL_PROCESS] = SyscallKillProcess, + [SYS_GET_PROCESS_LIST] = SyscallGetProcessList, + [SYS_GET_PROCESS_PID] = SyscallGetProcessPid, + [SYS_CREATE_THREAD] = SyscallCreateThread, + [SYS_GET_THREAD_LIST] = SyscallGetThreadList, + [SYS_GET_CURRENT_TID] = SyscallGetCurrentTid, + [SYS_GET_CURRENT_PID] = SyscallGetCurrentPid, + [SYS_SET_THREAD_PRIORITY] = SyscallSetThreadPriority, + [SYS_YIELD] = SyscallYield, + [SYS_SLEEP] = SyscallSleep, + [SYS_WAIT_SINGLE] = SyscallWaitSingle, + [SYS_WAIT_MANY] = SyscallWaitMany, + [SYS_SET_USER_APC_DISPATCHER] = SyscallSetUserApcDispatcher, + [SYS_APC_RETURN] = SyscallApcReturn, + + // Virtual memory, mappings, and handles. + [SYS_ALLOC_VM] = SyscallVmAlloc, + [SYS_FREE_VM] = SyscallVmFree, + [SYS_PROTECT_VM] = SyscallVmProtect, + [SYS_QUERY_VM] = SyscallVmQuery, + [SYS_CLOSE_HANDLE] = SyscallCloseHandle, + [SYS_DUPLICATE_HANDLE] = SyscallDuplicateHandle, + [SYS_CREATE_FILE_MAPPING] = SyscallCreateFileMapping, + [SYS_MAP_FILE] = SyscallMapFile, + + // Synchronization objects. + [SYS_CREATE_MUTEX] = SyscallMutexCreate, + [SYS_TRY_ACQUIRE_MUTEX] = SyscallMutexTryAcquire, + [SYS_RELEASE_MUTEX] = SyscallMutexRelease, + [SYS_CREATE_SEM] = SyscallSemCreate, + [SYS_TRY_WAIT_SEM] = SyscallSemTryWait, + [SYS_SIGNAL_SEM] = SyscallSemSignal, + [SYS_CREATE_EVENT] = SyscallEventCreate, + [SYS_SIGNAL_EVENT] = SyscallEventSignal, + [SYS_CLEAR_EVENT] = SyscallEventClear, + + // Queues and named queues. + [SYS_CREATE_QUEUE] = SyscallQueueCreate, + [SYS_SEND_QUEUE] = SyscallQueueSend, + [SYS_RECV_QUEUE] = SyscallQueueRecv, + [SYS_PUBLISH_QUEUE] = SyscallPublishQueue, + [SYS_OPEN_NAMED_QUEUE] = SyscallOpenNamedQueue, + [SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS] = SyscallOpenNamedQueueOwnerProcess, + + // Files, directories, and current directory. + [SYS_OPEN_FILE] = SyscallOpenFile, + [SYS_READ_FILE] = SyscallReadFile, + [SYS_WRITE_FILE] = SyscallWriteFile, + [SYS_READ_FILE_ASYNC] = SyscallReadFileAsync, + [SYS_WRITE_FILE_ASYNC] = SyscallWriteFileAsync, + [SYS_CANCEL_IO] = SyscallCancelIo, + [SYS_SEEK_FILE] = SyscallSeekFile, + [SYS_GET_FILE_POS] = SyscallGetFilePos, + [SYS_GET_FILE_SIZE] = SyscallGetFileSize, + [SYS_GET_FILE_STAT] = SyscallGetFileStat, + [SYS_STAT_FILE] = SyscallStatFile, + [SYS_TRUNCATE_FILE] = SyscallTruncateFile, + [SYS_FLUSH_FILE] = SyscallFlushFile, + [SYS_DELETE_FILE] = SyscallDeleteFile, + [SYS_RENAME_FILE] = SyscallRenameFile, + [SYS_LOCK_FILE] = SyscallLockFile, + [SYS_UNLOCK_FILE] = SyscallUnlockFile, + [SYS_OPEN_DIR] = SyscallOpenDir, + [SYS_READ_DIR] = SyscallReadDir, + [SYS_CREATE_DIR] = SyscallCreateDir, + [SYS_REMOVE_DIR] = SyscallRemoveDir, + [SYS_SET_CWD] = SyscallSetCwd, + [SYS_REAL_PATH] = SyscallRealPath, + + // Console, display, and surfaces. + [SYS_WRITE_CONSOLE] = SyscallWriteConsole, + [SYS_GET_CONSOLE_INFO] = SyscallGetConsoleInfo, + [SYS_SET_CONSOLE_CURSOR] = SyscallSetConsoleCursor, + [SYS_FILL_CONSOLE_CHARACTER] = SyscallFillConsoleCharacter, + [SYS_FILL_CONSOLE_ATTRIBUTE] = SyscallFillConsoleAttribute, + [SYS_PUT_SCREEN_CHAR] = SyscallPutScreenChar, + [SYS_CLEAR_SCREEN] = SyscallClearScreen, + [SYS_GET_SCREEN_SIZE] = SyscallGetScreenSize, + [SYS_QUERY_DISPLAY_INFO] = SyscallQueryDisplayInfo, + [SYS_CREATE_SURFACE] = SyscallCreateSurface, + [SYS_MAP_SURFACE] = SyscallMapSurface, + [SYS_QUERY_SURFACE] = SyscallQuerySurface, + [SYS_PRESENT_SURFACE] = SyscallPresentSurface, + + // Input. + [SYS_POLL_KEYBOARD] = SyscallKbPoll, + [SYS_IS_KEY_DOWN] = SyscallKbIsKeyDown, + [SYS_READ_KEYBOARD_CHAR] = SyscallKbReadChar, + [SYS_READ_KEY] = SyscallKbReadKey, + [SYS_GET_KEY_MODIFIERS] = SyscallKbGetModifiers, + [SYS_OPEN_INPUT_QUEUE] = SyscallOpenInputQueue, + [SYS_READ_INPUT_EVENT] = SyscallKbReadInputEvent, + [SYS_PEEK_INPUT_EVENT] = SyscallKbPeekInputEvent, + [SYS_PEEK_MOUSE] = SyscallMousePeek, + [SYS_READ_MOUSE] = SyscallMouseRead, + [SYS_GET_MOUSE_POS] = SyscallMouseGetPos, + + // Time, debug, and system information. + [SYS_CREATE_TIMER] = SyscallCreateTimer, + [SYS_SET_TIMER] = SyscallSetTimer, + [SYS_CANCEL_TIMER] = SyscallCancelTimer, + [SYS_GET_TICK] = SyscallGetTick, + [SYS_GET_TICK_FREQUENCY] = SyscallGetTickFrequency, + [SYS_GET_SYSTEM_TIME] = SyscallGetSystemTime, + [SYS_PRINT_DEBUG] = SyscallDebugPrint, + [SYS_QUERY_TRAP_FRAME] = SyscallQueryTrapFrame, + [SYS_GET_MEMORY_STATUS] = SyscallGetMemoryStatus, + [SYS_QUERY_KERNEL_STATS] = SyscallQueryKernelStats, + + // Native sockets and polling. + [SYS_CREATE_SOCKET] = SyscallSocket, + [SYS_BIND_SOCKET] = SyscallBind, + [SYS_LISTEN_SOCKET] = SyscallListen, + [SYS_ACCEPT_SOCKET] = SyscallAccept, + [SYS_CONNECT_SOCKET] = SyscallConnect, + [SYS_SEND_SOCKET] = SyscallSend, + [SYS_RECV_SOCKET] = SyscallRecv, + [SYS_SHUTDOWN_SOCKET] = SyscallShutdown, + [SYS_SEND_TO_SOCKET] = SyscallSendTo, + [SYS_RECV_FROM_SOCKET] = SyscallRecvFrom, + [SYS_RESOLVE_HOST] = SyscallResolveHost, + [SYS_SET_SOCKET_OPTION] = SyscallSetSocketOption, + [SYS_GET_SOCKET_OPTION] = SyscallGetSocketOption, + [SYS_POLL] = SyscallPoll, }; // KeSyscallDispatch is entered from ke_syscall.asm after the assembly stub has reordered registers into the C calling convention. @@ -4058,7 +4522,7 @@ uint64_t KeSyscallDispatch(uint64_t nr, uint64_t a0, uint64_t a1, PROCESS* owner = self ? self->Owner : (PROCESS*)0; if (owner && KeIsProcessTerminating(owner) && nr != SYS_EXIT_PROCESS && nr != SYS_EXIT_THREAD) { - int exitCode = owner->ExitCode; + int32_t exitCode = owner->ExitCode; KeExitThread(self, exitCode); while (true) ; } @@ -4073,7 +4537,7 @@ uint64_t KeSyscallDispatch(uint64_t nr, uint64_t a0, uint64_t a1, owner = self ? self->Owner : (PROCESS*)0; if (owner && KeIsProcessTerminating(owner) && nr != SYS_EXIT_PROCESS && nr != SYS_EXIT_THREAD) { - int exitCode = owner->ExitCode; + int32_t exitCode = owner->ExitCode; KeExitThread(self, exitCode); while (true) ; } diff --git a/code/kernel/ke_thread.c b/code/kernel/ke_thread.c index 6e2b92d..00e2cb8 100644 --- a/code/kernel/ke_thread.c +++ b/code/kernel/ke_thread.c @@ -75,7 +75,7 @@ static void KiThreadDestroy(OBJECT* obj) { if (remaining == 0) { // The last thread leaving a process is process exit even if user - // code called ExitThread instead of ExitProcess. KeExitProcess / + // code called ExitThread instead of ExitProcess. KeExitProcess / // KeTerminateProcess set Terminating earlier; this covers the last // thread voluntarily exiting without an explicit process-exit call. _InterlockedExchange(&owner->Terminating, 1); @@ -151,10 +151,10 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui return STATUS_NO_MEMORY; } thread->UserStackBase = ustack; - // Ring-3 starts at the image entry via IRET, not CALL. The MS x64 ABI + // Ring-3 starts at the image entry via IRET, not CALL. The MS x64 ABI // nevertheless lets callees immediately use the caller-provided 32-byte // home/shadow area above RSP, and MSVC commonly emits a first instruction - // like "mov [rsp+8], rcx" in unoptimized test code. Bias the synthetic + // like "mov [rsp+8], rcx" in unoptimized test code. Bias the synthetic // entry stack as if a CALL with a normal home area had happened: // // RSP % 16 == 8 at function entry @@ -190,12 +190,12 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui uintptr_t* ksp = (uintptr_t *)thread->KernelStackTop; *(--ksp) = userStackTop; - *(--ksp) = 0x23ULL; - *(--ksp) = userStackTop; - *(--ksp) = 0x202ULL; - *(--ksp) = 0x1BULL; - *(--ksp) = entry; - *(--ksp) = arg; + *(--ksp) = 0x23ULL; + *(--ksp) = userStackTop; + *(--ksp) = 0x202ULL; + *(--ksp) = 0x1BULL; + *(--ksp) = entry; + *(--ksp) = arg; ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t); KCONTEXT* ctx = (KCONTEXT *)ksp; @@ -233,7 +233,7 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui KeInitThreadFpuContext(thread); // Hand the creator a short-lived reference before making the thread - // runnable. On SMP a new user thread can run and exit before + // runnable. On SMP a new user thread can run and exit before // KeCreateThread() returns; without this reference, callers that merely // read ThreadId for logging or syscall output can race the scheduler's // dead-thread dereference and touch freed pool. @@ -332,8 +332,8 @@ static KSTATUS KiCreateKernelThreadWithAffinity(uintptr_t fn, uintptr_t arg, // Build the initial stack: arg, fn, then KCONTEXT. uintptr_t* ksp = (uintptr_t*)thread->KernelStackTop; - *(--ksp) = arg; // popped second (-> r12) - *(--ksp) = fn; // popped first (-> rbx) + *(--ksp) = arg; // popped second (-> r12) + *(--ksp) = fn; // popped first (-> rbx) ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t); KCONTEXT* ctx = (KCONTEXT*)ksp; @@ -351,9 +351,9 @@ static KSTATUS KiCreateKernelThreadWithAffinity(uintptr_t fn, uintptr_t arg, KeReleaseSpinlock(&owner->HandleLock, flags); // Hand the creator a short-lived reference before making the kernel - // thread runnable. Kernel workers can run, signal their completion event, + // thread runnable. Kernel workers can run, signal their completion event, // exit, and be reaped on another CPU before KeCreateKernelThread()'s - // caller drops its returned pointer. The live scheduler reference remains + // caller drops its returned pointer. The live scheduler reference remains // separate and is released by KeSchedulerPostSwitch() after the dead // thread has switched away. if (out) { @@ -395,7 +395,6 @@ void KePrepareBlockThread(THREAD* thread, OBJECT* waitObject, uint8_t alertable) thread->WaitRundownContext = (void*)0; } - // Updates thread Wait List. void KeSetThreadWaitList(THREAD* thread, LIST_ENTRY* waitListHead) { KeAssertIrqlAtOrBelow(DISPATCH_LEVEL); @@ -483,14 +482,12 @@ KSTATUS KeCommitBlockThread(THREAD* thread, uint8_t alertable) { return thread->WaitStatus; } - // Implements the block Thread operation. KSTATUS KeBlockThread(THREAD* thread, OBJECT* waitObject, uint8_t alertable) { KePrepareBlockThread(thread, waitObject, alertable); return KeCommitBlockThread(thread, alertable); } - // Implements the unblock Thread With Status operation. void KeUnblockThreadWithStatus(THREAD* thread, KSTATUS status) { if (!thread) return; diff --git a/code/kernel/ke_timer.c b/code/kernel/ke_timer.c new file mode 100644 index 0000000..ae14033 --- /dev/null +++ b/code/kernel/ke_timer.c @@ -0,0 +1,219 @@ +// Kernel waitable timer objects. This file owns timer deadlines and timer wait signaling. + +#include +#include +#include +#include +#include + +#define TIMER_SIGNAL_BATCH 32u + +static LIST_ENTRY gTimerList; +static SPINLOCK gTimerLock; +static uint8_t gTimerInitialized; + +// Initializes the global timer list on first use. +static void KiTimerEnsureInitialized(void) { + if (gTimerInitialized) + return; + + gTimerLock = (SPINLOCK)SPINLOCK_INIT; + LIST_INIT(&gTimerList); + gTimerInitialized = 1; +} + +// Reads the scheduler tick value used by timer deadlines. +static uint64_t KiTimerReadSchedulerTick(void) { + KE_SCHED_STATS stats; + KeGetSchedulerStats(&stats); + return stats.Ticks; +} + +// Converts a millisecond interval to at least one scheduler tick. +static uint64_t KiTimerMillisecondsToTicks(uint64_t milliseconds) { + if (milliseconds == 0) + return 0; + + uint64_t ticks = milliseconds / SCHED_TICK_MS; + if ((milliseconds % SCHED_TICK_MS) != 0) + ticks++; + return ticks ? ticks : 1; +} + +// Adds a tick interval to the current scheduler tick with saturation. +static uint64_t KiTimerDeadlineFromNow(uint64_t intervalTicks) { + uint64_t now = KiTimerReadSchedulerTick(); + uint64_t due = now + intervalTicks; + return due < now ? UINT64_MAX : due; +} + +// Removes an active timer from the global deadline list while the timer lock is held. +static uint8_t KiTimerRemoveLocked(KTIMER* timer) { + if (!timer || !timer->Linked) + return 0; + + ListRemove(&timer->Link); + timer->Linked = 0; + timer->Active = 0; + timer->DueTick = 0; + timer->PeriodTicks = 0; + return 1; +} + +// Implements the timer Destroy operation. +static void KiTimerDestroy(OBJECT* obj) { + KTIMER* timer = (KTIMER*)obj; + if (!timer) + return; + + KiTimerEnsureInitialized(); + uintptr_t flags = KeAcquireSpinlock(&gTimerLock); + (void)KiTimerRemoveLocked(timer); + KeReleaseSpinlock(&gTimerLock, flags); + + if (timer->Event) { + ObDereferenceObject(&timer->Event->Header); + timer->Event = (EVENT*)0; + } +} + +// Creates an unset waitable timer object. +KSTATUS KeTimerCreate(KTIMER** out) { + if (!out) + return STATUS_INVALID_PARAMETER; + *out = (KTIMER*)0; + + KiTimerEnsureInitialized(); + + KTIMER* timer = (KTIMER*)ObAllocateObject(ObjectTypeTimer, sizeof(KTIMER)); + if (!timer) + return STATUS_NO_MEMORY; + + timer->Header.Destroy = KiTimerDestroy; + LIST_INIT(&timer->Link); + + KSTATUS s = KeEventCreate(EventAutoReset, 0, &timer->Event); + if (!KSUCCESS(s)) { + ObDereferenceObject(&timer->Header); + return s; + } + + *out = timer; + return STATUS_SUCCESS; +} + +// Arms or rearms a timer with a relative due time and optional period. +KSTATUS KeTimerSet(KTIMER* timer, uint64_t dueTimeMilliseconds, uint64_t periodMilliseconds) { + if (!timer || !timer->Event) + return STATUS_INVALID_PARAMETER; + + KiTimerEnsureInitialized(); + + uint64_t dueTicks = KiTimerMillisecondsToTicks(dueTimeMilliseconds); + uint64_t periodTicks = KiTimerMillisecondsToTicks(periodMilliseconds); + uint8_t dropListReference = 0; + uint8_t addListReference = 0; + uint8_t signalNow = dueTicks == 0; + EVENT* event = timer->Event; + ObReferenceObject(&event->Header); + + uintptr_t flags = KeAcquireSpinlock(&gTimerLock); + + if (timer->Linked) { + if (signalNow && periodTicks == 0) { + dropListReference = KiTimerRemoveLocked(timer); + } else { + timer->DueTick = KiTimerDeadlineFromNow(signalNow ? periodTicks : dueTicks); + timer->PeriodTicks = periodTicks; + timer->Active = 1; + } + } else if (!signalNow || periodTicks != 0) { + timer->DueTick = KiTimerDeadlineFromNow(signalNow ? periodTicks : dueTicks); + timer->PeriodTicks = periodTicks; + timer->Active = 1; + timer->Linked = 1; + ListInsertTail(&gTimerList, &timer->Link); + addListReference = 1; + } + + KeReleaseSpinlock(&gTimerLock, flags); + + if (addListReference) + ObReferenceObject(&timer->Header); + if (dropListReference) + ObDereferenceObject(&timer->Header); + + KeClearEvent(event); + if (signalNow) + KeSignalEvent(event); + ObDereferenceObject(&event->Header); + return STATUS_SUCCESS; +} + +// Cancels a timer and clears any retained signal. +KSTATUS KeTimerCancel(KTIMER* timer) { + if (!timer || !timer->Event) + return STATUS_INVALID_PARAMETER; + + KiTimerEnsureInitialized(); + + uintptr_t flags = KeAcquireSpinlock(&gTimerLock); + uint8_t hadListReference = KiTimerRemoveLocked(timer); + KeReleaseSpinlock(&gTimerLock, flags); + + if (hadListReference) + ObDereferenceObject(&timer->Header); + KeClearEvent(timer->Event); + return STATUS_SUCCESS; +} + +// Returns the event that backs timer waits. +EVENT* KeTimerWaitEvent(KTIMER* timer) { + if (!timer) + return (EVENT*)0; + return timer->Event; +} + +// Services expired timers from the scheduler tick path. +void KeTimerTick(void) { + KiTimerEnsureInitialized(); + + EVENT* events[TIMER_SIGNAL_BATCH]; + KTIMER* listReferencesToDrop[TIMER_SIGNAL_BATCH]; + uint32_t count = 0; + uint32_t dropCount = 0; + uint64_t now = KiTimerReadSchedulerTick(); + + uintptr_t flags = KeAcquireSpinlock(&gTimerLock); + LIST_ENTRY* node = gTimerList.Fwd; + while (node != &gTimerList && count < TIMER_SIGNAL_BATCH) { + LIST_ENTRY* next = node->Fwd; + KTIMER* timer = CONTAINING_RECORD(node, KTIMER, Link); + + if (timer->Active && timer->DueTick <= now) { + if (timer->Event) { + ObReferenceObject(&timer->Event->Header); + events[count++] = timer->Event; + } + + if (timer->PeriodTicks != 0) { + uint64_t due = now + timer->PeriodTicks; + timer->DueTick = due < now ? UINT64_MAX : due; + } else { + if (KiTimerRemoveLocked(timer)) + listReferencesToDrop[dropCount++] = timer; + } + } + + node = next; + } + KeReleaseSpinlock(&gTimerLock, flags); + + for (uint32_t i = 0; i < dropCount; i++) + ObDereferenceObject(&listReferencesToDrop[i]->Header); + + for (uint32_t i = 0; i < count; i++) { + KeSignalEvent(events[i]); + ObDereferenceObject(&events[i]->Header); + } +} diff --git a/code/kernel/kmain.c b/code/kernel/kmain.c index c47a259..e2c6821 100644 --- a/code/kernel/kmain.c +++ b/code/kernel/kmain.c @@ -3,8 +3,6 @@ #include #include #include -#pragma intrinsic(_disable) -#pragma intrinsic(_enable) #include #include #include @@ -175,7 +173,6 @@ static void KiResolveSystemImports(BOOT_INFO* bootInfo) { } } - // Phase Helper: Initialize dynamically loaded .sys drivers. static void KiDriversIniCb(const char* section, const char* key, const char* val, void* ud) { @@ -199,7 +196,7 @@ static void KiInitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) { for (uint32_t i = 0; i < bootInfo->ModuleCount; i++) { BOOT_MODULE* mod = &bootInfo->Modules[i]; - // Skip modules that are already mapped (Kernel/HAL) + // Skip modules that are already mapped (Kernel/HAL) // or have no physical data (invalid). if (mod->VirtBase != 0 || mod->PhysBase == 0) continue; @@ -323,7 +320,7 @@ static void KiInitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) { DbgPrint("KiInitializeDrivers: Failed to resolve imports for runtime driver: %s\n", runtimeKeys[i]); continue; } - // Runtime modules may also export DDIs to drivers loaded later. Boot + // Runtime modules may also export DDIs to drivers loaded later. Boot // drivers were already registered above; keep the module model uniform. LdrRegisterModule(runtimeKeys[i], mappedBase); @@ -490,9 +487,8 @@ extern void KernelMain(BOOT_INFO* bootInfo) { // Physical & Virtual Memory initialization. - if (!MmInitializePhysical(&bootInfo->MemoryMap, bootInfo->PhysicalMapBase)) { - _disable(); while (true) __halt(); // there's nothing we can do - } + if (!MmInitializePhysical(&bootInfo->MemoryMap, bootInfo->PhysicalMapBase)) + HalStopProcessor(); PHYSICAL_ADDRESS pml4 = (PHYSICAL_ADDRESS)__readcr3(); // The early virtual allocator starts small because it only needs bootstrap page tables before the PMM is online. @@ -523,17 +519,15 @@ extern void KernelMain(BOOT_INFO* bootInfo) { }; KSTATUS s = HalInitialize(&halParams); - if (!KSUCCESS(s)) { - _disable(); while (true) __halt(); // there's nothing we can do. - } + if (!KSUCCESS(s)) + HalStopProcessor(); const HAL_API* hal = HalGetApi(); gKmainHal = hal; - KeInitDebug(hal->Screen.PrintChar, bootInfo->Debug); + KeInitDebug(hal, bootInfo->Debug); KeTimeInitialize(bootInfo, hal); KeDisplayInitialize(hal); - KeSetDebugConsoleWriter(KeDisplayDebugPrintChar); // CPU startup intentionally lives after HAL initialization but before IRQs // and scheduler startup. HAL owns ACPI/MADT discovery and LAPIC INIT/SIPI; @@ -595,11 +589,7 @@ extern void KernelMain(BOOT_INFO* bootInfo) { DbgPrint("KernelMain: Handing over to scheduler...\n"); - // Ignite the system! - _enable(); - - // The Idle Loop: The CPU rests here whenever all other threads are waiting on events. - while (true) { - __halt(); - } + // The bootstrap CPU becomes the scheduler idle context once interrupts are enabled. + while (true) + HalWaitForInterrupt(); } diff --git a/code/kernel/ldr_image.c b/code/kernel/ldr_image.c index 5fe64c1..68a6f4c 100644 --- a/code/kernel/ldr_image.c +++ b/code/kernel/ldr_image.c @@ -233,11 +233,31 @@ void LdrRegisterModulesFromBootInfo(const BOOT_INFO* bootInfo) { // Implements the ldr Register Module operation. void LdrRegisterModule(const char* name, uintptr_t base) { if (gModuleCount < MAX_MODULES) { - KiRegisterModuleEntry(&gModules[gModuleCount], name, base, 0); + size_t size = base ? LdrGetImageSize(base) : 0; + KiRegisterModuleEntry(&gModules[gModuleCount], name, base, size); gModuleCount++; } } +// Finds the loaded kernel module that owns an address. +const char* LdrFindKernelModuleByAddress(uintptr_t address, uintptr_t* baseOut, size_t* sizeOut) { + if (baseOut) *baseOut = 0; + if (sizeOut) *sizeOut = 0; + + for (uint32_t i = 0; i < gModuleCount; i++) { + uintptr_t base = gModules[i].Base; + size_t size = gModules[i].Size; + if (!base || !size) + continue; + if (address >= base && address < base + size) { + if (baseOut) *baseOut = base; + if (sizeOut) *sizeOut = size; + return gModules[i].Name; + } + } + return "unknown"; +} + // Returns global Module Base. static uintptr_t KiFindGlobalModuleBase(const char* name) { return KiFindModuleBaseInTable(gModules, gModuleCount, KiCanonicalModuleName(name)); @@ -327,7 +347,6 @@ size_t LdrGetMappedImageSize(PROCESS* process, uintptr_t base) { return LdrGetImageSize(base); } - // Releases loaded Image For Process. static void KiFreeLoadedImageForProcess(PROCESS* process, uintptr_t imageBase, bool user) { if (!imageBase) return; @@ -450,7 +469,6 @@ KSTATUS LdrProtectImageForProcess(PROCESS* process, uintptr_t imageBase, bool us return s; } - // Entry point lookup. uintptr_t LdrGetEntryPoint(uintptr_t base) { uint32_t pe_offset = *(uint32_t*)(base + 0x3C); @@ -720,7 +738,6 @@ KSTATUS LdrLoadModuleForProcess(PROCESS* process, const char* filename, uintptr_ LdrDbg("LDR: module mapped pid=%u file=%s base=%016llx size=%llx\n", process->ProcessId, filename, (unsigned long long)imageBase, (unsigned long long)LdrGetMappedImageSize(process, imageBase)); - s = LdrResolveImportsForProcess(process, imageBase); if (!KSUCCESS(s)) { KiFreeLoadedImageForProcess(process, imageBase, true); @@ -733,7 +750,6 @@ KSTATUS LdrLoadModuleForProcess(PROCESS* process, const char* filename, uintptr_ return s; } - s = LdrRegisterProcessModule(process, filename, imageBase); if (!KSUCCESS(s)) { KiFreeLoadedImageForProcess(process, imageBase, true); diff --git a/code/kernel/ldr_section.c b/code/kernel/ldr_section.c index 7324bfb..4c660fc 100644 --- a/code/kernel/ldr_section.c +++ b/code/kernel/ldr_section.c @@ -268,7 +268,6 @@ static IMAGE_SECTION_OBJECT* KiFindImageSectionLocked(const IO_FILE_IDENTITY* id return (IMAGE_SECTION_OBJECT*)0; } - // Verifies that a cached image section is still backed by the same file generation. static KSTATUS KiValidateImageSectionBackingFile(IMAGE_SECTION_OBJECT* section) { if (!section || !section->BackingFile) diff --git a/code/kernel/mm_file_cache.c b/code/kernel/mm_file_cache.c index dabc914..9edc2b2 100644 --- a/code/kernel/mm_file_cache.c +++ b/code/kernel/mm_file_cache.c @@ -253,7 +253,6 @@ static KSTATUS KiFileCacheReclaimOnePage(FILE_OBJECT* skipFile, uint64_t skipOff return STATUS_NOT_FOUND; } - static void KiFileCacheAddReadAheadStat(uint64_t* field, uint64_t value) { uintptr_t flags = KeAcquireSpinlock(&gFileCacheLock); *field += value; @@ -719,7 +718,6 @@ static KSTATUS KiFileCacheCompleteLoadingPage(MM_FILE_CACHE_ENTRY* entry, return STATUS_SUCCESS; } - static uint32_t KiFileCacheChooseForegroundBatchPages(void) { uintptr_t flags = KeAcquireSpinlock(&gFileCacheLock); uint64_t resident = gFileCacheResidentPages; @@ -936,9 +934,11 @@ static KSTATUS KiFileCacheGetWritablePage(FILE_OBJECT* file, } memset(page, 0, PAGE_SIZE); - uint32_t validBytes = PAGE_SIZE; - if (oldFileSize > aligned && oldFileSize - aligned < PAGE_SIZE) - validBytes = (uint32_t)(oldFileSize - aligned); + uint32_t validBytes = 0; + if (oldFileSize > aligned) { + uint64_t oldBytes = oldFileSize - aligned; + validBytes = oldBytes < PAGE_SIZE ? (uint32_t)oldBytes : PAGE_SIZE; + } s = KiFileCacheCompleteLoadingPage(entry, frame, validBytes, outPage, (uint64_t*)0); if (!KSUCCESS(s)) @@ -967,7 +967,6 @@ static void KiFileCacheMarkPageDirty(FILE_OBJECT* file, uint64_t aligned, uint32 } } - KSTATUS MmFileCacheGetWritablePage(FILE_OBJECT* file, uint64_t offset, uint64_t fileSizeBefore, int needExistingBytes, MM_FILE_CACHE_PAGE* outPage) { @@ -1180,7 +1179,6 @@ KSTATUS MmFileCacheDropOnePageForPressure(void) { return KiFileCacheReclaimOnePage((FILE_OBJECT*)0, 0); } - KSTATUS MmFileCacheScheduleReadAhead(FILE_OBJECT* file, uint64_t offset, uint32_t pages) { if (!file || pages == 0) return STATUS_INVALID_PARAMETER; diff --git a/code/kernel/mm_phys.c b/code/kernel/mm_phys.c index 5bdf0da..f711dcf 100644 --- a/code/kernel/mm_phys.c +++ b/code/kernel/mm_phys.c @@ -141,7 +141,6 @@ static void KiRegisterManagedRange(PHYSICAL_ADDRESS base, } } - // Returns whether the future page-replacement code may consider this frame a candidate. static int KiPfnEvictableLocked(const MM_PFN* entry) { if (!entry || entry->PinCount) @@ -156,8 +155,6 @@ static int KiPfnEvictableLocked(const MM_PFN* entry) { entry->Usage == MmPfnUseCow; } - - // Returns whether a standby frame can be counted as immediately reclaimable. static int KiPfnReclaimableLocked(const MM_PFN* entry) { if (!entry || entry->PinCount || entry->MappedCount) @@ -395,7 +392,6 @@ __declspec(dllexport) MM_PFN* MmPfnGet(PHYSICAL_ADDRESS frame) { return &gPfnDatabase[pfn]; } - // Returns the direct-map virtual address for a managed physical frame. __declspec(dllexport) void* MmPfnToVirtual(PHYSICAL_ADDRESS frame) { if (MmPfnIndex(frame) == MM_PFN_INVALID_INDEX) @@ -669,8 +665,6 @@ __declspec(dllexport) KSTATUS MmPfnRemoveMapping(PHYSICAL_ADDRESS frame) { return STATUS_SUCCESS; } - - // Moves a clean, unshared frame to standby so pressure can reclaim it later. __declspec(dllexport) KSTATUS MmPfnMoveToStandby(PHYSICAL_ADDRESS frame) { size_t pfn = MmPfnIndex(frame); diff --git a/code/kernel/mm_pte.c b/code/kernel/mm_pte.c index 6e79e45..8c10d50 100644 --- a/code/kernel/mm_pte.c +++ b/code/kernel/mm_pte.c @@ -77,7 +77,6 @@ static PHYSICAL_ADDRESS KiEnsureTable(PHYSICAL_ADDRESS parentPhys, return childPhys; } - // Initializes the page-table manager around the bootloader-created PML4. void MmPteInitialize(PHYSICAL_ADDRESS bootPml4Phys) { gPml4Phys = bootPml4Phys; @@ -246,7 +245,6 @@ __declspec(dllexport) KSTATUS MmPteMapPage(uintptr_t virt, PHYSICAL_ADDRESS phys return STATUS_SUCCESS; } - // Removes one 4 KiB leaf PTE from the shared page tables. __declspec(dllexport) KSTATUS MmPteUnmapPage(uintptr_t virt) { KiPteStatInc(&gPteStats.UnmapCalls); @@ -285,7 +283,6 @@ __declspec(dllexport) KSTATUS MmPteUnmapPage(uintptr_t virt) { return STATUS_SUCCESS; } - // Replaces leaf flags for a 4 KiB range and refuses huge pages instead of silently corrupting them. __declspec(dllexport) KSTATUS MmPteProtectRange(uintptr_t virt, size_t size, uint64_t flags) { KiPteStatInc(&gPteStats.ProtectCalls); @@ -346,10 +343,9 @@ __declspec(dllexport) KSTATUS MmPteProtectRange(uintptr_t virt, size_t size, uin return status; } - -// Tests and clears the hardware accessed bit on a mapped 4 KiB leaf. This is +// Tests and clears the hardware accessed bit on a mapped 4 KiB leaf. This is // used by the pressure trimmer as a second-chance test for clean file-backed -// pages. If the page was accessed recently, reclaim skips it once and flushes +// pages. If the page was accessed recently, reclaim skips it once and flushes // the TLB entry so future accesses can set PAGE_ACCESSED again. __declspec(dllexport) int MmPteTestAndClearAccessed(uintptr_t virt) { if (!gPml4Phys) @@ -572,4 +568,3 @@ void MmPteQueryStats(MM_PTE_STATS* out) { *out = gPteStats; } - diff --git a/code/kernel/mm_swap.c b/code/kernel/mm_swap.c index be1fb39..34cb563 100644 --- a/code/kernel/mm_swap.c +++ b/code/kernel/mm_swap.c @@ -93,7 +93,7 @@ KSTATUS MmSwapInitialize(SYS_STRING_VIEW path, uint64_t sizeBytes) { // Swap slots are never read before MmSwapPageOut overwrites the whole // page, so the filesystem may allocate backing clusters without zeroing - // them. Normal files still get zero-on-extend semantics. + // them. Normal files still get zero-on-extend semantics. file->Flags |= IO_FILE_FLAG_NO_CACHE | IO_FILE_FLAG_SKIP_ZERO_EXTEND; s = IoTruncateFile(file, sizeBytes); diff --git a/code/kernel/mm_virt.c b/code/kernel/mm_virt.c index 4fbd846..520ed02 100644 --- a/code/kernel/mm_virt.c +++ b/code/kernel/mm_virt.c @@ -1,6 +1,17 @@ // Kernel virtual-memory manager. This file owns kernel pool allocation, vad bookkeeping, and user-copy validation. // Page-table construction lives in mm_pte.c so the higher VMM can grow toward VADs, faults, COW, and swap without mixing those policies with raw PTE edits. +// Virtual address layout: +// 0000000000001000-00007FFFFFFFF000 is the lower-half user range. +// 0000100000000000-00007FFFFFFFF000 is the current VAD allocation arena. +// FFFF800000000000-FFFF87FFFFFFFFFF is reserved for kernel images and fixed kernel mappings. +// FFFF880000000000-FFFF8FFFFFFFFFFF is the physical direct map. +// FFFF900000000000-FFFF97FFFFFFFFFF is the PCI MMIO aperture owned by pci.sys. +// FFFFC00000000000-FFFFDFFFFFFFFFFF is the kernel pool. +// +// The current VM model uses one shared PML4, so user pointers are globally +// meaningful while protection and lifetime are still tracked per process VAD. + // Kernel pool: // A boundary-tag allocator starts at POOL_BASE and commits pages on demand. @@ -780,7 +791,6 @@ static MM_VAD_STATS gVadStats; #define MM_VAD_ARENA_BASE 0x0000100000000000ULL - // Initializes the VAD list after the shared page table and kernel pool are ready. static void KiVadInitialize(void) { LIST_INIT(&gVadList); @@ -820,7 +830,6 @@ void MmInitializeVirtual(PHYSICAL_ADDRESS bootPml4Phys, size_t poolSize) { (void)MmPfnPin(gVadZeroPageFrame); } - // Allocates zeroed kernel-pool memory from the non-user heap. __declspec(dllexport) void* MmAllocatePool(size_t size) { return KiHeapAllocEx(&gPoolHeap, size, POOL_TAG_DEFAULT, (uintptr_t)_ReturnAddress()); @@ -1100,11 +1109,11 @@ static void KiVadWaitForPagingComplete(MM_VAD_PAGE* page) { KeYield(); } -// Atomically claims the transient paging bit. This is deliberately not a spinlock: +// Atomically claims the transient paging bit. This is deliberately not a spinlock: // a page-in/page-out may allocate frames or do filesystem I/O, so waiters must yield -// instead of holding IRQL-raised locks. Without this CAS, two CPUs faulting the same +// instead of holding IRQL-raised locks. Without this CAS, two CPUs faulting the same // swapped/demand-zero page can both allocate/read/map it; one map fails and rolls -// metadata back to SWAPPED while the other has already published a valid PTE. That +// metadata back to SWAPPED while the other has already published a valid PTE. That // leaves stale or freed swap slots behind and corrupts user data under pressure. static int KiVadTryBeginPaging(MM_VAD_PAGE* page) { if (!page) return 0; @@ -1172,7 +1181,6 @@ static KSTATUS KiVadFillFileBackedPage(MM_VAD* vad, size_t pageIndex, PHYSICAL_A return STATUS_SUCCESS; } - // Materializes one image-backed prototype page or zero-fill page for an image VAD. static KSTATUS KiVadFillImageBackedPage(MM_VAD* vad, size_t pageIndex, int wantWrite) { if (!vad || !vad->ImageSection || pageIndex >= vad->PageCount) @@ -1277,7 +1285,7 @@ retry_after_paging: if (!KiVadTryBeginPaging(page)) goto retry_after_paging; - // Re-check after claiming the transition bit. Another CPU may have resolved + // Re-check after claiming the transition bit. Another CPU may have resolved // the page between our initial wait/access check and the CAS above. s = KiVadCheckPageAccess(page, wantWrite, wantExecute); if (!KSUCCESS(s)) { @@ -1906,7 +1914,6 @@ static KSTATUS KiCopyToUserPaged(void* userDst, const void* kernelSrc, size_t si return KiCopyUserPaged(userDst, kernelSrc, size, 1); } - // Reserves a VAD in the global user VA arena without committing physical pages. KSTATUS MmVadReserve(PROCESS* process, size_t size, size_t alignment, uint64_t pageFlags, uint32_t vadFlags, @@ -2071,9 +2078,9 @@ KSTATUS MmVadCreateCommitted(PROCESS* process, size_t size, size_t alignment, return STATUS_SUCCESS; } -// Creates a demand-paged file-backed user mapping. The VAD owns a reference to +// Creates a demand-paged file-backed user mapping. The VAD owns a reference to // the FILE_OBJECT so the caller may close the original file handle immediately -// after a view is established. Pages are read from the file on first fault and +// after a view is established. Pages are read from the file on first fault and // then behave like ordinary private user pages: if memory pressure trims them // after they are resident, the swap path preserves later private modifications. KSTATUS MmVadCreateFileMapping(PROCESS* process, FILE_OBJECT* file, @@ -2133,7 +2140,6 @@ KSTATUS MmVadCreateFileMapping(PROCESS* process, FILE_OBJECT* file, return STATUS_SUCCESS; } - // Creates an image-backed VAD whose pages fault from a shared image section object. KSTATUS MmVadCreateImageSectionMapping(PROCESS* process, IMAGE_SECTION_OBJECT* section, uint64_t pageFlags, uint32_t vadFlags, @@ -2425,9 +2431,9 @@ KSTATUS MmVadCreateSharedViewFromFrames(PROCESS* targetProcess, } // Returns whether a resident file-backed page can be discarded instead of -// swapped. This is safe only while the page is still clean by construction: +// swapped. This is safe only while the page is still clean by construction: // read-only mappings cannot have been modified, and the backing file can -// repopulate the page on the next fault. Writable/private file mappings still +// repopulate the page on the next fault. Writable/private file mappings still // fall back to the swap path because this VMM does not yet track dirty bits. static int KiVadPageCanDiscardCleanFile(const MM_VAD* vad, const MM_VAD_PAGE* page) { if (!vad || !page || vad->Type != MmVadMappedFile) @@ -2463,7 +2469,7 @@ static int KiVadPageCanDiscardCleanFileAfterPagingClaim(const MM_VAD* vad, const return (page->Flags & MM_VAD_PAGE_PAGING) != 0; } -// Returns whether a resident VAD page is safe to move to swap. Clean read-only +// Returns whether a resident VAD page is safe to move to swap. Clean read-only // file-backed pages are intentionally excluded; the pressure trimmer discards // them and reloads them from the file instead of wasting swap slots. static int KiVadPageCanSwap(const MM_VAD_PAGE* page) { @@ -2498,7 +2504,7 @@ static int KiVadPageCanSwapAfterPagingClaim(const MM_VAD_PAGE* page) { return 1; } -// Discards one resident clean file-backed page. No swap slot is allocated; the +// Discards one resident clean file-backed page. No swap slot is allocated; the // VAD keeps its FILE_BACKED state and refaults the page from the file later. static KSTATUS KiVadDiscardCleanFilePage(MM_VAD* vad, size_t pageIndex, void** baseOut) { if (baseOut) *baseOut = (void*)0; @@ -2516,9 +2522,9 @@ static KSTATUS KiVadDiscardCleanFilePage(MM_VAD* vad, size_t pageIndex, void** b uintptr_t va = vad->Base + pageIndex * PAGE_SIZE; - // Second chance for hot mapped-file pages. Dropping the active video/WAD + // Second chance for hot mapped-file pages. Dropping the active video/WAD // working set is much worse than swapping cold anonymous pages: it causes - // repeated file reads, page-table churn, and global TLB shootdowns. Clear + // repeated file reads, page-table churn, and global TLB shootdowns. Clear // the hardware accessed bit and skip the page once; if it stays cold until // a later pressure scan, it may be discarded then. if (MmPteTestAndClearAccessed(va)) @@ -2580,7 +2586,7 @@ static KSTATUS KiVadTrimPageToSwap(MM_VAD* vad, size_t pageIndex, void** baseOut return STATUS_NOT_FOUND; // Claim the transient paging bit atomically before pinning/removing the - // user PTE. This serializes page-out against another page-out and against + // user PTE. This serializes page-out against another page-out and against // a concurrent fault on the same VAD page without holding a spinlock across // pagefile I/O. if (!KiVadTryBeginPaging(page)) @@ -2655,7 +2661,7 @@ KSTATUS MmVadTrimOnePageForTest(PROCESS* process, void** baseOut) { // Reclaims one resident user page from any process during allocation pressure. // Prefer clean file-backed pages because they can be dropped and re-read without -// consuming swap. Fall back to anonymous/private swap only when no such page is +// consuming swap. Fall back to anonymous/private swap only when no such page is // available. KSTATUS MmVadTrimOnePageForPressure(void) { if (!gVadListReady) return STATUS_NOT_SUPPORTED; @@ -2798,7 +2804,6 @@ KSTATUS MmVadResolveUserPageFault(PROCESS* process, uintptr_t faultAddress, uint return STATUS_SUCCESS; } - // Resolves a page fault by looking up the owning VAD in the global user arena. // The single address space intentionally allows the kernel and other processes // to touch any lower-half mapping, so fault ownership is derived from the VAD diff --git a/code/kernel/net_socket.c b/code/kernel/net_socket.c index 2b477f1..7666926 100644 --- a/code/kernel/net_socket.c +++ b/code/kernel/net_socket.c @@ -81,7 +81,7 @@ KSTATUS NetSocketCreate(uint32_t domain, uint32_t type, uint32_t protocol, SOCKE } // Implements the net Socket Bind operation. -KSTATUS NetSocketBind(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_IPV4_K* address, size_t addressSize) { +KSTATUS NetSocketBind(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize) { const NET_STACK_PROVIDER* provider = NULL; void* providerSocket = NULL; KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket); @@ -97,7 +97,7 @@ KSTATUS NetSocketListen(SOCKET_OBJECT* socket, uint32_t backlog) { } // Implements the net Socket Accept operation. -KSTATUS NetSocketAccept(SOCKET_OBJECT* socket, SOCKET_OBJECT** acceptedOut, SYS_SOCKET_ADDRESS_IPV4_K* remoteOut) { +KSTATUS NetSocketAccept(SOCKET_OBJECT* socket, SOCKET_OBJECT** acceptedOut, SYS_SOCKET_ADDRESS_K* remoteOut) { if (!acceptedOut) return STATUS_INVALID_PARAMETER; *acceptedOut = NULL; @@ -127,7 +127,7 @@ KSTATUS NetSocketAccept(SOCKET_OBJECT* socket, SOCKET_OBJECT** acceptedOut, SYS_ } // Implements the net Socket Connect operation. -KSTATUS NetSocketConnect(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_IPV4_K* address, size_t addressSize) { +KSTATUS NetSocketConnect(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize) { const NET_STACK_PROVIDER* provider = NULL; void* providerSocket = NULL; KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket); @@ -165,7 +165,7 @@ KSTATUS NetSocketReceive(SOCKET_OBJECT* socket, void* buffer, size_t length, uin } KSTATUS NetSocketSendTo(SOCKET_OBJECT* socket, const void* buffer, size_t length, uint32_t flags, - const SYS_SOCKET_ADDRESS_IPV4_K* remote, size_t remoteSize, size_t* sentOut) { + const SYS_SOCKET_ADDRESS_K* remote, size_t remoteSize, size_t* sentOut) { if (sentOut) *sentOut = 0; if (!buffer && length) @@ -184,7 +184,7 @@ KSTATUS NetSocketSendTo(SOCKET_OBJECT* socket, const void* buffer, size_t length } KSTATUS NetSocketReceiveFrom(SOCKET_OBJECT* socket, void* buffer, size_t length, uint32_t flags, - SYS_SOCKET_ADDRESS_IPV4_K* remoteOut, size_t* receivedOut) { + SYS_SOCKET_ADDRESS_K* remoteOut, size_t* receivedOut) { if (receivedOut) *receivedOut = 0; if (!buffer && length) @@ -238,8 +238,8 @@ KSTATUS NetSocketPoll(SOCKET_OBJECT* socket, uint32_t events, uint32_t* returned return KSUCCESS(st) ? provider->Poll(providerSocket, events, returnedEventsOut) : st; } -// Implements the net Resolve Host Ipv4 operation. -KSTATUS NetResolveHostIpv4(const char* name, SYS_SOCKET_ADDRESS_IPV4_K* addressOut) { +// Implements the net Resolve Host operation. +KSTATUS NetResolveHost(const char* name, uint32_t family, SYS_SOCKET_ADDRESS_K* addressOut) { if (!name || !addressOut) return STATUS_INVALID_PARAMETER; @@ -247,5 +247,5 @@ KSTATUS NetResolveHostIpv4(const char* name, SYS_SOCKET_ADDRESS_IPV4_K* addressO if (!provider) return STATUS_NOT_SUPPORTED; - return provider->ResolveHostIpv4(name, addressOut); + return provider->ResolveHost(name, family, addressOut); } diff --git a/code/kernel/net_stack.c b/code/kernel/net_stack.c index 4b5e440..24e4d41 100644 --- a/code/kernel/net_stack.c +++ b/code/kernel/net_stack.c @@ -19,7 +19,7 @@ static KSTATUS NetValidateStackProvider(const NET_STACK_PROVIDER* p) { !p->Bind || !p->Listen || !p->Accept || !p->Connect || !p->Send || !p->Receive || !p->SendTo || !p->ReceiveFrom || !p->Shutdown || !p->SetOption || !p->GetOption || !p->Poll || - !p->ResolveHostIpv4) { + !p->ResolveHost) { return STATUS_INVALID_PARAMETER; } diff --git a/code/kernel/ob_object.c b/code/kernel/ob_object.c index eacfef0..2445529 100644 --- a/code/kernel/ob_object.c +++ b/code/kernel/ob_object.c @@ -26,6 +26,7 @@ static uint32_t KiObjectPoolTag(OBJECT_TYPE type) { case ObjectTypeSurface: return MM_POOL_TAG('s', 'u', 'r', 'f'); case ObjectTypeInputQueue: return MM_POOL_TAG('i', 'n', 'p', 'q'); case ObjectTypeImageSection: return MM_POOL_TAG('i', 'm', 's', 'e'); + case ObjectTypeTimer: return MM_POOL_TAG('t', 'm', 'r', ' '); default: return MM_POOL_TAG('o', 'b', 'j', ' '); } } diff --git a/code/public/ntlite/ddi/display.h b/code/public/ntlite/ddi/display.h index 542d5db..f88fda8 100644 --- a/code/public/ntlite/ddi/display.h +++ b/code/public/ntlite/ddi/display.h @@ -4,7 +4,7 @@ // display.h - display device DDI // Display providers register DISPLAY_DEVICE objects with the kernel display -// manager. The boot framebuffer backend is registered by the kernel itself; +// manager. The boot framebuffer backend is registered by the kernel itself; // PCI/virtual GPU drivers register additional providers later. #include diff --git a/code/public/ntlite/ddi/hal_api.h b/code/public/ntlite/ddi/hal_api.h index 89a527a..04c5f5d 100644 --- a/code/public/ntlite/ddi/hal_api.h +++ b/code/public/ntlite/ddi/hal_api.h @@ -25,6 +25,7 @@ typedef struct HAL_SCREEN { KSTATUS (*GetFramebuffer)(HAL_FRAMEBUFFER_INFO* outInfo); void (*Scroll)(int lines); void (*PutChar)(char c, int x, int y, uint32_t fg, uint32_t bg); + // Writes only to the boot framebuffer text console. Stream routing belongs to HAL_OUTPUT_ROUTER. void (*PrintChar)(char c, uint32_t fg, uint32_t bg); // Legacy software cursor hooks kept for early-boot/panic fallback only. // Normal input cursor ownership lives in ke_display. @@ -32,14 +33,69 @@ typedef struct HAL_SCREEN { void (*EraseCursor)(void); } HAL_SCREEN; +// Output streams describe the producer's intent. Endpoints subscribe by mask. +#define HAL_OUTPUT_STREAM_DEBUG 0x00000001u +#define HAL_OUTPUT_STREAM_CONSOLE 0x00000002u +#define HAL_OUTPUT_STREAM_CRASH 0x00000004u +#define HAL_OUTPUT_STREAM_PANIC 0x00000008u +#define HAL_OUTPUT_STREAM_ALL 0x0000000Fu + +// Emergency writes bypass endpoints that cannot run without scheduler services. +#define HAL_OUTPUT_WRITE_EMERGENCY 0x00000001u +#define HAL_OUTPUT_ENDPOINT_PANIC_SAFE 0x00000001u +#define HAL_OUTPUT_ENDPOINT_INVALID UINT32_MAX + +typedef struct HAL_OUTPUT_RECORD { + // Exactly one HAL_OUTPUT_STREAM_* value. + uint32_t Stream; + // HAL_OUTPUT_WRITE_* flags. + uint32_t Flags; + // X8R8G8B8 text color hints. + uint32_t Foreground; + uint32_t Background; + // The buffer remains valid only for the duration of Write(). + const char* Buffer; + size_t Length; +} HAL_OUTPUT_RECORD; + +// Endpoint callbacks consume or copy the record synchronously and must not retain Buffer. +typedef void (*HAL_OUTPUT_ENDPOINT_WRITE)(void* context, const HAL_OUTPUT_RECORD* record); + +typedef struct HAL_OUTPUT_ENDPOINT { + // The name remains stable for the endpoint lifetime. + const char* Name; + uint32_t StreamMask; + uint32_t Capabilities; + HAL_OUTPUT_ENDPOINT_WRITE Write; + void* Context; +} HAL_OUTPUT_ENDPOINT; + +typedef struct HAL_OUTPUT_STATS { + uint64_t RecordCount; + uint64_t ByteCount; + uint64_t EndpointWriteCount; + uint64_t EmergencyRecordCount; + uint32_t EndpointCount; +} HAL_OUTPUT_STATS; + +typedef struct HAL_OUTPUT_ROUTER { + // Registration is append-only; endpoint callbacks and context remain valid until shutdown. + KSTATUS (*RegisterEndpoint)(const HAL_OUTPUT_ENDPOINT* endpoint, uint32_t* endpointIdOut); + KSTATUS (*SetEndpointStreams)(uint32_t endpointId, uint32_t streamMask); + void (*Write)(const HAL_OUTPUT_RECORD* record); + void (*QueryStats)(HAL_OUTPUT_STATS* statsOut); + uint32_t SerialEndpointId; + uint32_t BootFramebufferEndpointId; +} HAL_OUTPUT_ROUTER; + typedef struct HAL_TIMER { uint64_t (*QueryTicks)(void); + uint64_t (*QueryTickFrequency)(void); void (*Stall)(uint32_t us); void (*Isr)(void); uint8_t IrqNumber; } HAL_TIMER; - #define HAL_RTC_TIME_FLAG_VALID 0x0001u #define HAL_RTC_TIME_FLAG_RTC 0x0002u @@ -60,7 +116,7 @@ typedef struct HAL_RTC { } HAL_RTC; typedef struct HAL_PCI { - // Raw PCI configuration access. Policy-level operations such as + // Raw PCI configuration access. Policy-level operations such as // enumeration, BAR decoding, interrupt discovery, and MSI programming // belong to pci.sys. uint32_t (*Read32)(uint16_t segment, @@ -94,6 +150,7 @@ typedef struct HAL_API { uint32_t Version; void *(*PhysToVirt)(uint64_t physAddr); HAL_SCREEN Screen; + HAL_OUTPUT_ROUTER Output; HAL_TIMER Timer; HAL_RTC Rtc; HAL_PCI Pci; @@ -120,8 +177,27 @@ typedef struct HAL_INIT_PARAMS { void* (*AllocatePool)(size_t size); } HAL_INIT_PARAMS; -#define HAL_API_VERSION // do not change during development +// Increment when the HAL API layout or semantics change when we go from indev to alpha. +#define HAL_API_VERSION 1 __declspec(dllexport) const HAL_API* HalGetApi(void); __declspec(dllexport) void HalRegisterScheduler(void (*pKeSchedulerTick)(void)); __declspec(dllexport) KSTATUS HalInitialize(const HAL_INIT_PARAMS* params); + +// Masks interrupts and returns the previous RFLAGS value. +__declspec(dllexport) uintptr_t HalDisableInterrupts(void); + +// Restores the RFLAGS value returned by HalDisableInterrupts. +__declspec(dllexport) void HalRestoreInterrupts(uintptr_t flags); + +// Enables maskable interrupts. +__declspec(dllexport) void HalEnableInterrupts(void); + +// Enables interrupts and halts until the processor receives an interrupt. +__declspec(dllexport) void HalWaitForInterrupt(void); + +// Halts once without changing the interrupt state. +__declspec(dllexport) void HalHalt(void); + +// Masks interrupts and permanently stops the current processor. +__declspec(dllexport) _Noreturn void HalStopProcessor(void); diff --git a/code/public/ntlite/ddi/net_abi.h b/code/public/ntlite/ddi/net_abi.h index 5dc7bf2..6cdbacb 100644 --- a/code/public/ntlite/ddi/net_abi.h +++ b/code/public/ntlite/ddi/net_abi.h @@ -1,4 +1,4 @@ -// Public NTLite net abi ABI declarations shared between kernel, drivers, and user mode. +// Public NTLite net ABI declarations shared between kernel, drivers, and user mode. #pragma once // net_abi.h - native networking ABI shared by the kernel socket facade @@ -8,18 +8,20 @@ #include #define SYS_AF_INET 2u +#define SYS_AF_INET6 10u #define SYS_SOCK_STREAM 1u #define SYS_SOCK_DGRAM 2u #define SYS_SOCK_RAW 3u #define SYS_IPPROTO_ICMP 1u #define SYS_IPPROTO_TCP 6u #define SYS_IPPROTO_UDP 17u +#define SYS_IPPROTO_ICMPV6 58u #define SYS_SHUTDOWN_READ 0u #define SYS_SHUTDOWN_WRITE 1u #define SYS_SHUTDOWN_BOTH 2u -#define SYS_SOCKET_ADDRESS_IPV4_SIZE 8u +#define SYS_SOCKET_ADDRESS_SIZE 28u #define SYS_RESOLVE_HOST_NAME_MAX 255u #define SYS_SOCKET_OPT_RECV_TIMEOUT_MS 1u @@ -33,11 +35,14 @@ #define SYS_POLL_INVALID 0x0010u #define SYS_POLL_TIMEOUT_INFINITE 0xFFFFFFFFu -typedef struct SYS_SOCKET_ADDRESS_IPV4_K { - uint16_t Family; // SYS_AF_INET - uint16_t Port; // host byte order - uint8_t Address[4]; // dotted-quad bytes, e.g. {10,0,2,2} -} SYS_SOCKET_ADDRESS_IPV4_K; +// Kernel socket providers use the same tagged native address shape as syscalls. +typedef struct SYS_SOCKET_ADDRESS_K { + uint16_t Family; + uint16_t Size; + uint16_t Port; + uint8_t Address[16]; + uint32_t ScopeId; +} SYS_SOCKET_ADDRESS_K; typedef struct SYS_POLLFD_K { uint32_t Handle; diff --git a/code/public/ntlite/ddi/net_stack.h b/code/public/ntlite/ddi/net_stack.h index 760498e..7331aaa 100644 --- a/code/public/ntlite/ddi/net_stack.h +++ b/code/public/ntlite/ddi/net_stack.h @@ -26,24 +26,24 @@ struct NET_STACK_PROVIDER { KSTATUS (*SocketCreate)(uint32_t domain, uint32_t type, uint32_t protocol, void** outSocket); void (*SocketClose)(void* socket); - KSTATUS (*Bind)(void* socket, const SYS_SOCKET_ADDRESS_IPV4_K* address, size_t addressSize); + KSTATUS (*Bind)(void* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize); KSTATUS (*Listen)(void* socket, uint32_t backlog); - KSTATUS (*Accept)(void* socket, void** acceptedOut, SYS_SOCKET_ADDRESS_IPV4_K* remoteOut); - KSTATUS (*Connect)(void* socket, const SYS_SOCKET_ADDRESS_IPV4_K* address, size_t addressSize); + KSTATUS (*Accept)(void* socket, void** acceptedOut, SYS_SOCKET_ADDRESS_K* remoteOut); + KSTATUS (*Connect)(void* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize); KSTATUS (*Send)(void* socket, const void* buffer, size_t length, uint32_t flags, size_t* sentOut); KSTATUS (*Receive)(void* socket, void* buffer, size_t length, uint32_t flags, size_t* receivedOut); KSTATUS (*SendTo)(void* socket, const void* buffer, size_t length, uint32_t flags, - const SYS_SOCKET_ADDRESS_IPV4_K* remote, size_t remoteSize, size_t* sentOut); + const SYS_SOCKET_ADDRESS_K* remote, size_t remoteSize, size_t* sentOut); KSTATUS (*ReceiveFrom)(void* socket, void* buffer, size_t length, uint32_t flags, - SYS_SOCKET_ADDRESS_IPV4_K* remoteOut, size_t* receivedOut); + SYS_SOCKET_ADDRESS_K* remoteOut, size_t* receivedOut); KSTATUS (*Shutdown)(void* socket, uint32_t how); KSTATUS (*SetOption)(void* socket, uint32_t option, const void* value, size_t valueSize); KSTATUS (*GetOption)(void* socket, uint32_t option, void* value, size_t valueSize, size_t* actualSizeOut); KSTATUS (*Poll)(void* socket, uint32_t events, uint32_t* returnedEventsOut); - KSTATUS (*ResolveHostIpv4)(const char* name, SYS_SOCKET_ADDRESS_IPV4_K* addressOut); + KSTATUS (*ResolveHost)(const char* name, uint32_t family, SYS_SOCKET_ADDRESS_K* addressOut); }; __declspec(dllexport) KSTATUS NetRegisterStackProvider(const NET_STACK_PROVIDER* provider); diff --git a/code/public/ntlite/ddi/pci.h b/code/public/ntlite/ddi/pci.h index 0a828d3..c4fa007 100644 --- a/code/public/ntlite/ddi/pci.h +++ b/code/public/ntlite/ddi/pci.h @@ -3,7 +3,7 @@ #pragma once // pci.h - PCI bus driver DDI exported by pci.sys -// Function drivers must not enumerate PCI or decode BARs themselves. They bind +// Function drivers must not enumerate PCI or decode BARs themselves. They bind // to PCI PDOs through the I/O manager and use these routines to access the PCI // function represented by their PDO. @@ -21,7 +21,6 @@ # endif #endif - typedef struct PCI_ADDRESS { uint16_t Segment; uint8_t Bus; diff --git a/code/public/ntlite/display.h b/code/public/ntlite/display.h index 5854e74..69b91bd 100644 --- a/code/public/ntlite/display.h +++ b/code/public/ntlite/display.h @@ -3,8 +3,8 @@ #pragma once // display.h - user/kernel shared display ABI types -// This header is safe for user-mode code. It contains only plain syscall ABI -// data types and capability bits. Kernel/driver provider interfaces live in +// This header is safe for user-mode code. It contains only plain syscall ABI +// data types and capability bits. Kernel/driver provider interfaces live in // . #include @@ -32,7 +32,6 @@ typedef struct DISPLAY_RECT { int32_t H; } DISPLAY_RECT; - #define SYS_SURFACE_FORMAT_XRGB8888 1u #define SYS_SURFACE_FLAG_NONE 0u #define SYS_SURFACE_MAX_RECTS 64u @@ -55,6 +54,8 @@ typedef struct SYS_SURFACE_DESC { } SYS_SURFACE_DESC; typedef struct SYS_SURFACE_STATS { + uint32_t Size; + uint32_t Version; uint64_t CreatedSurfaces; uint64_t DestroyedSurfaces; uint64_t LiveSurfaces; diff --git a/code/public/ntlite/file_stat.h b/code/public/ntlite/file_stat.h new file mode 100644 index 0000000..8a82d1f --- /dev/null +++ b/code/public/ntlite/file_stat.h @@ -0,0 +1,31 @@ +// Public NTLite file metadata ABI declarations shared between kernel, drivers, and user mode. + +#pragma once +#include +#include + +#define SYS_FILE_TYPE_UNKNOWN 0u +#define SYS_FILE_TYPE_REGULAR 1u +#define SYS_FILE_TYPE_DIRECTORY 2u + +#define SYS_FILE_ATTR_READ_ONLY 0x00000001u +#define SYS_FILE_ATTR_HIDDEN 0x00000002u +#define SYS_FILE_ATTR_SYSTEM 0x00000004u +#define SYS_FILE_ATTR_VOLUME_ID 0x00000008u +#define SYS_FILE_ATTR_DIRECTORY 0x00000010u +#define SYS_FILE_ATTR_ARCHIVE 0x00000020u + +// Metadata returned by path and handle stat syscalls. +typedef struct SYS_FILE_STAT { + uint32_t Size; + uint32_t Type; + uint32_t Attributes; + uint32_t LinkCount; + uint64_t FileSize; + uint64_t VolumeId; + uint64_t FileId; + SYS_SYSTEM_TIME CreationTime; + SYS_SYSTEM_TIME LastAccessTime; + SYS_SYSTEM_TIME LastWriteTime; + SYS_SYSTEM_TIME ChangeTime; +} SYS_FILE_STAT; diff --git a/code/public/ntlite/input.h b/code/public/ntlite/input.h index ef3f2cd..fc9a1a2 100644 --- a/code/public/ntlite/input.h +++ b/code/public/ntlite/input.h @@ -139,7 +139,6 @@ typedef struct INPUT_KEY_EVENT { typedef struct INPUT_KEY_RECORD { INPUT_KEY Key; char Char; - uint8_t Reserved; uint32_t Modifiers; } INPUT_KEY_RECORD; diff --git a/code/public/ntlite/memory_map.h b/code/public/ntlite/memory_map.h index 3a99a79..27e6aa5 100644 --- a/code/public/ntlite/memory_map.h +++ b/code/public/ntlite/memory_map.h @@ -1,7 +1,7 @@ // Public NTLite memory map ABI declarations shared between kernel, drivers, and user mode. #pragma once -// memory_map.h - shared between bootx64.efi and kernel.exe +// memory_map.h - shared between bootx64.efi and kernel.exe // Describes physical memory layout passed from bootloader to kernel. #include diff --git a/code/public/ntlite/status.h b/code/public/ntlite/status.h index db3386d..3d1a63d 100644 --- a/code/public/ntlite/status.h +++ b/code/public/ntlite/status.h @@ -1,70 +1,75 @@ // Public NTLite status ABI declarations shared between kernel, drivers, and user mode. #pragma once -// ke_status.h - kernel status codes -// All fallible kernel functions return KSTATUS. -// Success is always zero; failures are positive named constants. + #include +// All fallible kernel functions return KSTATUS. Bit 31 is the error bit, bits +// 30..24 identify the facility, and bits 23..0 contain the facility-local code. typedef uint32_t KSTATUS; -// --- General & Control (0x0000) ---. -#define STATUS_SUCCESS ((KSTATUS)0x0000) -#define STATUS_FAILURE ((KSTATUS)0x0001) -#define STATUS_NOT_SUPPORTED ((KSTATUS)0x0002) -#define STATUS_TIMEOUT ((KSTATUS)0x0003) -#define STATUS_PENDING ((KSTATUS)0x0004) -#define STATUS_ALERTED ((KSTATUS)0x0005) -#define STATUS_KERNEL_APC ((KSTATUS)0x0006) -#define STATUS_USER_APC ((KSTATUS)0x0007) +#define KSTATUS_ERROR_BIT 0x80000000u +#define KSTATUS_FACILITY_SHIFT 24u +#define KSTATUS_FACILITY_MASK 0x7Fu +#define KSTATUS_CODE_MASK 0x00FFFFFFu -// --- Validation & Parameters (0x0010) ---. -#define STATUS_INVALID_PARAMETER ((KSTATUS)0x0010) -#define STATUS_INVALID_HANDLE ((KSTATUS)0x0011) -#define STATUS_BUFFER_TOO_SMALL ((KSTATUS)0x0012) -#define STATUS_CHECKSUM_MISMATCH ((KSTATUS)0x0013) +#define KSTATUS_MAKE_SUCCESS(facility, code) \ + ((KSTATUS)((((uint32_t)(facility) & KSTATUS_FACILITY_MASK) << KSTATUS_FACILITY_SHIFT) | \ + ((uint32_t)(code) & KSTATUS_CODE_MASK))) +#define KSTATUS_MAKE_ERROR(facility, code) \ + ((KSTATUS)(KSTATUS_ERROR_BIT | \ + (((uint32_t)(facility) & KSTATUS_FACILITY_MASK) << KSTATUS_FACILITY_SHIFT) | \ + ((uint32_t)(code) & KSTATUS_CODE_MASK))) +#define KSTATUS_IS_ERROR(status) ((((KSTATUS)(status)) & KSTATUS_ERROR_BIT) != 0u) +#define KSTATUS_FACILITY(status) ((((KSTATUS)(status)) >> KSTATUS_FACILITY_SHIFT) & KSTATUS_FACILITY_MASK) +#define KSTATUS_CODE(status) (((KSTATUS)(status)) & KSTATUS_CODE_MASK) -// --- Resource & Access (0x0020) ---. -#define STATUS_NO_MEMORY ((KSTATUS)0x0020) -#define STATUS_ACCESS_DENIED ((KSTATUS)0x0021) -#define STATUS_NOT_FOUND ((KSTATUS)0x0022) -#define STATUS_ALREADY_EXISTS ((KSTATUS)0x0023) -#define STATUS_OBJECT_NAME_COLLISION ((KSTATUS)0x0024) +#define STATUS_FACILITY_WAIT 0x00u +#define STATUS_FACILITY_SYSTEM 0x01u +#define STATUS_FACILITY_PROCESS 0x61u -// --- State & Lifecycle (0x0030) ---. -#define STATUS_OBJECT_DEAD ((KSTATUS)0x0030) -#define STATUS_QUEUE_FULL ((KSTATUS)0x0031) -#define STATUS_QUEUE_EMPTY ((KSTATUS)0x0032) -#define STATUS_CANCELLED ((KSTATUS)0x0033) +// Wait-completion statuses are non-error statuses. STATUS_SUCCESS is exactly +// zero so old zero-initialisation and direct syscall success checks stay valid. +#define STATUS_SUCCESS ((KSTATUS)0x00000000u) +#define STATUS_TIMEOUT KSTATUS_MAKE_SUCCESS(STATUS_FACILITY_WAIT, 0x000001u) +#define STATUS_PENDING KSTATUS_MAKE_SUCCESS(STATUS_FACILITY_WAIT, 0x000002u) +#define STATUS_ALERTED KSTATUS_MAKE_SUCCESS(STATUS_FACILITY_WAIT, 0x000003u) +#define STATUS_KERNEL_APC KSTATUS_MAKE_SUCCESS(STATUS_FACILITY_WAIT, 0x000004u) +#define STATUS_USER_APC KSTATUS_MAKE_SUCCESS(STATUS_FACILITY_WAIT, 0x000005u) -// --- Hardware & External (0x0040) ---. -#define STATUS_DEVICE_ERROR ((KSTATUS)0x0040) +// System-facility failures. +// STATUS_WOULD_BLOCK is the canonical result for immediate nonblocking operations that cannot make progress. +// STATUS_TIMEOUT is reserved for finite waits whose deadline expires. +#define STATUS_FAILURE KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x000001u) +#define STATUS_NOT_SUPPORTED KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x000002u) +#define STATUS_INVALID_PARAMETER KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x000003u) +#define STATUS_INVALID_HANDLE KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x000004u) +#define STATUS_BUFFER_TOO_SMALL KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x000005u) +#define STATUS_CHECKSUM_MISMATCH KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x000006u) +#define STATUS_NO_MEMORY KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x000007u) +#define STATUS_ACCESS_DENIED KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x000008u) +#define STATUS_NOT_FOUND KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x000009u) +#define STATUS_ALREADY_EXISTS KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x00000Au) +#define STATUS_OBJECT_NAME_COLLISION KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x00000Bu) +#define STATUS_OBJECT_DEAD KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x00000Cu) +#define STATUS_QUEUE_FULL KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x00000Du) +#define STATUS_QUEUE_EMPTY KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x00000Eu) +#define STATUS_CANCELLED KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x00000Fu) +#define STATUS_DEVICE_ERROR KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x000010u) +#define STATUS_NO_MORE_FILES KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x000011u) +#define STATUS_INVALID_IRQL KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x000012u) +#define STATUS_WOULD_BLOCK KSTATUS_MAKE_ERROR(STATUS_FACILITY_SYSTEM, 0x000013u) -#define STATUS_NO_MORE_FILES ((KSTATUS)0x0050) +// Process termination and fault statuses use the process facility. +#define STATUS_PROCESS_BASE KSTATUS_MAKE_ERROR(STATUS_FACILITY_PROCESS, 0x000000u) +#define STATUS_PROCESS_KILLED KSTATUS_MAKE_ERROR(STATUS_FACILITY_PROCESS, 0x000000u) +#define STATUS_PROCESS_PAGE_FAULT KSTATUS_MAKE_ERROR(STATUS_FACILITY_PROCESS, 0x000010u) +#define STATUS_PROCESS_GENERAL_PROTECTION KSTATUS_MAKE_ERROR(STATUS_FACILITY_PROCESS, 0x000011u) +#define STATUS_PROCESS_ILLEGAL_OPCODE KSTATUS_MAKE_ERROR(STATUS_FACILITY_PROCESS, 0x000012u) +#define STATUS_PROCESS_DIVIDE_BY_ZERO KSTATUS_MAKE_ERROR(STATUS_FACILITY_PROCESS, 0x000013u) +#define STATUS_PROCESS_BREAKPOINT KSTATUS_MAKE_ERROR(STATUS_FACILITY_PROCESS, 0x000014u) +#define STATUS_PROCESS_ALIGNMENT_FAULT KSTATUS_MAKE_ERROR(STATUS_FACILITY_PROCESS, 0x000015u) +#define STATUS_PROCESS_UNKNOWN_FAULT KSTATUS_MAKE_ERROR(STATUS_FACILITY_PROCESS, 0x00001Fu) -// --- Execution level (0x0060) ---. -#define STATUS_INVALID_IRQL ((KSTATUS)0x0060) - -// --- Process termination reasons ---. -// Low values are reserved for ordinary KSTATUS returns. Abnormal process -// termination uses a high-bit private range so exit statuses cannot collide -// with normal kernel/API result codes. -// -// Layout: -// 31 : set => abnormal/private status -// 30..24 : subsystem group -// 23..0 : group-local reason -// -// Group 0x61 is process termination/fault status. -#define STATUS_PRIVATE_BASE ((KSTATUS)0x80000000u) -#define STATUS_PROCESS_BASE ((KSTATUS)0xE1000000u) -#define STATUS_PROCESS_KILLED ((KSTATUS)(STATUS_PROCESS_BASE | 0x0000u)) -#define STATUS_PROCESS_PAGE_FAULT ((KSTATUS)(STATUS_PROCESS_BASE | 0x0010u)) -#define STATUS_PROCESS_GENERAL_PROTECTION ((KSTATUS)(STATUS_PROCESS_BASE | 0x0011u)) -#define STATUS_PROCESS_ILLEGAL_OPCODE ((KSTATUS)(STATUS_PROCESS_BASE | 0x0012u)) -#define STATUS_PROCESS_DIVIDE_BY_ZERO ((KSTATUS)(STATUS_PROCESS_BASE | 0x0013u)) -#define STATUS_PROCESS_BREAKPOINT ((KSTATUS)(STATUS_PROCESS_BASE | 0x0014u)) -#define STATUS_PROCESS_ALIGNMENT_FAULT ((KSTATUS)(STATUS_PROCESS_BASE | 0x0015u)) -#define STATUS_PROCESS_UNKNOWN_FAULT ((KSTATUS)(STATUS_PROCESS_BASE | 0x001Fu)) - -#define KSUCCESS(s) ((s) == STATUS_SUCCESS) +#define KSUCCESS(status) (((KSTATUS)(status)) == STATUS_SUCCESS) +#define KFAILED(status) KSTATUS_IS_ERROR(status) diff --git a/code/public/ntlite/syscall.h b/code/public/ntlite/syscall.h index 314c01d..f6f6a31 100644 --- a/code/public/ntlite/syscall.h +++ b/code/public/ntlite/syscall.h @@ -1,34 +1,19 @@ // Public NTLite syscall ABI declarations shared between kernel, drivers, and user mode. #pragma once -// Syscall.h. - #include #include #include #include #include #include +#include #include #include #include +#include -#define SYS_STATUS_SUCCESS 0u -#define SYS_STATUS_TIMEOUT 3u -#define SYS_STATUS_PENDING 4u -#define SYS_STATUS_ALERTED 5u -#define SYS_STATUS_INVALID_PARAMETER 0x10u -#define SYS_STATUS_INVALID_HANDLE 0x11u -#define SYS_STATUS_BUFFER_TOO_SMALL 0x12u -#define SYS_STATUS_NOT_SUPPORTED 0x02u -#define SYS_STATUS_NO_MEMORY 0x20u -#define SYS_STATUS_ACCESS_DENIED 0x21u -#define SYS_STATUS_KERNEL_APC 6u -#define SYS_STATUS_USER_APC 7u -#define SYS_STATUS_CANCELLED 0x33u -#define SYS_STATUS_NOT_FOUND 0x22u - -// User process/thread environment +// User process/thread environment. // GS base points at SYS_TEB. GS:[0] is the process PEB pointer. #define SYS_PEB_MAGIC 0x30424550u // "PEB0" #define SYS_TEB_MAGIC 0x30424554u // "TEB0" @@ -57,18 +42,26 @@ typedef struct SYS_TEB { uint32_t ThreadId; uint32_t Magic; - // =The kernel leaves this NULL, userspace can modify and use. + // The kernel leaves this NULL so personalities can attach per-thread state. void* SubsystemData; } SYS_TEB; #endif // SYS_USER_ENV_TYPES_DEFINED -// Returns peb. +// Returns the current thread's process environment block. static inline SYS_PEB* SysGetPeb(void) { return (SYS_PEB*)(uintptr_t)__readgsqword(0); } -// Returns teb. +// Returns the kernel-maintained current-directory cache from the PEB. +static inline SYS_STRING_VIEW SysGetCurrentDirectoryView(void) { + SYS_PEB* peb = SysGetPeb(); + if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION) + return SysSvFromParts("", 0); + return peb->CurrentDirectory; +} + +// Returns the current thread environment block. static inline SYS_TEB* SysGetTeb(void) { return (SYS_TEB*)(uintptr_t)__readgsqword(offsetof(SYS_TEB, Self)); } @@ -111,18 +104,18 @@ typedef struct SYS_ASYNC_FILE_REQUEST { uint64_t Offset; // explicit byte offset; does not touch file pointer void* Buffer; size_t Size; - SYS_IO_STATUS_BLOCK* Iosb; // required; initialized to SYS_STATUS_PENDING + SYS_IO_STATUS_BLOCK* Iosb; // required; initialized to STATUS_PENDING SYS_USER_APC_ROUTINE ApcRoutine; // optional; requires SysSetUserApcDispatcher void* ApcContext; uint32_t* RequestHandleOut; // optional; receives cancel/query handle } SYS_ASYNC_FILE_REQUEST; +// User APC frames are kernel-issued control records, not resume contexts. +// User mode may read the routine and arguments, but SysApcReturn only accepts +// the active frame token and restores the kernel-owned saved return state. typedef struct SYS_USER_APC_FRAME { uint64_t Magic; - uint64_t OriginalRip; - uint64_t OriginalRsp; - uint64_t OriginalRflags; - uint64_t OriginalRax; + uint64_t ReturnToken; uint64_t Routine; uint64_t NormalContext; uint64_t SystemArgument1; @@ -167,20 +160,102 @@ typedef struct SYS_PATH_HANDLE_REQUEST { uint32_t* HandleOut; } SYS_PATH_HANDLE_REQUEST; +#define SYS_FILE_ACCESS_READ 0x00000001u +#define SYS_FILE_ACCESS_WRITE 0x00000002u +#define SYS_FILE_ACCESS_APPEND 0x00000004u +#define SYS_FILE_ACCESS_QUERY 0x00000008u +#define SYS_FILE_ACCESS_LOCK 0x00000010u + +#define SYS_FILE_OPEN_EXISTING 1u +#define SYS_FILE_CREATE_NEW 2u +#define SYS_FILE_CREATE_ALWAYS 3u +#define SYS_FILE_OPEN_ALWAYS 4u +#define SYS_FILE_TRUNCATE_EXISTING 5u + +#define SYS_FILE_FLAG_NONE 0x00000000u + +typedef struct SYS_OPEN_FILE_REQUEST { + SYS_STRING_VIEW Path; + uint32_t Access; + uint32_t Disposition; + uint32_t Flags; + uint32_t* HandleOut; +} SYS_OPEN_FILE_REQUEST; + +// Builds a flags-based file-open request. +static inline SYS_OPEN_FILE_REQUEST SysMakeOpenFileRequest(SYS_STRING_VIEW path, + uint32_t access, + uint32_t disposition, + uint32_t flags, + uint32_t* handleOut) { + SYS_OPEN_FILE_REQUEST request; + request.Path = path; + request.Access = access; + request.Disposition = disposition; + request.Flags = flags; + request.HandleOut = handleOut; + return request; +} + typedef struct SYS_PATH_REQUEST { SYS_STRING_VIEW Path; } SYS_PATH_REQUEST; +#define SYS_REAL_PATH_FLAG_NONE 0x00000000u + +// SysRealPath returns a canonical native path of the form "N:/path". It uses +// the kernel-owned process CWD for relative input, collapses '.', '..', and +// repeated separators, and clamps parent traversal at the volume root. The +// current filesystem model has no symlinks, so resolution is lexical and does +// not require the final path to exist. RequiredSizeOut receives the byte count +// including the terminating NUL; a NULL buffer with size zero only queries it. +typedef struct SYS_REAL_PATH_REQUEST { + uint32_t Size; + uint32_t Flags; + SYS_STRING_VIEW Path; + char* Buffer; + size_t BufferSize; + size_t* RequiredSizeOut; +} SYS_REAL_PATH_REQUEST; + +typedef struct SYS_STAT_FILE_REQUEST { + SYS_STRING_VIEW Path; + SYS_FILE_STAT* StatOut; +} SYS_STAT_FILE_REQUEST; + typedef struct SYS_RENAME_FILE_REQUEST { SYS_STRING_VIEW OldPath; SYS_STRING_VIEW NewPath; } SYS_RENAME_FILE_REQUEST; -typedef struct SYS_GET_CWD_REQUEST { - char* Buffer; - size_t Capacity; - size_t* LengthOut; -} SYS_GET_CWD_REQUEST; +#define SYS_FILE_LOCK_SHARED 1u +#define SYS_FILE_LOCK_EXCLUSIVE 2u +#define SYS_FILE_LOCK_FLAG_NONE 0x00000000u +#define SYS_FILE_LOCK_TO_EOF UINT64_MAX + +// File locks are advisory, process-owned byte-range locks keyed by filesystem +// identity. A zero timeout is an immediate try and returns STATUS_WOULD_BLOCK +// on conflict. A finite timeout returns STATUS_TIMEOUT only when its deadline +// expires. SYS_WAIT_TIMEOUT_INFINITE waits without a deadline. Length must be +// nonzero; SYS_FILE_LOCK_TO_EOF locks from Offset through end-of-file growth. +// Unlock removes this process's overlapping locks for the file identity. +typedef struct SYS_LOCK_FILE_REQUEST { + uint32_t Size; + uint32_t FileHandle; + uint32_t Mode; + uint32_t Flags; + uint64_t Offset; + uint64_t Length; + uint64_t TimeoutMilliseconds; +} SYS_LOCK_FILE_REQUEST; + +typedef struct SYS_UNLOCK_FILE_REQUEST { + uint32_t Size; + uint32_t FileHandle; + uint32_t Flags; + uint64_t Offset; + uint64_t Length; +} SYS_UNLOCK_FILE_REQUEST; typedef struct SYS_WRITE_CONSOLE_REQUEST { SYS_STRING_VIEW Text; @@ -202,12 +277,43 @@ typedef struct SYS_OPEN_NAMED_QUEUE_REQUEST { typedef struct SYS_NAMED_QUEUE_OWNER_REQUEST { SYS_STRING_VIEW Name; - uint32_t* PidOut; + uint32_t* ProcessHandleOut; } SYS_NAMED_QUEUE_OWNER_REQUEST; +#define SYS_QUEUE_FLAG_NONE 0x00000000u +#define SYS_QUEUE_MESSAGE_SIZE 56u + +// Queue send and receive requests use wait-style timeouts. A zero timeout is +// an immediate try operation and returns STATUS_WOULD_BLOCK if the queue cannot +// make progress without waiting. SYS_WAIT_TIMEOUT_INFINITE requests a blocking +// wait without a deadline. Message pointers reference SYS_QUEUE_MESSAGE_SIZE +// bytes owned by the caller for the duration of the syscall. The common IPC +// pattern is a small control record with one pointer-sized payload token: the +// shared address-space model means that token does not need translation when +// both endpoints agree on the object lifetime. Queues order the token; they do +// not copy storage behind it. +typedef struct SYS_QUEUE_SEND_REQUEST { + uint32_t Size; + uint32_t QueueHandle; + uint32_t Flags; + uint64_t TimeoutMilliseconds; + const void* Message; +} SYS_QUEUE_SEND_REQUEST; + +typedef struct SYS_QUEUE_RECV_REQUEST { + uint32_t Size; + uint32_t QueueHandle; + uint32_t Flags; + uint64_t TimeoutMilliseconds; + void* MessageOut; +} SYS_QUEUE_RECV_REQUEST; + #define SYS_WAIT_FLAG_NONE 0x00000000u #define SYS_WAIT_FLAG_ALERTABLE 0x00000001u #define SYS_WAIT_TIMEOUT_INFINITE UINT64_MAX + +// SysWaitMany copies handles through a fixed kernel stack buffer. This public +// ABI/resource limit is intentionally small to keep wait setup bounded. #define SYS_WAIT_MANY_MAX_HANDLES 32u typedef struct SYS_WAIT_MANY_REQUEST { @@ -228,19 +334,26 @@ typedef struct SYS_CREATE_THREAD_REQUEST { uint32_t* ThreadIdOut; } SYS_CREATE_THREAD_REQUEST; -// Process & thread lifecycle (0-14). -void SysExitProcess(int exitCode); -void SysExitThread(int exitCode); +void SysExitProcess(int32_t exitCode); +void SysExitThread(int32_t exitCode); uint32_t SysCreateProcess(const SYS_CREATE_PROCESS_REQUEST* request); uint32_t SysWaitSingle(uint32_t handle, uint32_t flags, uint64_t timeoutMilliseconds); uint32_t SysWaitMany(const SYS_WAIT_MANY_REQUEST* request); +// SysOpenInputQueue returns a readable and waitable handle to the default +// keyboard input queue. The handle does not create a second event stream; +// SysReadInputEvent, SysPeekInputEvent, SysReadKey, and SysReadKeyboardChar +// consume the same queue. SysWaitSingle succeeds for this handle when a +// keyboard input event can be read. uint32_t SysOpenInputQueue(uint32_t flags, uint32_t* handleOut); static inline uint32_t SysWait(uint32_t handle, uint32_t flags) { return SysWaitSingle(handle, flags, SYS_WAIT_TIMEOUT_INFINITE); } -uint32_t SysGetProcessExitCode(uint32_t handle, int* exitCodeOut); -uint32_t SysKillProcess(uint32_t pid, int exitCode); +uint32_t SysGetProcessExitCode(uint32_t handle, int32_t* exitCodeOut); +uint32_t SysOpenProcess(uint32_t pid, uint32_t* handleOut); +// Protected kernel processes remain openable for query and wait operations, +// but SysKillProcess returns STATUS_ACCESS_DENIED for them. +uint32_t SysKillProcess(uint32_t processHandle, int32_t exitCode); #define SYS_PROCESS_NAME_MAX 64u #define SYS_PROCESS_CWD_MAX 128u @@ -254,6 +367,7 @@ uint32_t SysKillProcess(uint32_t pid, int exitCode); #define SYS_PROCESS_FLAG_TERMINATING 0x00000004u #define SYS_PROCESS_FLAG_EXITED 0x00000008u #define SYS_PROCESS_FLAG_IDLE 0x00000010u +#define SYS_PROCESS_FLAG_TERMINATION_PROTECTED 0x00000020u #define SYS_THREAD_STATE_READY 0u #define SYS_THREAD_STATE_RUNNING 1u @@ -285,6 +399,7 @@ uint32_t SysKillProcess(uint32_t pid, int exitCode); #define SYS_THREAD_WAIT_USB_PIPE 14u #define SYS_THREAD_WAIT_FILE_MAPPING 15u #define SYS_THREAD_WAIT_SURFACE 16u +#define SYS_THREAD_WAIT_TIMER 17u #define SYS_THREAD_WAIT_UNKNOWN 0xffffffffu typedef struct SYS_THREAD_INFO { @@ -305,8 +420,7 @@ typedef struct SYS_THREAD_INFO { uint32_t WaitObjectType; uint32_t WaitStatus; - int32_t KernelApcDisable; - uint32_t Reserved0; + uint32_t KernelApcDisable; } SYS_THREAD_INFO; typedef struct SYS_PROCESS_INFO { @@ -321,7 +435,6 @@ typedef struct SYS_PROCESS_INFO { uint32_t CurrentDrive; int32_t ExitCode; - uint32_t Reserved0; uintptr_t ImageBase; size_t ImageSize; @@ -344,19 +457,44 @@ uint32_t SysGetCurrentTid(void); uint32_t SysGetCurrentPid(void); uint32_t SysSetThreadPriority(uint32_t priority); uint32_t SysYield(void); + +#define SYS_TIMER_FLAG_NONE 0u + +// Timer deadlines are relative millisecond delays. A zero due time makes the +// timer signaled immediately; a zero period makes it one-shot. Setting an +// already-set timer replaces the previous deadline and clears stale signal +// state before applying the new schedule. +typedef struct SYS_SET_TIMER_REQUEST { + uint32_t Size; + uint32_t TimerHandle; + uint32_t Flags; + uint64_t DueTimeMilliseconds; + uint64_t PeriodMilliseconds; +} SYS_SET_TIMER_REQUEST; + +uint32_t SysCreateTimer(uint32_t* handleOut); +uint32_t SysSetTimer(const SYS_SET_TIMER_REQUEST* request); +uint32_t SysCancelTimer(uint32_t timerHandle); + +// SysSleep blocks the current thread through the same timer backend used by +// waitable timer handles. A zero-length sleep yields the processor. uint32_t SysSleep(uint64_t ms); uint32_t SysSetUserApcDispatcher(void* dispatcher); + +// Returns from the active user APC dispatcher. The frame pointer must identify +// the kernel-issued frame for the current delivery; it is not a general resume +// or context-switch syscall. uint32_t SysApcReturn(const SYS_USER_APC_FRAME* frame); // Virtual memory and handles. -typedef enum SYS_PROTECT_VMION { +typedef enum SYS_VM_PROTECTION { SYS_VM_NOACCESS = 0, SYS_VM_READ = 1, SYS_VM_READWRITE = 2, SYS_VM_EXECUTE = 3, SYS_VM_EXECUTE_READ = 4, SYS_VM_EXECUTE_READWRITE = 5, -} SYS_PROTECT_VMION; +} SYS_VM_PROTECTION; #define SYS_VM_FLAG_NONE 0u #define SYS_VM_FLAG_FIXED 1u @@ -387,7 +525,29 @@ uint32_t SysMapFile(uint32_t mappingHandle, uint64_t offset, size_t size, uint32_t access, uint32_t flags, void** baseOut); uint32_t SysCloseHandle(uint32_t handle); -// Synchronization primitives (19-31). +#define SYS_HANDLE_RIGHT_READ 0x00000001u +#define SYS_HANDLE_RIGHT_WRITE 0x00000002u +#define SYS_HANDLE_RIGHT_WAIT 0x00000004u +#define SYS_HANDLE_RIGHT_SIGNAL 0x00000008u +#define SYS_HANDLE_RIGHT_DUPLICATE 0x00000010u +#define SYS_HANDLE_RIGHT_QUERY 0x00000020u +#define SYS_HANDLE_RIGHT_TERMINATE 0x00000040u +#define SYS_HANDLE_RIGHT_LOCK 0x00000080u +#define SYS_HANDLE_RIGHT_ALL 0x000000FFu + +#define SYS_DUPLICATE_HANDLE_FLAG_NONE 0x00000000u +#define SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS 0x00000001u + +typedef struct SYS_DUPLICATE_HANDLE_REQUEST { + uint32_t SourceHandle; + uint32_t TargetProcessHandle; + uint32_t DesiredRights; + uint32_t Flags; + uint32_t* TargetHandleOut; +} SYS_DUPLICATE_HANDLE_REQUEST; + +uint32_t SysDuplicateHandle(const SYS_DUPLICATE_HANDLE_REQUEST* request); + uint32_t SysCreateMutex(uint32_t* handleOut); uint32_t SysTryAcquireMutex(uint32_t handle); uint32_t SysReleaseMutex(uint32_t handle); @@ -400,38 +560,37 @@ uint32_t SysCreateEvent(uint32_t type, uint32_t initialState, uint32_t* handleOu uint32_t SysSignalEvent(uint32_t handle); uint32_t SysClearEvent(uint32_t handle); - -// Queues and named queues (32-38). uint32_t SysCreateQueue(uint32_t* handleOut); -uint32_t SysSendQueue(uint32_t handle, const void* msg, bool blocking); -uint32_t SysRecvQueue(uint32_t handle, void* msg, bool blocking); +uint32_t SysSendQueue(const SYS_QUEUE_SEND_REQUEST* request); +uint32_t SysRecvQueue(const SYS_QUEUE_RECV_REQUEST* request); uint32_t SysPublishQueue(const SYS_PUBLISH_QUEUE_REQUEST* request); uint32_t SysOpenNamedQueue(const SYS_OPEN_NAMED_QUEUE_REQUEST* request); -uint32_t SysDuplicateQueue(uint32_t handle, uint32_t targetPid, uint32_t* handleOut); -uint32_t SysGetNamedQueueOwnerPid(const SYS_NAMED_QUEUE_OWNER_REQUEST* request); +uint32_t SysOpenNamedQueueOwnerProcess(const SYS_NAMED_QUEUE_OWNER_REQUEST* request); -// Files, directories, and current directory (39-56). -uint32_t SysOpenFile(const SYS_PATH_HANDLE_REQUEST* request); -uint32_t SysCreateFile(const SYS_PATH_HANDLE_REQUEST* request); +uint32_t SysOpenFile(const SYS_OPEN_FILE_REQUEST* request); uint32_t SysReadFile(const SYS_FILE_IO_REQUEST* request); uint32_t SysWriteFile(const SYS_FILE_IO_REQUEST* request); uint32_t SysReadFileAsync(const SYS_ASYNC_FILE_REQUEST* request); uint32_t SysWriteFileAsync(const SYS_ASYNC_FILE_REQUEST* request); uint32_t SysCancelIo(uint32_t asyncRequestHandle); uint32_t SysSeekFile(uint32_t handle, uint64_t offset); -uint64_t SysGetFilePos(uint32_t handle); -uint64_t SysGetFileSize(uint32_t handle); +uint32_t SysGetFilePos(uint32_t handle, uint64_t* positionOut); +uint32_t SysGetFileSize(uint32_t handle, uint64_t* sizeOut); +uint32_t SysGetFileStat(uint32_t handle, SYS_FILE_STAT* statOut); +uint32_t SysStatFile(const SYS_STAT_FILE_REQUEST* request); uint32_t SysTruncateFile(uint32_t handle, uint64_t newSize); uint32_t SysFlushFile(uint32_t handle); uint32_t SysDeleteFile(const SYS_PATH_REQUEST* request); uint32_t SysRenameFile(const SYS_RENAME_FILE_REQUEST* request); +uint32_t SysLockFile(const SYS_LOCK_FILE_REQUEST* request); +uint32_t SysUnlockFile(const SYS_UNLOCK_FILE_REQUEST* request); uint32_t SysOpenDir(const SYS_PATH_HANDLE_REQUEST* request); uint32_t SysReadDir(uint32_t handle, SYS_DIR_ENTRY* entryOut); uint32_t SysCreateDir(const SYS_PATH_REQUEST* request); uint32_t SysRemoveDir(const SYS_PATH_REQUEST* request); uint32_t SysSetCwd(const SYS_PATH_REQUEST* request); -uint32_t SysGetCwd(const SYS_GET_CWD_REQUEST* request); +uint32_t SysRealPath(const SYS_REAL_PATH_REQUEST* request); // Console and graphics. typedef struct SYS_CONSOLE_INFO { @@ -457,27 +616,43 @@ uint32_t SysGetScreenSize(int* widthOut, int* heightOut); uint32_t SysQueryDisplayInfo(SYS_DISPLAY_INFO* infoOut); // Input. -uint64_t SysPollKeyboard(void); -uint64_t SysIsKeyDown(INPUT_KEY key); -char SysReadKeyboardChar(void); -INPUT_KEY SysReadKey(void); +#define SYS_INPUT_QUEUE_FLAG_NONE 0x00000000u +#define SYS_INPUT_EVENT_FLAG_NONE 0x00000000u + +// A zero QueueHandle means the default keyboard input queue. A +// nonzero QueueHandle must be a handle returned by SysOpenInputQueue. A zero +// timeout is an immediate try and returns STATUS_WOULD_BLOCK when the queue is +// empty. A finite timeout returns STATUS_TIMEOUT only when its deadline expires. +typedef struct SYS_INPUT_EVENT_REQUEST { + uint32_t Size; + uint32_t QueueHandle; + uint32_t Flags; + uint64_t TimeoutMilliseconds; + INPUT_KEY_RECORD* RecordOut; +} SYS_INPUT_EVENT_REQUEST; + +uint32_t SysPollKeyboard(INPUT_KEY* keyOut); +uint32_t SysIsKeyDown(INPUT_KEY key, bool* downOut); +uint32_t SysReadKeyboardChar(char* charOut); +uint32_t SysReadKey(INPUT_KEY* keyOut); uint32_t SysGetKeyModifiers(void); -uint32_t SysReadInputEvent(INPUT_KEY_RECORD* recordOut); -uint32_t SysPeekInputEvent(INPUT_KEY_RECORD* recordOut); - -uint64_t SysPollMouse(void); -uint64_t SysReadMouse(int* dx, int* dy, uint8_t* buttons); -void SysGetMousePos(int* x, int* y); +uint32_t SysReadInputEvent(const SYS_INPUT_EVENT_REQUEST* request); +uint32_t SysPeekInputEvent(const SYS_INPUT_EVENT_REQUEST* request); +uint32_t SysPeekMouse(INPUT_MOUSE_EVENT* eventOut); +uint32_t SysReadMouse(INPUT_MOUSE_EVENT* eventOut); +uint32_t SysGetMousePos(int* xOut, int* yOut); // Native sockets. #define SYS_AF_INET 2u +#define SYS_AF_INET6 10u #define SYS_SOCK_STREAM 1u #define SYS_SOCK_DGRAM 2u #define SYS_SOCK_RAW 3u #define SYS_IPPROTO_ICMP 1u #define SYS_IPPROTO_TCP 6u #define SYS_IPPROTO_UDP 17u +#define SYS_IPPROTO_ICMPV6 58u #define SYS_RESOLVE_HOST_NAME_MAX 255u #define SYS_SOCKET_OPT_RECV_TIMEOUT_MS 1u @@ -495,17 +670,24 @@ void SysGetMousePos(int* x, int* y); #define SYS_SHUTDOWN_WRITE 1u #define SYS_SHUTDOWN_BOTH 2u -typedef struct SYS_SOCKET_ADDRESS_IPV4 { - uint16_t Family; // SYS_AF_INET - uint16_t Port; // host byte order - uint8_t Address[4]; // dotted-quad bytes, e.g. {10,0,2,2} -} SYS_SOCKET_ADDRESS_IPV4; +#define SYS_SOCKET_ADDRESS_SIZE 28u + +// Socket addresses are tagged native records. For IPv4, Address[0..3] +// contains the address bytes. For IPv6, all 16 address bytes are used and +// ScopeId carries the zone index for scoped addresses. Port is host byte order. +typedef struct SYS_SOCKET_ADDRESS { + uint16_t Family; + uint16_t Size; + uint16_t Port; + uint8_t Address[16]; + uint32_t ScopeId; +} SYS_SOCKET_ADDRESS; typedef struct SYS_SOCKET_SEND_TO_REQUEST { const void* Buffer; size_t Length; uint32_t Flags; - const SYS_SOCKET_ADDRESS_IPV4* RemoteAddress; + const SYS_SOCKET_ADDRESS* RemoteAddress; size_t RemoteAddressSize; size_t* SentOut; } SYS_SOCKET_SEND_TO_REQUEST; @@ -514,17 +696,16 @@ typedef struct SYS_SOCKET_RECV_FROM_REQUEST { void* Buffer; size_t Length; uint32_t Flags; - SYS_SOCKET_ADDRESS_IPV4* RemoteAddressOut; + SYS_SOCKET_ADDRESS* RemoteAddressOut; size_t* RemoteAddressSizeInOut; size_t* ReceivedOut; } SYS_SOCKET_RECV_FROM_REQUEST; - typedef struct SYS_RESOLVE_HOST_REQUEST { SYS_STRING_VIEW Name; uint32_t Family; uint32_t Flags; - SYS_SOCKET_ADDRESS_IPV4* AddressOut; + SYS_SOCKET_ADDRESS* AddressOut; size_t* AddressSizeInOut; } SYS_RESOLVE_HOST_REQUEST; @@ -542,10 +723,10 @@ typedef struct SYS_POLLFD { } SYS_POLLFD; uint32_t SysCreateSocket(uint32_t domain, uint32_t type, uint32_t protocol, uint32_t* handleOut); -uint32_t SysBindSocket(uint32_t socket, const SYS_SOCKET_ADDRESS_IPV4* address, size_t addressSize); +uint32_t SysBindSocket(uint32_t socket, const SYS_SOCKET_ADDRESS* address, size_t addressSize); uint32_t SysListenSocket(uint32_t socket, uint32_t backlog); -uint32_t SysAcceptSocket(uint32_t socket, SYS_SOCKET_ADDRESS_IPV4* remoteAddressOut, size_t* remoteAddressSizeInOut, uint32_t* acceptedHandleOut); -uint32_t SysConnectSocket(uint32_t socket, const SYS_SOCKET_ADDRESS_IPV4* address, size_t addressSize); +uint32_t SysAcceptSocket(uint32_t socket, SYS_SOCKET_ADDRESS* remoteAddressOut, size_t* remoteAddressSizeInOut, uint32_t* acceptedHandleOut); +uint32_t SysConnectSocket(uint32_t socket, const SYS_SOCKET_ADDRESS* address, size_t addressSize); uint32_t SysSendSocket(uint32_t socket, const void* buffer, size_t length, uint32_t flags, size_t* sentOut); uint32_t SysRecvSocket(uint32_t socket, void* buffer, size_t length, uint32_t flags, size_t* receivedOut); uint32_t SysShutdownSocket(uint32_t socket, uint32_t how); @@ -580,9 +761,14 @@ typedef struct SYS_MEMORY_STATUS { #define SYS_KERNEL_STATS_FILESYSTEM 10u #define SYS_KERNEL_STATS_DISK 11u -#define SYS_KERNEL_STATS_MAX_CPUS 32u +// Public stats payloads start with Size and Version. Size is the byte count +// returned for that payload, including any trailing records. Version describes +// the field layout for fixed fields in the named payload. +#define SYS_STATS_VERSION 1u typedef struct SYS_SCHEDULER_STATS { + uint32_t Size; + uint32_t Version; uint64_t Ticks; uint64_t ContextSwitches; uint64_t KeepCurrentDecisions; @@ -598,6 +784,8 @@ typedef struct SYS_SCHEDULER_STATS { } SYS_SCHEDULER_STATS; typedef struct SYS_PMM_STATS { + uint32_t Size; + uint32_t Version; uint64_t TotalFrames; uint64_t FreeFrames; uint64_t UsedFrames; @@ -614,18 +802,26 @@ typedef struct SYS_CPU_STAT { uint32_t Flags; uint32_t CurrentTid; uint32_t CurrentThreadFlags; - uint32_t Reserved0; uint64_t SchedulerTicks; uint64_t ContextSwitches; } SYS_CPU_STAT; +// CPU stats use trailing SYS_CPU_STAT records after the fixed header. typedef struct SYS_CPU_STATS { + uint32_t Size; + uint32_t Version; + uint32_t HeaderSize; uint32_t CpuCount; - uint32_t Reserved; - SYS_CPU_STAT Cpu[SYS_KERNEL_STATS_MAX_CPUS]; + uint32_t RecordSize; + uint32_t RecordCount; } SYS_CPU_STATS; +#define SYS_CPU_STATS_RECORDS(stats) ((SYS_CPU_STAT*)((uint8_t*)(stats) + sizeof(SYS_CPU_STATS))) +#define SYS_CPU_STATS_CONST_RECORDS(stats) ((const SYS_CPU_STAT*)((const uint8_t*)(stats) + sizeof(SYS_CPU_STATS))) + typedef struct SYS_PFN_STATS { + uint32_t Size; + uint32_t Version; uint64_t TotalFrames; uint64_t FreeFrames; uint64_t UsedFrames; @@ -687,6 +883,8 @@ typedef struct SYS_PFN_STATS { } SYS_PFN_STATS; typedef struct SYS_VAD_STATS { + uint32_t Size; + uint32_t Version; uint64_t ResolveFaultCalls; uint64_t ResolvedFaults; uint64_t FailedFaults; @@ -720,8 +918,9 @@ typedef struct SYS_VAD_STATS { uint64_t SwapPageIns; } SYS_VAD_STATS; - typedef struct SYS_FILE_CACHE_STATS { + uint32_t Size; + uint32_t Version; uint64_t Lookups; uint64_t Hits; uint64_t Misses; @@ -764,7 +963,12 @@ typedef struct SYS_FILE_CACHE_STATS { uint64_t MaxResidentPages; } SYS_FILE_CACHE_STATS; +// Filesystem stats are generic VFS/driver telemetry. Drivers should map +// private metadata work onto these counters instead of exposing filesystem- +// specific cache names through the public ABI. typedef struct SYS_FILESYSTEM_STATS { + uint32_t Size; + uint32_t Version; uint64_t PathLookups; uint64_t PathCacheHits; uint64_t PathCacheMisses; @@ -772,14 +976,8 @@ typedef struct SYS_FILESYSTEM_STATS { uint64_t PathCacheInvalidations; uint64_t PathCacheGeneration; uint64_t DirectorySearches; - uint64_t DirectoryClustersRead; + uint64_t DirectoryBlocksRead; uint64_t DirectoryEntriesScanned; - uint64_t FatChainCacheLookups; - uint64_t FatChainCacheHits; - uint64_t FatChainCacheMisses; - uint64_t FatChainCacheStores; - uint64_t FatChainCacheInvalidations; - uint64_t FatChainWalkSteps; uint64_t DataReadRuns; uint64_t DataReadSectors; uint64_t DataReadRunMerges; @@ -787,6 +985,8 @@ typedef struct SYS_FILESYSTEM_STATS { } SYS_FILESYSTEM_STATS; typedef struct SYS_DISK_STATS { + uint32_t Size; + uint32_t Version; uint64_t RegisteredDisks; uint64_t ReadRequests; uint64_t ReadSectors; @@ -808,6 +1008,8 @@ typedef struct SYS_DISK_STATS { } SYS_DISK_STATS; typedef struct SYS_SWAP_STATS { + uint32_t Size; + uint32_t Version; uint64_t Enabled; uint64_t TotalSlots; uint64_t UsedSlots; @@ -822,7 +1024,17 @@ typedef struct SYS_SWAP_STATS { uint64_t SlotAllocationFailures; } SYS_SWAP_STATS; +#define SYS_PTE_QUERY_GENERIC 0u +#define SYS_PTE_QUERY_USER_PROBE 1u +#define SYS_PTE_QUERY_HEAP 2u +#define SYS_PTE_QUERY_VAD 3u +#define SYS_PTE_QUERY_DEBUG 4u +#define SYS_PTE_QUERY_SELF_TEST 5u +#define SYS_PTE_QUERY_PURPOSE_COUNT 6u + typedef struct SYS_PTE_STATS { + uint32_t Size; + uint32_t Version; uint64_t MapCalls; uint64_t MapFailures; uint64_t UnmapCalls; @@ -831,8 +1043,8 @@ typedef struct SYS_PTE_STATS { uint64_t ProtectFailures; uint64_t QueryCalls; uint64_t QueryFailures; - uint64_t QueryByPurpose[6]; - uint64_t QueryFailuresByPurpose[6]; + uint64_t QueryByPurpose[SYS_PTE_QUERY_PURPOSE_COUNT]; + uint64_t QueryFailuresByPurpose[SYS_PTE_QUERY_PURPOSE_COUNT]; uint64_t TranslateCalls; uint64_t LocalFlushes; uint64_t GlobalFlushes; @@ -855,7 +1067,25 @@ typedef struct SYS_PTE_STATS { #define SYS_CPU_FLAG_PRIMARY 0x00000020u // Time and diagnostics. +// SysGetTick returns monotonic ticks since boot. The tick unit is defined by +// SysGetTickFrequency and is not a wall-clock timestamp. uint64_t SysGetTick(void); + +// SysGetTickFrequency returns the number of SysGetTick ticks per second. +uint32_t SysGetTickFrequency(uint64_t* frequencyOut); + +// Converts a tick delta to milliseconds using the supplied tick frequency. +static inline uint64_t SysTicksToMilliseconds(uint64_t ticks, uint64_t frequency) { + if (!frequency) return 0; + return (ticks / frequency) * 1000ull + ((ticks % frequency) * 1000ull) / frequency; +} + +// Converts a tick delta to microseconds using the supplied tick frequency. +static inline uint64_t SysTicksToMicroseconds(uint64_t ticks, uint64_t frequency) { + if (!frequency) return 0; + return (ticks / frequency) * 1000000ull + ((ticks % frequency) * 1000000ull) / frequency; +} + uint32_t SysPrintDebug(const SYS_PRINT_DEBUG_REQUEST* request); uint32_t SysQueryTrapFrame(SYS_TRAP_FRAME_INFO* info); diff --git a/code/settings.ini b/code/settings.ini index d72ae7b..73ca3a7 100644 --- a/code/settings.ini +++ b/code/settings.ini @@ -6,6 +6,8 @@ address=10.0.2.15 netmask=255.255.255.0 gateway=10.0.2.2 dns=10.0.2.3 +ipv6=auto +dns6=2001:4860:4860::8888 [network.eth0] mode=static @@ -13,6 +15,8 @@ address=10.0.2.15 netmask=255.255.255.0 gateway=10.0.2.2 dns=10.0.2.3 +ipv6=auto +dns6=2001:4860:4860::8888 [time] ; Default wall-clock policy: Central European Time with automatic EU daylight saving. diff --git a/code/user/apps/doom/doom.c b/code/user/apps/doom/doom.c index a6f7188..2970610 100644 --- a/code/user/apps/doom/doom.c +++ b/code/user/apps/doom/doom.c @@ -55,12 +55,21 @@ static void* doom_open_wrapper(const char* path, const char* mode) uint32_t handle = 0; uint32_t result; - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), &handle }; + uint32_t access = SYS_FILE_ACCESS_QUERY; + uint32_t disposition = SYS_FILE_OPEN_EXISTING; if (mode[0] == 'w') { - result = SysCreateFile(&req); + access |= SYS_FILE_ACCESS_WRITE; + disposition = SYS_FILE_CREATE_ALWAYS; } else { - result = SysOpenFile(&req); + access |= SYS_FILE_ACCESS_READ; } + + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path), + access, + disposition, + SYS_FILE_FLAG_NONE, + &handle); + result = SysOpenFile(&req); if (result != 0) return 0; DOOM_FILE_HANDLE* f = (DOOM_FILE_HANDLE*)malloc(sizeof(DOOM_FILE_HANDLE)); @@ -69,9 +78,17 @@ static void* doom_open_wrapper(const char* path, const char* mode) return 0; } + uint64_t size = 0; + result = SysGetFileSize(handle, &size); + if (result != 0) { + SysCloseHandle(handle); + free(f); + return 0; + } + f->Handle = handle; f->Pos = 0; - f->Size = SysGetFileSize(handle); + f->Size = size; return f; } @@ -155,9 +172,16 @@ static int doom_eof_wrapper(void* handle) // --------------------------------------------------------------------------- static void doom_gettime_wrapper(int* sec, int* usec) { - uint64_t t = SysGetTick(); - *sec = (int)(t / 1000000); // 1,000,000 us = 1 second - *usec = (int)(t % 1000000); // Remainder in microseconds + uint64_t frequency = 0; + if (SysGetTickFrequency(&frequency) != STATUS_SUCCESS || !frequency) { + *sec = 0; + *usec = 0; + return; + } + + uint64_t totalUs = SysTicksToMicroseconds(SysGetTick(), frequency); + *sec = (int)(totalUs / 1000000); + *usec = (int)(totalUs % 1000000); } // --------------------------------------------------------------------------- @@ -169,7 +193,7 @@ static void doom_exit_wrapper(int code) } // --------------------------------------------------------------------------- -// doom_getenv_fn +// doom_getenv_fn // --------------------------------------------------------------------------- static char* doom_getenv_wrapper(const char* name) { @@ -204,7 +228,7 @@ static const KeyMap g_keymap[] = { { INPUT_KEY_LEFT, DOOM_KEY_LEFT_ARROW }, { INPUT_KEY_RIGHT, DOOM_KEY_RIGHT_ARROW }, - // Action keys + // Action keys { INPUT_KEY_LEFT_CTRL, DOOM_KEY_CTRL }, { INPUT_KEY_SPACE, DOOM_KEY_SPACE }, { INPUT_KEY_LEFT_SHIFT, DOOM_KEY_SHIFT }, @@ -250,10 +274,17 @@ static const KeyMap g_keymap[] = { static bool g_prev_key[KEYMAP_COUNT]; +// Returns whether one NTLite input key is currently held. +static bool ntlite_key_down(INPUT_KEY key) +{ + bool down = false; + return SysIsKeyDown(key, &down) == STATUS_SUCCESS && down; +} + static void poll_input(void) { for (int i = 0; i < (int)KEYMAP_COUNT; i++) { - bool cur_down = (SysIsKeyDown(g_keymap[i].inputKey) != 0); + bool cur_down = ntlite_key_down(g_keymap[i].inputKey); bool prev_down = g_prev_key[i]; if (cur_down && !prev_down) doom_key_down(g_keymap[i].key); @@ -280,7 +311,7 @@ static void init_surface(void) { uint32_t s = SysCreateSurface(DISP_W, DISP_H, SYS_SURFACE_FORMAT_XRGB8888, SYS_SURFACE_FLAG_NONE, &g_surface_handle); - if (s != SYS_STATUS_SUCCESS || !g_surface_handle) { + if (s != STATUS_SUCCESS || !g_surface_handle) { doom_print_wrapper("doom: SysCreateSurface failed\n"); SysExitProcess(1); } @@ -288,7 +319,7 @@ static void init_surface(void) void* base = 0; uint32_t pitchBytes = 0; s = SysMapSurface(g_surface_handle, &base, &pitchBytes); - if (s != SYS_STATUS_SUCCESS || !base || pitchBytes < DISP_W * sizeof(uint32_t)) { + if (s != STATUS_SUCCESS || !base || pitchBytes < DISP_W * sizeof(uint32_t)) { doom_print_wrapper("doom: SysMapSurface failed\n"); SysExitProcess(1); } @@ -328,7 +359,7 @@ static void render_frame(const uint8_t* rgba) DISPLAY_RECT rect = { 0, 0, DISP_W, DISP_H }; uint32_t code = SysPresentSurface(g_surface_handle, &rect, 1); - if (code != SYS_STATUS_SUCCESS) { + if (code != STATUS_SUCCESS) { SysExitProcess(code); } } diff --git a/code/user/apps/edit/edit.c b/code/user/apps/edit/edit.c index 6579ace..510954e 100644 --- a/code/user/apps/edit/edit.c +++ b/code/user/apps/edit/edit.c @@ -369,12 +369,14 @@ static void move_down(void) { // Updates file. static void save_file(void) { uint32_t h = 0; - SYS_PATH_HANDLE_REQUEST pathReq = { SysSvFromCstr(gFilename), &h }; - uint32_t st = SysCreateFile(&pathReq); - if (st != 0) st = SysOpenFile(&pathReq); + SYS_OPEN_FILE_REQUEST pathReq = SysMakeOpenFileRequest(SysSvFromCstr(gFilename), + SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_CREATE_ALWAYS, + SYS_FILE_FLAG_NONE, + &h); + uint32_t st = SysOpenFile(&pathReq); if (st != 0) { msg_set("save failed: cannot open file"); return; } - SysTruncateFile(h, 0); SysSeekFile(h, 0); for (int i = 0; i < gLineCount; i++) { @@ -411,7 +413,11 @@ static void load_file(void) { reset_buffer(); uint32_t h = 0; - SYS_PATH_HANDLE_REQUEST pathReq = { SysSvFromCstr(gFilename), &h }; + SYS_OPEN_FILE_REQUEST pathReq = SysMakeOpenFileRequest(SysSvFromCstr(gFilename), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); if (SysOpenFile(&pathReq) != 0) { msg_set("new file"); return; @@ -501,7 +507,8 @@ static void editor_loop(void) { while (true) { render(); - INPUT_KEY key = SysReadKey(); + INPUT_KEY key = INPUT_KEY_NONE; + if (SysReadKey(&key) != STATUS_SUCCESS) continue; uint32_t mods = SysGetKeyModifiers(); bool ctrl = (mods & INPUT_MOD_CTRL) != 0; diff --git a/code/user/apps/httpget/httpget.c b/code/user/apps/httpget/httpget.c index 00d1e45..c210172 100644 --- a/code/user/apps/httpget/httpget.c +++ b/code/user/apps/httpget/httpget.c @@ -69,6 +69,38 @@ static void format_ipv4(char* dst, size_t cap, const uint8_t ip[4]) { append_u8(dst, cap, &off, ip[3]); } +static void append_hex16(char* dst, size_t cap, size_t* off, uint16_t value) { + static const char hex[] = "0123456789abcdef"; + int started = 0; + for (int shift = 12; shift >= 0; shift -= 4) { + unsigned nibble = (value >> shift) & 0xfu; + if (nibble || started || shift == 0) { + append_char(dst, cap, off, hex[nibble]); + started = 1; + } + } +} + +// Implements the format ipv6 operation. +static void format_ipv6(char* dst, size_t cap, const uint8_t ip[16]) { + size_t off = 0; + if (!dst || cap == 0) return; + dst[0] = 0; + for (int i = 0; i < 8; ++i) { + uint16_t word = ((uint16_t)ip[i * 2] << 8) | ip[i * 2 + 1]; + append_hex16(dst, cap, &off, word); + if (i != 7) append_char(dst, cap, &off, ':'); + } +} + +// Implements the format socket address operation. +static void format_socket_address(char* dst, size_t cap, const SYS_SOCKET_ADDRESS* address) { + if (!address || address->Family == SYS_AF_INET) + format_ipv4(dst, cap, address ? address->Address : (const uint8_t*)"\0\0\0\0"); + else + format_ipv6(dst, cap, address->Address); +} + // Parses u8. static bool parse_u8(const char** p, uint8_t* out) { const char* s = *p; @@ -121,36 +153,39 @@ static void usage(void) { } // Implements the resolve host operation. -static uint32_t resolve_host(const char* host, uint8_t ip[4]) { - if (parse_ipv4(host, ip)) - return SYS_STATUS_SUCCESS; +static uint32_t resolve_host(const char* host, SYS_SOCKET_ADDRESS* address) { + uint8_t ip[4]; + if (parse_ipv4(host, ip)) { + memset(address, 0, sizeof(*address)); + address->Size = sizeof(*address); + address->Family = SYS_AF_INET; + memcpy(address->Address, ip, 4); + return STATUS_SUCCESS; + } - SYS_SOCKET_ADDRESS_IPV4 resolved; - size_t resolvedSize = sizeof(resolved); + size_t resolvedSize = sizeof(*address); SYS_RESOLVE_HOST_REQUEST req; - memset(&resolved, 0, sizeof(resolved)); + memset(address, 0, sizeof(*address)); memset(&req, 0, sizeof(req)); req.Name = SysSvFromCstr(host); - req.Family = SYS_AF_INET; - req.AddressOut = &resolved; + req.Family = SYS_AF_INET6; + req.AddressOut = address; req.AddressSizeInOut = &resolvedSize; uint32_t st = SysResolveHost(&req); - if (st != SYS_STATUS_SUCCESS) - return st; - if (resolved.Family != SYS_AF_INET) - return SYS_STATUS_NOT_FOUND; + if (st == STATUS_SUCCESS) + return STATUS_SUCCESS; - ip[0] = resolved.Address[0]; - ip[1] = resolved.Address[1]; - ip[2] = resolved.Address[2]; - ip[3] = resolved.Address[3]; - return SYS_STATUS_SUCCESS; + resolvedSize = sizeof(*address); + memset(address, 0, sizeof(*address)); + req.Family = SYS_AF_INET; + st = SysResolveHost(&req); + return st; } // Runs the requested operation. int main(void) { - uint8_t ip[4] = {10, 0, 2, 2}; + SYS_SOCKET_ADDRESS addr; char host[256]; char path[128]; strcpy(host, "10.0.2.2"); @@ -188,15 +223,15 @@ int main(void) { } } - uint32_t st = resolve_host(host, ip); - if (st != SYS_STATUS_SUCCESS) { + uint32_t st = resolve_host(host, &addr); + if (st != STATUS_SUCCESS) { print("httpget: resolve failed "); print_u64(st); print(" for "); print(host); print("\n"); usage(); SysExitProcess(1); } - char ipText[16]; - format_ipv4(ipText, sizeof(ipText), ip); + char ipText[64]; + format_socket_address(ipText, sizeof(ipText), &addr); print("httpget: GET http://"); print(host); @@ -206,23 +241,16 @@ int main(void) { print(")\n"); uint32_t sock = 0; - st = SysCreateSocket(SYS_AF_INET, SYS_SOCK_STREAM, SYS_IPPROTO_TCP, &sock); - if (st != SYS_STATUS_SUCCESS) { + st = SysCreateSocket(addr.Family, SYS_SOCK_STREAM, SYS_IPPROTO_TCP, &sock); + if (st != STATUS_SUCCESS) { print("httpget: SysCreateSocket failed "); print_u64(st); print("\n"); SysExitProcess(1); } - SYS_SOCKET_ADDRESS_IPV4 addr; - memset(&addr, 0, sizeof(addr)); - addr.Family = SYS_AF_INET; addr.Port = 80; - addr.Address[0] = ip[0]; - addr.Address[1] = ip[1]; - addr.Address[2] = ip[2]; - addr.Address[3] = ip[3]; st = SysConnectSocket(sock, &addr, sizeof(addr)); - if (st != SYS_STATUS_SUCCESS) { + if (st != STATUS_SUCCESS) { print("httpget: SysConnectSocket failed "); print_u64(st); print("\n"); SysCloseHandle(sock); SysExitProcess(1); @@ -241,7 +269,7 @@ int main(void) { while (totalSent < off) { size_t sent = 0; st = SysSendSocket(sock, request + totalSent, off - totalSent, 0, &sent); - if (st != SYS_STATUS_SUCCESS || sent == 0) { + if (st != STATUS_SUCCESS || sent == 0) { print("httpget: SysSendSocket failed "); print_u64(st); print(" sent="); print_u64(sent); print("\n"); SysCloseHandle(sock); SysExitProcess(1); @@ -254,7 +282,7 @@ int main(void) { for (;;) { size_t got = 0; st = SysRecvSocket(sock, buf, sizeof(buf) - 1, 0, &got); - if (st != SYS_STATUS_SUCCESS) { + if (st != STATUS_SUCCESS) { print("\nhttpget: SysRecvSocket failed "); print_u64(st); print(" after "); print_u64(total); print(" bytes\n"); SysCloseHandle(sock); SysExitProcess(1); diff --git a/code/user/apps/memtop/memtop.c b/code/user/apps/memtop/memtop.c index 72ea855..c187961 100644 --- a/code/user/apps/memtop/memtop.c +++ b/code/user/apps/memtop/memtop.c @@ -30,8 +30,8 @@ int main(void) { char metricsBuf[128]; while (true) { - if (SysGetConsoleInfo(&console) == SYS_STATUS_SUCCESS && - SysGetMemoryStatus(&mem) == SYS_STATUS_SUCCESS) { + if (SysGetConsoleInfo(&console) == STATUS_SUCCESS && + SysGetMemoryStatus(&mem) == STATUS_SUCCESS) { // If the system isn't reporting columns correctly, default to a safe standard width int columns = (console.Columns > 0) ? (int)console.Columns : 80; diff --git a/code/user/apps/ping/ping.c b/code/user/apps/ping/ping.c index d56fde6..a3eae11 100644 --- a/code/user/apps/ping/ping.c +++ b/code/user/apps/ping/ping.c @@ -133,9 +133,9 @@ static bool parse_u32(const char* s, uint32_t* out) { // Implements the resolve host operation. static uint32_t resolve_host(const char* host, uint8_t ip[4]) { if (parse_ipv4(host, ip)) - return SYS_STATUS_SUCCESS; + return STATUS_SUCCESS; - SYS_SOCKET_ADDRESS_IPV4 resolved; + SYS_SOCKET_ADDRESS resolved; size_t resolvedSize = sizeof(resolved); SYS_RESOLVE_HOST_REQUEST req; memset(&resolved, 0, sizeof(resolved)); @@ -146,16 +146,16 @@ static uint32_t resolve_host(const char* host, uint8_t ip[4]) { req.AddressSizeInOut = &resolvedSize; uint32_t st = SysResolveHost(&req); - if (st != SYS_STATUS_SUCCESS) + if (st != STATUS_SUCCESS) return st; if (resolved.Family != SYS_AF_INET) - return SYS_STATUS_NOT_FOUND; + return STATUS_NOT_FOUND; ip[0] = resolved.Address[0]; ip[1] = resolved.Address[1]; ip[2] = resolved.Address[2]; ip[3] = resolved.Address[3]; - return SYS_STATUS_SUCCESS; + return STATUS_SUCCESS; } // Implements the icmp checksum operation. @@ -258,7 +258,7 @@ int main(void) { uint8_t ip[4]; uint32_t st = resolve_host(host, ip); - if (st != SYS_STATUS_SUCCESS) { + if (st != STATUS_SUCCESS) { print("ping: resolve failed "); print_u64(st); print(" for "); print(host); print("\n"); SysExitProcess(1); } @@ -268,7 +268,7 @@ int main(void) { uint32_t sock = 0; st = SysCreateSocket(SYS_AF_INET, SYS_SOCK_RAW, SYS_IPPROTO_ICMP, &sock); - if (st != SYS_STATUS_SUCCESS) { + if (st != STATUS_SUCCESS) { print("ping: SysCreateSocket(raw icmp) failed "); print_u64(st); print("\n"); SysExitProcess(1); } @@ -281,8 +281,17 @@ int main(void) { opt.ValueSize = sizeof(timeout); (void)SysSetSocketOption(sock, &opt); - SYS_SOCKET_ADDRESS_IPV4 remote; + uint64_t tickFrequency = 0; + st = SysGetTickFrequency(&tickFrequency); + if (st != STATUS_SUCCESS || !tickFrequency) { + print("ping: SysGetTickFrequency failed "); print_u64(st); print("\n"); + SysCloseHandle(sock); + SysExitProcess(1); + } + + SYS_SOCKET_ADDRESS remote; memset(&remote, 0, sizeof(remote)); + remote.Size = sizeof(remote); remote.Family = SYS_AF_INET; remote.Port = 0; remote.Address[0] = ip[0]; @@ -314,7 +323,7 @@ int main(void) { uint64_t start = SysGetTick(); st = SysSendToSocket(sock, &sendReq); - if (st != SYS_STATUS_SUCCESS) { + if (st != STATUS_SUCCESS) { print("ping: send failed seq="); print_u64(seq); print(" status="); print_u64(st); print("\n"); if (i + 1 < count) SysSleep(1000); continue; @@ -325,7 +334,7 @@ int main(void) { uint8_t lastType = 0, lastCode = 0; for (;;) { uint8_t recvBuf[512]; - SYS_SOCKET_ADDRESS_IPV4 from; + SYS_SOCKET_ADDRESS from; size_t fromSize = sizeof(from); size_t got = 0; SYS_SOCKET_RECV_FROM_REQUEST recvReq; @@ -337,9 +346,9 @@ int main(void) { recvReq.ReceivedOut = &got; st = SysRecvFromSocket(sock, &recvReq); - if (st == SYS_STATUS_TIMEOUT) + if (st == STATUS_TIMEOUT) break; - if (st != SYS_STATUS_SUCCESS) { + if (st != STATUS_SUCCESS) { print("ping: recv failed status="); print_u64(st); print("\n"); break; } @@ -347,7 +356,7 @@ int main(void) { size_t icmpLen = 0; if (parse_echo_reply(recvBuf, got, ident, seq, &icmpLen, &lastType, &lastCode)) { uint64_t end = SysGetTick(); - uint64_t deltaUs = end >= start ? end - start : 0; + uint64_t deltaUs = end >= start ? SysTicksToMicroseconds(end - start, tickFrequency) : 0; receivedPackets++; sumUs += deltaUs; if (receivedPackets == 1 || deltaUs < minUs) minUs = deltaUs; @@ -361,7 +370,7 @@ int main(void) { } uint64_t now = SysGetTick(); - if (now - start >= ((uint64_t)PING_TIMEOUT_MS * US_PER_MS)) + if (SysTicksToMilliseconds(now - start, tickFrequency) >= PING_TIMEOUT_MS) break; } diff --git a/code/user/apps/resolve/resolve.c b/code/user/apps/resolve/resolve.c index 26a2565..1cf6156 100644 --- a/code/user/apps/resolve/resolve.c +++ b/code/user/apps/resolve/resolve.c @@ -49,40 +49,82 @@ static void print_ipv4(const uint8_t ip[4]) { print_u64(ip[2]); print("."); print_u64(ip[3]); } +// Prints ipv6. +static void print_ipv6(const uint8_t ip[16]) { + static const char hex[] = "0123456789abcdef"; + for (int i = 0; i < 8; ++i) { + uint16_t word = ((uint16_t)ip[i * 2] << 8) | ip[i * 2 + 1]; + int started = 0; + for (int shift = 12; shift >= 0; shift -= 4) { + unsigned nibble = (word >> shift) & 0xfu; + if (nibble || started || shift == 0) { + char c = hex[nibble]; + print_char(c); + started = 1; + } + } + if (i != 7) + print(":"); + } +} + // Runs the requested operation. int main(void) { + char familyText[16]; char name[256]; + uint32_t family = 0; const char* cmd = SysGetPeb()->CommandLine.Data; - cmd = next_token(cmd, name, sizeof(name)); // program path + cmd = next_token(cmd, name, sizeof(name)); (void)cmd; + familyText[0] = 0; name[0] = 0; - if (cmd) - (void)next_token(cmd, name, sizeof(name)); + if (cmd) { + const char* rest = next_token(cmd, familyText, sizeof(familyText)); + if (strcmp(familyText, "-4") == 0) { + family = SYS_AF_INET; + if (rest) + (void)next_token(rest, name, sizeof(name)); + } else if (strcmp(familyText, "-6") == 0) { + family = SYS_AF_INET6; + if (rest) + (void)next_token(rest, name, sizeof(name)); + } else { + strcpy(name, familyText); + } + } if (!name[0]) { - print("usage: resolve.exe hostname\n"); + print("usage: resolve.exe [-4|-6] hostname\n"); SysExitProcess(1); } - SYS_SOCKET_ADDRESS_IPV4 addr; + SYS_SOCKET_ADDRESS addr; size_t addrSize = sizeof(addr); SYS_RESOLVE_HOST_REQUEST req; memset(&addr, 0, sizeof(addr)); memset(&req, 0, sizeof(req)); req.Name = SysSvFromCstr(name); - req.Family = SYS_AF_INET; + req.Family = family ? family : SYS_AF_INET6; req.AddressOut = &addr; req.AddressSizeInOut = &addrSize; uint32_t st = SysResolveHost(&req); - if (st != SYS_STATUS_SUCCESS) { + if (st != STATUS_SUCCESS && family == 0) { + req.Family = SYS_AF_INET; + addrSize = sizeof(addr); + st = SysResolveHost(&req); + } + if (st != STATUS_SUCCESS) { print("resolve: SysResolveHost failed "); print_u64(st); print("\n"); SysExitProcess(1); } print(name); print(" -> "); - print_ipv4(addr.Address); + if (addr.Family == SYS_AF_INET6) + print_ipv6(addr.Address); + else + print_ipv4(addr.Address); print("\n"); SysExitProcess(0); return 0; diff --git a/code/user/apps/shell_native/shell_native.c b/code/user/apps/shell_native/shell_native.c index c36b796..dbfb5ee 100644 --- a/code/user/apps/shell_native/shell_native.c +++ b/code/user/apps/shell_native/shell_native.c @@ -18,14 +18,22 @@ static void print(const char* s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCs // Opens file. static uint32_t open_file(const char* path, uint32_t* handleOut) { - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut }; + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + handleOut); return SysOpenFile(&req); } // Creates file. static uint32_t create_file(const char* path, uint32_t* handleOut) { - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut }; - return SysCreateFile(&req); + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path), + SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_CREATE_ALWAYS, + SYS_FILE_FLAG_NONE, + handleOut); + return SysOpenFile(&req); } // Opens dir. @@ -513,7 +521,13 @@ static bool readLine(char* buf, int len) { while (true) { INPUT_KEY_RECORD input; - if (SysReadInputEvent(&input) != 0) { SysYield(); continue; } + SYS_INPUT_EVENT_REQUEST inputReq; + inputReq.Size = sizeof(inputReq); + inputReq.QueueHandle = 0; + inputReq.Flags = SYS_INPUT_EVENT_FLAG_NONE; + inputReq.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE; + inputReq.RecordOut = &input; + if (SysReadInputEvent(&inputReq) != STATUS_SUCCESS) { SysYield(); continue; } INPUT_KEY key = input.Key; if (key == INPUT_KEY_NONE) { SysYield(); continue; } @@ -609,7 +623,6 @@ static void printSizeKiB(uint64_t bytes) { print(" KiB"); } - // Prints pages Ki B. static void printPagesKiB(uint64_t pages) { printUInt64(pages * 4u); @@ -630,7 +643,8 @@ static void printCpuFlags(uint32_t flags) { static void cmd_stats(void) { SYS_SCHEDULER_STATS sched; SYS_PMM_STATS pmm; - SYS_CPU_STATS cpu; + SYS_CPU_STATS* cpu = 0; + void* cpuBuffer = 0; SYS_PFN_STATS pfn; SYS_VAD_STATS vad; SYS_SWAP_STATS swap; @@ -657,11 +671,32 @@ static void cmd_stats(void) { return; } - status = SysQueryKernelStats(SYS_KERNEL_STATS_CPU, &cpu, sizeof(cpu), NULL); + size_t cpuSize = 0; + status = SysQueryKernelStats(SYS_KERNEL_STATS_CPU, 0, 0, &cpuSize); + if (status != 0 || cpuSize < sizeof(SYS_CPU_STATS)) { + print("stats: cpu size query failed: "); + printUInt(status); + print("\n"); + return; + } + + size_t cpuPages = (cpuSize + 4095u) / 4096u; + if (cpuPages == 0) cpuPages = 1; + status = SysAllocVm(0, cpuPages, SYS_VM_READWRITE, SYS_VM_FLAG_NONE, &cpuBuffer); + if (status != 0 || !cpuBuffer) { + print("stats: cpu buffer allocation failed: "); + printUInt(status); + print("\n"); + return; + } + + cpu = (SYS_CPU_STATS*)cpuBuffer; + status = SysQueryKernelStats(SYS_KERNEL_STATS_CPU, cpu, cpuPages * 4096u, NULL); if (status != 0) { print("stats: cpu query failed: "); printUInt(status); print("\n"); + SysFreeVm(cpuBuffer); return; } @@ -670,6 +705,7 @@ static void cmd_stats(void) { print("stats: pfn query failed: "); printUInt(status); print("\n"); + SysFreeVm(cpuBuffer); return; } @@ -678,6 +714,7 @@ static void cmd_stats(void) { print("stats: vad query failed: "); printUInt(status); print("\n"); + SysFreeVm(cpuBuffer); return; } @@ -686,6 +723,7 @@ static void cmd_stats(void) { print("stats: swap query failed: "); printUInt(status); print("\n"); + SysFreeVm(cpuBuffer); return; } @@ -694,6 +732,7 @@ static void cmd_stats(void) { print("stats: file cache query failed: "); printUInt(status); print("\n"); + SysFreeVm(cpuBuffer); return; } @@ -702,6 +741,7 @@ static void cmd_stats(void) { print("stats: filesystem query failed: "); printUInt(status); print("\n"); + SysFreeVm(cpuBuffer); return; } @@ -710,6 +750,7 @@ static void cmd_stats(void) { print("stats: disk query failed: "); printUInt(status); print("\n"); + SysFreeVm(cpuBuffer); return; } @@ -718,6 +759,7 @@ static void cmd_stats(void) { print("stats: pte query failed: "); printUInt(status); print("\n"); + SysFreeVm(cpuBuffer); return; } @@ -726,6 +768,7 @@ static void cmd_stats(void) { print("stats: surface query failed: "); printUInt(status); print("\n"); + SysFreeVm(cpuBuffer); return; } @@ -743,11 +786,10 @@ static void cmd_stats(void) { print(" SMT primary pokes: "); printUInt64(sched.SmtPrimaryPokes); print("\n"); print("CPUs:\n"); - uint32_t n = cpu.CpuCount; - if (n > SYS_KERNEL_STATS_MAX_CPUS) - n = SYS_KERNEL_STATS_MAX_CPUS; + const SYS_CPU_STAT* cpuRecords = SYS_CPU_STATS_CONST_RECORDS(cpu); + uint32_t n = cpu->RecordCount; for (uint32_t i = 0; i < n; i++) { - SYS_CPU_STAT* c = &cpu.Cpu[i]; + const SYS_CPU_STAT* c = &cpuRecords[i]; print(" cpu"); printUInt(c->CpuIndex); print(" lapic="); printUInt(c->LapicId); print(" current="); @@ -929,14 +971,8 @@ static void cmd_stats(void) { print(" invalidation: count="); printUInt64(filesystem.PathCacheInvalidations); print(" generation="); printUInt64(filesystem.PathCacheGeneration); print("\n"); print(" directory search: searches="); printUInt64(filesystem.DirectorySearches); - print(" clusters="); printUInt64(filesystem.DirectoryClustersRead); + print(" blocks="); printUInt64(filesystem.DirectoryBlocksRead); print(" entries="); printUInt64(filesystem.DirectoryEntriesScanned); print("\n"); - print(" FAT chain cache: lookups="); printUInt64(filesystem.FatChainCacheLookups); - print(" hits="); printUInt64(filesystem.FatChainCacheHits); - print(" misses="); printUInt64(filesystem.FatChainCacheMisses); - print(" stores="); printUInt64(filesystem.FatChainCacheStores); print("\n"); - print(" FAT chain work: invalidations="); printUInt64(filesystem.FatChainCacheInvalidations); - print(" fat-steps="); printUInt64(filesystem.FatChainWalkSteps); print("\n"); print(" data read spans: runs="); printUInt64(filesystem.DataReadRuns); print(" sectors="); printUInt64(filesystem.DataReadSectors); print(" merges="); printUInt64(filesystem.DataReadRunMerges); @@ -988,18 +1024,18 @@ static void cmd_stats(void) { print(" query: total="); printUInt64(pte.QueryCalls); print(" failed="); printUInt64(pte.QueryFailures); print(" translate="); printUInt64(pte.TranslateCalls); print("\n"); - print(" generic: "); printUInt64(pte.QueryByPurpose[0]); - print(" failed="); printUInt64(pte.QueryFailuresByPurpose[0]); print("\n"); - print(" user-probe: "); printUInt64(pte.QueryByPurpose[1]); - print(" failed="); printUInt64(pte.QueryFailuresByPurpose[1]); print("\n"); - print(" heap: "); printUInt64(pte.QueryByPurpose[2]); - print(" failed="); printUInt64(pte.QueryFailuresByPurpose[2]); print("\n"); - print(" vad: "); printUInt64(pte.QueryByPurpose[3]); - print(" failed="); printUInt64(pte.QueryFailuresByPurpose[3]); print("\n"); - print(" debug: "); printUInt64(pte.QueryByPurpose[4]); - print(" failed="); printUInt64(pte.QueryFailuresByPurpose[4]); print("\n"); - print(" self-test: "); printUInt64(pte.QueryByPurpose[5]); - print(" failed="); printUInt64(pte.QueryFailuresByPurpose[5]); print("\n"); + print(" generic: "); printUInt64(pte.QueryByPurpose[SYS_PTE_QUERY_GENERIC]); + print(" failed="); printUInt64(pte.QueryFailuresByPurpose[SYS_PTE_QUERY_GENERIC]); print("\n"); + print(" user-probe: "); printUInt64(pte.QueryByPurpose[SYS_PTE_QUERY_USER_PROBE]); + print(" failed="); printUInt64(pte.QueryFailuresByPurpose[SYS_PTE_QUERY_USER_PROBE]); print("\n"); + print(" heap: "); printUInt64(pte.QueryByPurpose[SYS_PTE_QUERY_HEAP]); + print(" failed="); printUInt64(pte.QueryFailuresByPurpose[SYS_PTE_QUERY_HEAP]); print("\n"); + print(" vad: "); printUInt64(pte.QueryByPurpose[SYS_PTE_QUERY_VAD]); + print(" failed="); printUInt64(pte.QueryFailuresByPurpose[SYS_PTE_QUERY_VAD]); print("\n"); + print(" debug: "); printUInt64(pte.QueryByPurpose[SYS_PTE_QUERY_DEBUG]); + print(" failed="); printUInt64(pte.QueryFailuresByPurpose[SYS_PTE_QUERY_DEBUG]); print("\n"); + print(" self-test: "); printUInt64(pte.QueryByPurpose[SYS_PTE_QUERY_SELF_TEST]); + print(" failed="); printUInt64(pte.QueryFailuresByPurpose[SYS_PTE_QUERY_SELF_TEST]); print("\n"); print(" tlb: local="); printUInt64(pte.LocalFlushes); print(" global="); printUInt64(pte.GlobalFlushes); print(" new-map-skip="); printUInt64(pte.NewMappingNoFlushes); @@ -1009,9 +1045,9 @@ static void cmd_stats(void) { print(" skipped="); printUInt64(pte.ShootdownSkippedNoTargets); print(" acks="); printUInt64(pte.ShootdownAcks); print(" timeouts="); printUInt64(pte.ShootdownTimeouts); print("\n"); + SysFreeVm(cpuBuffer); } - // Implements the cmd time operation. static void cmd_time(void) { SYS_SYSTEM_TIME t; @@ -1197,16 +1233,17 @@ static void cmd_ps(void) { printUInt((uint32_t)count); print("):\n"); print(" PID State Thr Hnd Mod UserKiB ImageKiB Flags Name\n"); - print(" Flags: S=system I=idle *=current T=terminating X=exited.\n"); + print(" Flags: S=system I=idle P=protected *=current T=terminating X=exited.\n"); size_t displayCount = count < MAX_PROCS ? count : MAX_PROCS; for (size_t i = 0; i < displayCount; i++) { SYS_PROCESS_INFO* p = &procs[i]; - char flags[6]; + char flags[7]; size_t flagLen = 0; if (p->Flags & SYS_PROCESS_FLAG_SYSTEM) flags[flagLen++] = 'S'; if (p->Flags & SYS_PROCESS_FLAG_IDLE) flags[flagLen++] = 'I'; + if (p->Flags & SYS_PROCESS_FLAG_TERMINATION_PROTECTED) flags[flagLen++] = 'P'; if (p->Flags & SYS_PROCESS_FLAG_CURRENT) flags[flagLen++] = '*'; if (p->Flags & SYS_PROCESS_FLAG_TERMINATING) flags[flagLen++] = 'T'; if (p->Flags & SYS_PROCESS_FLAG_EXITED) flags[flagLen++] = 'X'; @@ -1310,9 +1347,20 @@ static void cmd_threads(const char* args) { static void cmd_kill(const char* args) { uint32_t pid = 0; - if (!parseUInt(args, &pid) || pid == 0) { print("kill: usage: kill \n"); return; } - uint32_t status = SysKillProcess(pid, (int)STATUS_PROCESS_KILLED); - if (status != 0) { + if (!parseUInt(args, &pid)) { print("kill: usage: kill \n"); return; } + + uint32_t hProc = 0; + uint32_t status = SysOpenProcess(pid, &hProc); + if (status == 0) { + status = SysKillProcess(hProc, (int32_t)STATUS_PROCESS_KILLED); + SysCloseHandle(hProc); + } + + if (status == STATUS_ACCESS_DENIED) { + print("kill: process "); + printUInt(pid); + print(" is termination-protected.\n"); + } else if (status != 0) { print("shell: failed to kill process "); printUInt(pid); print(", error code: "); @@ -1347,8 +1395,11 @@ static void cmd_dir(const char* path) { uint32_t hDir; const char* target = (path && *path != '\0') ? path : "."; - if (open_dir(target, &hDir) != 0) { - print("dir: failed to open directory\n"); + uint32_t st = open_dir(target, &hDir); + if (st != 0) { + print("dir: failed to open directory, status=0x"); + printHex32(st); + print("\n"); return; } @@ -1384,7 +1435,6 @@ static void cmd_type(const char* filename) { SysCloseHandle(hFile); print("\n"); } - // Implements the extract Program Path operation. static bool extractProgramPath(const char* cmdline, char* out, size_t outSize) { if (!cmdline || !out || outSize == 0) return false; @@ -1410,15 +1460,21 @@ static bool extractProgramPath(const char* cmdline, char* out, size_t outSize) { static bool shellConsumeQueuedCtrlC(void) { for (int i = 0; i < 4; i++) { INPUT_KEY_RECORD input; - if (SysPeekInputEvent(&input) != 0) return false; + SYS_INPUT_EVENT_REQUEST inputReq; + inputReq.Size = sizeof(inputReq); + inputReq.QueueHandle = 0; + inputReq.Flags = SYS_INPUT_EVENT_FLAG_NONE; + inputReq.TimeoutMilliseconds = 0; + inputReq.RecordOut = &input; + if (SysPeekInputEvent(&inputReq) != STATUS_SUCCESS) return false; if ((input.Modifiers & INPUT_MOD_CTRL) && input.Key == INPUT_KEY_C) { - (void)SysReadInputEvent(&input); + (void)SysReadInputEvent(&inputReq); return true; } if (input.Key == INPUT_KEY_LEFT_CTRL || input.Key == INPUT_KEY_RIGHT_CTRL) { - (void)SysReadInputEvent(&input); + (void)SysReadInputEvent(&inputReq); continue; } @@ -1428,10 +1484,14 @@ static bool shellConsumeQueuedCtrlC(void) { return false; } +// Returns whether one input key is currently held. +static bool shellIsKeyDown(INPUT_KEY key) { + bool down = false; + return SysIsKeyDown(key, &down) == STATUS_SUCCESS && down; +} + // Waits for a foreground process while watching Ctrl-C without stealing ordinary stdin. static uint32_t waitForegroundProcess(uint32_t hProc) { - uint32_t pid = 0; - bool havePid = SysGetProcessPid(hProc, &pid) == 0 && pid != 0; bool sentBreak = false; uint32_t inputQueue = 0; @@ -1451,7 +1511,7 @@ static uint32_t waitForegroundProcess(uint32_t hProc) { for (;;) { index = 0; uint32_t status = SysWaitMany(&waitReq); - if (status != SYS_STATUS_SUCCESS) { + if (status != STATUS_SUCCESS) { SysCloseHandle(inputQueue); return status; } @@ -1462,10 +1522,10 @@ static uint32_t waitForegroundProcess(uint32_t hProc) { } uint32_t modifiers = SysGetKeyModifiers(); - if (((modifiers & INPUT_MOD_CTRL) && SysIsKeyDown(INPUT_KEY_C)) || shellConsumeQueuedCtrlC()) { + if (((modifiers & INPUT_MOD_CTRL) && shellIsKeyDown(INPUT_KEY_C)) || shellConsumeQueuedCtrlC()) { if (!sentBreak) { print("^C\n"); - if (havePid) (void)SysKillProcess(pid, (int)STATUS_PROCESS_KILLED); + (void)SysKillProcess(hProc, (int32_t)STATUS_PROCESS_KILLED); sentBreak = true; } } @@ -1564,13 +1624,13 @@ static int runCommand(const char* cmd) { } else { // Foreground mode: Wait for completion and flush standard keyboard stream. uint32_t waitStatus = waitForegroundProcess(hProc); - if (waitStatus != SYS_STATUS_SUCCESS) { + if (waitStatus != STATUS_SUCCESS) { print("shell: wait failed: "); printUInt(waitStatus); print("\n"); } print("Process exited with code: "); - int exitCode; + int32_t exitCode; SysGetProcessExitCode(hProc, &exitCode); printInt(exitCode); const char* exitName = processExitStatusName((uint32_t)exitCode); @@ -1583,7 +1643,8 @@ static int runCommand(const char* cmd) { } print("\n"); SysCloseHandle(hProc); - while (SysPollKeyboard()) (void)SysReadKey(); + INPUT_KEY drainedKey; + while (SysPollKeyboard(&drainedKey) == STATUS_SUCCESS) (void)SysReadKey(&drainedKey); } } else { print("shell: launch failed\n"); diff --git a/code/user/apps/shell_posix/shell_posix.c b/code/user/apps/shell_posix/shell_posix.c index 2762363..f962360 100644 --- a/code/user/apps/shell_posix/shell_posix.c +++ b/code/user/apps/shell_posix/shell_posix.c @@ -60,7 +60,6 @@ static void readLine(char* buf, int len) { buf[pos] = '\0'; } - // Implements the split Command Line operation. static int splitCommandLine(char* line, char** argv, int maxArgv) { int argc = 0; diff --git a/code/user/apps/tcpserve/tcpserve.c b/code/user/apps/tcpserve/tcpserve.c index 640ef9d..325e6c3 100644 --- a/code/user/apps/tcpserve/tcpserve.c +++ b/code/user/apps/tcpserve/tcpserve.c @@ -35,6 +35,41 @@ static void print_ipv4(const uint8_t ip[4]) { print_u64(ip[2]); print("."); print_u64(ip[3]); } +// Prints ipv6. +static void print_ipv6(const uint8_t ip[16]) { + static const char hex[] = "0123456789abcdef"; + for (int i = 0; i < 8; ++i) { + uint16_t word = ((uint16_t)ip[i * 2] << 8) | ip[i * 2 + 1]; + int started = 0; + for (int shift = 12; shift >= 0; shift -= 4) { + unsigned nibble = (word >> shift) & 0xfu; + if (nibble || started || shift == 0) { + char c = hex[nibble]; + print_char(c); + started = 1; + } + } + if (i != 7) print(":"); + } +} + +// Prints socket address. +static void print_socket_address(const SYS_SOCKET_ADDRESS* address) { + if (!address) return; + if (address->Family == SYS_AF_INET) { + print_ipv4(address->Address); + print(":"); + print_u64(address->Port); + } else if (address->Family == SYS_AF_INET6) { + print("["); + print_ipv6(address->Address); + print("]:"); + print_u64(address->Port); + } else { + print("unknown"); + } +} + // Implements the next token operation. static const char* next_token(const char* s, char* out, size_t outSize) { if (!s || !out || outSize == 0) return NULL; @@ -66,19 +101,28 @@ static bool parse_port(const char* s, uint16_t* out) { // Implements the usage operation. static void usage(void) { - print("usage: tcpserve.exe [port]\n"); + print("usage: tcpserve.exe [-4|-6] [port]\n"); print("default: tcpserve.exe 8080\n"); } // Runs the requested operation. int main(void) { uint16_t port = 8080; + uint32_t family = SYS_AF_INET; const char* cmd = SysGetPeb()->CommandLine.Data; char tok[64]; cmd = next_token(cmd, tok, sizeof(tok)); // program path if (cmd) { - (void)next_token(cmd, tok, sizeof(tok)); + const char* rest = next_token(cmd, tok, sizeof(tok)); + if (strcmp(tok, "-4") == 0 || strcmp(tok, "-6") == 0) { + family = strcmp(tok, "-6") == 0 ? SYS_AF_INET6 : SYS_AF_INET; + cmd = rest; + if (cmd) + (void)next_token(cmd, tok, sizeof(tok)); + else + tok[0] = 0; + } if (tok[0] && !parse_port(tok, &port)) { usage(); SysExitProcess(1); @@ -86,61 +130,55 @@ int main(void) { } uint32_t listener = 0; - uint32_t st = SysCreateSocket(SYS_AF_INET, SYS_SOCK_STREAM, SYS_IPPROTO_TCP, &listener); - if (st != SYS_STATUS_SUCCESS) { + uint32_t st = SysCreateSocket(family, SYS_SOCK_STREAM, SYS_IPPROTO_TCP, &listener); + if (st != STATUS_SUCCESS) { print("tcpserve: SysCreateSocket failed "); print_u64(st); print("\n"); SysExitProcess(1); } - SYS_SOCKET_ADDRESS_IPV4 bindAddr; + SYS_SOCKET_ADDRESS bindAddr; memset(&bindAddr, 0, sizeof(bindAddr)); - bindAddr.Family = SYS_AF_INET; + bindAddr.Size = sizeof(bindAddr); + bindAddr.Family = family; bindAddr.Port = port; - bindAddr.Address[0] = 0; - bindAddr.Address[1] = 0; - bindAddr.Address[2] = 0; - bindAddr.Address[3] = 0; st = SysBindSocket(listener, &bindAddr, sizeof(bindAddr)); - if (st != SYS_STATUS_SUCCESS) { + if (st != STATUS_SUCCESS) { print("tcpserve: SysBindSocket failed "); print_u64(st); print("\n"); SysCloseHandle(listener); SysExitProcess(1); } st = SysListenSocket(listener, 4); - if (st != SYS_STATUS_SUCCESS) { + if (st != STATUS_SUCCESS) { print("tcpserve: SysListenSocket failed "); print_u64(st); print("\n"); SysCloseHandle(listener); SysExitProcess(1); } - print("tcpserve: listening on 0.0.0.0:"); print_u64(port); print("\n"); + print("tcpserve: listening on "); + if (family == SYS_AF_INET6) print("[::]:"); else print("0.0.0.0:"); + print_u64(port); print("\n"); print("tcpserve: accepting one connection...\n"); - SYS_SOCKET_ADDRESS_IPV4 remote; + SYS_SOCKET_ADDRESS remote; size_t remoteSize = sizeof(remote); uint32_t client = 0; st = SysAcceptSocket(listener, &remote, &remoteSize, &client); - if (st != SYS_STATUS_SUCCESS) { + if (st != STATUS_SUCCESS) { print("tcpserve: SysAcceptSocket failed "); print_u64(st); print("\n"); SysCloseHandle(listener); SysExitProcess(1); } print("tcpserve: accepted from "); - if (remote.Family == SYS_AF_INET) { - print_ipv4(remote.Address); - print(":"); print_u64(remote.Port); - } else { - print("unknown"); - } + print_socket_address(&remote); print("\n"); char request[1025]; size_t got = 0; st = SysRecvSocket(client, request, sizeof(request) - 1, 0, &got); - if (st != SYS_STATUS_SUCCESS) { + if (st != STATUS_SUCCESS) { print("tcpserve: SysRecvSocket failed "); print_u64(st); print("\n"); SysCloseHandle(client); SysCloseHandle(listener); @@ -179,9 +217,9 @@ int main(void) { size_t sent = 0; st = SysSendSocket(client, response, off, 0, &sent); - if (st == SYS_STATUS_SUCCESS) + if (st == STATUS_SUCCESS) st = SysSendSocket(client, body, sizeof(body) - 1, 0, &sent); - if (st != SYS_STATUS_SUCCESS) + if (st != STATUS_SUCCESS) print("tcpserve: SysSendSocket failed "), print_u64(st), print("\n"); SysShutdownSocket(client, SYS_SHUTDOWN_BOTH); diff --git a/code/user/apps/udp/udp.c b/code/user/apps/udp/udp.c index 13f15b0..76b2c40 100644 --- a/code/user/apps/udp/udp.c +++ b/code/user/apps/udp/udp.c @@ -35,6 +35,41 @@ static void print_ipv4(const uint8_t ip[4]) { print_u64(ip[2]); print("."); print_u64(ip[3]); } +// Prints ipv6. +static void print_ipv6(const uint8_t ip[16]) { + static const char hex[] = "0123456789abcdef"; + for (int i = 0; i < 8; ++i) { + uint16_t word = ((uint16_t)ip[i * 2] << 8) | ip[i * 2 + 1]; + int started = 0; + for (int shift = 12; shift >= 0; shift -= 4) { + unsigned nibble = (word >> shift) & 0xfu; + if (nibble || started || shift == 0) { + char c = hex[nibble]; + print_char(c); + started = 1; + } + } + if (i != 7) print(":"); + } +} + +// Prints socket address. +static void print_socket_address(const SYS_SOCKET_ADDRESS* address) { + if (!address) return; + if (address->Family == SYS_AF_INET) { + print_ipv4(address->Address); + print(":"); + print_u64(address->Port); + } else if (address->Family == SYS_AF_INET6) { + print("["); + print_ipv6(address->Address); + print("]:"); + print_u64(address->Port); + } else { + print("unknown"); + } +} + // Implements the next token operation. static const char* next_token(const char* s, char* out, size_t outSize) { if (!s || !out || outSize == 0) return NULL; @@ -97,21 +132,54 @@ static bool parse_port(const char* s, uint16_t* out) { // Implements the usage operation. static void usage(void) { print("usage:\n"); - print(" udp.exe send [message]\n"); - print(" udp.exe listen \n"); + print(" udp.exe send [message]\n"); + print(" udp.exe listen [-4|-6] \n"); print("examples:\n"); print(" udp.exe send 10.0.2.2 9999 hello\n"); - print(" udp.exe listen 9999\n"); + print(" udp.exe send ::1 9999 hello\n"); + print(" udp.exe listen -6 9999\n"); +} + +// Resolves an IPv4 or IPv6 destination. +static uint32_t resolve_address(const char* text, SYS_SOCKET_ADDRESS* out) { + uint8_t ip[4]; + if (parse_ipv4(text, ip)) { + memset(out, 0, sizeof(*out)); + out->Size = sizeof(*out); + out->Family = SYS_AF_INET; + memcpy(out->Address, ip, 4); + return STATUS_SUCCESS; + } + + SYS_RESOLVE_HOST_REQUEST req; + size_t addressSize = sizeof(*out); + memset(out, 0, sizeof(*out)); + memset(&req, 0, sizeof(req)); + req.Name = SysSvFromCstr(text); + req.Family = SYS_AF_INET6; + req.AddressOut = out; + req.AddressSizeInOut = &addressSize; + + uint32_t status = SysResolveHost(&req); + if (status == STATUS_SUCCESS) + return STATUS_SUCCESS; + + addressSize = sizeof(*out); + memset(out, 0, sizeof(*out)); + req.Family = SYS_AF_INET; + return SysResolveHost(&req); } // Runs send. static int run_send(const char* cmd) { char tok[256]; - uint8_t ip[4]; + SYS_SOCKET_ADDRESS remote; uint16_t port = 0; cmd = next_token(cmd, tok, sizeof(tok)); - if (!tok[0] || !parse_ipv4(tok, ip)) { usage(); return 1; } + if (!tok[0]) { usage(); return 1; } + uint32_t status = resolve_address(tok, &remote); + if (status != STATUS_SUCCESS) { print("udp: resolve failed "); print_u64(status); print("\n"); return 1; } cmd = next_token(cmd, tok, sizeof(tok)); if (!tok[0] || !parse_port(tok, &port)) { usage(); return 1; } @@ -128,20 +196,13 @@ static int run_send(const char* cmd) { } uint32_t sock = 0; - uint32_t st = SysCreateSocket(SYS_AF_INET, SYS_SOCK_DGRAM, SYS_IPPROTO_UDP, &sock); - if (st != SYS_STATUS_SUCCESS) { + uint32_t st = SysCreateSocket(remote.Family, SYS_SOCK_DGRAM, SYS_IPPROTO_UDP, &sock); + if (st != STATUS_SUCCESS) { print("udp: SysCreateSocket failed "); print_u64(st); print("\n"); return 1; } - SYS_SOCKET_ADDRESS_IPV4 remote; - memset(&remote, 0, sizeof(remote)); - remote.Family = SYS_AF_INET; remote.Port = port; - remote.Address[0] = ip[0]; - remote.Address[1] = ip[1]; - remote.Address[2] = ip[2]; - remote.Address[3] = ip[3]; size_t sent = 0; SYS_SOCKET_SEND_TO_REQUEST req; @@ -154,12 +215,12 @@ static int run_send(const char* cmd) { st = SysSendToSocket(sock, &req); SysCloseHandle(sock); - if (st != SYS_STATUS_SUCCESS) { + if (st != STATUS_SUCCESS) { print("udp: SysSendToSocket failed "); print_u64(st); print("\n"); return 1; } - print("udp: sent "); print_u64(sent); print(" bytes to "); print_ipv4(ip); print(":"); print_u64(port); print("\n"); + print("udp: sent "); print_u64(sent); print(" bytes to "); print_socket_address(&remote); print("\n"); return 0; } @@ -167,33 +228,44 @@ static int run_send(const char* cmd) { static int run_listen(const char* cmd) { char tok[64]; uint16_t port = 0; + uint32_t family = SYS_AF_INET; - (void)next_token(cmd, tok, sizeof(tok)); + const char* rest = next_token(cmd, tok, sizeof(tok)); + if (strcmp(tok, "-4") == 0 || strcmp(tok, "-6") == 0) { + family = strcmp(tok, "-6") == 0 ? SYS_AF_INET6 : SYS_AF_INET; + if (rest) + (void)next_token(rest, tok, sizeof(tok)); + else + tok[0] = 0; + } if (!tok[0] || !parse_port(tok, &port)) { usage(); return 1; } uint32_t sock = 0; - uint32_t st = SysCreateSocket(SYS_AF_INET, SYS_SOCK_DGRAM, SYS_IPPROTO_UDP, &sock); - if (st != SYS_STATUS_SUCCESS) { + uint32_t st = SysCreateSocket(family, SYS_SOCK_DGRAM, SYS_IPPROTO_UDP, &sock); + if (st != STATUS_SUCCESS) { print("udp: SysCreateSocket failed "); print_u64(st); print("\n"); return 1; } - SYS_SOCKET_ADDRESS_IPV4 bindAddr; + SYS_SOCKET_ADDRESS bindAddr; memset(&bindAddr, 0, sizeof(bindAddr)); - bindAddr.Family = SYS_AF_INET; + bindAddr.Size = sizeof(bindAddr); + bindAddr.Family = family; bindAddr.Port = port; st = SysBindSocket(sock, &bindAddr, sizeof(bindAddr)); - if (st != SYS_STATUS_SUCCESS) { + if (st != STATUS_SUCCESS) { print("udp: SysBindSocket failed "); print_u64(st); print("\n"); SysCloseHandle(sock); return 1; } - print("udp: listening on 0.0.0.0:"); print_u64(port); print(" for one datagram\n"); + print("udp: listening on "); + if (family == SYS_AF_INET6) print("[::]:"); else print("0.0.0.0:"); + print_u64(port); print(" for one datagram\n"); char buf[1025]; - SYS_SOCKET_ADDRESS_IPV4 remote; + SYS_SOCKET_ADDRESS remote; size_t remoteSize = sizeof(remote); size_t got = 0; SYS_SOCKET_RECV_FROM_REQUEST req; @@ -206,18 +278,14 @@ static int run_listen(const char* cmd) { st = SysRecvFromSocket(sock, &req); SysCloseHandle(sock); - if (st != SYS_STATUS_SUCCESS) { + if (st != STATUS_SUCCESS) { print("udp: SysRecvFromSocket failed "); print_u64(st); print("\n"); return 1; } buf[got] = 0; print("udp: received "); print_u64(got); print(" bytes from "); - if (remote.Family == SYS_AF_INET) { - print_ipv4(remote.Address); print(":"); print_u64(remote.Port); - } else { - print("unknown"); - } + print_socket_address(&remote); print("\n"); print(buf); if (got && buf[got - 1] != '\n') print("\n"); diff --git a/code/user/apps/video/video.c b/code/user/apps/video/video.c index e91cba6..8edc519 100644 --- a/code/user/apps/video/video.c +++ b/code/user/apps/video/video.c @@ -130,7 +130,11 @@ static const char* path_to_cstr(SYS_STRING_VIEW path, char* out, size_t outSize) } static uint32_t open_file_view(SYS_STRING_VIEW path, uint32_t* handleOut) { - SYS_PATH_HANDLE_REQUEST req = { path, handleOut }; + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(path, + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + handleOut); return SysOpenFile(&req); } @@ -146,7 +150,7 @@ int main(void) { uint32_t file = 0; uint32_t status = open_file_view(pathView, &file); - if (status != SYS_STATUS_SUCCESS) { + if (status != STATUS_SUCCESS) { char pathBuf[260]; print("video: failed to open input file: "); print(path_to_cstr(pathView, pathBuf, sizeof(pathBuf))); @@ -154,7 +158,12 @@ int main(void) { fail_status("SysOpenFile", status); } - uint64_t fileSize = SysGetFileSize(file); + uint64_t fileSize = 0; + status = SysGetFileSize(file, &fileSize); + if (status != STATUS_SUCCESS) { + SysCloseHandle(file); + fail_status("SysGetFileSize", status); + } if (fileSize == 0) { SysCloseHandle(file); print("video: input file is empty\n"); @@ -163,7 +172,7 @@ int main(void) { uint32_t mapping = 0; status = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, fileSize, &mapping); - if (status != SYS_STATUS_SUCCESS || !mapping) { + if (status != STATUS_SUCCESS || !mapping) { SysCloseHandle(file); fail_status("SysCreateFileMapping", status); } @@ -171,7 +180,7 @@ int main(void) { void* buf = 0; status = SysMapFile(mapping, 0, (size_t)fileSize, SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &buf); - if (status != SYS_STATUS_SUCCESS || !buf) { + if (status != STATUS_SUCCESS || !buf) { SysCloseHandle(mapping); SysCloseHandle(file); fail_status("SysMapFile", status); @@ -207,7 +216,7 @@ int main(void) { uint32_t surface = 0; status = SysCreateSurface(width, height, SYS_SURFACE_FORMAT_XRGB8888, SYS_SURFACE_FLAG_NONE, &surface); - if (status != SYS_STATUS_SUCCESS || !surface) { + if (status != STATUS_SUCCESS || !surface) { BinkClose(b); SysFreeVm(buf); fail_status("SysCreateSurface", status); @@ -216,7 +225,7 @@ int main(void) { void* surfaceBase = 0; uint32_t pitchBytes = 0; status = SysMapSurface(surface, &surfaceBase, &pitchBytes); - if (status != SYS_STATUS_SUCCESS || !surfaceBase || pitchBytes < width * sizeof(uint32_t)) { + if (status != STATUS_SUCCESS || !surfaceBase || pitchBytes < width * sizeof(uint32_t)) { SysCloseHandle(surface); BinkClose(b); SysFreeVm(buf); @@ -244,7 +253,7 @@ int main(void) { BinkCopyToBuffer(b, surfaceBase, pitchBytes, height, 0, 0, BINKSURFACE32); status = SysPresentSurface(surface, &fullFrame, 1); - if (status != SYS_STATUS_SUCCESS) { + if (status != STATUS_SUCCESS) { SysCloseHandle(surface); BinkClose(b); SysFreeVm(buf); diff --git a/code/user/personalities/posix/posix.h b/code/user/personalities/posix/posix.h index f1d772c..6db5764 100644 --- a/code/user/personalities/posix/posix.h +++ b/code/user/personalities/posix/posix.h @@ -89,6 +89,43 @@ typedef int32_t pid_t; typedef int32_t off_t; typedef uint32_t mode_t; typedef int64_t ssize_t; +#define S_IFMT 0170000 +#define S_IFSOCK 0140000 +#define S_IFCHR 0020000 +#define S_IFDIR 0040000 +#define S_IFREG 0100000 +#define S_IRUSR 0000400 +#define S_IWUSR 0000200 +#define S_IXUSR 0000100 +#define S_IRGRP 0000040 +#define S_IWGRP 0000020 +#define S_IXGRP 0000010 +#define S_IROTH 0000004 +#define S_IWOTH 0000002 +#define S_IXOTH 0000001 + +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) +#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) + +struct stat { + uint64_t st_dev; + uint64_t st_ino; + mode_t st_mode; + uint32_t st_nlink; + uint32_t st_uid; + uint32_t st_gid; + uint64_t st_rdev; + off_t st_size; + int64_t st_atime; + int64_t st_mtime; + int64_t st_ctime; +}; + +__declspec(dllexport) int stat(const char* path, struct stat* out); +__declspec(dllexport) int fstat(int fd, struct stat* out); +__declspec(dllexport) char* realpath(const char* path, char* resolved_path); __declspec(dllexport) void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset); __declspec(dllexport) int munmap(void* addr, size_t length); @@ -101,13 +138,17 @@ typedef uint32_t in_addr_t; typedef uint32_t socklen_t; #define AF_INET 2 +#define AF_INET6 10 +#define AF_UNSPEC 0 #define PF_INET AF_INET +#define PF_INET6 AF_INET6 #define SOCK_STREAM 1 #define SOCK_DGRAM 2 #define SOCK_RAW 3 #define IPPROTO_ICMP 1 #define IPPROTO_TCP 6 #define IPPROTO_UDP 17 +#define IPPROTO_ICMPV6 58 #define SHUT_RD 0 #define SHUT_WR 1 @@ -125,6 +166,10 @@ struct in_addr { in_addr_t s_addr; }; +struct in6_addr { + uint8_t s6_addr[16]; +}; + struct sockaddr { sa_family_t sa_family; char sa_data[14]; @@ -137,6 +182,14 @@ struct sockaddr_in { char sin_zero[8]; }; +struct sockaddr_in6 { + sa_family_t sin6_family; + in_port_t sin6_port; + uint32_t sin6_flowinfo; + struct in6_addr sin6_addr; + uint32_t sin6_scope_id; +}; + struct hostent { char* h_name; char** h_aliases; @@ -184,6 +237,8 @@ __declspec(dllexport) uint32_t htonl(uint32_t x); __declspec(dllexport) uint32_t ntohl(uint32_t x); __declspec(dllexport) in_addr_t inet_addr(const char* text); __declspec(dllexport) char* inet_ntoa(struct in_addr in); +__declspec(dllexport) int inet_pton(int af, const char* src, void* dst); +__declspec(dllexport) const char* inet_ntop(int af, const void* src, char* dst, socklen_t size); __declspec(dllexport) int socket(int domain, int type, int protocol); __declspec(dllexport) int bind(int fd, const struct sockaddr* addr, socklen_t addrlen); @@ -208,13 +263,13 @@ typedef struct { uint32_t Handle; } pthread_mutex_t; typedef void* pthread_attr_t; // unused in v1 typedef void* pthread_mutexattr_t; -// posix_spawn types (file_actions and attrp both ignored in v1). +// posix_spawn types (file_actions and attrp both ignored in v1). typedef void* posix_spawn_file_actions_t; typedef void* posix_spawnattr_t; __declspec(dllexport) const char* posix_get_command_line(void); -// dirent (used by posix_dir.c). +// dirent (used by posix_dir.c). #define NAME_MAX 259 typedef struct dirent { diff --git a/code/user/personalities/posix/posix_dir.c b/code/user/personalities/posix/posix_dir.c index fa13d17..71c9039 100644 --- a/code/user/personalities/posix/posix_dir.c +++ b/code/user/personalities/posix/posix_dir.c @@ -4,11 +4,11 @@ #include #include #include "posix.h" +#include "posix_fd.h" #include "posix_path.h" typedef SYS_DIR_ENTRY KDIR_ENTRY; - #define KATTR_DIR 0x10 // Implements the chdir operation. @@ -24,16 +24,84 @@ __declspec(dllexport) int chdir(const char* path) { // Implements the getcwd operation. __declspec(dllexport) char* getcwd(char* buf, size_t size) { if (!buf || size == 0) { errno = EINVAL; return (char*)0; } - char native[512]; - size_t nativeLen = 0; - SYS_GET_CWD_REQUEST req = { native, sizeof(native) - 1, &nativeLen }; - uint32_t s = SysGetCwd(&req); - if (s == 0) native[nativeLen] = '\0'; - if (s != 0) { errno = ERANGE; return (char*)0; } - NativeToPosix(native, buf, size); + + SYS_STRING_VIEW cwd = SysGetCurrentDirectoryView(); + if ((cwd.Length && !cwd.Data) || cwd.Length > SYS_CWD_MAX) { + errno = ERANGE; + return (char*)0; + } + + char native[SYS_CWD_MAX + 1]; + if (cwd.Length) memcpy(native, cwd.Data, cwd.Length); + native[cwd.Length] = '\0'; + + char posix[SYS_CWD_MAX + 1]; + NativeToPosix(native, posix, sizeof(posix)); + + size_t needed = strlen(posix) + 1; + if (needed > size) { + errno = ERANGE; + return (char*)0; + } + + memcpy(buf, posix, needed); return buf; } +// Copies one path string into either caller storage or a new allocation. +static char* PosixCopyResolvedPath(const char* value, char* resolvedPath) { + if (!value) { errno = EINVAL; return (char*)0; } + + size_t needed = strlen(value) + 1; + char* out = resolvedPath; + if (!out) { + out = (char*)malloc(needed); + if (!out) { errno = ENOMEM; return (char*)0; } + } + + memcpy(out, value, needed); + return out; +} + +// Implements the realpath operation over the kernel lexical resolver. +__declspec(dllexport) char* realpath(const char* path, char* resolved_path) { + if (!path) { errno = EINVAL; return (char*)0; } + + char native[SYS_PATH_MAX + 1]; + const char* translated = PosixTranslatePath(path, native, sizeof(native)); + if (strcmp(translated, SYNTH_DEVNULL) == 0 || strcmp(translated, SYNTH_DEVZERO) == 0) + return PosixCopyResolvedPath(path, resolved_path); + + size_t required = 0; + SYS_REAL_PATH_REQUEST query = { + sizeof(query), + SYS_REAL_PATH_FLAG_NONE, + SysSvFromCstr(translated), + (char*)0, + 0, + &required, + }; + uint32_t status = SysRealPath(&query); + if (status != STATUS_SUCCESS) { errno = PosixStatusToErrno(status); return (char*)0; } + if (required == 0 || required > SYS_CWD_MAX) { errno = ERANGE; return (char*)0; } + + char nativeResolved[SYS_CWD_MAX]; + SYS_REAL_PATH_REQUEST request = { + sizeof(request), + SYS_REAL_PATH_FLAG_NONE, + SysSvFromCstr(translated), + nativeResolved, + sizeof(nativeResolved), + &required, + }; + status = SysRealPath(&request); + if (status != STATUS_SUCCESS) { errno = PosixStatusToErrno(status); return (char*)0; } + + char posix[SYS_CWD_MAX]; + NativeToPosix(nativeResolved, posix, sizeof(posix)); + return PosixCopyResolvedPath(posix, resolved_path); +} + // Implements the opendir operation. __declspec(dllexport) DIR* opendir(const char* path) { char native[512]; @@ -74,3 +142,82 @@ __declspec(dllexport) int closedir(DIR* d) { free(d); return 0; } + +// Converts native file metadata to a POSIX stat structure. +static int PosixFillStat(const SYS_FILE_STAT* native, struct stat* out) { + if (!native || !out) { errno = EINVAL; return -1; } + if (native->FileSize > 0x7FFFFFFFULL) { errno = EINVAL; return -1; } + + memset(out, 0, sizeof(*out)); + out->st_dev = native->VolumeId; + out->st_ino = native->FileId; + out->st_nlink = native->LinkCount ? native->LinkCount : 1; + out->st_size = (off_t)native->FileSize; + out->st_atime = (int64_t)native->LastAccessTime.EpochSeconds; + out->st_mtime = (int64_t)native->LastWriteTime.EpochSeconds; + out->st_ctime = (int64_t)native->ChangeTime.EpochSeconds; + + if (native->Type == SYS_FILE_TYPE_DIRECTORY) { + out->st_mode = S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR | + S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + } else { + out->st_mode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + } + + if (native->Attributes & SYS_FILE_ATTR_READ_ONLY) + out->st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + return 0; +} + +// Fills stat for one synthetic descriptor. +static int PosixFillSyntheticStat(mode_t type, struct stat* out) { + if (!out) { errno = EINVAL; return -1; } + memset(out, 0, sizeof(*out)); + out->st_mode = type | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + out->st_nlink = 1; + return 0; +} + +// Implements the stat operation. +__declspec(dllexport) int stat(const char* path, struct stat* out) { + if (!path || !out) { errno = EINVAL; return -1; } + + char native[512]; + const char* p = PosixTranslatePath(path, native, sizeof(native)); + if (strcmp(p, SYNTH_DEVNULL) == 0 || strcmp(p, SYNTH_DEVZERO) == 0) + return PosixFillSyntheticStat(S_IFCHR, out); + + SYS_FILE_STAT nativeStat; + SYS_STAT_FILE_REQUEST req = { SysSvFromCstr(p), &nativeStat }; + uint32_t s = SysStatFile(&req); + if (s != 0) { errno = PosixStatusToErrno(s); return -1; } + return PosixFillStat(&nativeStat, out); +} + +// Implements the fstat operation. +__declspec(dllexport) int fstat(int fd, struct stat* out) { + if (!out) { errno = EINVAL; return -1; } + + uint32_t pid = SysGetCurrentPid(); + FD_ENTRY* e = FdGet(pid, fd); + if (!e) return -1; + + switch (e->Type) { + case FD_CONSOLE_R: + case FD_CONSOLE_W: + case FD_DEVNULL: + case FD_DEVZERO: + return PosixFillSyntheticStat(S_IFCHR, out); + case FD_SOCKET: + return PosixFillSyntheticStat(S_IFSOCK, out); + case FD_FILE: { + SYS_FILE_STAT nativeStat; + uint32_t s = SysGetFileStat(e->KHandle, &nativeStat); + if (s != 0) { errno = PosixStatusToErrno(s); return -1; } + return PosixFillStat(&nativeStat, out); + } + default: + errno = EBADF; + return -1; + } +} diff --git a/code/user/personalities/posix/posix_fd.c b/code/user/personalities/posix/posix_fd.c index 763ca72..05bbbbc 100644 --- a/code/user/personalities/posix/posix_fd.c +++ b/code/user/personalities/posix/posix_fd.c @@ -9,20 +9,24 @@ int errno = 0; // Implements the status To Errno operation. int PosixStatusToErrno(uint32_t s) { switch (s) { - case 0x0000: return 0; // STATUS_SUCCESS - case 0x0002: return ENOSYS; // STATUS_NOT_SUPPORTED - case 0x0003: return EAGAIN; // STATUS_TIMEOUT / would block - case 0x0010: return EINVAL; // STATUS_INVALID_PARAMETER - case 0x0011: return EBADF; // STATUS_INVALID_HANDLE - case 0x0012: return ERANGE; // STATUS_BUFFER_TOO_SMALL - case 0x0020: return ENOMEM; // STATUS_NO_MEMORY - case 0x0021: return EACCES; // STATUS_ACCESS_DENIED - case 0x0022: return ENOENT; // STATUS_NOT_FOUND - case 0x0023: return EEXIST; // STATUS_ALREADY_EXISTS - case 0x0030: return ECONNRESET; // STATUS_OBJECT_DEAD - case 0x0031: return EAGAIN; // STATUS_QUEUE_FULL - case 0x0032: return EAGAIN; // STATUS_QUEUE_EMPTY - default: return EIO; + case STATUS_SUCCESS: return 0; + case STATUS_NOT_SUPPORTED: return ENOSYS; + case STATUS_TIMEOUT: return EAGAIN; + case STATUS_WOULD_BLOCK: return EAGAIN; + case STATUS_INVALID_PARAMETER: return EINVAL; + case STATUS_INVALID_HANDLE: return EBADF; + case STATUS_BUFFER_TOO_SMALL: return ERANGE; + case STATUS_NO_MEMORY: return ENOMEM; + case STATUS_ACCESS_DENIED: return EACCES; + case STATUS_NOT_FOUND: return ENOENT; + case STATUS_ALREADY_EXISTS: return EEXIST; + case STATUS_OBJECT_NAME_COLLISION: return EEXIST; + case STATUS_OBJECT_DEAD: return ECONNRESET; + case STATUS_QUEUE_FULL: return EAGAIN; + case STATUS_QUEUE_EMPTY: return EAGAIN; + case STATUS_CANCELLED: return ECANCELED; + case STATUS_NO_MORE_FILES: return ENOENT; + default: return EIO; } } diff --git a/code/user/personalities/posix/posix_fd.h b/code/user/personalities/posix/posix_fd.h index 9ef343a..d4af5e5 100644 --- a/code/user/personalities/posix/posix_fd.h +++ b/code/user/personalities/posix/posix_fd.h @@ -8,7 +8,7 @@ typedef enum { FD_FREE, FD_FILE, - FD_CONSOLE_R, // fd 0 - reads from keyboard via SysReadKeyboardChar + FD_CONSOLE_R, // fd 0 - reads from keyboard through SysReadKeyboardChar FD_CONSOLE_W, // fd 1, 2 - writes via SysWriteConsole FD_DEVNULL, // /dev/null FD_DEVZERO, // /dev/zero diff --git a/code/user/personalities/posix/posix_io.c b/code/user/personalities/posix/posix_io.c index 38b1323..b8f956e 100644 --- a/code/user/personalities/posix/posix_io.c +++ b/code/user/personalities/posix/posix_io.c @@ -14,7 +14,11 @@ __declspec(dllexport) void clearscreen(void) { // Implements the clearkeyboard operation. __declspec(dllexport) void clearkeyboard(void) { - while (SysPollKeyboard()) (void)SysReadKeyboardChar(); + INPUT_KEY key; + while (SysPollKeyboard(&key) == STATUS_SUCCESS) { + char ignored; + (void)SysReadKeyboardChar(&ignored); + } } // Opens the requested operation. @@ -37,17 +41,33 @@ __declspec(dllexport) int open(const char* path, int flags, ...) { } uint32_t kh = 0; - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(p), &kh }; - uint32_t s; - if (flags & O_WRONLY) { - s = SysCreateFile(&req); - } else { - s = SysOpenFile(&req); - } - if (s != 0) { errno = PosixStatusToErrno(s); return -1; } + uint32_t access = SYS_FILE_ACCESS_QUERY; + uint32_t disposition = SYS_FILE_OPEN_EXISTING; - if ((flags & O_TRUNC) && (flags & O_WRONLY)) - SysTruncateFile(kh, 0); + if ((flags & O_RDWR) == O_RDWR) + access |= SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE; + else if (flags & O_WRONLY) + access |= SYS_FILE_ACCESS_WRITE; + else + access |= SYS_FILE_ACCESS_READ; + + if (flags & O_APPEND) + access |= SYS_FILE_ACCESS_APPEND; + + if ((flags & O_CREAT) && (flags & O_TRUNC)) + disposition = SYS_FILE_CREATE_ALWAYS; + else if (flags & O_CREAT) + disposition = SYS_FILE_OPEN_ALWAYS; + else if (flags & O_TRUNC) + disposition = SYS_FILE_TRUNCATE_EXISTING; + + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(p), + access, + disposition, + SYS_FILE_FLAG_NONE, + &kh); + uint32_t s = SysOpenFile(&req); + if (s != 0) { errno = PosixStatusToErrno(s); return -1; } int fd = FdAlloc(pid, type, kh); if (fd < 0) { @@ -69,7 +89,8 @@ __declspec(dllexport) ssize_t read(int fd, void* buf, size_t count) { // Read up to count chars from keyboard, blocking per char. char* dst = (char*)buf; for (size_t i = 0; i < count; i++) { - dst[i] = SysReadKeyboardChar(); + uint32_t s = SysReadKeyboardChar(&dst[i]); + if (s != STATUS_SUCCESS) { errno = PosixStatusToErrno(s); return -1; } if (dst[i] == '\n' || dst[i] == '\r') return (ssize_t)(i + 1); } return (ssize_t)count; @@ -174,13 +195,17 @@ __declspec(dllexport) off_t lseek(int fd, off_t offset, int whence) { target = (int64_t)offset; break; case SEEK_CUR: { - uint64_t cur = SysGetFilePos(e->KHandle); + uint64_t cur = 0; + uint32_t s = SysGetFilePos(e->KHandle, &cur); + if (s != 0) { errno = PosixStatusToErrno(s); return -1; } if (cur > 0x7FFFFFFFULL) { errno = EINVAL; return -1; } target = (int64_t)cur + (int64_t)offset; break; } case SEEK_END: { - uint64_t size = SysGetFileSize(e->KHandle); + uint64_t size = 0; + uint32_t s = SysGetFileSize(e->KHandle, &size); + if (s != 0) { errno = PosixStatusToErrno(s); return -1; } if (size > 0x7FFFFFFFULL) { errno = EINVAL; return -1; } target = (int64_t)size + (int64_t)offset; break; diff --git a/code/user/personalities/posix/posix_mman.c b/code/user/personalities/posix/posix_mman.c index 562371a..5a1ebdb 100644 --- a/code/user/personalities/posix/posix_mman.c +++ b/code/user/personalities/posix/posix_mman.c @@ -52,7 +52,7 @@ __declspec(dllexport) void* mmap(void* addr, size_t length, int prot, uint32_t mapping = 0; uint32_t s = SysCreateFileMapping(e->KHandle, protect, SYS_MAP_FLAG_NONE, maxSize, &mapping); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { errno = PosixStatusToErrno(s); return MAP_FAILED; } @@ -62,7 +62,7 @@ __declspec(dllexport) void* mmap(void* addr, size_t length, int prot, PosixProtToSysMapAccess(prot, flags), SYS_MAP_FLAG_NONE, &base); SysCloseHandle(mapping); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { errno = PosixStatusToErrno(s); return MAP_FAILED; } @@ -73,7 +73,7 @@ __declspec(dllexport) int munmap(void* addr, size_t length) { (void)length; if (!addr) { errno = EINVAL; return -1; } uint32_t s = SysFreeVm(addr); - if (s != SYS_STATUS_SUCCESS) { errno = PosixStatusToErrno(s); return -1; } + if (s != STATUS_SUCCESS) { errno = PosixStatusToErrno(s); return -1; } return 0; } diff --git a/code/user/personalities/posix/posix_proc.c b/code/user/personalities/posix/posix_proc.c index dec224a..91ecbd7 100644 --- a/code/user/personalities/posix/posix_proc.c +++ b/code/user/personalities/posix/posix_proc.c @@ -6,7 +6,6 @@ #include "posix.h" #include "posix_path.h" - static SYS_PEB* gProcessPeb = NULL; // Implements the peb operation. @@ -128,7 +127,7 @@ __declspec(dllexport) pid_t waitpid(pid_t pid, int* status, int options) { SysWait(handle, SYS_WAIT_FLAG_NONE); - int exitCode = 0; + int32_t exitCode = 0; SysGetProcessExitCode(handle, &exitCode); if (status) *status = exitCode << 8; // WEXITSTATUS convention diff --git a/code/user/personalities/posix/posix_socket.c b/code/user/personalities/posix/posix_socket.c index ea426db..5b88d61 100644 --- a/code/user/personalities/posix/posix_socket.c +++ b/code/user/personalities/posix/posix_socket.c @@ -58,6 +58,95 @@ static int ParseIpv4Bytes(const char* text, uint8_t out[4]) { return *p == '\0'; } +static int ParseHex(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +static int ParseIpv6Bytes(const char* text, uint8_t out[16]) { + uint16_t words[8]; + int count = 0; + int compress = -1; + const char* p = text; + + if (!text || !*text || !out) + return 0; + memset(words, 0, sizeof(words)); + + if (p[0] == ':' && p[1] == ':') { + compress = 0; + p += 2; + if (*p == '\0') + count = 0; + } + + while (*p) { + if (count >= 8) + return 0; + + unsigned value = 0; + int digits = 0; + while (digits < 4) { + int h = ParseHex(*p); + if (h < 0) + break; + value = (value << 4) | (unsigned)h; + p++; + digits++; + } + if (digits == 0) + return 0; + words[count++] = (uint16_t)value; + + if (*p == '\0') + break; + if (*p != ':') + return 0; + p++; + if (*p == ':') { + if (compress >= 0) + return 0; + compress = count; + p++; + if (*p == '\0') + break; + } + } + + if (compress >= 0) { + int zeros = 8 - count; + if (zeros <= 0) + return 0; + for (int i = count - 1; i >= compress; --i) + words[i + zeros] = words[i]; + for (int i = 0; i < zeros; ++i) + words[compress + i] = 0; + } else if (count != 8) { + return 0; + } + + for (int i = 0; i < 8; ++i) { + out[i * 2 + 0] = (uint8_t)(words[i] >> 8); + out[i * 2 + 1] = (uint8_t)words[i]; + } + return 1; +} + +static char* AppendHex16(char* out, uint16_t value) { + static const char hex[] = "0123456789abcdef"; + int started = 0; + for (int shift = 12; shift >= 0; shift -= 4) { + unsigned nibble = (value >> shift) & 0xfu; + if (nibble || started || shift == 0) { + *out++ = hex[nibble]; + started = 1; + } + } + return out; +} + // Implements the inet addr operation. __declspec(dllexport) in_addr_t inet_addr(const char* text) { uint8_t b[4]; @@ -85,41 +174,114 @@ __declspec(dllexport) char* inet_ntoa(struct in_addr in) { return buf; } -// Implements the sock Addr To Sys operation. -static int SockAddrToSys(const struct sockaddr* addr, socklen_t addrlen, SYS_SOCKET_ADDRESS_IPV4* out) { - if (!addr || !out || addrlen < sizeof(struct sockaddr_in)) { errno = EINVAL; return -1; } - const struct sockaddr_in* in = (const struct sockaddr_in*)addr; - if (in->sin_family != AF_INET) { errno = EINVAL; return -1; } +// Implements the inet pton operation. +__declspec(dllexport) int inet_pton(int af, const char* src, void* dst) { + if (!src || !dst) { errno = EINVAL; return -1; } + if (af == AF_INET) { + uint8_t b[4]; + if (!ParseIpv4Bytes(src, b)) + return 0; + memcpy(dst, b, 4); + return 1; + } + if (af == AF_INET6) { + uint8_t b[16]; + if (!ParseIpv6Bytes(src, b)) + return 0; + memcpy(dst, b, 16); + return 1; + } + errno = EAFNOSUPPORT; + return -1; +} +// Implements the inet ntop operation. +__declspec(dllexport) const char* inet_ntop(int af, const void* src, char* dst, socklen_t size) { + if (!src || !dst) { errno = EINVAL; return (const char*)0; } + if (af == AF_INET) { + if (size < 16) { errno = ERANGE; return (const char*)0; } + struct in_addr in; + memcpy(&in, src, sizeof(in)); + strcpy(dst, inet_ntoa(in)); + return dst; + } + if (af == AF_INET6) { + if (size < 40) { errno = ERANGE; return (const char*)0; } + const uint8_t* b = (const uint8_t*)src; + char* w = dst; + for (int i = 0; i < 8; ++i) { + uint16_t value = ((uint16_t)b[i * 2] << 8) | b[i * 2 + 1]; + w = AppendHex16(w, value); + if (i != 7) + *w++ = ':'; + } + *w = '\0'; + return dst; + } + errno = EAFNOSUPPORT; + return (const char*)0; +} + +// Implements the sock Addr To Sys operation. +static int SockAddrToSys(const struct sockaddr* addr, socklen_t addrlen, SYS_SOCKET_ADDRESS* out) { + if (!addr || !out) { errno = EINVAL; return -1; } memset(out, 0, sizeof(*out)); - out->Family = SYS_AF_INET; - out->Port = ntohs(in->sin_port); - const uint8_t* p = (const uint8_t*)&in->sin_addr.s_addr; - out->Address[0] = p[0]; - out->Address[1] = p[1]; - out->Address[2] = p[2]; - out->Address[3] = p[3]; - return 0; + out->Size = sizeof(*out); + + if (addr->sa_family == AF_INET) { + if (addrlen < sizeof(struct sockaddr_in)) { errno = EINVAL; return -1; } + const struct sockaddr_in* in = (const struct sockaddr_in*)addr; + out->Family = SYS_AF_INET; + out->Port = ntohs(in->sin_port); + memcpy(out->Address, &in->sin_addr.s_addr, 4); + return 0; + } + + if (addr->sa_family == AF_INET6) { + if (addrlen < sizeof(struct sockaddr_in6)) { errno = EINVAL; return -1; } + const struct sockaddr_in6* in6 = (const struct sockaddr_in6*)addr; + out->Family = SYS_AF_INET6; + out->Port = ntohs(in6->sin6_port); + memcpy(out->Address, in6->sin6_addr.s6_addr, 16); + out->ScopeId = in6->sin6_scope_id; + return 0; + } + + errno = EAFNOSUPPORT; + return -1; } // Implements the to Sock Addr operation. -static void SysToSockAddr(const SYS_SOCKET_ADDRESS_IPV4* in, struct sockaddr* addr, socklen_t* addrlen) { - if (!addr || !addrlen) return; - if (*addrlen < sizeof(struct sockaddr_in)) { +static void SysToSockAddr(const SYS_SOCKET_ADDRESS* in, struct sockaddr* addr, socklen_t* addrlen) { + if (!in || !addr || !addrlen) return; + + if (in->Family == SYS_AF_INET) { + if (*addrlen < sizeof(struct sockaddr_in)) { + *addrlen = sizeof(struct sockaddr_in); + return; + } + struct sockaddr_in* out = (struct sockaddr_in*)addr; + memset(out, 0, sizeof(*out)); + out->sin_family = AF_INET; + out->sin_port = htons(in->Port); + memcpy(&out->sin_addr.s_addr, in->Address, 4); *addrlen = sizeof(struct sockaddr_in); return; } - struct sockaddr_in* out = (struct sockaddr_in*)addr; - memset(out, 0, sizeof(*out)); - out->sin_family = AF_INET; - out->sin_port = htons(in->Port); - uint8_t* p = (uint8_t*)&out->sin_addr.s_addr; - p[0] = in->Address[0]; - p[1] = in->Address[1]; - p[2] = in->Address[2]; - p[3] = in->Address[3]; - *addrlen = sizeof(struct sockaddr_in); + if (in->Family == SYS_AF_INET6) { + if (*addrlen < sizeof(struct sockaddr_in6)) { + *addrlen = sizeof(struct sockaddr_in6); + return; + } + struct sockaddr_in6* out = (struct sockaddr_in6*)addr; + memset(out, 0, sizeof(*out)); + out->sin6_family = AF_INET6; + out->sin6_port = htons(in->Port); + memcpy(out->sin6_addr.s6_addr, in->Address, 16); + out->sin6_scope_id = in->ScopeId; + *addrlen = sizeof(struct sockaddr_in6); + } } // Implements the socket Fd Get operation. @@ -148,7 +310,7 @@ __declspec(dllexport) int socket(int domain, int type, int protocol) { __declspec(dllexport) int bind(int fd, const struct sockaddr* addr, socklen_t addrlen) { FD_ENTRY* e = SocketFdGet(fd); if (!e) return -1; - SYS_SOCKET_ADDRESS_IPV4 a; + SYS_SOCKET_ADDRESS a; if (SockAddrToSys(addr, addrlen, &a) != 0) return -1; uint32_t s = SysBindSocket(e->KHandle, &a, sizeof(a)); if (s != 0) { errno = PosixStatusToErrno(s); return -1; } @@ -169,10 +331,10 @@ __declspec(dllexport) int accept(int fd, struct sockaddr* addr, socklen_t* addrl FD_ENTRY* e = SocketFdGet(fd); if (!e) return -1; - SYS_SOCKET_ADDRESS_IPV4 remote; + SYS_SOCKET_ADDRESS remote; size_t remoteSize = sizeof(remote); uint32_t kh = 0; - uint32_t s = SysAcceptSocket(e->KHandle, addr ? &remote : (SYS_SOCKET_ADDRESS_IPV4*)0, + uint32_t s = SysAcceptSocket(e->KHandle, addr ? &remote : (SYS_SOCKET_ADDRESS*)0, addr ? &remoteSize : (size_t*)0, &kh); if (s != 0) { errno = PosixStatusToErrno(s); return -1; } @@ -189,7 +351,7 @@ __declspec(dllexport) int accept(int fd, struct sockaddr* addr, socklen_t* addrl __declspec(dllexport) int connect(int fd, const struct sockaddr* addr, socklen_t addrlen) { FD_ENTRY* e = SocketFdGet(fd); if (!e) return -1; - SYS_SOCKET_ADDRESS_IPV4 a; + SYS_SOCKET_ADDRESS a; if (SockAddrToSys(addr, addrlen, &a) != 0) return -1; uint32_t s = SysConnectSocket(e->KHandle, &a, sizeof(a)); if (s != 0) { errno = PosixStatusToErrno(s); return -1; } @@ -220,7 +382,7 @@ __declspec(dllexport) ssize_t sendto(int fd, const void* buf, size_t len, int fl const struct sockaddr* dest, socklen_t destlen) { FD_ENTRY* e = SocketFdGet(fd); if (!e) return -1; - SYS_SOCKET_ADDRESS_IPV4 remote; + SYS_SOCKET_ADDRESS remote; if (SockAddrToSys(dest, destlen, &remote) != 0) return -1; size_t sent = 0; @@ -243,7 +405,7 @@ __declspec(dllexport) ssize_t recvfrom(int fd, void* buf, size_t len, int flags, FD_ENTRY* e = SocketFdGet(fd); if (!e) return -1; - SYS_SOCKET_ADDRESS_IPV4 remote; + SYS_SOCKET_ADDRESS remote; size_t remoteSize = sizeof(remote); size_t got = 0; SYS_SOCKET_RECV_FROM_REQUEST req; @@ -251,7 +413,7 @@ __declspec(dllexport) ssize_t recvfrom(int fd, void* buf, size_t len, int flags, req.Buffer = buf; req.Length = len; req.Flags = (uint32_t)flags; - req.RemoteAddressOut = src ? &remote : (SYS_SOCKET_ADDRESS_IPV4*)0; + req.RemoteAddressOut = src ? &remote : (SYS_SOCKET_ADDRESS*)0; req.RemoteAddressSizeInOut = src ? &remoteSize : (size_t*)0; req.ReceivedOut = &got; @@ -408,21 +570,36 @@ static unsigned ParsePort(const char* s) { return *s == '\0' ? v : 0; } -// Implements the resolve Name Ipv4 operation. -static int ResolveNameIpv4(const char* name, SYS_SOCKET_ADDRESS_IPV4* out) { - uint8_t b[4]; - if (ParseIpv4Bytes(name, b)) { - memset(out, 0, sizeof(*out)); - out->Family = SYS_AF_INET; - out->Address[0] = b[0]; out->Address[1] = b[1]; out->Address[2] = b[2]; out->Address[3] = b[3]; - return 0; +// Implements the resolve Name operation. +static int ResolveNameFamily(const char* name, int family, SYS_SOCKET_ADDRESS* out) { + if (!name || !out) { errno = EINVAL; return -1; } + memset(out, 0, sizeof(*out)); + out->Size = sizeof(*out); + + if (family == AF_INET) { + uint8_t b[4]; + if (ParseIpv4Bytes(name, b)) { + out->Family = SYS_AF_INET; + memcpy(out->Address, b, 4); + return 0; + } + } else if (family == AF_INET6) { + uint8_t b[16]; + if (ParseIpv6Bytes(name, b)) { + out->Family = SYS_AF_INET6; + memcpy(out->Address, b, 16); + return 0; + } + } else { + errno = EAFNOSUPPORT; + return -1; } size_t size = sizeof(*out); SYS_RESOLVE_HOST_REQUEST req; memset(&req, 0, sizeof(req)); req.Name = SysSvFromCstr(name); - req.Family = SYS_AF_INET; + req.Family = family == AF_INET6 ? SYS_AF_INET6 : SYS_AF_INET; req.AddressOut = out; req.AddressSizeInOut = &size; uint32_t s = SysResolveHost(&req); @@ -430,6 +607,16 @@ static int ResolveNameIpv4(const char* name, SYS_SOCKET_ADDRESS_IPV4* out) { return 0; } +static int ResolveNameAny(const char* name, int family, SYS_SOCKET_ADDRESS* out) { + if (family == AF_INET || family == AF_INET6) + return ResolveNameFamily(name, family, out); + if (family != AF_UNSPEC) { errno = EAFNOSUPPORT; return -1; } + + if (ResolveNameFamily(name, AF_INET6, out) == 0) + return 0; + return ResolveNameFamily(name, AF_INET, out); +} + // Implements the gethostbyname operation. __declspec(dllexport) struct hostent* gethostbyname(const char* name) { static struct hostent h; @@ -440,8 +627,8 @@ __declspec(dllexport) struct hostent* gethostbyname(const char* name) { if (!name || strlen(name) >= sizeof(hname)) { errno = EINVAL; return (struct hostent*)0; } - SYS_SOCKET_ADDRESS_IPV4 a; - if (ResolveNameIpv4(name, &a) != 0) return (struct hostent*)0; + SYS_SOCKET_ADDRESS a; + if (ResolveNameFamily(name, AF_INET, &a) != 0) return (struct hostent*)0; strcpy(hname, name); memcpy(addr, a.Address, 4); @@ -462,35 +649,34 @@ __declspec(dllexport) int getaddrinfo(const char* node, const char* service, if (!res) return EINVAL; *res = (struct addrinfo*)0; - int family = hints ? hints->ai_family : AF_INET; - if (family != AF_INET && family != 0) return EINVAL; + int family = hints ? hints->ai_family : AF_UNSPEC; + if (family != AF_INET && family != AF_INET6 && family != AF_UNSPEC) return EAFNOSUPPORT; - SYS_SOCKET_ADDRESS_IPV4 a; + SYS_SOCKET_ADDRESS a; memset(&a, 0, sizeof(a)); - a.Family = SYS_AF_INET; + a.Size = sizeof(a); if (!node) { - a.Address[0] = 0; a.Address[1] = 0; a.Address[2] = 0; a.Address[3] = 0; - } else if (ResolveNameIpv4(node, &a) != 0) { + a.Family = family == AF_INET6 ? SYS_AF_INET6 : SYS_AF_INET; + } else if (ResolveNameAny(node, family, &a) != 0) { return errno; } a.Port = service ? ParsePort(service) : 0; - size_t total = sizeof(struct addrinfo) + sizeof(struct sockaddr_in); + socklen_t addrLen = a.Family == SYS_AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); + size_t total = sizeof(struct addrinfo) + addrLen; struct addrinfo* ai = (struct addrinfo*)malloc(total); if (!ai) return ENOMEM; memset(ai, 0, total); - struct sockaddr_in* sin = (struct sockaddr_in*)((uint8_t*)ai + sizeof(struct addrinfo)); - sin->sin_family = AF_INET; - sin->sin_port = htons(a.Port); - uint8_t* p = (uint8_t*)&sin->sin_addr.s_addr; - p[0] = a.Address[0]; p[1] = a.Address[1]; p[2] = a.Address[2]; p[3] = a.Address[3]; + struct sockaddr* sa = (struct sockaddr*)((uint8_t*)ai + sizeof(struct addrinfo)); + socklen_t cap = addrLen; + SysToSockAddr(&a, sa, &cap); - ai->ai_family = AF_INET; + ai->ai_family = a.Family == SYS_AF_INET6 ? AF_INET6 : AF_INET; ai->ai_socktype = hints ? hints->ai_socktype : 0; ai->ai_protocol = hints ? hints->ai_protocol : 0; - ai->ai_addrlen = sizeof(struct sockaddr_in); - ai->ai_addr = (struct sockaddr*)sin; + ai->ai_addrlen = cap; + ai->ai_addr = sa; ai->ai_next = (struct addrinfo*)0; *res = ai; return 0; diff --git a/code/user/personalities/win32/gdi32.c b/code/user/personalities/win32/gdi32.c index fc73f7e..77b9a3d 100644 --- a/code/user/personalities/win32/gdi32.c +++ b/code/user/personalities/win32/gdi32.c @@ -54,7 +54,6 @@ typedef struct tagBITMAPINFO { RGBQUAD bmiColors[1]; } BITMAPINFO; - // GDI Exports. // Static buffers to persist between function calls. diff --git a/code/user/personalities/win32/kernel32.c b/code/user/personalities/win32/kernel32.c index af89d31..3bac136 100644 --- a/code/user/personalities/win32/kernel32.c +++ b/code/user/personalities/win32/kernel32.c @@ -13,11 +13,8 @@ // multiple heaps, and NULL is a legal (no-op) heap argument under Win32. // - Win32 per-thread state lives behind SYS_TEB::SubsystemData so the native // TEB ABI stays subsystem-neutral. -// - File I/O uses SYS_OPEN_FILE / SYS_READ_FILE / SYS_SEEK_FILE / -// SYS_GET_FILE_SIZE / SYS_CLOSE_HANDLE (unified close path - no separate -// SysCloseFile). -// - WriteFile / CreateFileA in write-mode / SetEndOfFile are stubs because -// the VFS has no write path yet. +// - File I/O uses the flags-based SYS_OPEN_FILE request plus the common +// read/write/seek/query/close syscalls. #include #include @@ -221,19 +218,19 @@ static void TranslatePath(const char* input, char* output) { } } - // Implements the k32 Open File Native operation. -static uint32_t K32OpenFileNative(const char* path, uint32_t* handleOut) { - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut }; +static uint32_t K32OpenFileNative(const char* path, + uint32_t access, + uint32_t disposition, + uint32_t* handleOut) { + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path), + access, + disposition, + SYS_FILE_FLAG_NONE, + handleOut); return SysOpenFile(&req); } -// Implements the k32 Create File Native operation. -static uint32_t K32CreateFileNative(const char* path, uint32_t* handleOut) { - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut }; - return SysCreateFile(&req); -} - // Implements the k32 Delete File Native operation. static uint32_t K32DeleteFileNative(const char* path) { SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; @@ -254,13 +251,14 @@ static uint32_t K32SetCwdNative(const char* path) { // Implements the k32 Get Cwd Native operation. static uint32_t K32GetCwdNative(char* buffer, size_t capacity, size_t* lengthOut) { - size_t len = 0; - size_t writable = (buffer && capacity) ? capacity - 1 : 0; - SYS_GET_CWD_REQUEST req = { buffer, writable, &len }; - uint32_t s = SysGetCwd(&req); - if (lengthOut) *lengthOut = len; - if (s == 0 && buffer && capacity) buffer[len] = '\0'; - return s; + SYS_STRING_VIEW cwd = SysGetCurrentDirectoryView(); + if ((cwd.Length && !cwd.Data) || cwd.Length > SYS_CWD_MAX) return STATUS_INVALID_PARAMETER; + if (lengthOut) *lengthOut = cwd.Length; + if (!buffer || cwd.Length + 1 > capacity) return STATUS_BUFFER_TOO_SMALL; + + if (cwd.Length) memcpy(buffer, cwd.Data, cwd.Length); + buffer[cwd.Length] = '\0'; + return STATUS_SUCCESS; } // Implements the k32 Create Process Native operation. @@ -323,7 +321,10 @@ static BOOL KiFileExistsA(const char* path) { char native[MAX_PATH]; TranslatePath(path, native); uint32_t h = 0; - if (K32OpenFileNative(native, &h) != 0) return FALSE; + if (K32OpenFileNative(native, + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + &h) != 0) return FALSE; SysCloseHandle(h); return TRUE; } @@ -396,7 +397,7 @@ __declspec(dllexport) DWORD SearchPathA(const char* lpPath, const char* lpFileNa return 0; } -// GetLastError / SetLastError (single-threaded stub). +// GetLastError / SetLastError (single-threaded stub). static SYS_PEB* gProcessPeb = NULL; // Implements the peb operation. @@ -416,7 +417,7 @@ __declspec(dllexport) void SetLastError(DWORD err) { // Process termination. __declspec(dllexport) _Noreturn void ExitProcess(unsigned int code) { - SysExitProcess((int)code); + SysExitProcess((int32_t)code); while (true) ; // unreachable } @@ -425,17 +426,31 @@ __declspec(dllexport) void OutputDebugStringA(const char* str) { if (str) K32DebugPrintA(str); } +// Queries the native monotonic tick frequency. +static uint64_t K32TickFrequency(void) { + uint64_t frequency = 0; + if (SysGetTickFrequency(&frequency) != STATUS_SUCCESS) return 0; + return frequency; +} + +// Returns monotonic milliseconds since boot. +static uint64_t K32TickMilliseconds(void) { + uint64_t frequency = K32TickFrequency(); + if (!frequency) return 0; + return SysTicksToMilliseconds(SysGetTick(), frequency); +} + // Timer. __declspec(dllexport) DWORD GetTickCount(void) { - return (DWORD)(SysGetTick() / 1000); + return (DWORD)K32TickMilliseconds(); } // Returns tick Count64. __declspec(dllexport) uint64_t GetTickCount64(void) { - return SysGetTick() / 1000; + return K32TickMilliseconds(); } -// Memory (heap argument is ignored - single user-mode heap). +// Memory (heap argument is ignored - single user-mode heap). #define HEAP_ZERO_MEMORY 0x00000008UL #define HEAP_REALLOC_IN_PLACE_ONLY 0x00000010UL @@ -511,8 +526,8 @@ __declspec(dllexport) void* VirtualAlloc(void* lpAddress, size_t dwSize, void* base = NULL; uint32_t flags = lpAddress ? SYS_VM_FLAG_FIXED : SYS_VM_FLAG_NONE; uint32_t s = SysAllocVm(lpAddress, pages, WinProtectToSysVm(flProtect), flags, &base); - if (s != SYS_STATUS_SUCCESS) { - gLastError = (s == SYS_STATUS_NOT_FOUND || s == SYS_STATUS_INVALID_PARAMETER) ? 87 : 8; + if (s != STATUS_SUCCESS) { + gLastError = (s == STATUS_NOT_FOUND || s == STATUS_INVALID_PARAMETER) ? 87 : 8; return NULL; } return base; @@ -523,7 +538,7 @@ __declspec(dllexport) BOOL VirtualFree(void* lpAddress, size_t dwSize, DWORD dwF (void)dwSize; (void)dwFreeType; if (!lpAddress) return TRUE; uint32_t s = SysFreeVm(lpAddress); - if (s != SYS_STATUS_SUCCESS) { gLastError = 87; return FALSE; } + if (s != STATUS_SUCCESS) { gLastError = 87; return FALSE; } return TRUE; } @@ -534,7 +549,7 @@ __declspec(dllexport) BOOL VirtualProtect(void* lpAddress, size_t dwSize, uint32_t oldSysProtect = 0; uint32_t s = SysProtectVm(lpAddress, pages, WinProtectToSysVm(flNewProtect), lpflOldProtect ? &oldSysProtect : NULL); - if (s != SYS_STATUS_SUCCESS) { gLastError = 87; return FALSE; } + if (s != STATUS_SUCCESS) { gLastError = 87; return FALSE; } if (lpflOldProtect) *lpflOldProtect = SysProtectVmToWin(oldSysProtect); return TRUE; } @@ -549,7 +564,7 @@ __declspec(dllexport) size_t VirtualQuery(const void* lpAddress, SYS_VM_REGION_INFO info; uint32_t s = SysQueryVm((void*)lpAddress, &info); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { gLastError = 87; return 0; } @@ -585,8 +600,8 @@ __declspec(dllexport) HANDLE CreateFileMappingA(HANDLE hFile, SYS_MAP_FLAG_NONE, maximumSize, &kh); - if (s != SYS_STATUS_SUCCESS) { - gLastError = (s == SYS_STATUS_NOT_SUPPORTED) ? 120 : ((s == SYS_STATUS_NO_MEMORY) ? 8 : 87); + if (s != STATUS_SUCCESS) { + gLastError = (s == STATUS_NOT_SUPPORTED) ? 120 : ((s == STATUS_NO_MEMORY) ? 8 : 87); return NULL; } return KH_TO_HANDLE(kh); @@ -621,8 +636,8 @@ __declspec(dllexport) void* MapViewOfFile(HANDLE hFileMappingObject, WinFileMapAccessToSys(dwDesiredAccess), SYS_MAP_FLAG_NONE, &base); - if (s != SYS_STATUS_SUCCESS) { - gLastError = (s == SYS_STATUS_NOT_SUPPORTED) ? 120 : ((s == SYS_STATUS_NO_MEMORY) ? 8 : 87); + if (s != STATUS_SUCCESS) { + gLastError = (s == STATUS_NOT_SUPPORTED) ? 120 : ((s == STATUS_NO_MEMORY) ? 8 : 87); return NULL; } return base; @@ -631,7 +646,7 @@ __declspec(dllexport) void* MapViewOfFile(HANDLE hFileMappingObject, __declspec(dllexport) BOOL UnmapViewOfFile(const void* lpBaseAddress) { if (!lpBaseAddress) { gLastError = 87; return FALSE; } uint32_t s = SysFreeVm((void*)lpBaseAddress); - if (s != SYS_STATUS_SUCCESS) { gLastError = 87; return FALSE; } + if (s != STATUS_SUCCESS) { gLastError = 87; return FALSE; } return TRUE; } @@ -645,16 +660,22 @@ __declspec(dllexport) BOOL FlushViewOfFile(const void* lpBaseAddress, size_t dwN // --- Win32 API ---. +// Returns whether one input key is currently held for Win32 adapters. +static bool Win32IsInputKeyDown(INPUT_KEY key) { + bool down = false; + return SysIsKeyDown(key, &down) == STATUS_SUCCESS && down; +} + // GetAsyncKeyState implementation over the canonical input key namespace. __declspec(dllexport) SHORT GetAsyncKeyState(int vKey) { if (vKey == 0x11) { // VK_CONTROL - return (SysIsKeyDown(INPUT_KEY_LEFT_CTRL) || SysIsKeyDown(INPUT_KEY_RIGHT_CTRL)) ? (SHORT)0x8000 : 0; + return (Win32IsInputKeyDown(INPUT_KEY_LEFT_CTRL) || Win32IsInputKeyDown(INPUT_KEY_RIGHT_CTRL)) ? (SHORT)0x8000 : 0; } if (vKey == 0x10) { // VK_SHIFT - return (SysIsKeyDown(INPUT_KEY_LEFT_SHIFT) || SysIsKeyDown(INPUT_KEY_RIGHT_SHIFT)) ? (SHORT)0x8000 : 0; + return (Win32IsInputKeyDown(INPUT_KEY_LEFT_SHIFT) || Win32IsInputKeyDown(INPUT_KEY_RIGHT_SHIFT)) ? (SHORT)0x8000 : 0; } if (vKey == 0x12) { // VK_MENU / ALT - return (SysIsKeyDown(INPUT_KEY_LEFT_ALT) || SysIsKeyDown(INPUT_KEY_RIGHT_ALT)) ? (SHORT)0x8000 : 0; + return (Win32IsInputKeyDown(INPUT_KEY_LEFT_ALT) || Win32IsInputKeyDown(INPUT_KEY_RIGHT_ALT)) ? (SHORT)0x8000 : 0; } static const struct { int vk; INPUT_KEY key; } vk_to_key[] = { @@ -697,7 +718,7 @@ __declspec(dllexport) SHORT GetAsyncKeyState(int vKey) { for (size_t i = 0; i < sizeof(vk_to_key) / sizeof(vk_to_key[0]); ++i) { if (vk_to_key[i].vk == vKey) { - if (SysIsKeyDown(vk_to_key[i].key)) + if (Win32IsInputKeyDown(vk_to_key[i].key)) return (SHORT)0x8000; break; } @@ -709,8 +730,10 @@ __declspec(dllexport) SHORT GetAsyncKeyState(int vKey) { __declspec(dllexport) BOOL FlushConsoleInputBuffer(HANDLE hConsoleInput) { (void)hConsoleInput; // Drain the keyboard buffer by reading until empty. - while (SysPollKeyboard()) { - (void)SysReadKeyboardChar(); + INPUT_KEY key; + while (SysPollKeyboard(&key) == STATUS_SUCCESS) { + char ignored; + (void)SysReadKeyboardChar(&ignored); } return TRUE; } @@ -788,71 +811,42 @@ __declspec(dllexport) HANDLE CreateFileA(const char* name, char translatedName[260]; TranslatePath(name, translatedName); - int wantWrite = (access & GENERIC_WRITE) != 0; + uint32_t sysAccess = SYS_FILE_ACCESS_QUERY; + if (access & GENERIC_READ) + sysAccess |= SYS_FILE_ACCESS_READ; + if (access & GENERIC_WRITE) + sysAccess |= SYS_FILE_ACCESS_WRITE; - if (disposition == OPEN_EXISTING || disposition == TRUNCATE_EXISTING) { - // Open the file via the read path (it must already exist). - uint32_t kh = 0; - uint32_t s = K32OpenFileNative(translatedName, &kh); - if (s != 0) { gLastError = 2; return INVALID_HANDLE_VALUE; } - - if (disposition == TRUNCATE_EXISTING && wantWrite) { - // Immediately truncate to zero. - uint32_t ts = SysTruncateFile(kh, 0); - if (ts != 0) { - SysCloseHandle(kh); - gLastError = 5; - return INVALID_HANDLE_VALUE; - } - } - return KH_TO_HANDLE(kh); + uint32_t sysDisposition; + switch (disposition) { + case OPEN_EXISTING: sysDisposition = SYS_FILE_OPEN_EXISTING; break; + case OPEN_ALWAYS: sysDisposition = SYS_FILE_OPEN_ALWAYS; break; + case CREATE_ALWAYS: sysDisposition = SYS_FILE_CREATE_ALWAYS; break; + case CREATE_NEW: sysDisposition = SYS_FILE_CREATE_NEW; break; + case TRUNCATE_EXISTING: sysDisposition = SYS_FILE_TRUNCATE_EXISTING; break; + default: + gLastError = 87; + return INVALID_HANDLE_VALUE; } - if (disposition == CREATE_ALWAYS && wantWrite) { - // Create-or-replace: try to create; if the file already exists, - // open it and truncate it to zero instead. - uint32_t kh = 0; - uint32_t s = K32CreateFileNative(translatedName, &kh); - if (s != 0) { - // File may already exist - open and truncate. - s = K32OpenFileNative(translatedName, &kh); - if (s != 0) { gLastError = 5; return INVALID_HANDLE_VALUE; } - SysTruncateFile(kh, 0); - } - return KH_TO_HANDLE(kh); + uint32_t kh = 0; + uint32_t s = K32OpenFileNative(translatedName, sysAccess, sysDisposition, &kh); + if (s != 0) { + gLastError = (s == STATUS_OBJECT_NAME_COLLISION) ? 80 : + (s == STATUS_NOT_FOUND) ? 2 : 5; + return INVALID_HANDLE_VALUE; } - if (disposition == OPEN_ALWAYS && wantWrite) { - // Open if exists, create if not. - uint32_t kh = 0; - if (K32OpenFileNative(translatedName, &kh) != 0) { - if (K32CreateFileNative(translatedName, &kh) != 0) { - gLastError = 5; - return INVALID_HANDLE_VALUE; - } - } - return KH_TO_HANDLE(kh); - } - - if (disposition == CREATE_NEW && wantWrite) { - // Fail if the file already exists (we can't check atomically, - // but SysCreateFile returns NAME_COLLISION for existing files). - uint32_t kh = 0; - uint32_t s = K32CreateFileNative(translatedName, &kh); - if (s != 0) { gLastError = 80; return INVALID_HANDLE_VALUE; } // ERROR_FILE_EXISTS - return KH_TO_HANDLE(kh); - } - - // Any other combination (e.g. read-only CREATE_ALWAYS) is not supported. - gLastError = 87; // ERROR_INVALID_PARAMETER - return INVALID_HANDLE_VALUE; + return KH_TO_HANDLE(kh); } // GetFileSize - fills optional high-DWORD pointer, returns low DWORD. // Returns 0xFFFFFFFF on error, matching Win32 behaviour. __declspec(dllexport) DWORD GetFileSize(HANDLE hFile, DWORD* fileSizeHigh) { if (!hFile || hFile == INVALID_HANDLE_VALUE) { gLastError = 6; return 0xFFFFFFFFUL; } - uint64_t sz = SysGetFileSize(HANDLE_TO_KH(hFile)); + uint64_t sz = 0; + uint32_t s = SysGetFileSize(HANDLE_TO_KH(hFile), &sz); + if (s != 0) { gLastError = 6; return 0xFFFFFFFFUL; } if (fileSizeHigh) *fileSizeHigh = (DWORD)(sz >> 32); return (DWORD)(sz & 0xFFFFFFFFULL); } @@ -922,12 +916,14 @@ __declspec(dllexport) DWORD SetFilePointer(HANDLE hFile, long dist, if (method == FILE_BEGIN) { target = delta; } else if (method == FILE_CURRENT) { - uint64_t cur = SysGetFilePos(kh); - if (cur == (uint64_t)-1 || cur > 0x7FFFFFFFFFFFFFFFULL) { gLastError = 6; return 0xFFFFFFFFUL; } + uint64_t cur = 0; + uint32_t s = SysGetFilePos(kh, &cur); + if (s != 0 || cur > 0x7FFFFFFFFFFFFFFFULL) { gLastError = 6; return 0xFFFFFFFFUL; } target = (int64_t)cur + delta; } else if (method == FILE_END) { - uint64_t sz = SysGetFileSize(kh); - if (sz > 0x7FFFFFFFFFFFFFFFULL) { gLastError = 6; return 0xFFFFFFFFUL; } + uint64_t sz = 0; + uint32_t s = SysGetFileSize(kh, &sz); + if (s != 0 || sz > 0x7FFFFFFFFFFFFFFFULL) { gLastError = 6; return 0xFFFFFFFFUL; } target = (int64_t)sz + delta; } else { gLastError = 87; return 0xFFFFFFFFUL; @@ -949,9 +945,10 @@ __declspec(dllexport) DWORD SetFilePointer(HANDLE hFile, long dist, __declspec(dllexport) BOOL SetEndOfFile(HANDLE hFile) { if (!hFile || hFile == INVALID_HANDLE_VALUE) { gLastError = 6; return FALSE; } uint32_t kh = HANDLE_TO_KH(hFile); - uint64_t pos = SysGetFilePos(kh); - if (pos == (uint64_t)-1) { gLastError = 6; return FALSE; } - uint32_t s = SysTruncateFile(kh, pos); + uint64_t pos = 0; + uint32_t s = SysGetFilePos(kh, &pos); + if (s != 0) { gLastError = 6; return FALSE; } + s = SysTruncateFile(kh, pos); if (s != 0) { gLastError = 1; return FALSE; } return TRUE; } @@ -1184,7 +1181,7 @@ __declspec(dllexport) DWORD GetModuleFileNameA(HANDLE hModule, // Implements the terminate Process operation. __declspec(dllexport) BOOL TerminateProcess(HANDLE hProcess, unsigned int uExitCode) { if (hProcess == GetCurrentProcess()) { - SysExitProcess((int)uExitCode); + SysExitProcess((int32_t)uExitCode); while (true) ; } @@ -1193,16 +1190,9 @@ __declspec(dllexport) BOOL TerminateProcess(HANDLE hProcess, unsigned int uExitC return FALSE; } - uint32_t pid = 0; - uint32_t s = SysGetProcessPid(HANDLE_TO_KH(hProcess), &pid); - if (s != 0 || pid == 0) { - gLastError = 6; // ERROR_INVALID_HANDLE - return FALSE; - } - - s = SysKillProcess(pid, (int)uExitCode); + uint32_t s = SysKillProcess(HANDLE_TO_KH(hProcess), (int32_t)uExitCode); if (s != 0) { - gLastError = 5; // ERROR_ACCESS_DENIED / generic failure for now + gLastError = (s == STATUS_INVALID_HANDLE) ? 6 : 5; return FALSE; } return TRUE; @@ -1290,7 +1280,6 @@ __declspec(dllexport) DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMillisec return 0xFFFFFFFFUL; } - // Implements the extract Application Path A operation. static BOOL ExtractApplicationPathA(const char* commandLine, char* out, size_t outSize) { if (!commandLine || !out || outSize == 0) return FALSE; @@ -1411,7 +1400,7 @@ __declspec(dllexport) BOOL GetExitCodeProcess(HANDLE hProcess, DWORD* lpExitCode if (!lpExitCode) { gLastError = 87; return FALSE; } uint32_t kh = HANDLE_TO_KH(hProcess); if (!kh) { gLastError = 6; return FALSE; } // ERROR_INVALID_HANDLE - int code = 0; + int32_t code = 0; uint32_t s = SysGetProcessExitCode(kh, &code); if (s != 0) { gLastError = 6; return FALSE; } *lpExitCode = (DWORD)code; @@ -1471,13 +1460,13 @@ static BOOL K32IsConsoleOutputHandle(HANDLE h) { // Implements the k32 Set Console Last Error operation. static void K32SetConsoleLastError(uint32_t status) { switch (status) { - case SYS_STATUS_SUCCESS: + case STATUS_SUCCESS: gLastError = 0; break; - case SYS_STATUS_INVALID_PARAMETER: + case STATUS_INVALID_PARAMETER: gLastError = 87; break; - case SYS_STATUS_NO_MEMORY: + case STATUS_NO_MEMORY: gLastError = 8; break; default: @@ -1506,7 +1495,8 @@ __declspec(dllexport) BOOL ReadConsoleA(HANDLE hConsoleInput, DWORD i = 0; // Notice we switched to a while loop while (i < nNumberOfCharsToRead) { - char c = SysReadKeyboardChar(); + char c = 0; + if (SysReadKeyboardChar(&c) != STATUS_SUCCESS) continue; // Ignore unmapped keys (Shift, Ctrl, etc.). if (c == 0) continue; @@ -1518,7 +1508,7 @@ __declspec(dllexport) BOOL ReadConsoleA(HANDLE hConsoleInput, // Stop reading when the user presses Enter. if (c == '\n' || c == '\r') { // If your shell expects strings to be \n terminated, leave this. - // If your shell expects \0 termination without a newline, + // If your shell expects \0 termination without a newline, // you might want to replace buf[i-1] with \0 here! break; } @@ -1558,7 +1548,7 @@ __declspec(dllexport) BOOL GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, SYS_CONSOLE_INFO info; uint32_t s = SysGetConsoleInfo(&info); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { K32SetConsoleLastError(s); return FALSE; } @@ -1589,7 +1579,7 @@ __declspec(dllexport) BOOL SetConsoleCursorPosition(HANDLE hConsoleOutput, } uint32_t s = SysSetConsoleCursor((int)dwCursorPosition.X, (int)dwCursorPosition.Y); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { K32SetConsoleLastError(s); return FALSE; } @@ -1618,7 +1608,7 @@ __declspec(dllexport) BOOL FillConsoleOutputCharacterA(HANDLE hConsoleOutput, (int)dwWriteCoord.Y, (size_t)nLength, &written); if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = (DWORD)written; - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { K32SetConsoleLastError(s); return FALSE; } @@ -1647,18 +1637,17 @@ __declspec(dllexport) BOOL FillConsoleOutputAttribute(HANDLE hConsoleOutput, (int)dwWriteCoord.Y, (size_t)nLength, &written); if (lpNumberOfAttrsWritten) *lpNumberOfAttrsWritten = (DWORD)written; - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { K32SetConsoleLastError(s); return FALSE; } return TRUE; } -// Directory enumeration (FindFirstFileA / FindNextFileA / FindClose). +// Directory enumeration (FindFirstFileA / FindNextFileA / FindClose). typedef SYS_DIR_ENTRY KDIR_ENTRY; - // FIND_HANDLE is a user-side wrapper: it stores the kernel dir handle and // the wildcard pattern so FindNextFileA can filter subsequent entries. // We return a pointer to this struct cast to HANDLE; it is NOT a kernel @@ -1912,16 +1901,16 @@ __declspec(dllexport) BOOL SetCurrentDirectoryA(const char* lpPathName) { // Returns current Directory A. __declspec(dllexport) DWORD GetCurrentDirectoryA(DWORD nBufferLength, char* lpBuffer) { if (!lpBuffer || nBufferLength == 0) { - // Return required size (including NUL). Max CWD is 512 bytes + "C:\\". - return 515; + // Return a conservative required size including NUL. + return SYS_CWD_MAX + 1; } // Kernel returns native "N:/path". Present it as a Win32 drive letter. - char raw[512]; + char raw[SYS_CWD_MAX + 1]; uint32_t s = K32GetCwdNative(raw, sizeof(raw), NULL); if (s != 0) { gLastError = 1; return 0; } - char full[515]; + char full[SYS_CWD_MAX + 1]; char drive = 'C'; const char* rel = raw; if (raw[0] >= '0' && raw[0] <= '7' && raw[1] == ':' && @@ -1948,7 +1937,6 @@ __declspec(dllexport) DWORD GetCurrentDirectoryA(DWORD nBufferLength, char* lpBu return (DWORD)(needed - 1); // length without NUL, matching Win32 } - __declspec(dllexport) HANDLE CreateMutexA(void* lpMutexAttributes, BOOL bInitialOwner, const char* lpName) { (void)lpMutexAttributes; (void)lpName; @@ -2108,26 +2096,28 @@ static long long TscFreqFromCpuid(void) { return (long long)crystal_hz * numer / denom; } -// Calibrate TSC against SysGetTick (1 unit = 1 microsecond). +// Calibrates TSC against the native monotonic tick source. static long long TscFreqCalibrate(void) { - // We'll wait for 100ms (100,000 microseconds) to get a stable sample. - const uint64_t CALIBRATION_US = 100000; + uint64_t frequency = K32TickFrequency(); + if (!frequency) return 0; + + uint64_t calibrationTicks = frequency / 10ull; + if (!calibrationTicks) calibrationTicks = 1; uint64_t t0 = SysGetTick(); uint64_t tsc0 = __rdtsc(); - // Simple busy wait for the duration. - while ((SysGetTick() - t0) < CALIBRATION_US) { - _mm_pause(); + while ((SysGetTick() - t0) < calibrationTicks) { + _mm_pause(); } uint64_t t1 = SysGetTick(); uint64_t tsc1 = __rdtsc(); uint64_t delta_tsc = tsc1 - tsc0; - uint64_t delta_us = t1 - t0; + uint64_t delta_us = SysTicksToMicroseconds(t1 - t0, frequency); + if (!delta_us) return 0; - // Hz = (Delta TSC / Delta Microseconds) * 1,000,000. return (long long)(delta_tsc * 1000000ULL / delta_us); } @@ -2199,7 +2189,7 @@ __declspec(dllexport) void GetSystemTime(SYSTEMTIME* lpSystemTime) { if (!lpSystemTime) return; SYS_SYSTEM_TIME st; - if (SysGetSystemTime(&st) != SYS_STATUS_SUCCESS) { + if (SysGetSystemTime(&st) != STATUS_SUCCESS) { memset(lpSystemTime, 0, sizeof(*lpSystemTime)); return; } @@ -2237,11 +2227,12 @@ __declspec(dllexport) void GetSystemTimeAsFileTime(void* lpSystemTimeAsFileTime) SYS_SYSTEM_TIME st; uint64_t ft; - if (SysGetSystemTime(&st) == SYS_STATUS_SUCCESS) { + if (SysGetSystemTime(&st) == STATUS_SUCCESS) { ft = K32EpochToFileTime(st.EpochSeconds, st.Nanosecond); } else { + uint64_t frequency = K32TickFrequency(); const uint64_t FALLBACK_2024_01_01_FT = 133485408000000000ULL; - ft = FALLBACK_2024_01_01_FT + (SysGetTick() * 10ULL); + ft = FALLBACK_2024_01_01_FT + SysTicksToMicroseconds(SysGetTick(), frequency) * 10ULL; } memcpy(lpSystemTimeAsFileTime, &ft, sizeof(ft)); } @@ -2461,7 +2452,7 @@ __declspec(dllexport) BOOL IsDebuggerPresent(void) { return FALSE; } -// RTL Exception / Unwind (SEH infrastructure - all stubs). +// RTL Exception / Unwind (SEH infrastructure - all stubs). typedef struct _RUNTIME_FUNCTION { unsigned int BeginAddress; @@ -2635,7 +2626,7 @@ __declspec(dllexport) BOOL FlsFree(DWORD dwFlsIndex) { return TRUE; } -// Pointer encoding (security cookie obfuscation) +// Pointer encoding (security cookie obfuscation) // We have no security cookie; return the pointer unchanged. __declspec(dllexport) void* EncodePointer(void* Ptr) { return Ptr; } diff --git a/code/user/personalities/win32/user32.c b/code/user/personalities/win32/user32.c index 78091ae..487d1f6 100644 --- a/code/user/personalities/win32/user32.c +++ b/code/user/personalities/win32/user32.c @@ -14,7 +14,6 @@ typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; char res[32]; } PAINTSTRUCT; - // Implements the user32 Debug Print A operation. static void User32DebugPrintA(const char* s) { if (!s) return; diff --git a/code/user/personalities/win32/winmm.c b/code/user/personalities/win32/winmm.c index 79eaea3..c9cff70 100644 --- a/code/user/personalities/win32/winmm.c +++ b/code/user/personalities/win32/winmm.c @@ -49,7 +49,9 @@ typedef struct { // Timer. __declspec(dllexport) unsigned long timeGetTime(void) { - return (unsigned long)(SysGetTick() / 1000u); + uint64_t frequency = 0; + if (SysGetTickFrequency(&frequency) != STATUS_SUCCESS || !frequency) return 0; + return (unsigned long)SysTicksToMilliseconds(SysGetTick(), frequency); } // Waveform audio (stubs - no audio hardware at this stage). diff --git a/code/user/runtime/heap.c b/code/user/runtime/heap.c index 7b1d323..f71eb87 100644 --- a/code/user/runtime/heap.c +++ b/code/user/runtime/heap.c @@ -117,7 +117,7 @@ static HEAP_SEGMENT* AddSegment(size_t payloadSize) { void* base = 0; uint32_t s = SysAllocVm(0, pages, SYS_VM_READWRITE, SYS_VM_FLAG_NONE, &base); - if (s != SYS_STATUS_SUCCESS || !base) + if (s != STATUS_SUCCESS || !base) return (HEAP_SEGMENT*)0; HEAP_SEGMENT* seg = (HEAP_SEGMENT*)base; @@ -210,7 +210,6 @@ void* _calloc(size_t count, size_t size) { return ptr; } - // Returns the usable payload size of an allocation made by this heap. size_t _malloc_usable_size(const void* ptr) { if (!ptr) return (size_t)-1; diff --git a/code/user/syscall/syscall_nr.inc b/code/user/syscall/syscall_nr.inc index f839a86..226358f 100644 --- a/code/user/syscall/syscall_nr.inc +++ b/code/user/syscall/syscall_nr.inc @@ -1,112 +1,134 @@ -; syscall_nr.inc +; Syscall numbers shared by MASM user-mode stubs. -SYS_EXIT_PROCESS EQU 0 -SYS_EXIT_THREAD EQU 1 -SYS_CREATE_PROCESS EQU 2 -SYS_WAIT_SINGLE EQU 3 -SYS_GET_PROCESS_EXIT_CODE EQU 4 -SYS_KILL_PROCESS EQU 5 -SYS_GET_PROCESS_LIST EQU 6 -SYS_GET_PROCESS_PID EQU 7 -SYS_CREATE_THREAD EQU 8 -SYS_RETIRED_WAIT_THREAD EQU 9 -SYS_GET_CURRENT_TID EQU 10 -SYS_GET_CURRENT_PID EQU 11 -SYS_SET_THREAD_PRIORITY EQU 12 -SYS_YIELD EQU 13 -SYS_SLEEP EQU 14 -SYS_SET_USER_APC_DISPATCHER EQU 15 -SYS_APC_RETURN EQU 16 -SYS_ALLOC_VM EQU 17 -SYS_FREE_VM EQU 18 -SYS_PROTECT_VM EQU 19 -SYS_QUERY_VM EQU 20 -SYS_CLOSE_HANDLE EQU 21 -SYS_CREATE_MUTEX EQU 22 -SYS_RETIRED_ACQUIRE_MUTEX EQU 23 -SYS_TRY_ACQUIRE_MUTEX EQU 24 -SYS_RELEASE_MUTEX EQU 25 -SYS_CREATE_SEM EQU 26 -SYS_RETIRED_WAIT_SEM EQU 27 -SYS_TRY_WAIT_SEM EQU 28 -SYS_SIGNAL_SEM EQU 29 -SYS_CREATE_EVENT EQU 30 -SYS_SIGNAL_EVENT EQU 31 -SYS_CLEAR_EVENT EQU 32 -SYS_RETIRED_WAIT_EVENT EQU 33 -SYS_CREATE_QUEUE EQU 34 -SYS_SEND_QUEUE EQU 35 -SYS_RECV_QUEUE EQU 36 -SYS_PUBLISH_QUEUE EQU 37 -SYS_OPEN_NAMED_QUEUE EQU 38 -SYS_DUPLICATE_QUEUE EQU 39 -SYS_GET_NAMED_QUEUE_OWNER_PID EQU 40 -SYS_OPEN_FILE EQU 41 -SYS_CREATE_FILE EQU 42 -SYS_READ_FILE EQU 43 -SYS_WRITE_FILE EQU 44 -SYS_READ_FILE_ASYNC EQU 45 -SYS_WRITE_FILE_ASYNC EQU 46 -SYS_CANCEL_IO EQU 47 -SYS_SEEK_FILE EQU 48 -SYS_GET_FILE_POS EQU 49 -SYS_GET_FILE_SIZE EQU 50 -SYS_TRUNCATE_FILE EQU 51 -SYS_DELETE_FILE EQU 52 -SYS_RENAME_FILE EQU 53 -SYS_OPEN_DIR EQU 54 -SYS_READ_DIR EQU 55 -SYS_CREATE_DIR EQU 56 -SYS_REMOVE_DIR EQU 57 -SYS_SET_CWD EQU 58 -SYS_GET_CWD EQU 59 -SYS_WRITE_CONSOLE EQU 60 -SYS_GET_CONSOLE_INFO EQU 61 -SYS_SET_CONSOLE_CURSOR EQU 62 -SYS_FILL_CONSOLE_CHARACTER EQU 63 -SYS_FILL_CONSOLE_ATTRIBUTE EQU 64 -SYS_PUT_SCREEN_CHAR EQU 65 -SYS_CLEAR_SCREEN EQU 66 -SYS_GET_SCREEN_SIZE EQU 67 -SYS_QUERY_DISPLAY_INFO EQU 68 -SYS_POLL_KEYBOARD EQU 69 -SYS_IS_KEY_DOWN EQU 70 -SYS_READ_KEYBOARD_CHAR EQU 71 -SYS_READ_KEY EQU 72 -SYS_GET_KEY_MODIFIERS EQU 73 -SYS_POLL_MOUSE EQU 74 -SYS_READ_MOUSE EQU 75 -SYS_GET_MOUSE_POS EQU 76 -SYS_GET_TICK EQU 77 -SYS_PRINT_DEBUG EQU 78 -SYS_QUERY_TRAP_FRAME EQU 79 -SYS_CREATE_SOCKET EQU 80 -SYS_BIND_SOCKET EQU 81 -SYS_LISTEN_SOCKET EQU 82 -SYS_ACCEPT_SOCKET EQU 83 -SYS_CONNECT_SOCKET EQU 84 -SYS_SEND_SOCKET EQU 85 -SYS_RECV_SOCKET EQU 86 -SYS_SHUTDOWN_SOCKET EQU 87 -SYS_SEND_TO_SOCKET EQU 88 -SYS_RECV_FROM_SOCKET EQU 89 -SYS_RESOLVE_HOST EQU 90 -SYS_SET_SOCKET_OPTION EQU 91 -SYS_GET_SOCKET_OPTION EQU 92 -SYS_POLL EQU 93 -SYS_GET_MEMORY_STATUS EQU 94 -SYS_QUERY_KERNEL_STATS EQU 95 -SYS_GET_SYSTEM_TIME EQU 96 -SYS_CREATE_FILE_MAPPING EQU 97 -SYS_MAP_FILE EQU 98 -SYS_CREATE_SURFACE EQU 99 -SYS_MAP_SURFACE EQU 100 -SYS_QUERY_SURFACE EQU 101 -SYS_PRESENT_SURFACE EQU 102 -SYS_GET_THREAD_LIST EQU 103 -SYS_READ_INPUT_EVENT EQU 104 -SYS_PEEK_INPUT_EVENT EQU 105 -SYS_WAIT_MANY EQU 106 -SYS_OPEN_INPUT_QUEUE EQU 107 -SYS_FLUSH_FILE EQU 108 -SYS_MAX EQU 109 +; Process, threads, waits, and APC. +SYS_EXIT_PROCESS EQU 0 +SYS_EXIT_THREAD EQU 1 +SYS_CREATE_PROCESS EQU 2 +SYS_OPEN_PROCESS EQU 3 +SYS_GET_PROCESS_EXIT_CODE EQU 4 +SYS_KILL_PROCESS EQU 5 +SYS_GET_PROCESS_LIST EQU 6 +SYS_GET_PROCESS_PID EQU 7 +SYS_CREATE_THREAD EQU 8 +SYS_GET_THREAD_LIST EQU 9 +SYS_GET_CURRENT_TID EQU 10 +SYS_GET_CURRENT_PID EQU 11 +SYS_SET_THREAD_PRIORITY EQU 12 +SYS_YIELD EQU 13 +SYS_SLEEP EQU 14 +SYS_WAIT_SINGLE EQU 15 +SYS_WAIT_MANY EQU 16 +SYS_SET_USER_APC_DISPATCHER EQU 17 +SYS_APC_RETURN EQU 18 + +; Virtual memory, mappings, and handles. +SYS_ALLOC_VM EQU 19 +SYS_FREE_VM EQU 20 +SYS_PROTECT_VM EQU 21 +SYS_QUERY_VM EQU 22 +SYS_CLOSE_HANDLE EQU 23 +SYS_DUPLICATE_HANDLE EQU 24 +SYS_CREATE_FILE_MAPPING EQU 25 +SYS_MAP_FILE EQU 26 + +; Synchronization objects. +SYS_CREATE_MUTEX EQU 27 +SYS_TRY_ACQUIRE_MUTEX EQU 28 +SYS_RELEASE_MUTEX EQU 29 +SYS_CREATE_SEM EQU 30 +SYS_TRY_WAIT_SEM EQU 31 +SYS_SIGNAL_SEM EQU 32 +SYS_CREATE_EVENT EQU 33 +SYS_SIGNAL_EVENT EQU 34 +SYS_CLEAR_EVENT EQU 35 + +; Queues and named queues. +SYS_CREATE_QUEUE EQU 36 +SYS_SEND_QUEUE EQU 37 +SYS_RECV_QUEUE EQU 38 +SYS_PUBLISH_QUEUE EQU 39 +SYS_OPEN_NAMED_QUEUE EQU 40 +SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS EQU 41 + +; Files, directories, and current directory. +SYS_OPEN_FILE EQU 42 +SYS_READ_FILE EQU 43 +SYS_WRITE_FILE EQU 44 +SYS_READ_FILE_ASYNC EQU 45 +SYS_WRITE_FILE_ASYNC EQU 46 +SYS_CANCEL_IO EQU 47 +SYS_SEEK_FILE EQU 48 +SYS_GET_FILE_POS EQU 49 +SYS_GET_FILE_SIZE EQU 50 +SYS_GET_FILE_STAT EQU 51 +SYS_STAT_FILE EQU 52 +SYS_TRUNCATE_FILE EQU 53 +SYS_FLUSH_FILE EQU 54 +SYS_DELETE_FILE EQU 55 +SYS_RENAME_FILE EQU 56 +SYS_LOCK_FILE EQU 57 +SYS_UNLOCK_FILE EQU 58 +SYS_OPEN_DIR EQU 59 +SYS_READ_DIR EQU 60 +SYS_CREATE_DIR EQU 61 +SYS_REMOVE_DIR EQU 62 +SYS_SET_CWD EQU 63 +SYS_REAL_PATH EQU 64 + +; Console, display, and surfaces. +SYS_WRITE_CONSOLE EQU 65 +SYS_GET_CONSOLE_INFO EQU 66 +SYS_SET_CONSOLE_CURSOR EQU 67 +SYS_FILL_CONSOLE_CHARACTER EQU 68 +SYS_FILL_CONSOLE_ATTRIBUTE EQU 69 +SYS_PUT_SCREEN_CHAR EQU 70 +SYS_CLEAR_SCREEN EQU 71 +SYS_GET_SCREEN_SIZE EQU 72 +SYS_QUERY_DISPLAY_INFO EQU 73 +SYS_CREATE_SURFACE EQU 74 +SYS_MAP_SURFACE EQU 75 +SYS_QUERY_SURFACE EQU 76 +SYS_PRESENT_SURFACE EQU 77 + +; Input. +SYS_POLL_KEYBOARD EQU 78 +SYS_IS_KEY_DOWN EQU 79 +SYS_READ_KEYBOARD_CHAR EQU 80 +SYS_READ_KEY EQU 81 +SYS_GET_KEY_MODIFIERS EQU 82 +SYS_OPEN_INPUT_QUEUE EQU 83 +SYS_READ_INPUT_EVENT EQU 84 +SYS_PEEK_INPUT_EVENT EQU 85 +SYS_PEEK_MOUSE EQU 86 +SYS_READ_MOUSE EQU 87 +SYS_GET_MOUSE_POS EQU 88 + +; Time, debug, and system information. +SYS_CREATE_TIMER EQU 89 +SYS_SET_TIMER EQU 90 +SYS_CANCEL_TIMER EQU 91 +SYS_GET_TICK EQU 92 +SYS_GET_TICK_FREQUENCY EQU 93 +SYS_GET_SYSTEM_TIME EQU 94 +SYS_PRINT_DEBUG EQU 95 +SYS_QUERY_TRAP_FRAME EQU 96 +SYS_GET_MEMORY_STATUS EQU 97 +SYS_QUERY_KERNEL_STATS EQU 98 + +; Native sockets and polling. +SYS_CREATE_SOCKET EQU 99 +SYS_BIND_SOCKET EQU 100 +SYS_LISTEN_SOCKET EQU 101 +SYS_ACCEPT_SOCKET EQU 102 +SYS_CONNECT_SOCKET EQU 103 +SYS_SEND_SOCKET EQU 104 +SYS_RECV_SOCKET EQU 105 +SYS_SHUTDOWN_SOCKET EQU 106 +SYS_SEND_TO_SOCKET EQU 107 +SYS_RECV_FROM_SOCKET EQU 108 +SYS_RESOLVE_HOST EQU 109 +SYS_SET_SOCKET_OPTION EQU 110 +SYS_GET_SOCKET_OPTION EQU 111 +SYS_POLL EQU 112 + +SYS_MAX EQU 113 diff --git a/code/user/syscall/syscall_stubs.asm b/code/user/syscall/syscall_stubs.asm index 76b2489..a9e0a5e 100644 --- a/code/user/syscall/syscall_stubs.asm +++ b/code/user/syscall/syscall_stubs.asm @@ -13,8 +13,8 @@ ; args 5+ on the stack above the home area ([rsp+28h], [rsp+30h], ...). ; ; SYSCALL_STUB2/3 - 4 or fewer args: pure register passing, no stack read. -; SYSCALL_STUB5 - exactly 5 args: loads [rsp+28h] -> R10 only. -; SYSCALL_STUB6 - 5 or 6 args: loads [rsp+28h] -> R10 and [rsp+30h] -> R11. +; SYSCALL_STUB5 - exactly 5 args: loads [rsp+28h] -> R10 only. +; SYSCALL_STUB6 - 5 or 6 args: loads [rsp+28h] -> R10 and [rsp+30h] -> R11. ; ; Handle lifecycle: ; SysCloseHandle is the single close stub for every object type. @@ -24,14 +24,11 @@ INCLUDE syscall_nr.inc SYS_USER_APC_FRAME_Magic EQU 0 -SYS_USER_APC_FRAME_OriginalRip EQU 8 -SYS_USER_APC_FRAME_OriginalRsp EQU 16 -SYS_USER_APC_FRAME_OriginalRflags EQU 24 -SYS_USER_APC_FRAME_OriginalRax EQU 32 -SYS_USER_APC_FRAME_Routine EQU 40 -SYS_USER_APC_FRAME_NormalContext EQU 48 -SYS_USER_APC_FRAME_SystemArgument1 EQU 56 -SYS_USER_APC_FRAME_SystemArgument2 EQU 64 +SYS_USER_APC_FRAME_ReturnToken EQU 8 +SYS_USER_APC_FRAME_Routine EQU 16 +SYS_USER_APC_FRAME_NormalContext EQU 24 +SYS_USER_APC_FRAME_SystemArgument1 EQU 32 +SYS_USER_APC_FRAME_SystemArgument2 EQU 40 .CODE @@ -88,6 +85,7 @@ SYSCALL_STUB3 WaitSingle, SYS_WAIT_SINGLE SYSCALL_STUB2 WaitMany, SYS_WAIT_MANY SYSCALL_STUB2 OpenInputQueue, SYS_OPEN_INPUT_QUEUE SYSCALL_STUB2 GetProcessExitCode, SYS_GET_PROCESS_EXIT_CODE +SYSCALL_STUB2 OpenProcess, SYS_OPEN_PROCESS SYSCALL_STUB2 KillProcess, SYS_KILL_PROCESS SYSCALL_STUB2 GetProcessList, SYS_GET_PROCESS_LIST SYSCALL_STUB2 GetProcessPid, SYS_GET_PROCESS_PID @@ -139,6 +137,7 @@ SYSCALL_STUB2 FreeVm, SYS_FREE_VM SYSCALL_STUB2 ProtectVm, SYS_PROTECT_VM SYSCALL_STUB2 QueryVm, SYS_QUERY_VM SYSCALL_STUB2 CloseHandle, SYS_CLOSE_HANDLE +SYSCALL_STUB2 DuplicateHandle, SYS_DUPLICATE_HANDLE ; --------------------------------------------------------------------------- ; Synchronization objects @@ -163,14 +162,12 @@ SYSCALL_STUB2 SendQueue, SYS_SEND_QUEUE SYSCALL_STUB2 RecvQueue, SYS_RECV_QUEUE SYSCALL_STUB2 PublishQueue, SYS_PUBLISH_QUEUE SYSCALL_STUB2 OpenNamedQueue, SYS_OPEN_NAMED_QUEUE -SYSCALL_STUB2 DuplicateQueue, SYS_DUPLICATE_QUEUE -SYSCALL_STUB2 GetNamedQueueOwnerPid, SYS_GET_NAMED_QUEUE_OWNER_PID +SYSCALL_STUB2 OpenNamedQueueOwnerProcess, SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS ; --------------------------------------------------------------------------- ; Files, directories, and current directory ; --------------------------------------------------------------------------- SYSCALL_STUB2 OpenFile, SYS_OPEN_FILE -SYSCALL_STUB2 CreateFile, SYS_CREATE_FILE SYSCALL_STUB2 ReadFile, SYS_READ_FILE SYSCALL_STUB2 WriteFile, SYS_WRITE_FILE SYSCALL_STUB2 ReadFileAsync, SYS_READ_FILE_ASYNC @@ -179,17 +176,21 @@ SYSCALL_STUB2 CancelIo, SYS_CANCEL_IO SYSCALL_STUB2 SeekFile, SYS_SEEK_FILE SYSCALL_STUB2 GetFilePos, SYS_GET_FILE_POS SYSCALL_STUB2 GetFileSize, SYS_GET_FILE_SIZE +SYSCALL_STUB2 GetFileStat, SYS_GET_FILE_STAT +SYSCALL_STUB2 StatFile, SYS_STAT_FILE SYSCALL_STUB2 TruncateFile, SYS_TRUNCATE_FILE SYSCALL_STUB2 FlushFile, SYS_FLUSH_FILE SYSCALL_STUB2 DeleteFile, SYS_DELETE_FILE SYSCALL_STUB2 RenameFile, SYS_RENAME_FILE +SYSCALL_STUB2 LockFile, SYS_LOCK_FILE +SYSCALL_STUB2 UnlockFile, SYS_UNLOCK_FILE SYSCALL_STUB2 OpenDir, SYS_OPEN_DIR SYSCALL_STUB2 ReadDir, SYS_READ_DIR SYSCALL_STUB2 CreateDir, SYS_CREATE_DIR SYSCALL_STUB2 RemoveDir, SYS_REMOVE_DIR SYSCALL_STUB2 SetCwd, SYS_SET_CWD -SYSCALL_STUB2 GetCwd, SYS_GET_CWD +SYSCALL_STUB2 RealPath, SYS_REAL_PATH ; --------------------------------------------------------------------------- ; Console and graphics @@ -219,14 +220,18 @@ SYSCALL_STUB2 GetKeyModifiers, SYS_GET_KEY_MODIFIERS SYSCALL_STUB2 ReadInputEvent, SYS_READ_INPUT_EVENT SYSCALL_STUB2 PeekInputEvent, SYS_PEEK_INPUT_EVENT -SYSCALL_STUB2 PollMouse, SYS_POLL_MOUSE +SYSCALL_STUB2 PeekMouse, SYS_PEEK_MOUSE SYSCALL_STUB2 ReadMouse, SYS_READ_MOUSE SYSCALL_STUB2 GetMousePos, SYS_GET_MOUSE_POS ; --------------------------------------------------------------------------- ; Time and debug ; --------------------------------------------------------------------------- +SYSCALL_STUB2 CreateTimer, SYS_CREATE_TIMER +SYSCALL_STUB2 SetTimer, SYS_SET_TIMER +SYSCALL_STUB2 CancelTimer, SYS_CANCEL_TIMER SYSCALL_STUB2 GetTick, SYS_GET_TICK +SYSCALL_STUB2 GetTickFrequency, SYS_GET_TICK_FREQUENCY SYSCALL_STUB2 PrintDebug, SYS_PRINT_DEBUG SYSCALL_STUB2 QueryTrapFrame, SYS_QUERY_TRAP_FRAME diff --git a/code/user/system/init/init.c b/code/user/system/init/init.c index d77b468..d5b71e4 100644 --- a/code/user/system/init/init.c +++ b/code/user/system/init/init.c @@ -5,8 +5,6 @@ #include #include -#define STATUS_SUCCESS 0u - // Implements the itoa operation. void itoa(int num, char* str) { int i = 0; @@ -80,7 +78,7 @@ void _start(void) if (s != STATUS_SUCCESS) debug_print("[init] WARNING: SysWait returned error\n"); - int exitCode = 0; + int32_t exitCode = 0; if (SysGetProcessExitCode(h, &exitCode) != STATUS_SUCCESS) { debug_print("[init] WARNING: failed to read exit code\n"); exitCode = 1; diff --git a/code/user/tests/apc_return_smoke/apc_return_smoke.c b/code/user/tests/apc_return_smoke/apc_return_smoke.c new file mode 100644 index 0000000..3362a88 --- /dev/null +++ b/code/user/tests/apc_return_smoke/apc_return_smoke.c @@ -0,0 +1,172 @@ +// User-mode smoke test for APC return hardening. + +#include +#include +#include +#include +#include +#include + +#define EVENT_AUTO_RESET 0u + +// Prints a C string to the native console. +static void print(const char* text) { + SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) }; + SysWriteConsole(&request); +} + +// Prints an unsigned integer. +static void print_u64(uint64_t value) { + char buffer[32]; + int count = 0; + if (value == 0) { + print("0"); + return; + } + while (value && count < (int)sizeof(buffer)) { + buffer[count++] = (char)('0' + (value % 10)); + value /= 10; + } + while (count) { + char c[2] = { buffer[--count], 0 }; + print(c); + } +} + +// Opens a stable payload for async read completion. +static uint32_t open_test_file(uint32_t* handleOut) { + static const char* paths[] = { + "apc_return_smoke.exe", + "/apps/apc_return_smoke.exe", + "1:/apps/apc_return_smoke.exe", + "0:/apps/apc_return_smoke.exe", + "async_apc.exe", + "/apps/async_apc.exe", + "1:/apps/async_apc.exe", + "0:/apps/async_apc.exe", + "/system/init.exe", + "0:/system/init.exe", + "1:/system/init.exe", + }; + + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { + uint32_t handle = 0; + SYS_OPEN_FILE_REQUEST request = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &handle); + uint32_t status = SysOpenFile(&request); + if (status == STATUS_SUCCESS) { + *handleOut = handle; + return STATUS_SUCCESS; + } + } + + return STATUS_NOT_FOUND; +} + +typedef struct APC_RETURN_STATE { + volatile uint32_t Delivered; + volatile uint32_t Status; + volatile uint64_t Information; +} APC_RETURN_STATE; + +// Records that the user APC callback ran and observed the I/O status block. +static void file_apc(void* context, SYS_IO_STATUS_BLOCK* iosb, uint64_t information) { + APC_RETURN_STATE* state = (APC_RETURN_STATE*)context; + state->Status = iosb ? iosb->Status : STATUS_FAILURE; + state->Information = information; + state->Delivered++; +} + +// Exits after printing a status mismatch. +static void fail_status(const char* what, uint32_t got, uint32_t expected) { + print("apc_return_smoke: "); + print(what); + print(" got="); + print_u64(got); + print(" expected="); + print_u64(expected); + print("\n"); + SysExitProcess(1); +} + +// Runs the APC return hardening smoke test. +int main(void) { + print("apc_return_smoke: start\n"); + + uint32_t status = SysApcReturn((const SYS_USER_APC_FRAME*)0); + if (status != STATUS_INVALID_PARAMETER) + fail_status("inactive null return", status, STATUS_INVALID_PARAMETER); + + SYS_USER_APC_FRAME fakeFrame; + memset(&fakeFrame, 0, sizeof(fakeFrame)); + fakeFrame.Magic = SYS_USER_APC_FRAME_MAGIC; + status = SysApcReturn(&fakeFrame); + if (status != STATUS_INVALID_PARAMETER) + fail_status("inactive forged return", status, STATUS_INVALID_PARAMETER); + + status = SysSetUserApcDispatcher((void*)SysUserApcDispatcher); + if (status != STATUS_SUCCESS) + fail_status("set dispatcher", status, STATUS_SUCCESS); + + uint32_t file = 0; + status = open_test_file(&file); + if (status != STATUS_SUCCESS) + fail_status("open", status, STATUS_SUCCESS); + + uint32_t event = 0; + status = SysCreateEvent(EVENT_AUTO_RESET, 0, &event); + if (status != STATUS_SUCCESS) + fail_status("create event", status, STATUS_SUCCESS); + + uint8_t* buffer = (uint8_t*)malloc(512); + if (!buffer) { + print("apc_return_smoke: allocation failed\n"); + SysExitProcess(1); + } + memset(buffer, 0, 512); + + SYS_IO_STATUS_BLOCK iosb; + iosb.Status = 0; + iosb.Information = 0; + + APC_RETURN_STATE state; + memset(&state, 0, sizeof(state)); + + SYS_ASYNC_FILE_REQUEST request; + memset(&request, 0, sizeof(request)); + request.FileHandle = file; + request.EventHandle = event; + request.Buffer = buffer; + request.Size = 512; + request.Iosb = &iosb; + request.ApcRoutine = file_apc; + request.ApcContext = &state; + + status = SysReadFileAsync(&request); + if (status != STATUS_PENDING) + fail_status("submit", status, STATUS_PENDING); + + while (!state.Delivered) { + status = SysWait(event, SYS_WAIT_FLAG_ALERTABLE); + if (status != STATUS_USER_APC && status != STATUS_SUCCESS && status != STATUS_PENDING) + fail_status("alertable wait", status, STATUS_USER_APC); + } + + uint32_t ok = state.Delivered == 1 && state.Status == STATUS_SUCCESS && state.Information == 512; + + SysCloseHandle(event); + SysCloseHandle(file); + free(buffer); + + if (!ok) { + print("apc_return_smoke: FAIL\n"); + SysExitProcess(1); + } + + print("apc_return_smoke: PASS\n"); + SysExitProcess(0); + return 0; +} diff --git a/code/user/tests/async_apc/async_apc.c b/code/user/tests/async_apc/async_apc.c index 8304931..96339ca 100644 --- a/code/user/tests/async_apc/async_apc.c +++ b/code/user/tests/async_apc/async_apc.c @@ -33,17 +33,21 @@ static uint32_t open_test_file(uint32_t* handleOut, const char** pathOut) { for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h }; + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); uint32_t s = SysOpenFile(&req); - if (s == SYS_STATUS_SUCCESS) { + if (s == STATUS_SUCCESS) { *handleOut = h; if (pathOut) *pathOut = paths[i]; - return SYS_STATUS_SUCCESS; + return STATUS_SUCCESS; } } - return SYS_STATUS_NOT_FOUND; + return STATUS_NOT_FOUND; } // Prints hex8. @@ -91,7 +95,7 @@ int main(void) { print("async_apc: start\n"); uint32_t s = SysSetUserApcDispatcher((void*)SysUserApcDispatcher); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { print("async_apc: dispatcher registration failed status="); print_u64(s); print("\n"); @@ -101,7 +105,7 @@ int main(void) { uint32_t file = 0; const char* openedPath = 0; s = open_test_file(&file, &openedPath); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { print("async_apc: open failed status="); print_u64(s); print("\n"); @@ -114,7 +118,7 @@ int main(void) { uint32_t event = 0; s = SysCreateEvent(EVENT_AUTO_RESET, 0, &event); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { print("async_apc: event create failed status="); print_u64(s); print("\n"); @@ -147,7 +151,7 @@ int main(void) { req.ApcContext = &state; s = SysReadFileAsync(&req); - if (s != SYS_STATUS_PENDING) { + if (s != STATUS_PENDING) { print("async_apc: submit failed status="); print_u64(s); print("\n"); @@ -159,7 +163,7 @@ int main(void) { print("async_apc: alertable wait returned "); print_u64(s); print("\n"); - if (s != SYS_STATUS_USER_APC && s != SYS_STATUS_SUCCESS && s != SYS_STATUS_PENDING) + if (s != STATUS_USER_APC && s != STATUS_SUCCESS && s != STATUS_PENDING) break; } @@ -175,7 +179,7 @@ int main(void) { print_hex8(buffer[2]); print(" "); print_hex8(buffer[3]); print("\n"); - uint32_t ok = (state.Delivered == 1 && state.Status == SYS_STATUS_SUCCESS && + uint32_t ok = (state.Delivered == 1 && state.Status == STATUS_SUCCESS && state.Information == 512 && buffer[0] == 'M' && buffer[1] == 'Z'); SysCloseHandle(event); diff --git a/code/user/tests/async_cancel/async_cancel.c b/code/user/tests/async_cancel/async_cancel.c index 6ea12ed..891207b 100644 --- a/code/user/tests/async_cancel/async_cancel.c +++ b/code/user/tests/async_cancel/async_cancel.c @@ -32,17 +32,21 @@ static uint32_t open_test_file(uint32_t* handleOut, const char** pathOut) { for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h }; + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); uint32_t s = SysOpenFile(&req); - if (s == SYS_STATUS_SUCCESS) { + if (s == STATUS_SUCCESS) { *handleOut = h; if (pathOut) *pathOut = paths[i]; - return SYS_STATUS_SUCCESS; + return STATUS_SUCCESS; } } - return SYS_STATUS_NOT_FOUND; + return STATUS_NOT_FOUND; } // Prints u64. @@ -73,7 +77,7 @@ int main(void) { const char* openedPath = 0; uint32_t s = open_test_file(&file, &openedPath); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { print("async_cancel: open failed status="); print_u64(s); print("\n"); SysExitProcess(1); } @@ -83,7 +87,7 @@ int main(void) { print("\n"); s = SysCreateEvent(EVENT_AUTO_RESET, 0, &event); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { print("async_cancel: event create failed status="); print_u64(s); print("\n"); SysExitProcess(1); } @@ -110,7 +114,7 @@ int main(void) { req.RequestHandleOut = &request; s = SysReadFileAsync(&req); - if (s != SYS_STATUS_PENDING || request == 0) { + if (s != STATUS_PENDING || request == 0) { print("async_cancel: submit failed status="); print_u64(s); print(" request="); print_u64(request); print("\n"); SysExitProcess(1); @@ -119,10 +123,10 @@ int main(void) { uint32_t cs = SysCancelIo(request); print("async_cancel: cancel returned "); print_u64(cs); print("\n"); - while (iosb.Status == SYS_STATUS_PENDING) { + while (iosb.Status == STATUS_PENDING) { s = SysWait(event, SYS_WAIT_FLAG_ALERTABLE); print("async_cancel: wait returned "); print_u64(s); print("\n"); - if (s != SYS_STATUS_SUCCESS && s != SYS_STATUS_KERNEL_APC && s != SYS_STATUS_USER_APC && s != SYS_STATUS_ALERTED) + if (s != STATUS_SUCCESS && s != STATUS_KERNEL_APC && s != STATUS_USER_APC && s != STATUS_ALERTED) break; } @@ -131,9 +135,9 @@ int main(void) { // If the worker had already started, cancellation is best-effort and the // request may complete successfully. If it was still queued, it must report - // SYS_STATUS_CANCELLED. Both outcomes are valid; SYS_STATUS_PENDING is not. - uint32_t ok = (iosb.Status == SYS_STATUS_CANCELLED) || - (iosb.Status == SYS_STATUS_SUCCESS && iosb.Information == 512); + // STATUS_CANCELLED. Both outcomes are valid; STATUS_PENDING is not. + uint32_t ok = (iosb.Status == STATUS_CANCELLED) || + (iosb.Status == STATUS_SUCCESS && iosb.Information == 512); SysCloseHandle(request); SysCloseHandle(event); diff --git a/code/user/tests/cwd_peb_smoke/cwd_peb_smoke.c b/code/user/tests/cwd_peb_smoke/cwd_peb_smoke.c new file mode 100644 index 0000000..213837f --- /dev/null +++ b/code/user/tests/cwd_peb_smoke/cwd_peb_smoke.c @@ -0,0 +1,108 @@ +// Current-directory PEB cache smoke test. +// Verifies that SysSetCwd updates the user-visible PEB cache without a getter syscall. + +#include +#include +#include +#include + +// Prints one NUL-terminated string to the kernel console. +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +// Prints one unsigned integer to the kernel console. +static void print_u64(uint64_t v) { + char buf[32]; + int i = 0; + if (v == 0) { + print("0"); + return; + } + while (v && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i--) { + char c = buf[i]; + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current test process. +static void exit_now(int code) { + for (;;) SysExitProcess(code); +} + +// Fails the test with one status value. +static void fail_status(const char* what, uint32_t status) { + print("cwd_peb_smoke: "); + print(what); + print(" status="); + print_u64(status); + print("\n"); + exit_now(1); +} + +// Fails the test with a simple message. +static void fail(const char* what) { + print("cwd_peb_smoke: "); + print(what); + print("\n"); + exit_now(1); +} + +// Sets the current directory to one native path. +static void set_cwd_or_fail(const char* path, const char* what) { + SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; + uint32_t status = SysSetCwd(&req); + if (status != STATUS_SUCCESS) fail_status(what, status); +} + +// Verifies the current-directory PEB view against one native path. +static void expect_cwd(const char* expected) { + SYS_STRING_VIEW cwd = SysGetCurrentDirectoryView(); + size_t expectedLen = strlen(expected); + if (cwd.Length != expectedLen) fail("cwd length mismatch"); + if (cwd.Length && !cwd.Data) fail("cwd data missing"); + if (cwd.Length && memcmp(cwd.Data, expected, cwd.Length) != 0) fail("cwd value mismatch"); +} + +// Program entry point. +int main(void) { + static const char rootPath[] = "0:/"; + static const char tempDir[] = "0:/cwd_peb_smoke_dir"; + + print("cwd_peb_smoke: start\n"); + + SYS_PEB* peb = SysGetPeb(); + if (!peb) fail("peb missing"); + if (peb->Magic != SYS_PEB_MAGIC) fail("peb magic mismatch"); + if (peb->Version != SYS_ENV_VERSION) fail("peb version mismatch"); + + set_cwd_or_fail(rootPath, "set root cwd"); + expect_cwd(rootPath); + + SYS_PATH_REQUEST dirReq = { SysSvFromCstr(tempDir) }; + uint32_t status = SysRemoveDir(&dirReq); + if (status != STATUS_SUCCESS && status != STATUS_NOT_FOUND) + fail_status("remove stale temp dir", status); + + status = SysCreateDir(&dirReq); + if (status != STATUS_SUCCESS) fail_status("create temp dir", status); + + set_cwd_or_fail(tempDir, "set temp cwd"); + expect_cwd(tempDir); + + set_cwd_or_fail(rootPath, "restore root cwd"); + expect_cwd(rootPath); + + status = SysRemoveDir(&dirReq); + if (status != STATUS_SUCCESS) fail_status("remove temp dir", status); + + print("cwd_peb_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/dirty_writeback/dirty_writeback.c b/code/user/tests/dirty_writeback/dirty_writeback.c index 91e4ef4..f438782 100644 --- a/code/user/tests/dirty_writeback/dirty_writeback.c +++ b/code/user/tests/dirty_writeback/dirty_writeback.c @@ -78,23 +78,31 @@ static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) { for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; delete_path(paths[i]); - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h }; - uint32_t s = SysCreateFile(&req); - if (s == SYS_STATUS_SUCCESS) { + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_CREATE_NEW, + SYS_FILE_FLAG_NONE, + &h); + uint32_t s = SysOpenFile(&req); + if (s == STATUS_SUCCESS) { *fileOut = h; if (pathOut) *pathOut = paths[i]; - return SYS_STATUS_SUCCESS; + return STATUS_SUCCESS; } } - return SYS_STATUS_NOT_FOUND; + return STATUS_NOT_FOUND; } static uint32_t open_path(const char* path, uint32_t* fileOut) { uint32_t h = 0; - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), &h }; + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); uint32_t s = SysOpenFile(&req); - if (s == SYS_STATUS_SUCCESS) + if (s == STATUS_SUCCESS) *fileOut = h; return s; } @@ -110,7 +118,7 @@ static void checked_write(uint32_t file, const void* buffer, size_t size, const size_t done = 0; SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(file, buffer, size, &done); uint32_t s = SysWriteFile(&req); - if (s != SYS_STATUS_SUCCESS) fail_status(what, s); + if (s != STATUS_SUCCESS) fail_status(what, s); if (done != size) fail("short write"); } @@ -119,13 +127,13 @@ static void checked_read(uint32_t file, void* buffer, size_t size, size_t* gotOu memset(buffer, 0xCC, size); SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(file, buffer, size, &got); uint32_t s = SysReadFile(&req); - if (s != SYS_STATUS_SUCCESS) fail_status(what, s); + if (s != STATUS_SUCCESS) fail_status(what, s); if (gotOut) *gotOut = got; } static void checked_seek(uint32_t file, uint64_t offset, const char* what) { uint32_t s = SysSeekFile(file, offset); - if (s != SYS_STATUS_SUCCESS) fail_status(what, s); + if (s != STATUS_SUCCESS) fail_status(what, s); } static void expect_writeback_progress(const SYS_FILE_CACHE_STATS* before, @@ -150,7 +158,7 @@ int main(void) { uint32_t file = 0; const char* path = 0; uint32_t s = create_temp(&file, &path); - if (s != SYS_STATUS_SUCCESS) fail_status("create temp", s); + if (s != STATUS_SUCCESS) fail_status("create temp", s); print("dirty_writeback: using "); print(path ? path : ""); @@ -162,11 +170,11 @@ int main(void) { SYS_FILE_CACHE_STATS beforeClose; SYS_FILE_CACHE_STATS afterClose; - if (query_cache(&before) != SYS_STATUS_SUCCESS) fail("initial stats query failed"); + if (query_cache(&before) != STATUS_SUCCESS) fail("initial stats query failed"); checked_write(file, gPatternA, sizeof(gPatternA), "write pattern A"); - if (query_cache(&afterWrite) != SYS_STATUS_SUCCESS) fail("post-write stats query failed"); + if (query_cache(&afterWrite) != STATUS_SUCCESS) fail("post-write stats query failed"); if (afterWrite.DirtyMarks <= before.DirtyMarks) fail("write did not mark dirty cache pages"); if (afterWrite.DirtyPages <= before.DirtyPages) fail("write did not leave dirty pages resident"); @@ -177,9 +185,9 @@ int main(void) { if (memcmp(gReadback, gPatternA, sizeof(gPatternA)) != 0) fail("cached read before flush mismatch"); s = SysFlushFile(file); - if (s != SYS_STATUS_SUCCESS) fail_status("explicit flush", s); + if (s != STATUS_SUCCESS) fail_status("explicit flush", s); - if (query_cache(&afterFlush) != SYS_STATUS_SUCCESS) fail("post-flush stats query failed"); + if (query_cache(&afterFlush) != STATUS_SUCCESS) fail("post-flush stats query failed"); if (afterFlush.FlushFileCalls <= afterWrite.FlushFileCalls) fail("flush call counter did not increase"); expect_writeback_progress(&afterWrite, &afterFlush, "explicit flush"); @@ -188,19 +196,19 @@ int main(void) { checked_seek(file, REWRITE_SIZE, "seek close-tail"); checked_write(file, gPatternA + REWRITE_SIZE, CLOSE_SIZE - REWRITE_SIZE, "write close tail"); - if (query_cache(&beforeClose) != SYS_STATUS_SUCCESS) fail("pre-close stats query failed"); + if (query_cache(&beforeClose) != STATUS_SUCCESS) fail("pre-close stats query failed"); if (beforeClose.DirtyMarks <= afterFlush.DirtyMarks) fail("rewrite did not mark dirty pages"); s = SysCloseHandle(file); file = 0; - if (s != SYS_STATUS_SUCCESS) fail_status("close", s); + if (s != STATUS_SUCCESS) fail_status("close", s); - if (query_cache(&afterClose) != SYS_STATUS_SUCCESS) fail("post-close stats query failed"); + if (query_cache(&afterClose) != STATUS_SUCCESS) fail("post-close stats query failed"); if (afterClose.FlushFileCalls <= beforeClose.FlushFileCalls) fail("close did not flush file"); expect_writeback_progress(&beforeClose, &afterClose, "close flush"); s = open_path(path, &file); - if (s != SYS_STATUS_SUCCESS) fail_status("reopen", s); + if (s != STATUS_SUCCESS) fail_status("reopen", s); checked_read(file, gReadback, CLOSE_SIZE, &got, "read after reopen"); if (got != CLOSE_SIZE) fail("read after reopen size mismatch"); @@ -210,7 +218,7 @@ int main(void) { s = SysCloseHandle(file); file = 0; - if (s != SYS_STATUS_SUCCESS) fail_status("close reopened", s); + if (s != STATUS_SUCCESS) fail_status("close reopened", s); delete_path(path); print("dirty_writeback: dirty-marks+="); diff --git a/code/user/tests/file_lock_smoke/file_lock_smoke.c b/code/user/tests/file_lock_smoke/file_lock_smoke.c new file mode 100644 index 0000000..2388fea --- /dev/null +++ b/code/user/tests/file_lock_smoke/file_lock_smoke.c @@ -0,0 +1,377 @@ +// Advisory file-lock smoke test. +// Verifies process-owned byte-range conflicts, timeout behavior, and exit cleanup. + +#include +#include +#include +#include + +#define LOCK_OFFSET 0u +#define LOCK_LENGTH 128u +#define NONOVERLAP_OFFSET 512u +#define CHILD_PREFIX_MAX 32u +#define CHILD_PATH_MAX 256u + +// Prints one NUL-terminated string to the kernel console. +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +// Prints one unsigned integer to the kernel console. +static void print_u64(uint64_t v) { + char buf[32]; + int i = 0; + if (v == 0) { + print("0"); + return; + } + while (v && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i--) { + char c = buf[i]; + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current test process. +static void exit_now(int32_t code) { + for (;;) SysExitProcess(code); +} + +// Fails the test with one status value. +static void fail_status(const char* what, uint32_t status) { + print("file_lock_smoke: "); + print(what); + print(" status="); + print_u64(status); + print("\n"); + exit_now(1); +} + +// Fails the test with one plain message. +static void fail(const char* what) { + print("file_lock_smoke: "); + print(what); + print("\n"); + exit_now(1); +} + +// Deletes one path if it already exists. +static void delete_path(const char* path) { + SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; + (void)SysDeleteFile(&req); +} + +// Opens one path with lock rights. +static uint32_t open_existing_lockable(const char* path, uint32_t* fileOut) { + uint32_t h = 0; + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path), + SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); + uint32_t s = SysOpenFile(&req); + if (s == STATUS_SUCCESS) + *fileOut = h; + return s; +} + +// Creates a temporary file on the first writable path that works. +static uint32_t create_temp_file(uint32_t* fileOut, const char** pathOut) { + static const char* paths[] = { + "file_lock_smoke.tmp", + "/file_lock_smoke.tmp", + "1:/file_lock_smoke.tmp", + "0:/file_lock_smoke.tmp", + }; + + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { + uint32_t h = 0; + delete_path(paths[i]); + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK, + SYS_FILE_CREATE_NEW, + SYS_FILE_FLAG_NONE, + &h); + uint32_t s = SysOpenFile(&req); + if (s == STATUS_SUCCESS) { + *fileOut = h; + *pathOut = paths[i]; + return STATUS_SUCCESS; + } + } + return STATUS_NOT_FOUND; +} + +// Locks a range through the public request ABI. +static uint32_t lock_range(uint32_t file, uint32_t mode, uint64_t offset, uint64_t length, uint64_t timeoutMs) { + SYS_LOCK_FILE_REQUEST req = { + sizeof(req), + file, + mode, + SYS_FILE_LOCK_FLAG_NONE, + offset, + length, + timeoutMs, + }; + return SysLockFile(&req); +} + +// Unlocks a range through the public request ABI. +static uint32_t unlock_range(uint32_t file, uint64_t offset, uint64_t length) { + SYS_UNLOCK_FILE_REQUEST req = { + sizeof(req), + file, + SYS_FILE_LOCK_FLAG_NONE, + offset, + length, + }; + return SysUnlockFile(&req); +} + +// Splits the child command line into mode and path. +static int parse_child_command(SYS_STRING_VIEW view, char* mode, size_t modeCap, char* path, size_t pathCap) { + if (!view.Data || view.Length == 0) + return 0; + size_t sep = 0; + while (sep < view.Length && view.Data[sep] != ':') + sep++; + if (sep == view.Length || sep + 1 >= view.Length) + return 0; + if (sep + 1 > modeCap || view.Length - sep > pathCap) + return 0; + memcpy(mode, view.Data, sep); + mode[sep] = '\0'; + memcpy(path, view.Data + sep + 1, view.Length - sep - 1); + path[view.Length - sep - 1] = '\0'; + return 1; +} + +// Returns whether two NUL-terminated strings are equal. +static int str_eq(const char* a, const char* b) { + return strcmp(a, b) == 0; +} + +// Returns whether one mode name belongs to the child command protocol. +static int is_child_mode(const char* mode) { + return str_eq(mode, "excl-blocked") || + str_eq(mode, "excl-timeout") || + str_eq(mode, "excl-wait") || + str_eq(mode, "shared-ok") || + str_eq(mode, "excl-nonoverlap") || + str_eq(mode, "hold-exit"); +} + +// Child process entry for cross-process lock checks. +static void run_child(SYS_STRING_VIEW commandLine) { + char mode[CHILD_PREFIX_MAX]; + char path[CHILD_PATH_MAX]; + if (!parse_child_command(commandLine, mode, sizeof(mode), path, sizeof(path))) + exit_now(2); + + uint32_t file = 0; + uint32_t s = open_existing_lockable(path, &file); + if (s != STATUS_SUCCESS) + exit_now(3); + + if (str_eq(mode, "excl-blocked")) { + s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, 0); + SysCloseHandle(file); + exit_now(s == STATUS_WOULD_BLOCK ? 0 : 4); + } + + if (str_eq(mode, "excl-timeout")) { + s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, 20); + SysCloseHandle(file); + exit_now(s == STATUS_TIMEOUT ? 0 : 5); + } + + if (str_eq(mode, "excl-wait")) { + s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, SYS_WAIT_TIMEOUT_INFINITE); + if (s == STATUS_SUCCESS) + (void)unlock_range(file, LOCK_OFFSET, LOCK_LENGTH); + SysCloseHandle(file); + exit_now(s == STATUS_SUCCESS ? 0 : 6); + } + + if (str_eq(mode, "shared-ok")) { + s = lock_range(file, SYS_FILE_LOCK_SHARED, LOCK_OFFSET, LOCK_LENGTH, 0); + if (s == STATUS_SUCCESS) + (void)unlock_range(file, LOCK_OFFSET, LOCK_LENGTH); + SysCloseHandle(file); + exit_now(s == STATUS_SUCCESS ? 0 : 7); + } + + if (str_eq(mode, "excl-nonoverlap")) { + s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, NONOVERLAP_OFFSET, LOCK_LENGTH, 0); + if (s == STATUS_SUCCESS) + (void)unlock_range(file, NONOVERLAP_OFFSET, LOCK_LENGTH); + SysCloseHandle(file); + exit_now(s == STATUS_SUCCESS ? 0 : 8); + } + + if (str_eq(mode, "hold-exit")) { + s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, 0); + SysCloseHandle(file); + exit_now(s == STATUS_SUCCESS ? 0 : 9); + } + + SysCloseHandle(file); + exit_now(10); +} + +// Builds a child command line of the form mode:path. +static void build_child_command(char* out, size_t outCap, const char* mode, const char* path) { + size_t modeLen = strlen(mode); + size_t pathLen = strlen(path); + if (modeLen + 1 + pathLen + 1 > outCap) + fail("child command too long"); + memcpy(out, mode, modeLen); + out[modeLen] = ':'; + memcpy(out + modeLen + 1, path, pathLen + 1); +} + +// Runs this image as a child and requires a zero exit code. +static void run_child_expect_success(const char* mode, const char* path) { + SYS_PEB* peb = SysGetPeb(); + if (!peb || !peb->ImagePath.Data || peb->ImagePath.Length == 0) + fail("missing image path"); + + char command[CHILD_PREFIX_MAX + CHILD_PATH_MAX]; + build_child_command(command, sizeof(command), mode, path); + + uint32_t child = 0; + SYS_CREATE_PROCESS_REQUEST req = { + peb->ImagePath, + SysSvFromCstr(command), + &child, + }; + uint32_t s = SysCreateProcess(&req); + if (s != STATUS_SUCCESS) fail_status("create child", s); + + s = SysWait(child, SYS_WAIT_FLAG_NONE); + if (s != STATUS_SUCCESS) fail_status("wait child", s); + + int32_t exitCode = 0; + s = SysGetProcessExitCode(child, &exitCode); + if (s != STATUS_SUCCESS) fail_status("query child", s); + SysCloseHandle(child); + if (exitCode != 0) { + print("file_lock_smoke: child "); + print(mode); + print(" exited "); + print_u64((uint32_t)exitCode); + print("\n"); + exit_now(1); + } +} + +// Verifies validation paths that should not depend on filesystem state. +static void test_validation(uint32_t file) { + SYS_LOCK_FILE_REQUEST lockReq = { + 0, + file, + SYS_FILE_LOCK_EXCLUSIVE, + SYS_FILE_LOCK_FLAG_NONE, + LOCK_OFFSET, + LOCK_LENGTH, + 0, + }; + uint32_t s = SysLockFile(&lockReq); + if (s != STATUS_INVALID_PARAMETER) fail_status("bad lock size", s); + + lockReq.Size = sizeof(lockReq); + lockReq.Length = 0; + s = SysLockFile(&lockReq); + if (s != STATUS_INVALID_PARAMETER) fail_status("zero lock length", s); + + SYS_UNLOCK_FILE_REQUEST unlockReq = { + sizeof(unlockReq), + file, + SYS_FILE_LOCK_FLAG_NONE, + LOCK_OFFSET, + LOCK_LENGTH, + }; + s = SysUnlockFile(&unlockReq); + if (s != STATUS_NOT_FOUND) fail_status("unlock missing range", s); + + lockReq.Length = LOCK_LENGTH; + lockReq.FileHandle = 0xeeeeeeeeu; + s = SysLockFile(&lockReq); + if (s != STATUS_INVALID_HANDLE) fail_status("bad lock handle", s); +} + +// Program entry point. +int main(void) { + SYS_PEB* peb = SysGetPeb(); + if (peb && peb->CommandLine.Data && peb->CommandLine.Length != 0) { + char mode[CHILD_PREFIX_MAX]; + char path[CHILD_PATH_MAX]; + if (parse_child_command(peb->CommandLine, mode, sizeof(mode), path, sizeof(path)) && + is_child_mode(mode)) { + run_child(peb->CommandLine); + } + } + + print("file_lock_smoke: start\n"); + + uint32_t file = 0; + const char* path = 0; + uint32_t s = create_temp_file(&file, &path); + if (s != STATUS_SUCCESS) fail_status("create temp", s); + + test_validation(file); + + s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, 0); + if (s != STATUS_SUCCESS) fail_status("exclusive parent lock", s); + run_child_expect_success("excl-blocked", path); + run_child_expect_success("excl-timeout", path); + run_child_expect_success("excl-nonoverlap", path); + s = unlock_range(file, LOCK_OFFSET, LOCK_LENGTH); + if (s != STATUS_SUCCESS) fail_status("exclusive parent unlock", s); + + s = lock_range(file, SYS_FILE_LOCK_SHARED, LOCK_OFFSET, LOCK_LENGTH, 0); + if (s != STATUS_SUCCESS) fail_status("shared parent lock", s); + run_child_expect_success("shared-ok", path); + run_child_expect_success("excl-blocked", path); + s = unlock_range(file, LOCK_OFFSET, LOCK_LENGTH); + if (s != STATUS_SUCCESS) fail_status("shared parent unlock", s); + + s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, 0); + if (s != STATUS_SUCCESS) fail_status("blocking setup lock", s); + SYS_PEB* selfPeb = SysGetPeb(); + if (!selfPeb || !selfPeb->ImagePath.Data || selfPeb->ImagePath.Length == 0) fail("missing self image"); + char command[CHILD_PREFIX_MAX + CHILD_PATH_MAX]; + build_child_command(command, sizeof(command), "excl-wait", path); + uint32_t child = 0; + SYS_CREATE_PROCESS_REQUEST createReq = { selfPeb->ImagePath, SysSvFromCstr(command), &child }; + s = SysCreateProcess(&createReq); + if (s != STATUS_SUCCESS) fail_status("create blocking child", s); + SysSleep(30); + s = unlock_range(file, LOCK_OFFSET, LOCK_LENGTH); + if (s != STATUS_SUCCESS) fail_status("wake unlock", s); + s = SysWait(child, SYS_WAIT_FLAG_NONE); + if (s != STATUS_SUCCESS) fail_status("wait blocking child", s); + int32_t exitCode = 0; + s = SysGetProcessExitCode(child, &exitCode); + if (s != STATUS_SUCCESS) fail_status("query blocking child", s); + SysCloseHandle(child); + if (exitCode != 0) fail("blocking child failed"); + + run_child_expect_success("hold-exit", path); + s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, 0); + if (s != STATUS_SUCCESS) fail_status("exit released lock", s); + s = unlock_range(file, LOCK_OFFSET, LOCK_LENGTH); + if (s != STATUS_SUCCESS) fail_status("exit release cleanup unlock", s); + + SysCloseHandle(file); + delete_path(path); + + print("file_lock_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/file_stat_smoke/file_stat_smoke.c b/code/user/tests/file_stat_smoke/file_stat_smoke.c new file mode 100644 index 0000000..fbec137 --- /dev/null +++ b/code/user/tests/file_stat_smoke/file_stat_smoke.c @@ -0,0 +1,258 @@ +// Native file metadata smoke test. +// Verifies path stat, handle stat, directory metadata, missing paths, and invalid handles. + +#include +#include +#include +#include + +#define TEST_FILE_SIZE 1537u + +static uint8_t gPattern[TEST_FILE_SIZE]; + +// Prints one NUL-terminated string to the kernel console. +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +// Prints one unsigned integer to the kernel console. +static void print_u64(uint64_t v) { + char buf[32]; + int i = 0; + if (v == 0) { + print("0"); + return; + } + while (v && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i--) { + char c = buf[i]; + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current test process. +static void exit_now(int code) { + for (;;) SysExitProcess(code); +} + +// Fails the test with one plain message. +static void fail(const char* msg) { + print("file_stat_smoke: "); + print(msg); + print("\n"); + exit_now(1); +} + +// Fails the test with one status value. +static void fail_status(const char* what, uint32_t status) { + print("file_stat_smoke: "); + print(what); + print(" failed status="); + print_u64(status); + print("\n"); + exit_now(1); +} + +// Removes a file path if it already exists. +static void delete_file_path(const char* path) { + SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; + (void)SysDeleteFile(&req); +} + +// Removes a directory path if it already exists. +static void remove_dir_path(const char* path) { + SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; + (void)SysRemoveDir(&req); +} + +// Opens a new temporary file on the first writable path that works. +static uint32_t create_temp_file(uint32_t* fileOut, const char** pathOut) { + static const char* paths[] = { + "file_stat_smoke.tmp", + "/file_stat_smoke.tmp", + "1:/file_stat_smoke.tmp", + "0:/file_stat_smoke.tmp", + }; + + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { + uint32_t h = 0; + delete_file_path(paths[i]); + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_CREATE_NEW, + SYS_FILE_FLAG_NONE, + &h); + uint32_t s = SysOpenFile(&req); + if (s == STATUS_SUCCESS) { + *fileOut = h; + *pathOut = paths[i]; + return STATUS_SUCCESS; + } + } + + return STATUS_NOT_FOUND; +} + +// Creates a temporary directory on the first writable path that works. +static uint32_t create_temp_dir(const char** pathOut) { + static const char* paths[] = { + "file_stat_smoke.dir", + "/file_stat_smoke.dir", + "1:/file_stat_smoke.dir", + "0:/file_stat_smoke.dir", + }; + + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { + remove_dir_path(paths[i]); + SYS_PATH_REQUEST req = { SysSvFromCstr(paths[i]) }; + uint32_t s = SysCreateDir(&req); + if (s == STATUS_SUCCESS) { + *pathOut = paths[i]; + return STATUS_SUCCESS; + } + } + + return STATUS_NOT_FOUND; +} + +// Fills the write pattern used by the test file. +static void fill_pattern(void) { + for (size_t i = 0; i < sizeof(gPattern); ++i) + gPattern[i] = (uint8_t)(0x31u + (uint8_t)(i * 17u + (i >> 3))); +} + +// Writes the test file contents and flushes them to the filesystem. +static void write_test_file(uint32_t file) { + size_t done = 0; + SYS_FILE_IO_REQUEST writeReq = SysMakeFileIoRequest(file, gPattern, sizeof(gPattern), &done); + uint32_t s = SysWriteFile(&writeReq); + if (s != STATUS_SUCCESS) fail_status("write test file", s); + if (done != sizeof(gPattern)) fail("short write"); + + s = SysFlushFile(file); + if (s != STATUS_SUCCESS) fail_status("flush test file", s); +} + +// Checks one regular-file metadata result. +static void check_regular_stat(const SYS_FILE_STAT* stat, uint64_t expectedSize, const char* what) { + if (stat->Size != sizeof(*stat)) fail("stat abi size mismatch"); + if (stat->Type != SYS_FILE_TYPE_REGULAR) { + print("file_stat_smoke: "); + print(what); + print(" type="); + print_u64(stat->Type); + print("\n"); + exit_now(1); + } + if ((stat->Attributes & SYS_FILE_ATTR_DIRECTORY) != 0) fail("regular file has directory attribute"); + if (stat->LinkCount == 0) fail("regular file has zero links"); + if (stat->FileSize != expectedSize) { + print("file_stat_smoke: "); + print(what); + print(" size="); + print_u64(stat->FileSize); + print(" expected="); + print_u64(expectedSize); + print("\n"); + exit_now(1); + } +} + +// Checks one directory metadata result. +static void check_directory_stat(const SYS_FILE_STAT* stat) { + if (stat->Size != sizeof(*stat)) fail("directory stat abi size mismatch"); + if (stat->Type != SYS_FILE_TYPE_DIRECTORY) fail("directory stat type mismatch"); + if ((stat->Attributes & SYS_FILE_ATTR_DIRECTORY) == 0) fail("directory attribute missing"); + if (stat->LinkCount == 0) fail("directory has zero links"); +} + +// Exercises handle and path stat for one regular file. +static void test_file_stat(uint32_t file, const char* path) { + SYS_FILE_STAT byHandle; + memset(&byHandle, 0xA5, sizeof(byHandle)); + uint32_t s = SysGetFileStat(file, &byHandle); + if (s != STATUS_SUCCESS) fail_status("get file stat", s); + check_regular_stat(&byHandle, sizeof(gPattern), "handle stat"); + + uint64_t size = 0; + s = SysGetFileSize(file, &size); + if (s != STATUS_SUCCESS) fail_status("get file size", s); + if (size != byHandle.FileSize) fail("file size and file stat disagree"); + + SYS_FILE_STAT byPath; + memset(&byPath, 0x5A, sizeof(byPath)); + SYS_STAT_FILE_REQUEST statReq = { SysSvFromCstr(path), &byPath }; + s = SysStatFile(&statReq); + if (s != STATUS_SUCCESS) fail_status("path stat", s); + check_regular_stat(&byPath, sizeof(gPattern), "path stat"); + + if (byHandle.VolumeId != 0 && byPath.VolumeId != 0 && byHandle.VolumeId != byPath.VolumeId) + fail("handle and path volume ids differ"); + if (byHandle.FileId != 0 && byPath.FileId != 0 && byHandle.FileId != byPath.FileId) + fail("handle and path file ids differ"); +} + +// Exercises path stat for one directory. +static void test_directory_stat(const char* path) { + SYS_FILE_STAT stat; + memset(&stat, 0xCC, sizeof(stat)); + SYS_STAT_FILE_REQUEST statReq = { SysSvFromCstr(path), &stat }; + uint32_t s = SysStatFile(&statReq); + if (s != STATUS_SUCCESS) fail_status("directory stat", s); + check_directory_stat(&stat); +} + +// Checks failure status paths that stat callers depend on. +static void test_failure_paths(void) { + SYS_FILE_STAT stat; + SYS_STAT_FILE_REQUEST missingReq = { SysSvFromCstr("file_stat_smoke_missing.nope"), &stat }; + uint32_t s = SysStatFile(&missingReq); + if (s != STATUS_NOT_FOUND) fail_status("missing path status", s); + + s = SysGetFileStat(0xFFFFFFFFu, &stat); + if (s != STATUS_INVALID_HANDLE) fail_status("invalid handle status", s); + + SYS_STAT_FILE_REQUEST badReq = { SysSvFromCstr("file_stat_smoke_missing.nope"), 0 }; + s = SysStatFile(&badReq); + if (s != STATUS_INVALID_PARAMETER) fail_status("null stat output status", s); +} + +// Runs the native file metadata smoke test. +int main(void) { + print("file_stat_smoke: start\n"); + + fill_pattern(); + + uint32_t file = 0; + const char* filePath = 0; + uint32_t s = create_temp_file(&file, &filePath); + if (s != STATUS_SUCCESS) fail_status("create temp file", s); + + write_test_file(file); + test_file_stat(file, filePath); + SysCloseHandle(file); + delete_file_path(filePath); + + const char* dirPath = 0; + s = create_temp_dir(&dirPath); + if (s != STATUS_SUCCESS) fail_status("create temp dir", s); + test_directory_stat(dirPath); + remove_dir_path(dirPath); + + SYS_PATH_REQUEST dirReq = { SysSvFromCstr(dirPath) }; + s = SysCreateDir(&dirReq); + if (s != STATUS_SUCCESS) fail_status("recreate temp dir", s); + test_directory_stat(dirPath); + remove_dir_path(dirPath); + + test_failure_paths(); + + print("file_stat_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/handle_dup_smoke/handle_dup_smoke.c b/code/user/tests/handle_dup_smoke/handle_dup_smoke.c new file mode 100644 index 0000000..aa98ae6 --- /dev/null +++ b/code/user/tests/handle_dup_smoke/handle_dup_smoke.c @@ -0,0 +1,205 @@ +// Native handle duplication smoke test. +// Verifies explicit rights reduction, same-rights duplication, and invalid duplicate requests. + +#include +#include +#include + +typedef struct TEST_QUEUE_MESSAGE { + uint32_t Type; + uint32_t Flags; + uint64_t Param[6]; +} TEST_QUEUE_MESSAGE; + +typedef char TEST_QUEUE_MESSAGE_SIZE_CHECK[ + (sizeof(TEST_QUEUE_MESSAGE) == SYS_QUEUE_MESSAGE_SIZE) ? 1 : -1 +]; + +// Prints one NUL-terminated string to the kernel console. +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +// Prints one unsigned integer to the kernel console. +static void print_u64(uint64_t v) { + char buf[32]; + int i = 0; + if (v == 0) { + print("0"); + return; + } + while (v && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i--) { + char c = buf[i]; + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current test process. +static void exit_now(int code) { + for (;;) SysExitProcess(code); +} + +// Fails the test with one plain message. +static void fail(const char* msg) { + print("handle_dup_smoke: "); + print(msg); + print("\n"); + exit_now(1); +} + +// Fails the test with one status value. +static void fail_status(const char* what, uint32_t status) { + print("handle_dup_smoke: "); + print(what); + print(" status="); + print_u64(status); + print("\n"); + exit_now(1); +} + +// Requires one exact status value. +static void expect_status(const char* what, uint32_t got, uint32_t expected) { + if (got == expected) return; + print("handle_dup_smoke: "); + print(what); + print(" got="); + print_u64(got); + print(" expected="); + print_u64(expected); + print("\n"); + exit_now(1); +} + +// Duplicates one handle into the current process. +static uint32_t duplicate_handle(uint32_t source, uint32_t process, uint32_t desiredRights, uint32_t flags, uint32_t* out) { + SYS_DUPLICATE_HANDLE_REQUEST req = { + source, + process, + desiredRights, + flags, + out, + }; + return SysDuplicateHandle(&req); +} + +// Sends one queue message. +static uint32_t send_message(uint32_t queue, uint32_t type, uint64_t value) { + TEST_QUEUE_MESSAGE msg = {0}; + msg.Type = type; + msg.Param[0] = value; + SYS_QUEUE_SEND_REQUEST req = { + sizeof(req), + queue, + SYS_QUEUE_FLAG_NONE, + 0, + &msg, + }; + return SysSendQueue(&req); +} + +// Receives one queue message. +static uint32_t recv_message(uint32_t queue, TEST_QUEUE_MESSAGE* msg) { + SYS_QUEUE_RECV_REQUEST req = { + sizeof(req), + queue, + SYS_QUEUE_FLAG_NONE, + 0, + msg, + }; + return SysRecvQueue(&req); +} + +// Verifies one received queue message. +static void check_message(const TEST_QUEUE_MESSAGE* msg, uint32_t type, uint64_t value) { + if (msg->Type != type || msg->Param[0] != value) fail("message payload mismatch"); +} + +// Runs the native handle duplication smoke test. +int main(void) { + print("handle_dup_smoke: start\n"); + + uint32_t currentPid = SysGetCurrentPid(); + uint32_t process = 0; + uint32_t s = SysOpenProcess(currentPid, &process); + if (s != STATUS_SUCCESS) fail_status("open current process", s); + + uint32_t queue = 0; + s = SysCreateQueue(&queue); + if (s != STATUS_SUCCESS) fail_status("create queue", s); + + uint32_t readOnly = 0; + s = duplicate_handle(queue, process, SYS_HANDLE_RIGHT_READ, SYS_DUPLICATE_HANDLE_FLAG_NONE, &readOnly); + if (s != STATUS_SUCCESS) fail_status("duplicate read-only", s); + + uint32_t writeOnly = 0; + s = duplicate_handle(queue, process, SYS_HANDLE_RIGHT_WRITE, SYS_DUPLICATE_HANDLE_FLAG_NONE, &writeOnly); + if (s != STATUS_SUCCESS) fail_status("duplicate write-only", s); + + uint32_t sameRights = 0; + s = duplicate_handle(queue, process, 0, SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &sameRights); + if (s != STATUS_SUCCESS) fail_status("duplicate same rights", s); + + s = send_message(readOnly, 10, 100); + if (s == STATUS_SUCCESS) fail("read-only duplicate allowed send"); + + TEST_QUEUE_MESSAGE msg; + s = recv_message(writeOnly, &msg); + if (s == STATUS_SUCCESS) fail("write-only duplicate allowed receive"); + + s = send_message(queue, 11, 101); + if (s != STATUS_SUCCESS) fail_status("send original", s); + s = recv_message(readOnly, &msg); + if (s != STATUS_SUCCESS) fail_status("receive read-only", s); + check_message(&msg, 11, 101); + + s = send_message(writeOnly, 12, 102); + if (s != STATUS_SUCCESS) fail_status("send write-only", s); + s = recv_message(queue, &msg); + if (s != STATUS_SUCCESS) fail_status("receive original", s); + check_message(&msg, 12, 102); + + s = send_message(sameRights, 13, 103); + if (s != STATUS_SUCCESS) fail_status("send same-rights", s); + s = recv_message(sameRights, &msg); + if (s != STATUS_SUCCESS) fail_status("receive same-rights", s); + check_message(&msg, 13, 103); + + uint32_t ignored = 0; + s = duplicate_handle(readOnly, process, SYS_HANDLE_RIGHT_READ, SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored); + expect_status("duplicate non-duplicable handle", s, STATUS_ACCESS_DENIED); + + s = duplicate_handle(queue, process, SYS_HANDLE_RIGHT_READ | 0x80000000u, SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored); + expect_status("duplicate unknown rights", s, STATUS_INVALID_PARAMETER); + + s = duplicate_handle(queue, process, SYS_HANDLE_RIGHT_READ, SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &ignored); + expect_status("duplicate same-rights with desired rights", s, STATUS_INVALID_PARAMETER); + + s = duplicate_handle(queue, process, 0, SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored); + expect_status("duplicate zero rights", s, STATUS_INVALID_PARAMETER); + + SYS_DUPLICATE_HANDLE_REQUEST badOut = { + queue, + process, + SYS_HANDLE_RIGHT_READ, + SYS_DUPLICATE_HANDLE_FLAG_NONE, + 0, + }; + s = SysDuplicateHandle(&badOut); + expect_status("duplicate null output", s, STATUS_INVALID_PARAMETER); + + SysCloseHandle(sameRights); + SysCloseHandle(writeOnly); + SysCloseHandle(readOnly); + SysCloseHandle(queue); + SysCloseHandle(process); + + print("handle_dup_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/input_api_smoke/input_api_smoke.c b/code/user/tests/input_api_smoke/input_api_smoke.c new file mode 100644 index 0000000..c4afb7b --- /dev/null +++ b/code/user/tests/input_api_smoke/input_api_smoke.c @@ -0,0 +1,122 @@ +// Native input API smoke test. +// Verifies status-returning keyboard and mouse polling contracts without requiring live input. + +#include +#include +#include + +// Prints one NUL-terminated string to the kernel console. +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +// Prints one unsigned integer to the kernel console. +static void print_u64(uint64_t v) { + char buf[32]; + int i = 0; + if (v == 0) { + print("0"); + return; + } + while (v && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i--) { + char c = buf[i]; + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current test process. +static void exit_now(int code) { + for (;;) SysExitProcess(code); +} + +// Fails the test with one status value. +static void fail_status(const char* what, uint32_t status) { + print("input_api_smoke: "); + print(what); + print(" status="); + print_u64(status); + print("\n"); + exit_now(1); +} + +// Requires one exact status value. +static void expect_status(const char* what, uint32_t got, uint32_t expected) { + if (got == expected) return; + print("input_api_smoke: "); + print(what); + print(" got="); + print_u64(got); + print(" expected="); + print_u64(expected); + print("\n"); + exit_now(1); +} + +// Program entry point. +int main(void) { + print("input_api_smoke: start\n"); + + expect_status("poll keyboard null", SysPollKeyboard((INPUT_KEY*)0), STATUS_INVALID_PARAMETER); + expect_status("is key down null", SysIsKeyDown(INPUT_KEY_A, (bool*)0), STATUS_INVALID_PARAMETER); + expect_status("read key null", SysReadKey((INPUT_KEY*)0), STATUS_INVALID_PARAMETER); + expect_status("read char null", SysReadKeyboardChar((char*)0), STATUS_INVALID_PARAMETER); + + bool down = true; + uint32_t status = SysIsKeyDown(INPUT_KEY_A, &down); + if (status != STATUS_SUCCESS) + fail_status("is key down", status); + + INPUT_KEY key = INPUT_KEY_A; + status = SysPollKeyboard(&key); + if (status != STATUS_SUCCESS && status != STATUS_WOULD_BLOCK) + fail_status("poll keyboard", status); + if (status == STATUS_WOULD_BLOCK && key != INPUT_KEY_NONE) + fail_status("poll keyboard would-block key", key); + + INPUT_KEY_RECORD input; + SYS_INPUT_EVENT_REQUEST inputReq; + inputReq.Size = sizeof(inputReq); + inputReq.QueueHandle = 0; + inputReq.Flags = SYS_INPUT_EVENT_FLAG_NONE; + inputReq.TimeoutMilliseconds = 0; + inputReq.RecordOut = &input; + + expect_status("peek input null", SysPeekInputEvent((const SYS_INPUT_EVENT_REQUEST*)0), STATUS_INVALID_PARAMETER); + status = SysPeekInputEvent(&inputReq); + if (status != STATUS_SUCCESS && status != STATUS_WOULD_BLOCK) + fail_status("peek input", status); + + INPUT_MOUSE_EVENT mouse; + expect_status("peek mouse null", SysPeekMouse((INPUT_MOUSE_EVENT*)0), STATUS_INVALID_PARAMETER); + expect_status("read mouse null", SysReadMouse((INPUT_MOUSE_EVENT*)0), STATUS_INVALID_PARAMETER); + + status = SysPeekMouse(&mouse); + if (status != STATUS_SUCCESS && status != STATUS_WOULD_BLOCK) + fail_status("peek mouse", status); + + uint32_t readStatus = SysReadMouse(&mouse); + if (status == STATUS_SUCCESS) { + if (readStatus != STATUS_SUCCESS) + fail_status("read mouse after peek", readStatus); + } else if (readStatus != STATUS_WOULD_BLOCK) { + fail_status("read mouse would-block", readStatus); + } + + int x = 0; + int y = 0; + expect_status("get mouse pos null x", SysGetMousePos((int*)0, &y), STATUS_INVALID_PARAMETER); + expect_status("get mouse pos null y", SysGetMousePos(&x, (int*)0), STATUS_INVALID_PARAMETER); + status = SysGetMousePos(&x, &y); + if (status != STATUS_SUCCESS) + fail_status("get mouse pos", status); + + print("input_api_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/input_model_smoke/input_model_smoke.c b/code/user/tests/input_model_smoke/input_model_smoke.c new file mode 100644 index 0000000..53b175d --- /dev/null +++ b/code/user/tests/input_model_smoke/input_model_smoke.c @@ -0,0 +1,114 @@ +// Input model smoke test. +// Verifies that input queue handles and request-based reads target one keyboard queue. + +#include +#include + +// Prints one NUL-terminated string to the kernel console. +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +// Prints one unsigned integer to the kernel console. +static void print_u64(uint64_t v) { + char buf[32]; + int i = 0; + if (v == 0) { + print("0"); + return; + } + while (v && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i--) { + char c = buf[i]; + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current test process. +static void exit_now(int code) { + for (;;) SysExitProcess(code); +} + +// Fails the test with one status value. +static void fail_status(const char* what, uint32_t status) { + print("input_model_smoke: "); + print(what); + print(" status="); + print_u64(status); + print("\n"); + exit_now(1); +} + +// Requires one exact status value. +static void expect_status(const char* what, uint32_t got, uint32_t expected) { + if (got == expected) return; + print("input_model_smoke: "); + print(what); + print(" got="); + print_u64(got); + print(" expected="); + print_u64(expected); + print("\n"); + exit_now(1); +} + +// Program entry point. +int main(void) { + print("input_model_smoke: start\n"); + + uint32_t inputQueue = 0; + expect_status("open input queue null", SysOpenInputQueue(SYS_INPUT_QUEUE_FLAG_NONE, (uint32_t*)0), STATUS_INVALID_PARAMETER); + expect_status("open input queue bad flags", SysOpenInputQueue(1, &inputQueue), STATUS_INVALID_PARAMETER); + + uint32_t status = SysOpenInputQueue(SYS_INPUT_QUEUE_FLAG_NONE, &inputQueue); + if (status != STATUS_SUCCESS) + fail_status("open input queue", status); + + INPUT_KEY_RECORD record; + SYS_INPUT_EVENT_REQUEST req; + req.Size = sizeof(req); + req.QueueHandle = inputQueue; + req.Flags = SYS_INPUT_EVENT_FLAG_NONE; + req.TimeoutMilliseconds = 0; + req.RecordOut = &record; + + SYS_INPUT_EVENT_REQUEST badReq = req; + badReq.Size = 0; + expect_status("read input bad size", SysReadInputEvent(&badReq), STATUS_INVALID_PARAMETER); + + badReq = req; + badReq.RecordOut = (INPUT_KEY_RECORD*)0; + expect_status("peek input null out", SysPeekInputEvent(&badReq), STATUS_INVALID_PARAMETER); + + badReq = req; + badReq.QueueHandle = 0xdeadbeefu; + expect_status("read input bad handle", SysReadInputEvent(&badReq), STATUS_INVALID_HANDLE); + + status = SysPeekInputEvent(&req); + if (status != STATUS_SUCCESS && status != STATUS_WOULD_BLOCK) + fail_status("peek input immediate", status); + + uint32_t waitStatus = SysWaitSingle(inputQueue, SYS_WAIT_FLAG_NONE, 0); + if (status == STATUS_SUCCESS) { + if (waitStatus != STATUS_SUCCESS) + fail_status("wait input after readable peek", waitStatus); + } else if (waitStatus != STATUS_TIMEOUT) { + fail_status("wait empty input immediate", waitStatus); + } + + req.TimeoutMilliseconds = 1; + status = SysReadInputEvent(&req); + if (status != STATUS_SUCCESS && status != STATUS_TIMEOUT) + fail_status("read input finite timeout", status); + + SysCloseHandle(inputQueue); + + print("input_model_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/mmap_smoke/mmap_smoke.c b/code/user/tests/mmap_smoke/mmap_smoke.c index 62670bb..817c7df 100644 --- a/code/user/tests/mmap_smoke/mmap_smoke.c +++ b/code/user/tests/mmap_smoke/mmap_smoke.c @@ -70,17 +70,21 @@ static uint32_t open_first_existing(uint32_t* fileOut, const char** pathOut) { for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) { uint32_t file = 0; - SYS_PATH_HANDLE_REQUEST openReq = { SysSvFromCstr(candidates[i]), &file }; + SYS_OPEN_FILE_REQUEST openReq = SysMakeOpenFileRequest(SysSvFromCstr(candidates[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &file); uint32_t s = SysOpenFile(&openReq); - if (s == SYS_STATUS_SUCCESS) { + if (s == STATUS_SUCCESS) { *fileOut = file; if (pathOut) *pathOut = candidates[i]; - return SYS_STATUS_SUCCESS; + return STATUS_SUCCESS; } } - return SYS_STATUS_NOT_FOUND; + return STATUS_NOT_FOUND; } int main(void) { @@ -89,7 +93,7 @@ int main(void) { uint32_t file = 0; const char* openedPath = 0; uint32_t s = open_first_existing(&file, &openedPath); - if (s != SYS_STATUS_SUCCESS) + if (s != STATUS_SUCCESS) fail_status("open test file", s); print("mmap_smoke: using "); @@ -101,7 +105,7 @@ int main(void) { SYS_FILE_IO_REQUEST ioReq = SysMakeFileIoRequest(file, gExpected, sizeof(gExpected), &got); s = SysReadFile(&ioReq); - if (s != SYS_STATUS_SUCCESS) + if (s != STATUS_SUCCESS) fail_status("read baseline", s); if (got < 64) fail_msg("baseline read was unexpectedly short"); @@ -114,7 +118,7 @@ int main(void) { SYS_MAP_FLAG_NONE, sizeof(gExpected), &mapping); - if (s != SYS_STATUS_SUCCESS) + if (s != STATUS_SUCCESS) fail_status("create file mapping", s); print("mmap_smoke: mapping created\n"); @@ -137,7 +141,7 @@ int main(void) { SysCloseHandle(mapping); mapping = 0; - if (s != SYS_STATUS_SUCCESS) + if (s != STATUS_SUCCESS) fail_status("map file view", s); if (!base) fail_msg("map returned null base"); @@ -187,7 +191,7 @@ int main(void) { print("mmap_smoke: EOF fill OK\n"); s = SysFreeVm(base); - if (s != SYS_STATUS_SUCCESS) + if (s != STATUS_SUCCESS) fail_status("unmap", s); print("mmap_smoke: PASS\n"); diff --git a/code/user/tests/pcache_invalidate/pcache_invalidate.c b/code/user/tests/pcache_invalidate/pcache_invalidate.c index b5e1467..b9a6617 100644 --- a/code/user/tests/pcache_invalidate/pcache_invalidate.c +++ b/code/user/tests/pcache_invalidate/pcache_invalidate.c @@ -73,16 +73,20 @@ static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) { for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; delete_path(paths[i]); - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h }; - uint32_t s = SysCreateFile(&req); - if (s == SYS_STATUS_SUCCESS) { + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_CREATE_NEW, + SYS_FILE_FLAG_NONE, + &h); + uint32_t s = SysOpenFile(&req); + if (s == STATUS_SUCCESS) { *fileOut = h; if (pathOut) *pathOut = paths[i]; - return SYS_STATUS_SUCCESS; + return STATUS_SUCCESS; } } - return SYS_STATUS_NOT_FOUND; + return STATUS_NOT_FOUND; } static void fill_patterns(void) { @@ -96,7 +100,7 @@ static void checked_write(uint32_t file, const void* buffer, size_t size, const size_t done = 0; SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(file, buffer, size, &done); uint32_t s = SysWriteFile(&req); - if (s != SYS_STATUS_SUCCESS) fail_status(what, s); + if (s != STATUS_SUCCESS) fail_status(what, s); if (done != size) fail("short write"); } @@ -105,7 +109,7 @@ static void checked_read(uint32_t file, void* buffer, size_t size, size_t* gotOu memset(buffer, 0xCC, size); SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(file, buffer, size, &got); uint32_t s = SysReadFile(&req); - if (s != SYS_STATUS_SUCCESS) fail_status(what, s); + if (s != STATUS_SUCCESS) fail_status(what, s); if (gotOut) *gotOut = got; } @@ -116,7 +120,7 @@ int main(void) { uint32_t file = 0; const char* path = 0; uint32_t s = create_temp(&file, &path); - if (s != SYS_STATUS_SUCCESS) fail_status("create temp", s); + if (s != STATUS_SUCCESS) fail_status("create temp", s); print("pcache_invalidate: using "); print(path ? path : ""); @@ -125,7 +129,7 @@ int main(void) { checked_write(file, gOriginal, sizeof(gOriginal), "write original"); s = SysSeekFile(file, 0); - if (s != SYS_STATUS_SUCCESS) fail_status("seek fill", s); + if (s != STATUS_SUCCESS) fail_status("seek fill", s); SYS_FILE_CACHE_STATS before; SYS_FILE_CACHE_STATS afterFill; @@ -133,44 +137,44 @@ int main(void) { SYS_FILE_CACHE_STATS afterWrite; SYS_FILE_CACHE_STATS afterRewriteRead; SYS_FILE_CACHE_STATS afterTruncate; - if (query_cache(&before) != SYS_STATUS_SUCCESS) fail("initial stats query failed"); + if (query_cache(&before) != STATUS_SUCCESS) fail("initial stats query failed"); size_t got = 0; checked_read(file, gReadback, sizeof(gReadback), &got, "first read"); if (got != sizeof(gOriginal)) fail("first read size mismatch"); if (memcmp(gReadback, gOriginal, sizeof(gOriginal)) != 0) fail("first read data mismatch"); - if (query_cache(&afterFill) != SYS_STATUS_SUCCESS) fail("fill stats query failed"); + if (query_cache(&afterFill) != STATUS_SUCCESS) fail("fill stats query failed"); if (afterFill.Loads <= before.Loads) fail("initial read did not populate cache"); s = SysSeekFile(file, 0); - if (s != SYS_STATUS_SUCCESS) fail_status("seek second", s); + if (s != STATUS_SUCCESS) fail_status("seek second", s); checked_read(file, gReadback, sizeof(gReadback), &got, "second read"); if (got != sizeof(gOriginal)) fail("second read size mismatch"); if (memcmp(gReadback, gOriginal, sizeof(gOriginal)) != 0) fail("second read data mismatch"); - if (query_cache(&afterSecond) != SYS_STATUS_SUCCESS) fail("second stats query failed"); + if (query_cache(&afterSecond) != STATUS_SUCCESS) fail("second stats query failed"); // Do not require this second read to hit: the cache is intentionally // bounded and pcache_pressure.exe may have left it at the soft limit, so // the newly-populated temp pages are eligible for immediate pressure - // eviction. This test is about write/truncate freshness; mmap/read reuse + // eviction. This test is about write/truncate freshness; mmap/read reuse // is covered by pcache_mmap_read_reuse.exe. s = SysSeekFile(file, 0); - if (s != SYS_STATUS_SUCCESS) fail_status("seek rewrite", s); + if (s != STATUS_SUCCESS) fail_status("seek rewrite", s); checked_write(file, gReplacement, sizeof(gReplacement), "rewrite first page"); - if (query_cache(&afterWrite) != SYS_STATUS_SUCCESS) fail("post-write stats query failed"); + if (query_cache(&afterWrite) != STATUS_SUCCESS) fail("post-write stats query failed"); if (afterWrite.Invalidations <= afterSecond.Invalidations) fail("write did not invalidate cached page"); uint32_t mapping = 0; s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, sizeof(gReplacement), &mapping); - if (s != SYS_STATUS_SUCCESS) fail_status("create mapping after rewrite", s); + if (s != STATUS_SUCCESS) fail_status("create mapping after rewrite", s); void* mappedBase = 0; s = SysMapFile(mapping, 0, sizeof(gReplacement), SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &mappedBase); SysCloseHandle(mapping); - if (s != SYS_STATUS_SUCCESS) fail_status("map after rewrite", s); + if (s != STATUS_SUCCESS) fail_status("map after rewrite", s); if (!mappedBase) fail("map after rewrite returned null"); volatile uint8_t* mappedView = (volatile uint8_t*)mappedBase; @@ -181,27 +185,30 @@ int main(void) { fail("stale mmap data after write invalidation"); } s = SysFreeVm(mappedBase); - if (s != SYS_STATUS_SUCCESS) fail_status("unmap after rewrite", s); + if (s != STATUS_SUCCESS) fail_status("unmap after rewrite", s); s = SysSeekFile(file, 0); - if (s != SYS_STATUS_SUCCESS) fail_status("seek verify rewrite", s); + if (s != STATUS_SUCCESS) fail_status("seek verify rewrite", s); checked_read(file, gReadback, sizeof(gReadback), &got, "read after rewrite"); if (got != sizeof(gOriginal)) fail("rewrite read size mismatch"); if (memcmp(gReadback, gReplacement, sizeof(gReplacement)) != 0) fail("stale data after write invalidation"); if (memcmp(gReadback + sizeof(gReplacement), gOriginal + sizeof(gReplacement), sizeof(gOriginal) - sizeof(gReplacement)) != 0) fail("unwritten page changed after rewrite"); - if (query_cache(&afterRewriteRead) != SYS_STATUS_SUCCESS) fail("rewrite-read stats query failed"); + if (query_cache(&afterRewriteRead) != STATUS_SUCCESS) fail("rewrite-read stats query failed"); s = SysTruncateFile(file, TRUNC_SIZE); - if (s != SYS_STATUS_SUCCESS) fail_status("truncate", s); - if (SysGetFileSize(file) != TRUNC_SIZE) fail("truncate size mismatch"); + if (s != STATUS_SUCCESS) fail_status("truncate", s); + uint64_t truncatedSize = 0; + s = SysGetFileSize(file, &truncatedSize); + if (s != STATUS_SUCCESS) fail_status("get truncated size", s); + if (truncatedSize != TRUNC_SIZE) fail("truncate size mismatch"); - if (query_cache(&afterTruncate) != SYS_STATUS_SUCCESS) fail("truncate stats query failed"); + if (query_cache(&afterTruncate) != STATUS_SUCCESS) fail("truncate stats query failed"); if (afterTruncate.Invalidations <= afterRewriteRead.Invalidations) fail("truncate did not invalidate cached tail"); s = SysSeekFile(file, 0); - if (s != SYS_STATUS_SUCCESS) fail_status("seek truncated", s); + if (s != STATUS_SUCCESS) fail_status("seek truncated", s); checked_read(file, gReadback, sizeof(gReadback), &got, "read truncated"); if (got != TRUNC_SIZE) fail("truncated read length mismatch"); if (memcmp(gReadback, gReplacement, TRUNC_SIZE) != 0) fail("truncated data mismatch"); diff --git a/code/user/tests/pcache_mmap_read_reuse/pcache_mmap_read_reuse.c b/code/user/tests/pcache_mmap_read_reuse/pcache_mmap_read_reuse.c index 872933e..8d054e8 100644 --- a/code/user/tests/pcache_mmap_read_reuse/pcache_mmap_read_reuse.c +++ b/code/user/tests/pcache_mmap_read_reuse/pcache_mmap_read_reuse.c @@ -72,15 +72,19 @@ static uint32_t open_test_file(uint32_t* out, const char** pathOut) { for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h }; + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); uint32_t s = SysOpenFile(&req); - if (s == SYS_STATUS_SUCCESS) { + if (s == STATUS_SUCCESS) { *out = h; if (pathOut) *pathOut = paths[i]; - return SYS_STATUS_SUCCESS; + return STATUS_SUCCESS; } } - return SYS_STATUS_NOT_FOUND; + return STATUS_NOT_FOUND; } int main(void) { @@ -90,14 +94,16 @@ int main(void) { SYS_FILE_CACHE_STATS afterFault; SYS_FILE_CACHE_STATS afterRead; uint32_t s = query_cache(&before); - if (s != SYS_STATUS_SUCCESS) fail_status("query initial stats", s); + if (s != STATUS_SUCCESS) fail_status("query initial stats", s); uint32_t file = 0; const char* path = 0; s = open_test_file(&file, &path); - if (s != SYS_STATUS_SUCCESS) fail_status("open test file", s); + if (s != STATUS_SUCCESS) fail_status("open test file", s); - uint64_t fileSize = SysGetFileSize(file); + uint64_t fileSize = 0; + s = SysGetFileSize(file, &fileSize); + if (s != STATUS_SUCCESS) fail_status("get file size", s); if (fileSize < 4096) fail("test file too small"); size_t mapSize = (fileSize < TEST_SIZE) ? (size_t)fileSize : TEST_SIZE; @@ -109,12 +115,12 @@ int main(void) { uint32_t mapping = 0; s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, mapSize, &mapping); - if (s != SYS_STATUS_SUCCESS) fail_status("create mapping", s); + if (s != STATUS_SUCCESS) fail_status("create mapping", s); void* base = 0; s = SysMapFile(mapping, 0, mapSize, SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base); SysCloseHandle(mapping); - if (s != SYS_STATUS_SUCCESS) fail_status("map file", s); + if (s != STATUS_SUCCESS) fail_status("map file", s); if (!base) fail("map returned null"); volatile uint8_t* view = (volatile uint8_t*)base; @@ -123,14 +129,14 @@ int main(void) { (void)view[mapSize - 1]; s = query_cache(&afterFault); - if (s != SYS_STATUS_SUCCESS) fail_status("query after mmap faults", s); + if (s != STATUS_SUCCESS) fail_status("query after mmap faults", s); if (afterFault.Loads <= before.Loads) { SysFreeVm(base); fail("mmap faults did not populate file cache"); } s = SysSeekFile(file, 0); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { SysFreeVm(base); fail_status("seek", s); } @@ -139,7 +145,7 @@ int main(void) { memset(gReadback, 0xCC, sizeof(gReadback)); SYS_FILE_IO_REQUEST readReq = SysMakeFileIoRequest(file, gReadback, mapSize, &got); s = SysReadFile(&readReq); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { SysFreeVm(base); fail_status("read", s); } @@ -153,7 +159,7 @@ int main(void) { } s = query_cache(&afterRead); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { SysFreeVm(base); fail_status("query after read", s); } diff --git a/code/user/tests/pcache_multithread/pcache_multithread.c b/code/user/tests/pcache_multithread/pcache_multithread.c index 53ba344..0668a38 100644 --- a/code/user/tests/pcache_multithread/pcache_multithread.c +++ b/code/user/tests/pcache_multithread/pcache_multithread.c @@ -81,16 +81,20 @@ static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) { for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; delete_path(paths[i]); - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h }; - uint32_t s = SysCreateFile(&req); - if (s == SYS_STATUS_SUCCESS) { + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_CREATE_NEW, + SYS_FILE_FLAG_NONE, + &h); + uint32_t s = SysOpenFile(&req); + if (s == STATUS_SUCCESS) { *fileOut = h; if (pathOut) *pathOut = paths[i]; - return SYS_STATUS_SUCCESS; + return STATUS_SUCCESS; } } - return SYS_STATUS_NOT_FOUND; + return STATUS_NOT_FOUND; } static void fill_pattern(void) { @@ -103,7 +107,7 @@ static uint32_t validate_mapping(const uint8_t* base) { if (base[i] != gPattern[i]) return 0x10000000u | (uint32_t)(i & 0x0FFFFFFFu); } - return SYS_STATUS_SUCCESS; + return STATUS_SUCCESS; } static void thread_main(uintptr_t arg) { @@ -114,8 +118,8 @@ static void thread_main(uintptr_t arg) { void* base = 0; uint32_t s = SysMapFile(ctx->Mapping, 0, TEST_SIZE, SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base); - if (s != SYS_STATUS_SUCCESS || !base) { - ctx->Status = s ? s : SYS_STATUS_INVALID_PARAMETER; + if (s != STATUS_SUCCESS || !base) { + ctx->Status = s ? s : STATUS_INVALID_PARAMETER; SysExitThread(1); } @@ -126,18 +130,18 @@ static void thread_main(uintptr_t arg) { s = validate_mapping((const uint8_t*)base); uint32_t fs = SysFreeVm(base); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { ctx->Status = s; SysExitThread(1); } - if (fs != SYS_STATUS_SUCCESS) { + if (fs != STATUS_SUCCESS) { ctx->Status = fs; SysExitThread(1); } ctx->IterationsDone++; } - ctx->Status = SYS_STATUS_SUCCESS; + ctx->Status = STATUS_SUCCESS; SysExitThread(0); } @@ -148,21 +152,21 @@ int main(void) { uint32_t file = 0; const char* path = 0; uint32_t s = create_temp(&file, &path); - if (s != SYS_STATUS_SUCCESS) fail_status("create temp", s); + if (s != STATUS_SUCCESS) fail_status("create temp", s); size_t written = 0; SYS_FILE_IO_REQUEST writeReq = SysMakeFileIoRequest(file, gPattern, sizeof(gPattern), &written); s = SysWriteFile(&writeReq); - if (s != SYS_STATUS_SUCCESS) fail_status("write pattern", s); + if (s != STATUS_SUCCESS) fail_status("write pattern", s); if (written != sizeof(gPattern)) fail("short write"); uint32_t mapping = 0; s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, TEST_SIZE, &mapping); - if (s != SYS_STATUS_SUCCESS) fail_status("create mapping", s); + if (s != STATUS_SUCCESS) fail_status("create mapping", s); SYS_FILE_CACHE_STATS before; SYS_FILE_CACHE_STATS after; - if (query_cache(&before) != SYS_STATUS_SUCCESS) fail("initial stats query failed"); + if (query_cache(&before) != STATUS_SUCCESS) fail("initial stats query failed"); uint32_t handles[THREAD_COUNT]; uint32_t tids[THREAD_COUNT]; @@ -173,7 +177,7 @@ int main(void) { for (uint32_t i = 0; i < THREAD_COUNT; ++i) { gThreads[i].Index = i; gThreads[i].Mapping = mapping; - gThreads[i].Status = SYS_STATUS_PENDING; + gThreads[i].Status = STATUS_PENDING; SYS_CREATE_THREAD_REQUEST req = { (uintptr_t)thread_main, (uintptr_t)&gThreads[i], @@ -183,17 +187,17 @@ int main(void) { &tids[i] }; s = SysCreateThread(&req); - if (s != SYS_STATUS_SUCCESS) fail_status("create thread", s); + if (s != STATUS_SUCCESS) fail_status("create thread", s); } for (uint32_t i = 0; i < THREAD_COUNT; ++i) { s = SysWait(handles[i], SYS_WAIT_FLAG_NONE); - if (s != SYS_STATUS_SUCCESS) fail_status("wait thread", s); + if (s != STATUS_SUCCESS) fail_status("wait thread", s); SysCloseHandle(handles[i]); } for (uint32_t i = 0; i < THREAD_COUNT; ++i) { - if (gThreads[i].Status != SYS_STATUS_SUCCESS) { + if (gThreads[i].Status != STATUS_SUCCESS) { print("pcache_multithread: worker "); print_u64(i); print(" status="); @@ -206,7 +210,7 @@ int main(void) { if (gThreads[i].IterationsDone != ITERATIONS) fail("worker iteration count mismatch"); } - if (query_cache(&after) != SYS_STATUS_SUCCESS) fail("final stats query failed"); + if (query_cache(&after) != STATUS_SUCCESS) fail("final stats query failed"); if (after.Loads <= before.Loads) fail("concurrent mmap did not load cache pages"); if (after.Hits <= before.Hits) fail("concurrent mmap did not reuse cache pages"); diff --git a/code/user/tests/pcache_pressure/pcache_pressure.c b/code/user/tests/pcache_pressure/pcache_pressure.c index 5f3f7ca..f5f5156 100644 --- a/code/user/tests/pcache_pressure/pcache_pressure.c +++ b/code/user/tests/pcache_pressure/pcache_pressure.c @@ -71,23 +71,32 @@ static uint32_t open_first_large(uint32_t* fileOut, const char** pathOut, uint64 for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) { uint32_t h = 0; - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(candidates[i]), &h }; + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(candidates[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); uint32_t s = SysOpenFile(&req); - if (s != SYS_STATUS_SUCCESS) + if (s != STATUS_SUCCESS) continue; - uint64_t size = SysGetFileSize(h); + uint64_t size = 0; + s = SysGetFileSize(h, &size); + if (s != STATUS_SUCCESS) { + SysCloseHandle(h); + continue; + } if (size >= MIN_PRESSURE_BYTES) { *fileOut = h; *pathOut = candidates[i]; *sizeOut = size; - return SYS_STATUS_SUCCESS; + return STATUS_SUCCESS; } SysCloseHandle(h); } - return SYS_STATUS_NOT_FOUND; + return STATUS_NOT_FOUND; } int main(void) { @@ -95,14 +104,14 @@ int main(void) { SYS_FILE_CACHE_STATS before; SYS_FILE_CACHE_STATS after; - if (query_cache(&before) != SYS_STATUS_SUCCESS) + if (query_cache(&before) != STATUS_SUCCESS) fail("initial cache stats query failed"); uint32_t file = 0; const char* path = 0; uint64_t size = 0; uint32_t s = open_first_large(&file, &path, &size); - if (s != SYS_STATUS_SUCCESS) + if (s != STATUS_SUCCESS) fail_status("open large asset", s); print("pcache_pressure: using "); @@ -117,7 +126,7 @@ int main(void) { size_t got = 0; SYS_FILE_IO_REQUEST readReq = SysMakeFileIoRequest(file, gBuf, sizeof(gBuf), &got); s = SysReadFile(&readReq); - if (s != SYS_STATUS_SUCCESS) + if (s != STATUS_SUCCESS) fail_status("read", s); if (got == 0) break; @@ -129,7 +138,7 @@ int main(void) { if (total < MIN_PRESSURE_BYTES) fail("asset read was too short to pressure the cache"); - if (query_cache(&after) != SYS_STATUS_SUCCESS) + if (query_cache(&after) != STATUS_SUCCESS) fail("final cache stats query failed"); uint64_t loads = after.Loads - before.Loads; diff --git a/code/user/tests/pcache_smoke/pcache_smoke.c b/code/user/tests/pcache_smoke/pcache_smoke.c index 53de5ba..95680c2 100644 --- a/code/user/tests/pcache_smoke/pcache_smoke.c +++ b/code/user/tests/pcache_smoke/pcache_smoke.c @@ -66,15 +66,19 @@ static uint32_t open_self(uint32_t* out) { for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h }; + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); uint32_t s = SysOpenFile(&req); - if (s == SYS_STATUS_SUCCESS) { + if (s == STATUS_SUCCESS) { *out = h; print("pcache_smoke: using "); print(paths[i]); print("\n"); - return SYS_STATUS_SUCCESS; + return STATUS_SUCCESS; } } - return SYS_STATUS_NOT_FOUND; + return STATUS_NOT_FOUND; } int main(void) { @@ -82,52 +86,52 @@ int main(void) { SYS_FILE_CACHE_STATS before, afterFirst, afterSecond, afterMap; uint32_t s = query_cache(&before); - if (s != SYS_STATUS_SUCCESS) fail_status("query initial stats", s); + if (s != STATUS_SUCCESS) fail_status("query initial stats", s); uint32_t file = 0; s = open_self(&file); - if (s != SYS_STATUS_SUCCESS) fail_status("open test file", s); + if (s != STATUS_SUCCESS) fail_status("open test file", s); size_t gotA = 0; memset(gA, 0xA5, sizeof(gA)); SYS_FILE_IO_REQUEST readReqA = SysMakeFileIoRequest(file, gA, sizeof(gA), &gotA); s = SysReadFile(&readReqA); - if (s != SYS_STATUS_SUCCESS) fail_status("first read", s); + if (s != STATUS_SUCCESS) fail_status("first read", s); if (gotA < 4096) fail("first read too short"); print("pcache_smoke: first read OK\n"); s = query_cache(&afterFirst); - if (s != SYS_STATUS_SUCCESS) fail_status("query after first read", s); + if (s != STATUS_SUCCESS) fail_status("query after first read", s); if (afterFirst.Loads <= before.Loads) fail("first read did not load file-cache pages"); s = SysSeekFile(file, 0); - if (s != SYS_STATUS_SUCCESS) fail_status("seek", s); + if (s != STATUS_SUCCESS) fail_status("seek", s); size_t gotB = 0; memset(gB, 0x5A, sizeof(gB)); SYS_FILE_IO_REQUEST readReqB = SysMakeFileIoRequest(file, gB, sizeof(gB), &gotB); s = SysReadFile(&readReqB); - if (s != SYS_STATUS_SUCCESS) fail_status("second read", s); + if (s != STATUS_SUCCESS) fail_status("second read", s); if (gotB != gotA) fail("second read length mismatch"); if (memcmp(gA, gB, gotA) != 0) fail("second read data mismatch"); print("pcache_smoke: second read OK\n"); s = query_cache(&afterSecond); - if (s != SYS_STATUS_SUCCESS) fail_status("query after second read", s); + if (s != STATUS_SUCCESS) fail_status("query after second read", s); if (afterSecond.Hits <= afterFirst.Hits) fail("second read did not hit file cache"); uint32_t mapping = 0; s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, sizeof(gA), &mapping); - if (s != SYS_STATUS_SUCCESS) fail_status("create mapping", s); + if (s != STATUS_SUCCESS) fail_status("create mapping", s); SysCloseHandle(file); file = 0; void* base = 0; s = SysMapFile(mapping, 0, sizeof(gA), SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base); SysCloseHandle(mapping); - if (s != SYS_STATUS_SUCCESS) fail_status("map file", s); + if (s != STATUS_SUCCESS) fail_status("map file", s); if (!base) fail("map returned null"); volatile uint8_t* view = (volatile uint8_t*)base; @@ -141,7 +145,7 @@ int main(void) { } s = query_cache(&afterMap); - if (s != SYS_STATUS_SUCCESS) fail_status("query after map", s); + if (s != STATUS_SUCCESS) fail_status("query after map", s); if (afterMap.Hits <= afterSecond.Hits) fail("mmap faults did not hit already cached pages"); diff --git a/code/user/tests/process_protection_smoke/process_protection_smoke.c b/code/user/tests/process_protection_smoke/process_protection_smoke.c new file mode 100644 index 0000000..d4af26b --- /dev/null +++ b/code/user/tests/process_protection_smoke/process_protection_smoke.c @@ -0,0 +1,103 @@ +// Protected-process smoke test. +// Verifies that kernel-owned processes remain queryable but cannot be terminated. + +#include +#include +#include + +#define MAX_PROCESSES 16u + +// Prints one NUL-terminated string to the console. +static void print(const char* text) { + SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) }; + (void)SysWriteConsole(&request); +} + +// Prints one unsigned integer to the console. +static void print_u32(uint32_t value) { + char buffer[16]; + size_t length = 0; + + if (value == 0) { + print("0"); + return; + } + + while (value && length < sizeof(buffer)) { + buffer[length++] = (char)('0' + (value % 10u)); + value /= 10u; + } + + while (length) { + char digit = buffer[--length]; + SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromParts(&digit, 1) }; + (void)SysWriteConsole(&request); + } +} + +// Terminates the current test process. +static void exit_now(int32_t code) { + for (;;) (void)SysExitProcess(code); +} + +// Fails the test with one status value. +static void fail_status(const char* operation, uint32_t status) { + print("process_protection_smoke: "); + print(operation); + print(" status="); + print_u32(status); + print("\n"); + exit_now(1); +} + +// Verifies one protected process remains alive after a kill attempt. +static void verify_protected_process(const SYS_PROCESS_INFO* process) { + uint32_t handle = 0; + uint32_t status = SysOpenProcess(process->ProcessId, &handle); + if (status != STATUS_SUCCESS) + fail_status("open protected process", status); + + status = SysKillProcess(handle, (int32_t)STATUS_PROCESS_KILLED); + if (status != STATUS_ACCESS_DENIED) { + SysCloseHandle(handle); + fail_status("kill protected process", status); + } + + status = SysWaitSingle(handle, SYS_WAIT_FLAG_NONE, 0); + SysCloseHandle(handle); + if (status != STATUS_TIMEOUT) + fail_status("protected process remained signaled", status); +} + +// Runs the protected-process contract checks. +void main(void) { + print("process_protection_smoke: start\n"); + + SYS_PROCESS_INFO processes[MAX_PROCESSES]; + size_t count = 0; + uint32_t status = SysGetProcessList(processes, MAX_PROCESSES, &count); + if (status != STATUS_SUCCESS) + fail_status("query process list", status); + + int sawIdle = 0; + int sawSystem = 0; + size_t limit = count < MAX_PROCESSES ? count : MAX_PROCESSES; + for (size_t i = 0; i < limit; i++) { + const SYS_PROCESS_INFO* process = &processes[i]; + if (!(process->Flags & (SYS_PROCESS_FLAG_IDLE | SYS_PROCESS_FLAG_SYSTEM))) + continue; + + if (!(process->Flags & SYS_PROCESS_FLAG_TERMINATION_PROTECTED)) + fail_status("missing protected flag", process->ProcessId); + + verify_protected_process(process); + if (process->Flags & SYS_PROCESS_FLAG_IDLE) sawIdle = 1; + if (process->Flags & SYS_PROCESS_FLAG_SYSTEM) sawSystem = 1; + } + + if (!sawIdle || !sawSystem) + fail_status("missing kernel process", (uint32_t)((sawIdle ? 1u : 0u) | (sawSystem ? 2u : 0u))); + + print("process_protection_smoke: PASS\n"); + exit_now(0); +} diff --git a/code/user/tests/realpath_smoke/realpath_smoke.c b/code/user/tests/realpath_smoke/realpath_smoke.c new file mode 100644 index 0000000..9c98dd0 --- /dev/null +++ b/code/user/tests/realpath_smoke/realpath_smoke.c @@ -0,0 +1,177 @@ +// Realpath ABI smoke test. +// Verifies lexical canonicalization, size reporting, and root clamping. + +#include +#include +#include +#include + +// Prints one NUL-terminated string to the kernel console. +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +// Prints one unsigned integer to the kernel console. +static void print_u64(uint64_t v) { + char buf[32]; + int i = 0; + if (v == 0) { + print("0"); + return; + } + while (v && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i--) { + char c = buf[i]; + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current test process. +static void exit_now(int code) { + for (;;) SysExitProcess(code); +} + +// Fails the test with one status value. +static void fail_status(const char* what, uint32_t status) { + print("realpath_smoke: "); + print(what); + print(" status="); + print_u64(status); + print("\n"); + exit_now(1); +} + +// Fails the test with one plain message. +static void fail(const char* what) { + print("realpath_smoke: "); + print(what); + print("\n"); + exit_now(1); +} + +// Appends one string to a fixed test buffer. +static void append(char* out, size_t cap, const char* suffix) { + size_t len = strlen(out); + size_t add = strlen(suffix); + if (len + add + 1 > cap) fail("test buffer too small"); + memcpy(out + len, suffix, add + 1); +} + +// Builds the current drive root path. +static void get_current_drive_root(char* out, size_t cap) { + char current[SYS_CWD_MAX]; + size_t required = 0; + SYS_REAL_PATH_REQUEST req = { + sizeof(req), + SYS_REAL_PATH_FLAG_NONE, + SysSvFromCstr("."), + current, + sizeof(current), + &required, + }; + uint32_t status = SysRealPath(&req); + if (status != STATUS_SUCCESS) fail_status("query current path", status); + if (required < 4 || current[0] < '0' || current[0] > '7' || current[1] != ':' || current[2] != '/') + fail("current path is not native drive-rooted"); + if (cap < 4) fail("drive root output too small"); + out[0] = current[0]; + out[1] = ':'; + out[2] = '/'; + out[3] = '\0'; +} + +// Resolves a path and compares the exact native string. +static void expect_realpath(const char* input, const char* expected) { + char out[SYS_CWD_MAX]; + size_t required = 0; + SYS_REAL_PATH_REQUEST req = { + sizeof(req), + SYS_REAL_PATH_FLAG_NONE, + SysSvFromCstr(input), + out, + sizeof(out), + &required, + }; + uint32_t status = SysRealPath(&req); + if (status != STATUS_SUCCESS) fail_status("realpath", status); + if (required != strlen(expected) + 1) fail("required size mismatch"); + if (strcmp(out, expected) != 0) fail("resolved path mismatch"); +} + +// Verifies size-only and too-small buffer behavior. +static void check_size_reporting(const char* input, const char* expected) { + size_t required = 0; + SYS_REAL_PATH_REQUEST query = { + sizeof(query), + SYS_REAL_PATH_FLAG_NONE, + SysSvFromCstr(input), + (char*)0, + 0, + &required, + }; + uint32_t status = SysRealPath(&query); + if (status != STATUS_SUCCESS) fail_status("size query", status); + if (required != strlen(expected) + 1) fail("size query required mismatch"); + + char small[4]; + size_t smallRequired = 0; + SYS_REAL_PATH_REQUEST smallReq = { + sizeof(smallReq), + SYS_REAL_PATH_FLAG_NONE, + SysSvFromCstr(input), + small, + sizeof(small), + &smallRequired, + }; + status = SysRealPath(&smallReq); + if (status != STATUS_BUFFER_TOO_SMALL) fail_status("small buffer", status); + if (smallRequired != required) fail("small required mismatch"); +} + +// Program entry point. +int main(void) { + print("realpath_smoke: start\n"); + + char root[4]; + get_current_drive_root(root, sizeof(root)); + + char input[SYS_CWD_MAX]; + char expected[SYS_CWD_MAX]; + + memcpy(input, root, sizeof(root)); + append(input, sizeof(input), "//foo///bar\\baz/."); + memcpy(expected, root, sizeof(root)); + append(expected, sizeof(expected), "foo/bar/baz"); + expect_realpath(input, expected); + check_size_reporting(input, expected); + + memcpy(input, root, sizeof(root)); + append(input, sizeof(input), "../../root_escape"); + memcpy(expected, root, sizeof(root)); + append(expected, sizeof(expected), "root_escape"); + expect_realpath(input, expected); + + SYS_REAL_PATH_REQUEST currentReq = { + sizeof(currentReq), + SYS_REAL_PATH_FLAG_NONE, + SysSvFromCstr("."), + expected, + sizeof(expected), + (size_t*)0, + }; + uint32_t status = SysRealPath(¤tReq); + if (status != STATUS_SUCCESS) fail_status("current realpath", status); + + memcpy(input, "missing_realpath_dir/../missing_file", sizeof("missing_realpath_dir/../missing_file")); + append(expected, sizeof(expected), expected[strlen(expected) - 1] == '/' ? "missing_file" : "/missing_file"); + expect_realpath(input, expected); + + print("realpath_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/shared_mmap/shared_mmap.c b/code/user/tests/shared_mmap/shared_mmap.c index 8ed5799..c9aa50c 100644 --- a/code/user/tests/shared_mmap/shared_mmap.c +++ b/code/user/tests/shared_mmap/shared_mmap.c @@ -73,36 +73,44 @@ static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) { for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; delete_path(paths[i]); - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h }; - uint32_t s = SysCreateFile(&req); - if (s == SYS_STATUS_SUCCESS) { + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_CREATE_NEW, + SYS_FILE_FLAG_NONE, + &h); + uint32_t s = SysOpenFile(&req); + if (s == STATUS_SUCCESS) { *fileOut = h; if (pathOut) *pathOut = paths[i]; - return SYS_STATUS_SUCCESS; + return STATUS_SUCCESS; } } - return SYS_STATUS_NOT_FOUND; + return STATUS_NOT_FOUND; } static uint32_t open_path(const char* path, uint32_t* fileOut) { uint32_t h = 0; - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), &h }; + SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); uint32_t s = SysOpenFile(&req); - if (s == SYS_STATUS_SUCCESS) + if (s == STATUS_SUCCESS) *fileOut = h; return s; } static void checked_seek(uint32_t file, uint64_t offset, const char* what) { uint32_t s = SysSeekFile(file, offset); - if (s != SYS_STATUS_SUCCESS) fail_status(what, s); + if (s != STATUS_SUCCESS) fail_status(what, s); } static void checked_write(uint32_t file, const void* buffer, size_t size, const char* what) { size_t done = 0; SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(file, buffer, size, &done); uint32_t s = SysWriteFile(&req); - if (s != SYS_STATUS_SUCCESS) fail_status(what, s); + if (s != STATUS_SUCCESS) fail_status(what, s); if (done != size) fail("short write"); } @@ -111,7 +119,7 @@ static void checked_read(uint32_t file, void* buffer, size_t size, size_t* gotOu memset(buffer, 0xCC, size); SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(file, buffer, size, &got); uint32_t s = SysReadFile(&req); - if (s != SYS_STATUS_SUCCESS) fail_status(what, s); + if (s != STATUS_SUCCESS) fail_status(what, s); if (gotOut) *gotOut = got; } @@ -129,7 +137,7 @@ int main(void) { uint32_t file = 0; const char* path = 0; uint32_t s = create_temp(&file, &path); - if (s != SYS_STATUS_SUCCESS) fail_status("create temp", s); + if (s != STATUS_SUCCESS) fail_status("create temp", s); print("shared_mmap: creating temporary file "); print(path ? path : ""); @@ -137,16 +145,16 @@ int main(void) { checked_write(file, gInitial, TEST_SIZE, "initial write"); s = SysFlushFile(file); - if (s != SYS_STATUS_SUCCESS) fail_status("initial flush", s); + if (s != STATUS_SUCCESS) fail_status("initial flush", s); uint32_t mapping = 0; s = SysCreateFileMapping(file, SYS_VM_READWRITE, SYS_MAP_FLAG_NONE, TEST_SIZE, &mapping); - if (s != SYS_STATUS_SUCCESS) fail_status("create mapping", s); + if (s != STATUS_SUCCESS) fail_status("create mapping", s); uint8_t* view = 0; s = SysMapFile(mapping, 0, TEST_SIZE, SYS_MAP_ACCESS_READ | SYS_MAP_ACCESS_WRITE, SYS_MAP_FLAG_NONE, (void**)&view); - if (s != SYS_STATUS_SUCCESS) fail_status("map shared write", s); + if (s != STATUS_SUCCESS) fail_status("map shared write", s); for (size_t i = 128; i < 128 + 3000; ++i) { view[i] = (uint8_t)(0xA0u + (uint8_t)i); @@ -159,12 +167,12 @@ int main(void) { SYS_FILE_CACHE_STATS beforeFlush; SYS_FILE_CACHE_STATS afterFlush; - if (query_cache(&beforeFlush) != SYS_STATUS_SUCCESS) fail("pre-flush stats failed"); + if (query_cache(&beforeFlush) != STATUS_SUCCESS) fail("pre-flush stats failed"); s = SysFlushFile(file); - if (s != SYS_STATUS_SUCCESS) fail_status("flush mapped writes", s); + if (s != STATUS_SUCCESS) fail_status("flush mapped writes", s); - if (query_cache(&afterFlush) != SYS_STATUS_SUCCESS) fail("post-flush stats failed"); + if (query_cache(&afterFlush) != STATUS_SUCCESS) fail("post-flush stats failed"); if (afterFlush.MappedDirtyPages <= beforeFlush.MappedDirtyPages) fail("mapped dirty pages were not harvested"); if (afterFlush.WritebackBytes <= beforeFlush.WritebackBytes) @@ -183,18 +191,18 @@ int main(void) { s = SysFreeVm(view); view = 0; - if (s != SYS_STATUS_SUCCESS) fail_status("unmap", s); + if (s != STATUS_SUCCESS) fail_status("unmap", s); s = SysCloseHandle(mapping); mapping = 0; - if (s != SYS_STATUS_SUCCESS) fail_status("close mapping", s); + if (s != STATUS_SUCCESS) fail_status("close mapping", s); s = SysCloseHandle(file); file = 0; - if (s != SYS_STATUS_SUCCESS) fail_status("close file", s); + if (s != STATUS_SUCCESS) fail_status("close file", s); s = open_path(path, &file); - if (s != SYS_STATUS_SUCCESS) fail_status("reopen", s); + if (s != STATUS_SUCCESS) fail_status("reopen", s); checked_read(file, gReadback, TEST_SIZE, &got, "read after close"); if (got != TEST_SIZE) fail("read after close size mismatch"); @@ -202,7 +210,7 @@ int main(void) { s = SysCloseHandle(file); file = 0; - if (s != SYS_STATUS_SUCCESS) fail_status("close reopened", s); + if (s != STATUS_SUCCESS) fail_status("close reopened", s); delete_path(path); print("shared_mmap: mapped-dirty-pages+="); diff --git a/code/user/tests/socket_addr_smoke/socket_addr_smoke.c b/code/user/tests/socket_addr_smoke/socket_addr_smoke.c new file mode 100644 index 0000000..3e0c824 --- /dev/null +++ b/code/user/tests/socket_addr_smoke/socket_addr_smoke.c @@ -0,0 +1,150 @@ +// Native socket address ABI smoke test. +// Verifies generic IPv4/IPv6 address records and basic socket validation. + +#include +#include +#include +#include + +// Prints one NUL-terminated string to the kernel console. +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +// Prints one unsigned integer. +static void print_u64(uint64_t v) { + char buf[32]; + int i = 0; + if (v == 0) { + print("0"); + return; + } + while (v && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i--) { + char c = buf[i]; + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current process. +static void exit_now(int32_t code) { + for (;;) SysExitProcess(code); +} + +// Fails the test with one message. +static void fail(const char* message) { + print("socket_addr_smoke: "); + print(message); + print("\n"); + exit_now(1); +} + +// Fails the test with one status value. +static void fail_status(const char* what, uint32_t status) { + print("socket_addr_smoke: "); + print(what); + print(" status="); + print_u64(status); + print("\n"); + exit_now(1); +} + +// Verifies public address record shape. +static void check_address_shape(void) { + if (sizeof(SYS_SOCKET_ADDRESS) != SYS_SOCKET_ADDRESS_SIZE) + fail("SYS_SOCKET_ADDRESS size mismatch"); +} + +// Verifies numeric IPv4 resolution returns the generic address shape. +static void check_ipv4_resolve(void) { + SYS_SOCKET_ADDRESS address; + size_t addressSize = sizeof(address); + SYS_RESOLVE_HOST_REQUEST req; + memset(&address, 0, sizeof(address)); + memset(&req, 0, sizeof(req)); + req.Name = SysSvFromCstr("127.0.0.1"); + req.Family = SYS_AF_INET; + req.AddressOut = &address; + req.AddressSizeInOut = &addressSize; + + uint32_t status = SysResolveHost(&req); + if (status != STATUS_SUCCESS) fail_status("resolve ipv4", status); + if (addressSize != sizeof(address)) fail("resolve ipv4 size mismatch"); + if (address.Family != SYS_AF_INET || address.Size != sizeof(address)) fail("resolve ipv4 tag mismatch"); + if (address.Address[0] != 127 || address.Address[1] != 0 || address.Address[2] != 0 || address.Address[3] != 1) + fail("resolve ipv4 bytes mismatch"); +} + +// Verifies numeric IPv6 resolution returns the generic address shape. +static void check_ipv6_resolve(void) { + SYS_SOCKET_ADDRESS address; + size_t addressSize = sizeof(address); + SYS_RESOLVE_HOST_REQUEST req; + memset(&address, 0, sizeof(address)); + memset(&req, 0, sizeof(req)); + req.Name = SysSvFromCstr("::1"); + req.Family = SYS_AF_INET6; + req.AddressOut = &address; + req.AddressSizeInOut = &addressSize; + + uint32_t status = SysResolveHost(&req); + if (status != STATUS_SUCCESS) fail_status("resolve ipv6", status); + if (addressSize != sizeof(address)) fail("resolve ipv6 size mismatch"); + if (address.Family != SYS_AF_INET6 || address.Size != sizeof(address)) fail("resolve ipv6 tag mismatch"); + for (int i = 0; i < 15; i++) { + if (address.Address[i] != 0) fail("resolve ipv6 prefix mismatch"); + } + if (address.Address[15] != 1) fail("resolve ipv6 loopback mismatch"); +} + +// Verifies IPv4 and IPv6 sockets accept only matching generic address records. +static void check_socket_address_validation(void) { + uint32_t ipv4 = 0; + uint32_t ipv6 = 0; + uint32_t status = SysCreateSocket(SYS_AF_INET, SYS_SOCK_DGRAM, SYS_IPPROTO_UDP, &ipv4); + if (status != STATUS_SUCCESS) fail_status("create ipv4 udp", status); + + status = SysCreateSocket(SYS_AF_INET6, SYS_SOCK_DGRAM, SYS_IPPROTO_UDP, &ipv6); + if (status != STATUS_SUCCESS) fail_status("create ipv6 udp", status); + + SYS_SOCKET_ADDRESS ipv4Address; + memset(&ipv4Address, 0, sizeof(ipv4Address)); + ipv4Address.Size = sizeof(ipv4Address); + ipv4Address.Family = SYS_AF_INET; + ipv4Address.Address[0] = 127; + ipv4Address.Address[3] = 1; + + SYS_SOCKET_ADDRESS ipv6Address; + memset(&ipv6Address, 0, sizeof(ipv6Address)); + ipv6Address.Size = sizeof(ipv6Address); + ipv6Address.Family = SYS_AF_INET6; + ipv6Address.Address[15] = 1; + + SYS_SOCKET_ADDRESS badSize = ipv6Address; + badSize.Size = sizeof(badSize) - 1; + status = SysBindSocket(ipv6, &badSize, sizeof(badSize)); + if (status == STATUS_SUCCESS) fail("bind accepted wrong ipv6 size"); + + status = SysConnectSocket(ipv4, &ipv6Address, sizeof(ipv6Address)); + if (status == STATUS_SUCCESS) fail("connect accepted wrong address family"); + + SysCloseHandle(ipv4); + SysCloseHandle(ipv6); +} + +// Runs the socket address ABI smoke test. +int main(void) { + print("socket_addr_smoke: start\n"); + check_address_shape(); + check_ipv4_resolve(); + check_ipv6_resolve(); + check_socket_address_validation(); + print("socket_addr_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/stats_abi_smoke/stats_abi_smoke.c b/code/user/tests/stats_abi_smoke/stats_abi_smoke.c new file mode 100644 index 0000000..36664a4 --- /dev/null +++ b/code/user/tests/stats_abi_smoke/stats_abi_smoke.c @@ -0,0 +1,141 @@ +// Kernel statistics ABI smoke test. +// Verifies size queries, trailing CPU records, and generic filesystem stats. + +#include +#include +#include + +// Prints one NUL-terminated string to the kernel console. +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +// Prints one unsigned integer to the kernel console. +static void print_u64(uint64_t v) { + char buf[32]; + int i = 0; + if (v == 0) { + print("0"); + return; + } + while (v && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i--) { + char c = buf[i]; + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current test process. +static void exit_now(int code) { + for (;;) SysExitProcess(code); +} + +// Fails the test with one plain message. +static void fail(const char* what) { + print("stats_abi_smoke: "); + print(what); + print("\n"); + exit_now(1); +} + +// Fails the test with one status value. +static void fail_status(const char* what, uint32_t status) { + print("stats_abi_smoke: "); + print(what); + print(" status="); + print_u64(status); + print("\n"); + exit_now(1); +} + +// Requires one exact status value. +static void expect_status(const char* what, uint32_t got, uint32_t expected) { + if (got == expected) return; + print("stats_abi_smoke: "); + print(what); + print(" got="); + print_u64(got); + print(" expected="); + print_u64(expected); + print("\n"); + exit_now(1); +} + +// Verifies fixed-size stats classes report and fill their public header. +static void check_fixed_stats(void) { + size_t required = 0; + uint32_t status = SysQueryKernelStats(SYS_KERNEL_STATS_SCHEDULER, 0, 0, &required); + if (status != STATUS_SUCCESS) fail_status("scheduler size", status); + if (required != sizeof(SYS_SCHEDULER_STATS)) fail("scheduler required size mismatch"); + + uint8_t small[sizeof(SYS_SCHEDULER_STATS) - 1u]; + size_t smallRequired = 0; + status = SysQueryKernelStats(SYS_KERNEL_STATS_SCHEDULER, small, sizeof(small), &smallRequired); + expect_status("scheduler small buffer", status, STATUS_BUFFER_TOO_SMALL); + if (smallRequired != sizeof(SYS_SCHEDULER_STATS)) fail("scheduler small required mismatch"); + + SYS_SCHEDULER_STATS scheduler; + status = SysQueryKernelStats(SYS_KERNEL_STATS_SCHEDULER, &scheduler, sizeof(scheduler), 0); + if (status != STATUS_SUCCESS) fail_status("scheduler exact", status); + if (scheduler.Size != sizeof(scheduler)) fail("scheduler size field mismatch"); + if (scheduler.Version != SYS_STATS_VERSION) fail("scheduler version mismatch"); + + SYS_FILESYSTEM_STATS filesystem; + status = SysQueryKernelStats(SYS_KERNEL_STATS_FILESYSTEM, &filesystem, sizeof(filesystem), 0); + if (status != STATUS_SUCCESS) fail_status("filesystem exact", status); + if (filesystem.Size != sizeof(filesystem)) fail("filesystem size field mismatch"); + if (filesystem.Version != SYS_STATS_VERSION) fail("filesystem version mismatch"); +} + +// Verifies CPU stats use a size-reported trailing-record layout. +static void check_cpu_stats(void) { + size_t required = 0; + uint32_t status = SysQueryKernelStats(SYS_KERNEL_STATS_CPU, 0, 0, &required); + if (status != STATUS_SUCCESS) fail_status("cpu size", status); + if (required < sizeof(SYS_CPU_STATS) + sizeof(SYS_CPU_STAT)) fail("cpu required size too small"); + + SYS_CPU_STATS headerOnly; + size_t smallRequired = 0; + status = SysQueryKernelStats(SYS_KERNEL_STATS_CPU, &headerOnly, sizeof(headerOnly), &smallRequired); + expect_status("cpu header-only buffer", status, STATUS_BUFFER_TOO_SMALL); + if (smallRequired != required) fail("cpu small required mismatch"); + + void* buffer = 0; + size_t pages = (required + 4095u) / 4096u; + if (pages == 0) pages = 1; + status = SysAllocVm(0, pages, SYS_VM_READWRITE, SYS_VM_FLAG_NONE, &buffer); + if (status != STATUS_SUCCESS || !buffer) fail_status("cpu buffer alloc", status); + + SYS_CPU_STATS* cpu = (SYS_CPU_STATS*)buffer; + status = SysQueryKernelStats(SYS_KERNEL_STATS_CPU, cpu, pages * 4096u, 0); + if (status != STATUS_SUCCESS) { + SysFreeVm(buffer); + fail_status("cpu exact", status); + } + + if (cpu->Size != required) fail("cpu size field mismatch"); + if (cpu->Version != SYS_STATS_VERSION) fail("cpu version mismatch"); + if (cpu->HeaderSize != sizeof(SYS_CPU_STATS)) fail("cpu header size mismatch"); + if (cpu->RecordSize != sizeof(SYS_CPU_STAT)) fail("cpu record size mismatch"); + if (cpu->RecordCount == 0 || cpu->RecordCount > cpu->CpuCount) fail("cpu record count mismatch"); + + const SYS_CPU_STAT* records = SYS_CPU_STATS_CONST_RECORDS(cpu); + if (records[0].CpuIndex >= cpu->CpuCount) fail("cpu first record index mismatch"); + + SysFreeVm(buffer); +} + +// Runs the stats ABI smoke test. +int main(void) { + print("stats_abi_smoke: start\n"); + check_fixed_stats(); + check_cpu_stats(); + print("stats_abi_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/surface_invalid_rect/surface_invalid_rect.c b/code/user/tests/surface_invalid_rect/surface_invalid_rect.c index 5c64283..de74b3e 100644 --- a/code/user/tests/surface_invalid_rect/surface_invalid_rect.c +++ b/code/user/tests/surface_invalid_rect/surface_invalid_rect.c @@ -66,37 +66,37 @@ int main(void) { uint32_t bad = 0; uint32_t s = SysCreateSurface(0, SURFACE_H, SYS_SURFACE_FORMAT_XRGB8888, SYS_SURFACE_FLAG_NONE, &bad); - expect_status("zero width", s, SYS_STATUS_INVALID_PARAMETER); + expect_status("zero width", s, STATUS_INVALID_PARAMETER); s = SysCreateSurface(SURFACE_W, SURFACE_H, 0x9999u, SYS_SURFACE_FLAG_NONE, &bad); - expect_status("bad format", s, SYS_STATUS_NOT_SUPPORTED); + expect_status("bad format", s, STATUS_NOT_SUPPORTED); uint32_t surface = 0; s = SysCreateSurface(SURFACE_W, SURFACE_H, SYS_SURFACE_FORMAT_XRGB8888, SYS_SURFACE_FLAG_NONE, &surface); - if (s != SYS_STATUS_SUCCESS) fail_status("create surface", s); + if (s != STATUS_SUCCESS) fail_status("create surface", s); void* base = 0; uint32_t pitch = 0; s = SysMapSurface(surface, &base, &pitch); - if (s != SYS_STATUS_SUCCESS) fail_status("map surface", s); + if (s != STATUS_SUCCESS) fail_status("map surface", s); if (!base || pitch < SURFACE_W * sizeof(uint32_t)) fail("invalid mapping"); s = SysPresentSurface(surface, 0, 1); - expect_status("null rect array with nonzero count", s, SYS_STATUS_INVALID_PARAMETER); + expect_status("null rect array with nonzero count", s, STATUS_INVALID_PARAMETER); DISPLAY_RECT rects[SYS_SURFACE_MAX_RECTS + 1]; memset(rects, 0, sizeof(rects)); s = SysPresentSurface(surface, rects, SYS_SURFACE_MAX_RECTS + 1); - expect_status("too many rects", s, SYS_STATUS_INVALID_PARAMETER); + expect_status("too many rects", s, STATUS_INVALID_PARAMETER); DISPLAY_RECT clipped = { -8, -8, 24, 24 }; s = SysPresentSurface(surface, &clipped, 1); - if (s != SYS_STATUS_SUCCESS) fail_status("present clipped rect", s); + if (s != STATUS_SUCCESS) fail_status("present clipped rect", s); DISPLAY_RECT empty = { 10000, 10000, 16, 16 }; s = SysPresentSurface(surface, &empty, 1); - if (s != SYS_STATUS_SUCCESS) fail_status("present empty clipped rect", s); + if (s != STATUS_SUCCESS) fail_status("present empty clipped rect", s); SysCloseHandle(surface); print("surface_invalid_rect: PASS\n"); diff --git a/code/user/tests/surface_multithread_present/surface_multithread_present.c b/code/user/tests/surface_multithread_present/surface_multithread_present.c index a382e64..4165a5a 100644 --- a/code/user/tests/surface_multithread_present/surface_multithread_present.c +++ b/code/user/tests/surface_multithread_present/surface_multithread_present.c @@ -80,7 +80,7 @@ static void worker_main(uintptr_t arg) { DISPLAY_RECT rect = { 0, (int32_t)y0, (int32_t)SURFACE_W, (int32_t)h }; uint32_t s = SysPresentSurface(st->Surface, &rect, 1); - if (s != SYS_STATUS_SUCCESS) { + if (s != STATUS_SUCCESS) { st->Status = s; SysExitThread(1); } @@ -88,7 +88,7 @@ static void worker_main(uintptr_t arg) { SysYield(); } - st->Status = SYS_STATUS_SUCCESS; + st->Status = STATUS_SUCCESS; SysExitThread(0); } @@ -98,12 +98,12 @@ int main(void) { uint32_t surface = 0; uint32_t s = SysCreateSurface(SURFACE_W, SURFACE_H, SYS_SURFACE_FORMAT_XRGB8888, SYS_SURFACE_FLAG_NONE, &surface); - if (s != SYS_STATUS_SUCCESS) fail_status("create surface", s); + if (s != STATUS_SUCCESS) fail_status("create surface", s); void* base = 0; uint32_t pitch = 0; s = SysMapSurface(surface, &base, &pitch); - if (s != SYS_STATUS_SUCCESS) fail_status("map surface", s); + if (s != STATUS_SUCCESS) fail_status("map surface", s); if (!base || pitch < SURFACE_W * sizeof(uint32_t)) fail("invalid mapping"); uint32_t handles[THREAD_COUNT]; @@ -117,7 +117,7 @@ int main(void) { gWorkers[i].Surface = surface; gWorkers[i].Base = (uint8_t*)base; gWorkers[i].Pitch = pitch; - gWorkers[i].Status = SYS_STATUS_PENDING; + gWorkers[i].Status = STATUS_PENDING; SYS_CREATE_THREAD_REQUEST req = { (uintptr_t)worker_main, (uintptr_t)&gWorkers[i], @@ -127,17 +127,17 @@ int main(void) { &tids[i] }; s = SysCreateThread(&req); - if (s != SYS_STATUS_SUCCESS) fail_status("create thread", s); + if (s != STATUS_SUCCESS) fail_status("create thread", s); } for (uint32_t i = 0; i < THREAD_COUNT; ++i) { s = SysWait(handles[i], SYS_WAIT_FLAG_NONE); - if (s != SYS_STATUS_SUCCESS) fail_status("wait thread", s); + if (s != STATUS_SUCCESS) fail_status("wait thread", s); SysCloseHandle(handles[i]); } for (uint32_t i = 0; i < THREAD_COUNT; ++i) { - if (gWorkers[i].Status != SYS_STATUS_SUCCESS) { + if (gWorkers[i].Status != STATUS_SUCCESS) { print("surface_multithread_present: worker "); print_u64(i); print(" status="); diff --git a/code/user/tests/surface_smoke/surface_smoke.c b/code/user/tests/surface_smoke/surface_smoke.c index c8304b0..d78adec 100644 --- a/code/user/tests/surface_smoke/surface_smoke.c +++ b/code/user/tests/surface_smoke/surface_smoke.c @@ -55,19 +55,19 @@ int main(void) { uint32_t surface = 0; uint32_t s = SysCreateSurface(SURFACE_W, SURFACE_H, SYS_SURFACE_FORMAT_XRGB8888, SYS_SURFACE_FLAG_NONE, &surface); - if (s != SYS_STATUS_SUCCESS) fail_status("create surface", s); + if (s != STATUS_SUCCESS) fail_status("create surface", s); if (!surface) fail("surface handle is zero"); void* base = 0; uint32_t pitch = 0; s = SysMapSurface(surface, &base, &pitch); - if (s != SYS_STATUS_SUCCESS) fail_status("map surface", s); + if (s != STATUS_SUCCESS) fail_status("map surface", s); if (!base || pitch < SURFACE_W * sizeof(uint32_t)) fail("invalid mapping"); SYS_SURFACE_DESC desc; memset(&desc, 0, sizeof(desc)); s = SysQuerySurface(surface, &desc); - if (s != SYS_STATUS_SUCCESS) fail_status("query surface", s); + if (s != STATUS_SUCCESS) fail_status("query surface", s); if (desc.Width != SURFACE_W || desc.Height != SURFACE_H) fail("query dimensions mismatch"); if (desc.PitchBytes != pitch || desc.Format != SYS_SURFACE_FORMAT_XRGB8888) fail("query metadata mismatch"); if (desc.Base != base) fail("query base mismatch"); @@ -81,10 +81,10 @@ int main(void) { DISPLAY_RECT rect = { 0, 0, (int32_t)SURFACE_W, (int32_t)SURFACE_H }; s = SysPresentSurface(surface, &rect, 1); - if (s != SYS_STATUS_SUCCESS) fail_status("present surface", s); + if (s != STATUS_SUCCESS) fail_status("present surface", s); s = SysPresentSurface(surface, 0, 0); - if (s != SYS_STATUS_SUCCESS) fail_status("present full surface", s); + if (s != STATUS_SUCCESS) fail_status("present full surface", s); SysCloseHandle(surface); print("surface_smoke: PASS\n"); diff --git a/code/user/tests/syscall_contract_smoke/syscall_contract_smoke.c b/code/user/tests/syscall_contract_smoke/syscall_contract_smoke.c new file mode 100644 index 0000000..7964225 --- /dev/null +++ b/code/user/tests/syscall_contract_smoke/syscall_contract_smoke.c @@ -0,0 +1,125 @@ +// Native syscall contract smoke test. +// Verifies public tick units and process exit-code round trips. + +#include +#include +#include + +#define CHILD_EXIT_CODE ((int32_t)-1234567) + +// Prints one NUL-terminated string to the kernel console. +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +// Prints one unsigned integer to the kernel console. +static void print_u64(uint64_t v) { + char buf[32]; + int i = 0; + if (v == 0) { + print("0"); + return; + } + while (v && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i--) { + char c = buf[i]; + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current test process. +static void exit_now(int32_t code) { + for (;;) SysExitProcess(code); +} + +// Checks whether a string view equals a NUL-terminated string. +static int view_equals_cstr(SYS_STRING_VIEW view, const char* text) { + size_t n = 0; + while (text[n]) n++; + if (view.Length != n || (!view.Data && n != 0)) return 0; + for (size_t i = 0; i < n; i++) { + if (view.Data[i] != text[i]) return 0; + } + return 1; +} + +// Fails the test with one plain message. +static void fail(const char* msg) { + print("syscall_contract_smoke: "); + print(msg); + print("\n"); + exit_now(1); +} + +// Fails the test with one status value. +static void fail_status(const char* what, uint32_t status) { + print("syscall_contract_smoke: "); + print(what); + print(" status="); + print_u64(status); + print("\n"); + exit_now(1); +} + +// Verifies the documented tick unit without relying on wall-clock precision. +static void check_tick_contract(void) { + uint64_t frequency = 0; + uint32_t s = SysGetTickFrequency(&frequency); + if (s != STATUS_SUCCESS) fail_status("query tick frequency", s); + if (!frequency) fail("tick frequency was zero"); + + uint64_t t0 = SysGetTick(); + s = SysSleep(1); + if (s != STATUS_SUCCESS) fail_status("sleep", s); + + uint64_t t1 = SysGetTick(); + if (t1 < t0) fail("tick moved backwards across sleep"); + if (SysTicksToMicroseconds(t1 - t0, frequency) < 1000ULL) + fail("tick delta was smaller than one millisecond"); +} + +// Verifies process exit-code storage, wait, and query agree on int32_t. +static void check_exit_code_contract(void) { + SYS_PEB* peb = SysGetPeb(); + if (!peb || !peb->ImagePath.Data || peb->ImagePath.Length == 0) fail("missing image path"); + + uint32_t child = 0; + SYS_CREATE_PROCESS_REQUEST req = { + peb->ImagePath, + SysSvFromCstr("child"), + &child, + }; + + uint32_t s = SysCreateProcess(&req); + if (s != STATUS_SUCCESS) fail_status("create child", s); + + s = SysWait(child, SYS_WAIT_FLAG_NONE); + if (s != STATUS_SUCCESS) fail_status("wait child", s); + + int32_t exitCode = 0; + s = SysGetProcessExitCode(child, &exitCode); + if (s != STATUS_SUCCESS) fail_status("query child exit code", s); + if (exitCode != CHILD_EXIT_CODE) fail("child exit code mismatch"); + + SysCloseHandle(child); +} + +// Runs the syscall ABI contract smoke test. +int main(void) { + SYS_PEB* peb = SysGetPeb(); + if (peb && view_equals_cstr(peb->CommandLine, "child")) { + exit_now(CHILD_EXIT_CODE); + } + + print("syscall_contract_smoke: start\n"); + check_tick_contract(); + check_exit_code_contract(); + print("syscall_contract_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/timer_smoke/timer_smoke.c b/code/user/tests/timer_smoke/timer_smoke.c new file mode 100644 index 0000000..70bfee1 --- /dev/null +++ b/code/user/tests/timer_smoke/timer_smoke.c @@ -0,0 +1,193 @@ +// Waitable timer smoke test. +// Verifies one-shot, cancellation, reset, invalid handle, and wait timeout behavior. + +#include +#include + +// Prints one NUL-terminated string to the kernel console. +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +// Prints one unsigned integer to the kernel console. +static void print_u64(uint64_t v) { + char buf[32]; + int i = 0; + if (v == 0) { + print("0"); + return; + } + while (v && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i--) { + char c = buf[i]; + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current test process. +static void exit_now(int code) { + for (;;) SysExitProcess(code); +} + +// Fails the test with one status value. +static void fail_status(const char* what, uint32_t status) { + print("timer_smoke: "); + print(what); + print(" status="); + print_u64(status); + print("\n"); + exit_now(1); +} + +// Requires one exact status value. +static void expect_status(const char* what, uint32_t got, uint32_t expected) { + if (got == expected) return; + print("timer_smoke: "); + print(what); + print(" got="); + print_u64(got); + print(" expected="); + print_u64(expected); + print("\n"); + exit_now(1); +} + +// Arms a timer through the public request ABI. +static uint32_t set_timer(uint32_t timer, uint64_t dueMs, uint64_t periodMs) { + SYS_SET_TIMER_REQUEST req = { + sizeof(req), + timer, + SYS_TIMER_FLAG_NONE, + dueMs, + periodMs, + }; + return SysSetTimer(&req); +} + +// Verifies timer request validation paths. +static void test_validation(void) { + uint32_t timer = 0; + expect_status("create timer null", SysCreateTimer((uint32_t*)0), STATUS_INVALID_PARAMETER); + + uint32_t s = SysCreateTimer(&timer); + if (s != STATUS_SUCCESS) fail_status("create validation timer", s); + + expect_status("set timer null", SysSetTimer((const SYS_SET_TIMER_REQUEST*)0), STATUS_INVALID_PARAMETER); + expect_status("cancel invalid", SysCancelTimer(0xdeadbeefu), STATUS_INVALID_HANDLE); + expect_status("wait invalid", SysWaitSingle(0xdeadbeefu, SYS_WAIT_FLAG_NONE, 0), STATUS_INVALID_HANDLE); + + SYS_SET_TIMER_REQUEST req = { + 0, + timer, + SYS_TIMER_FLAG_NONE, + 1, + 0, + }; + expect_status("set bad size", SysSetTimer(&req), STATUS_INVALID_PARAMETER); + + req.Size = sizeof(req); + req.Flags = 1; + expect_status("set bad flags", SysSetTimer(&req), STATUS_INVALID_PARAMETER); + + SysCloseHandle(timer); +} + +// Verifies one-shot timers wake waiters and are consumed by the wait. +static void test_one_shot(void) { + uint32_t timer = 0; + uint32_t s = SysCreateTimer(&timer); + if (s != STATUS_SUCCESS) fail_status("create one-shot timer", s); + + expect_status("unset immediate wait", SysWaitSingle(timer, SYS_WAIT_FLAG_NONE, 0), STATUS_TIMEOUT); + s = set_timer(timer, 20, 0); + if (s != STATUS_SUCCESS) fail_status("set one-shot", s); + + s = SysWaitSingle(timer, SYS_WAIT_FLAG_NONE, SYS_WAIT_TIMEOUT_INFINITE); + if (s != STATUS_SUCCESS) fail_status("wait one-shot", s); + + expect_status("one-shot consumed", SysWaitSingle(timer, SYS_WAIT_FLAG_NONE, 0), STATUS_TIMEOUT); + SysCloseHandle(timer); +} + +// Verifies cancellation clears an armed timer and its retained signal. +static void test_cancel(void) { + uint32_t timer = 0; + uint32_t s = SysCreateTimer(&timer); + if (s != STATUS_SUCCESS) fail_status("create cancel timer", s); + + s = set_timer(timer, 100, 0); + if (s != STATUS_SUCCESS) fail_status("set cancel timer", s); + s = SysCancelTimer(timer); + if (s != STATUS_SUCCESS) fail_status("cancel timer", s); + + expect_status("cancelled timer wait", SysWaitSingle(timer, SYS_WAIT_FLAG_NONE, 30), STATUS_TIMEOUT); + + s = set_timer(timer, 0, 0); + if (s != STATUS_SUCCESS) fail_status("set immediate", s); + expect_status("immediate timer wait", SysWaitSingle(timer, SYS_WAIT_FLAG_NONE, 0), STATUS_SUCCESS); + + s = set_timer(timer, 0, 0); + if (s != STATUS_SUCCESS) fail_status("set immediate before cancel", s); + s = SysCancelTimer(timer); + if (s != STATUS_SUCCESS) fail_status("cancel signaled", s); + expect_status("cancel clears signal", SysWaitSingle(timer, SYS_WAIT_FLAG_NONE, 0), STATUS_TIMEOUT); + + SysCloseHandle(timer); +} + +// Verifies resetting an active timer replaces its previous deadline. +static void test_reset_changes_deadline(void) { + uint32_t timer = 0; + uint32_t s = SysCreateTimer(&timer); + if (s != STATUS_SUCCESS) fail_status("create reset timer", s); + + s = set_timer(timer, 100, 0); + if (s != STATUS_SUCCESS) fail_status("set reset timer long", s); + s = set_timer(timer, 20, 0); + if (s != STATUS_SUCCESS) fail_status("set reset timer short", s); + + s = SysWaitSingle(timer, SYS_WAIT_FLAG_NONE, 80); + if (s != STATUS_SUCCESS) fail_status("wait reset timer", s); + + SysCloseHandle(timer); +} + +// Verifies a periodic timer can wake more than once. +static void test_periodic(void) { + uint32_t timer = 0; + uint32_t s = SysCreateTimer(&timer); + if (s != STATUS_SUCCESS) fail_status("create periodic timer", s); + + s = set_timer(timer, 10, 20); + if (s != STATUS_SUCCESS) fail_status("set periodic timer", s); + s = SysWaitSingle(timer, SYS_WAIT_FLAG_NONE, 80); + if (s != STATUS_SUCCESS) fail_status("wait periodic first", s); + s = SysWaitSingle(timer, SYS_WAIT_FLAG_NONE, 80); + if (s != STATUS_SUCCESS) fail_status("wait periodic second", s); + + s = SysCancelTimer(timer); + if (s != STATUS_SUCCESS) fail_status("cancel periodic", s); + expect_status("periodic cancelled wait", SysWaitSingle(timer, SYS_WAIT_FLAG_NONE, 30), STATUS_TIMEOUT); + + SysCloseHandle(timer); +} + +// Program entry point. +int main(void) { + print("timer_smoke: start\n"); + + test_validation(); + test_one_shot(); + test_cancel(); + test_reset_changes_deadline(); + test_periodic(); + + print("timer_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/would_block_smoke/would_block_smoke.c b/code/user/tests/would_block_smoke/would_block_smoke.c new file mode 100644 index 0000000..83f57cc --- /dev/null +++ b/code/user/tests/would_block_smoke/would_block_smoke.c @@ -0,0 +1,246 @@ +// Native would-block and queue-timeout contract smoke test. +// Verifies that queue try, finite-timeout, and infinite-wait requests return distinct statuses. + +#include +#include + +#define TEST_FILL_LIMIT 1024u +#define TEST_MESSAGE_WAKE 0x77616b65u +#define TEST_WAKE_VALUE 0x12345678u + +typedef struct TEST_QUEUE_MESSAGE { + uint32_t Type; + uint32_t Flags; + uint64_t Param[6]; +} TEST_QUEUE_MESSAGE; + +typedef char TEST_QUEUE_MESSAGE_SIZE_CHECK[ + (sizeof(TEST_QUEUE_MESSAGE) == SYS_QUEUE_MESSAGE_SIZE) ? 1 : -1 +]; + +typedef struct TEST_SENDER_CONTEXT { + uint32_t Queue; + volatile uint32_t Status; +} TEST_SENDER_CONTEXT; + +// Sends one message with the queue timeout request ABI. +static uint32_t send_queue(uint32_t queue, const TEST_QUEUE_MESSAGE* msg, uint64_t timeoutMilliseconds) { + SYS_QUEUE_SEND_REQUEST req = { + sizeof(req), + queue, + SYS_QUEUE_FLAG_NONE, + timeoutMilliseconds, + msg, + }; + return SysSendQueue(&req); +} + +// Receives one message with the queue timeout request ABI. +static uint32_t recv_queue(uint32_t queue, TEST_QUEUE_MESSAGE* msg, uint64_t timeoutMilliseconds) { + SYS_QUEUE_RECV_REQUEST req = { + sizeof(req), + queue, + SYS_QUEUE_FLAG_NONE, + timeoutMilliseconds, + msg, + }; + return SysRecvQueue(&req); +} + +// Prints one NUL-terminated string to the kernel console. +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +// Prints one unsigned integer to the kernel console. +static void print_u64(uint64_t v) { + char buf[32]; + int i = 0; + if (v == 0) { + print("0"); + return; + } + while (v && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i--) { + char c = buf[i]; + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current test process. +static void exit_now(int code) { + for (;;) SysExitProcess(code); +} + +// Fails the test with one plain message. +static void fail(const char* msg) { + print("would_block_smoke: "); + print(msg); + print("\n"); + exit_now(1); +} + +// Fails the test with one status value. +static void fail_status(const char* what, uint32_t status) { + print("would_block_smoke: "); + print(what); + print(" status="); + print_u64(status); + print("\n"); + exit_now(1); +} + +// Requires one exact status value. +static void expect_status(const char* what, uint32_t got, uint32_t expected) { + if (got == expected) return; + print("would_block_smoke: "); + print(what); + print(" got="); + print_u64(got); + print(" expected="); + print_u64(expected); + print("\n"); + exit_now(1); +} + +// Sends one wake message after the receiver has had a chance to block. +static void sender_thread(uintptr_t arg) { + TEST_SENDER_CONTEXT* ctx = (TEST_SENDER_CONTEXT*)arg; + if (!ctx) SysExitThread(1); + + SysSleep(20); + + TEST_QUEUE_MESSAGE msg = {0}; + msg.Type = TEST_MESSAGE_WAKE; + msg.Param[0] = TEST_WAKE_VALUE; + ctx->Status = send_queue(ctx->Queue, &msg, SYS_WAIT_TIMEOUT_INFINITE); + SysExitThread(ctx->Status == STATUS_SUCCESS ? 0 : 1); +} + +// Fills one queue with immediate sends until the queue reports would-block. +static void fill_until_would_block(uint32_t queue) { + TEST_QUEUE_MESSAGE msg = {0}; + msg.Type = 1; + + uint32_t sends = 0; + for (; sends < TEST_FILL_LIMIT; sends++) { + msg.Param[0] = sends; + uint32_t s = send_queue(queue, &msg, 0); + if (s == STATUS_WOULD_BLOCK) + break; + if (s != STATUS_SUCCESS) + fail_status("fill send", s); + } + + if (sends == 0 || sends == TEST_FILL_LIMIT) + fail("queue fill did not reach would-block"); +} + +// Verifies queue try and finite-timeout status distinctions. +static void test_queue_empty_and_full(void) { + uint32_t queue = 0; + uint32_t s = SysCreateQueue(&queue); + if (s != STATUS_SUCCESS) fail_status("create empty queue", s); + + TEST_QUEUE_MESSAGE msg = {0}; + expect_status("empty nonblocking receive", recv_queue(queue, &msg, 0), STATUS_WOULD_BLOCK); + expect_status("empty finite receive", recv_queue(queue, &msg, 20), STATUS_TIMEOUT); + SysCloseHandle(queue); + + queue = 0; + s = SysCreateQueue(&queue); + if (s != STATUS_SUCCESS) fail_status("create full queue", s); + + fill_until_would_block(queue); + expect_status("full nonblocking send", send_queue(queue, &msg, 0), STATUS_WOULD_BLOCK); + expect_status("full finite send", send_queue(queue, &msg, 20), STATUS_TIMEOUT); + SysCloseHandle(queue); +} + +// Verifies that an infinite receive composes with a sender wakeup. +static void test_queue_infinite_receive_wakeup(void) { + uint32_t queue = 0; + uint32_t s = SysCreateQueue(&queue); + if (s != STATUS_SUCCESS) fail_status("create wake queue", s); + + TEST_SENDER_CONTEXT ctx = { queue, STATUS_PENDING }; + uint32_t threadHandle = 0; + uint32_t threadId = 0; + SYS_CREATE_THREAD_REQUEST req = { + (uintptr_t)sender_thread, + (uintptr_t)&ctx, + 1, + 0, + &threadHandle, + &threadId + }; + s = SysCreateThread(&req); + if (s != STATUS_SUCCESS) fail_status("create sender", s); + + TEST_QUEUE_MESSAGE msg = {0}; + s = recv_queue(queue, &msg, SYS_WAIT_TIMEOUT_INFINITE); + if (s != STATUS_SUCCESS) fail_status("infinite receive", s); + if (msg.Type != TEST_MESSAGE_WAKE || msg.Param[0] != TEST_WAKE_VALUE) + fail("wake message payload mismatch"); + + s = SysWait(threadHandle, SYS_WAIT_FLAG_NONE); + if (s != STATUS_SUCCESS) fail_status("wait sender", s); + if (ctx.Status != STATUS_SUCCESS) fail_status("sender status", ctx.Status); + + SysCloseHandle(threadHandle); + SysCloseHandle(queue); +} + +// Verifies that request metadata is part of the queue ABI contract. +static void test_queue_request_validation(void) { + uint32_t queue = 0; + uint32_t s = SysCreateQueue(&queue); + if (s != STATUS_SUCCESS) fail_status("create validation queue", s); + + TEST_QUEUE_MESSAGE msg = {0}; + expect_status("null send request", SysSendQueue((const SYS_QUEUE_SEND_REQUEST*)0), STATUS_INVALID_PARAMETER); + expect_status("null recv request", SysRecvQueue((const SYS_QUEUE_RECV_REQUEST*)0), STATUS_INVALID_PARAMETER); + + SYS_QUEUE_SEND_REQUEST sendReq = { + 0, + queue, + SYS_QUEUE_FLAG_NONE, + 0, + &msg, + }; + expect_status("bad send size", SysSendQueue(&sendReq), STATUS_INVALID_PARAMETER); + + sendReq.Size = sizeof(sendReq); + sendReq.Flags = 1; + expect_status("bad send flags", SysSendQueue(&sendReq), STATUS_INVALID_PARAMETER); + + sendReq.Flags = SYS_QUEUE_FLAG_NONE; + sendReq.Message = (const void*)0; + expect_status("bad send message", SysSendQueue(&sendReq), STATUS_INVALID_PARAMETER); + + SYS_QUEUE_RECV_REQUEST recvReq = { + sizeof(recvReq), + queue, + SYS_QUEUE_FLAG_NONE, + 0, + (void*)0, + }; + expect_status("bad recv message", SysRecvQueue(&recvReq), STATUS_INVALID_PARAMETER); + + SysCloseHandle(queue); +} + +// Runs the native would-block smoke test. +int main(void) { + print("would_block_smoke: start\n"); + test_queue_request_validation(); + test_queue_empty_and_full(); + test_queue_infinite_receive_wakeup(); + print("would_block_smoke: PASS\n"); + exit_now(0); +}