diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index 9a9904a..2bbd2e3 100644 Binary files a/OVMF/OVMF_VARS_4M_d.fd and b/OVMF/OVMF_VARS_4M_d.fd differ diff --git a/code/boot.ini b/code/boot.ini index 4be974f..0bf213d 100644 --- a/code/boot.ini +++ b/code/boot.ini @@ -6,9 +6,9 @@ system_volume = mbr/4E544C31/2 [kernel] ; Debug bitmask. Values are defined canonically in ntlite/ddk/debug.h. -; 0x001 console output, 0x002 boot self-tests, 0x004 file log. +; 0x001 console output, 0x002 explicit kernel diagnostics, 0x004 file log. ; 0x008 pool canaries, 0x010 pool poison, 0x020 pool quarantine, 0x040 pool caller tracking. ; 0x080 display assertions, 0x100 intrusive-list checks. -debug = 0x1ff +debug = 0x004 [init] commandline = apps/shell_native.exe diff --git a/code/boot/uefi/main.c b/code/boot/uefi/main.c index cb15f64..3760ac9 100644 --- a/code/boot/uefi/main.c +++ b/code/boot/uefi/main.c @@ -1,6 +1,6 @@ // UEFI bootloader entry point. This file performs firmware discovery and the minimum fixed-image/raw-file handoff needed to enter the kernel. -// The loader interprets only the [boot] system_volume bootstrap selector. It scans drivers.ini [boot], +// The loader interprets only the [boot] system_volume bootstrap selector. It scans drivers.ini v2, // verifies and maps fixed kernel/HAL images, translates firmware state, and exits boot services. #include @@ -853,50 +853,80 @@ static EFI_STATUS BootOpenSystemVolume(EFI_HANDLE imageHandle, return fs->OpenVolume(fs, rootOut); } -typedef EFI_STATUS (*BOOT_DRIVER_VISITOR)(const char* name, UINTN length, void* context); +typedef EFI_STATUS (*BOOT_DRIVER_VISITOR)(const char* id, const char* image, void* context); -// Visits raw driver filenames from the exact [boot] section without implementing general INI semantics. +// The firmware recognizes only the identity, image, and start fields in driver sections. +// Graph validation and all other configuration semantics belong to the kernel. static EFI_STATUS BootVisitDriverLines(const char* text, UINTN length, BOOT_DRIVER_VISITOR visitor, void* context) { - if (!text || !visitor) + if (!text || !visitor || length == 0 || length > 64u * 1024u) return EFI_INVALID_PARAMETER; - bool inBootSection = false; + char id[BOOT_FILE_NAME_MAX] = {0}; + char image[BOOT_FILE_NAME_MAX] = {0}; + bool driverSection = false; + bool bootPhase = false; UINTN offset = 0; - while (offset < length) { + while (offset <= length) { UINTN lineStart = offset; - while (offset < length && text[offset] != '\n') - ++offset; - UINTN lineEnd = offset; - if (offset < length) - ++offset; - + while (offset < length && text[offset] != '\n') ++offset; const char* begin = text + lineStart; - const char* end = text + lineEnd; + const char* end = text + offset; + if (offset < length) ++offset; + else ++offset; // Process the final section exactly once. BootTrimSpan(&begin, &end); - if (begin == end || *begin == ';' || *begin == '#') - continue; - + if (begin == end || *begin == ';' || *begin == '#') continue; if (*begin == '[') { - if (end[-1] != ']') - return EFI_INVALID_PARAMETER; - const char* sectionBegin = begin + 1; + if (driverSection && bootPhase) { + if (!id[0] || !image[0]) return EFI_INVALID_PARAMETER; + EFI_STATUS status = visitor(id, image, context); + if (EFI_ERROR(status)) return status; + } + driverSection = false; + bootPhase = false; + id[0] = '\0'; + image[0] = '\0'; + if (end[-1] != ']') return EFI_INVALID_PARAMETER; + const char* section = begin + 1; const char* sectionEnd = end - 1; - BootTrimSpan(§ionBegin, §ionEnd); - inBootSection = BootSpanEquals(sectionBegin, sectionEnd, "boot"); + if ((UINTN)(sectionEnd - section) > 7u && + memcmp(section, "driver.", 7u) == 0) { + section += 7; + UINTN n = (UINTN)(sectionEnd - section); + if (n == 0 || n >= sizeof(id)) return EFI_INVALID_PARAMETER; + memcpy(id, section, n); + id[n] = '\0'; + driverSection = true; + } continue; } - - if (!inBootSection) - continue; - for (const char* p = begin; p < end; ++p) { - if (*p == '=') - return EFI_INVALID_PARAMETER; + if (!driverSection) continue; + const char* equals = begin; + while (equals < end && *equals != '=') ++equals; + if (equals == end) return EFI_INVALID_PARAMETER; + const char* keyEnd = equals; + const char* value = equals + 1; + BootTrimSpan(&begin, &keyEnd); + BootTrimSpan(&value, &end); + for (const char* p = value; p < end; ++p) { + if (*p == '#' || *p == ';') { + end = p; + BootTrimSpan(&value, &end); + break; + } } - - EFI_STATUS status = visitor(begin, (UINTN)(end - begin), context); - if (EFI_ERROR(status)) - return status; + if (BootSpanEquals(begin, keyEnd, "image")) { + UINTN n = (UINTN)(end - value); + if (n == 0 || n >= sizeof(image)) return EFI_INVALID_PARAMETER; + memcpy(image, value, n); + image[n] = '\0'; + } else if (BootSpanEquals(begin, keyEnd, "start")) { + bootPhase = BootSpanEquals(value, end, "boot"); + } + } + if (driverSection && bootPhase) { + if (!id[0] || !image[0]) return EFI_INVALID_PARAMETER; + return visitor(id, image, context); } return EFI_SUCCESS; } @@ -905,14 +935,11 @@ typedef struct BOOT_DRIVER_COUNT_CONTEXT { uint32_t Count; } BOOT_DRIVER_COUNT_CONTEXT; -// Counts one boot-driver filename while checking the 32-bit handoff count for overflow. -static EFI_STATUS BootCountDriver(const char* name, UINTN length, void* context) { - (void)name; - if (!context || length == 0 || length >= BOOT_FILE_NAME_MAX) - return EFI_INVALID_PARAMETER; +static EFI_STATUS BootCountDriver(const char* id, const char* image, void* context) { + (void)id; + (void)image; BOOT_DRIVER_COUNT_CONTEXT* count = (BOOT_DRIVER_COUNT_CONTEXT*)context; - if (count->Count == UINT32_MAX) - return EFI_OUT_OF_RESOURCES; + if (count->Count == UINT32_MAX) return EFI_OUT_OF_RESOURCES; ++count->Count; return EFI_SUCCESS; } @@ -923,84 +950,64 @@ typedef struct BOOT_DRIVER_LOAD_CONTEXT { uint32_t Index; } BOOT_DRIVER_LOAD_CONTEXT; -// Loads one [boot] driver as a page-backed raw file and records its exact allocation. -static EFI_STATUS BootLoadDriver(const char* name, UINTN length, void* context) { +// Loads one identified boot image into retained physical pages. +static EFI_STATUS BootLoadDriver(const char* id, const char* image, void* context) { BOOT_DRIVER_LOAD_CONTEXT* load = (BOOT_DRIVER_LOAD_CONTEXT*)context; - if (!load || load->Index >= load->Capacity || length == 0 || length >= BOOT_FILE_NAME_MAX) + if (!load || load->Index >= load->Capacity) return EFI_INVALID_PARAMETER; + UINTN idLength = strlen(id); + UINTN length = strlen(image); + if (idLength == 0 || idLength >= BOOT_FILE_NAME_MAX || + length < 5 || length >= BOOT_FILE_NAME_MAX || + memcmp(image + length - 4, ".sys", 4) != 0) return EFI_INVALID_PARAMETER; - for (UINTN i = 0; i < length; ++i) { - unsigned char c = (unsigned char)name[i]; - if (c < 0x21u || c > 0x7Eu || name[i] == '/' || name[i] == '\\' || name[i] == '=') - return EFI_INVALID_PARAMETER; + char c = image[i]; + if (!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || + c == '-' || c == '_' || c == '.')) return EFI_INVALID_PARAMETER; } - - char asciiName[BOOT_FILE_NAME_MAX]; - memcpy(asciiName, name, length); - asciiName[length] = '\0'; - CHAR16 unicodeName[BOOT_FILE_NAME_MAX]; - if (AsciiStrToUnicodeStrS(asciiName, unicodeName, BOOT_FILE_NAME_MAX) != 0) + if (AsciiStrToUnicodeStrS(image, unicodeName, BOOT_FILE_NAME_MAX) != 0) return EFI_INVALID_PARAMETER; - CHAR16 path[128]; UnicodeSPrint(path, sizeof(path), L"drivers\\%s", unicodeName); - BOOT_BLOB blob = {0}; EFI_STATUS status = BootReadBlobFile(gSystemRoot, path, &blob); - if (EFI_ERROR(status)) - return status; + if (EFI_ERROR(status)) return status; if (blob.Length == 0) { gBS->FreePages(blob.PhysicalBase, blob.PageCount); return EFI_LOAD_ERROR; } - BOOT_FILE* file = &load->Files[load->Index++]; - AsciiStrCpyS(file->Name, sizeof(file->Name), asciiName); + AsciiStrCpyS(file->DriverId, sizeof(file->DriverId), id); + AsciiStrCpyS(file->Image, sizeof(file->Image), image); file->PhysicalBase = blob.PhysicalBase; file->Length = blob.Length; file->PageCount = blob.PageCount; return EFI_SUCCESS; } -// Builds the dynamic raw boot-driver descriptor list from drivers.ini [boot]. static EFI_STATUS BootLoadDrivers(const char* text, UINTN length, BOOT_FILE_LIST* listOut) { - if (!text || !listOut) - return EFI_INVALID_PARAMETER; - + if (!text || !listOut) return EFI_INVALID_PARAMETER; *listOut = (BOOT_FILE_LIST){0}; BOOT_DRIVER_COUNT_CONTEXT count = {0}; EFI_STATUS status = BootVisitDriverLines(text, length, BootCountDriver, &count); - if (EFI_ERROR(status)) - return status; - if (count.Count == 0) - return EFI_NOT_FOUND; - + if (EFI_ERROR(status)) return status; + if (!count.Count || count.Count > 64u) return EFI_INVALID_PARAMETER; UINTN descriptorBytes = (UINTN)count.Count * sizeof(BOOT_FILE); - if (descriptorBytes / sizeof(BOOT_FILE) != count.Count) - return EFI_OUT_OF_RESOURCES; UINTN pageCount = (descriptorBytes + PAGE_SIZE - 1u) / PAGE_SIZE; - if (pageCount == 0 || pageCount > UINT32_MAX) - return EFI_OUT_OF_RESOURCES; - EFI_PHYSICAL_ADDRESS physical = 0; status = BootAllocatePages(pageCount, &physical); - if (EFI_ERROR(status)) - return status; - + if (EFI_ERROR(status)) return status; BOOT_DRIVER_LOAD_CONTEXT load = {0}; load.Files = (BOOT_FILE*)physical; load.Capacity = count.Count; status = BootVisitDriverLines(text, length, BootLoadDriver, &load); if (EFI_ERROR(status) || load.Index != count.Count) { - for (uint32_t i = 0; i < load.Index; ++i) { - if (load.Files[i].PhysicalBase && load.Files[i].PageCount) - gBS->FreePages(load.Files[i].PhysicalBase, load.Files[i].PageCount); - } + for (uint32_t i = 0; i < load.Index; ++i) + gBS->FreePages(load.Files[i].PhysicalBase, load.Files[i].PageCount); gBS->FreePages(physical, pageCount); return EFI_ERROR(status) ? status : EFI_LOAD_ERROR; } - listOut->FilesPhysical = (uintptr_t)physical; listOut->Count = count.Count; listOut->PageCount = (uint32_t)pageCount; @@ -1543,17 +1550,15 @@ EFI_STATUS EFIAPI UefiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable return status; } - UINT8* driversIni = NULL; - UINTN driversIniSize = 0; - status = BootReadPoolFile(gSystemRoot, L"drivers\\drivers.ini", &driversIni, &driversIniSize); + status = BootReadBlobFile(gSystemRoot, L"drivers\\drivers.ini", &info->DriversIni); if (EFI_ERROR(status)) { Print(L"Mandatory drivers.ini could not be loaded: %r\n", status); return status; } - status = BootLoadDrivers((const char*)driversIni, driversIniSize, &info->BootDrivers); - gBS->FreePool(driversIni); + status = BootLoadDrivers((const char*)info->DriversIni.PhysicalBase, + info->DriversIni.Length, &info->BootDrivers); if (EFI_ERROR(status)) { - Print(L"drivers.ini [boot] is invalid or a boot driver could not be loaded: %r\n", status); + Print(L"drivers.ini preload failed: %r\n", status); return status; } diff --git a/code/build/build.c b/code/build/build.c index 176f3f8..1bb8841 100644 --- a/code/build/build.c +++ b/code/build/build.c @@ -48,18 +48,20 @@ typedef struct Driver_Target { const char* sources; const char* cflags; const char* libs; + const char* entry_flag; } Driver_Target; -#define DRIVER_TARGET(name_, obj_dir_, sources_) { (name_), (obj_dir_), (sources_), DRIVER_CFLAGS_LITERAL, "" } -#define DRIVER_TARGET_LIBS(name_, obj_dir_, sources_, libs_) { (name_), (obj_dir_), (sources_), DRIVER_CFLAGS_LITERAL, (libs_) } -#define DRIVER_TARGET_FLAGS(name_, obj_dir_, sources_, cflags_) { (name_), (obj_dir_), (sources_), (cflags_), "" } +#define DRIVER_TARGET(name_, obj_dir_, sources_) { (name_), (obj_dir_), (sources_), DRIVER_CFLAGS_LITERAL, "", "/NOENTRY" } +#define DRIVER_TARGET_LIBS(name_, obj_dir_, sources_, libs_) { (name_), (obj_dir_), (sources_), DRIVER_CFLAGS_LITERAL, (libs_), "/NOENTRY" } +#define DRIVER_TARGET_FLAGS(name_, obj_dir_, sources_, cflags_) { (name_), (obj_dir_), (sources_), (cflags_), "", "/NOENTRY" } +#define DRIVER_TARGET_DECLARATIVE(name_, obj_dir_, sources_) { (name_), (obj_dir_), (sources_), DRIVER_CFLAGS_LITERAL, "", "/NOENTRY" } static const Driver_Target PCI_BUS_DRIVER = DRIVER_TARGET_FLAGS("pci", "obj\\drivers\\pci", "code\\drivers\\bus\\pci\\pci.c", DRIVER_CFLAGS_LITERAL " /DPCI_SYS_BUILD"); static const Driver_Target STANDALONE_DRIVERS[] = { DRIVER_TARGET("ps2", "obj\\drivers\\ps2", "code\\drivers\\input\\ps2\\ps2.c code\\drivers\\input\\ps2\\ps2_i8042.c"), - DRIVER_TARGET("serial", "obj\\drivers\\serial", "code\\drivers\\input\\serial\\serial.c"), + DRIVER_TARGET_DECLARATIVE("serial", "obj\\drivers\\serial", "code\\drivers\\input\\serial\\serial.c"), DRIVER_TARGET_LIBS("hda", "obj\\drivers\\hda", "code\\drivers\\audio\\hda\\hda.c code\\drivers\\audio\\hda\\hda_transport.c code\\drivers\\audio\\hda\\hda_codec.c", "bin\\pci.lib"), 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"), @@ -73,7 +75,7 @@ static const Driver_Target STANDALONE_DRIVERS[] = { // Creates layout. void create_layout() { const char* dirs[] = { - "obj", "bin", "obj\\boot", "obj\\kernel", "obj\\lwip", "obj\\ext4", "obj\\ntfs3g", "obj\\hal", + "obj", "bin", "obj\\boot", "obj\\ddk", "obj\\kernel", "obj\\lwip", "obj\\ext4", "obj\\ntfs3g", "obj\\hal", "obj\\syscall", "obj\\runtime", "obj\\win32", "obj\\posix", "obj\\drivers", "obj\\init", "obj\\rssrv", "obj\\posixsrv", "obj\\dwm", "obj\\shell_win32", "obj\\shell_posix", "obj\\shell_native", "obj\\async_apc", "obj\\async_cancel", "obj\\apc_return_smoke", "obj\\edit", "obj\\httpget", @@ -114,7 +116,7 @@ bool clean_object_directory(const char* obj_dir) { // Cleans object directories that are linked through wildcards. bool clean_build_object_directories(void) { const char* dirs[] = { - "obj\\boot", "obj\\kernel", "obj\\lwip", "obj\\ext4", "obj\\hal", + "obj\\boot", "obj\\ddk", "obj\\kernel", "obj\\lwip", "obj\\ext4", "obj\\hal", "obj\\syscall", "obj\\runtime", "obj\\win32", "obj\\posix", "obj\\init", "obj\\rssrv", "obj\\posixsrv", "obj\\dwm", "obj\\shell_win32", "obj\\shell_posix", "obj\\shell_native", "obj\\async_apc", "obj\\async_cancel", "obj\\apc_return_smoke", "obj\\edit", "obj\\httpget", @@ -323,6 +325,11 @@ bool create_disk_vhd_if_missing(void) { // Runs the requested operation. int main(int argc, char** argv) { NOB_GO_REBUILD_URSELF(argc, argv); + bool with_tests = argc == 2 && strcmp(argv[1], "--with-tests") == 0; + if (argc != 1 && !with_tests) { + nob_log(NOB_ERROR, "Usage: build.exe [--with-tests]"); + return 1; + } if (!create_disk_vhd_if_missing()) return 1; if (!create_usbfatroot_if_missing()) return 1; @@ -378,7 +385,7 @@ int main(int argc, char** argv) { nob_temp_sprintf("cl %s /DNTL_HAL_BUILD /MP /c /Foobj\\hal\\ code\\kernel\\hal\\*.c && link %s /DLL /FIXED /DYNAMICBASE:NO /HIGHENTROPYVA:NO /RELEASE /BASE:0xFFFF800010000000 /OUT:bin\\hal.dll /NOENTRY /IGNORE:4216 obj\\printf.obj obj\\libc.obj obj\\hal\\*.obj", KernelCflags, LFLAGS), // Kernel. - nob_temp_sprintf("cl %s /DKE_DISPLAY_BUILD /MP /c /Foobj\\kernel\\ code\\kernel\\*.c code\\common\\boot_volume_selector.c && link %s /SUBSYSTEM:NATIVE /FIXED /DYNAMICBASE:NO /HIGHENTROPYVA:NO /RELEASE /BASE:0xFFFF800000000000 /ENTRY:KernelMain /OUT:bin\\kernel.exe bin\\hal.lib obj\\printf.obj obj\\libc.obj obj\\kernel\\*.obj", KernelCflags, LFLAGS), + nob_temp_sprintf("cl %s /DNTL_KERNEL_BUILD /DKE_DISPLAY_BUILD /MP /c /Foobj\\kernel\\ code\\kernel\\*.c code\\common\\boot_volume_selector.c && link %s /SUBSYSTEM:NATIVE /FIXED /DYNAMICBASE:NO /HIGHENTROPYVA:NO /RELEASE /BASE:0xFFFF800000000000 /ENTRY:KernelMain /MAP:bin\\kernel.map /OUT:bin\\kernel.exe bin\\hal.lib obj\\printf.obj obj\\libc.obj obj\\kernel\\*.obj", KernelCflags, LFLAGS), // Subsystem DLLs. "link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL " USER_IMAGE_FLAGS_LITERAL " /OUT:bin\\syscall.dll obj\\syscall\\stubs.obj obj\\syscall\\pe_loader.obj obj\\syscall\\module_loader.obj obj\\syscall\\process_loader.obj obj\\syscall\\resource.obj obj\\syscall\\input_service.obj obj\\syscall\\console_service.obj obj\\syscall\\desktop.obj obj\\syscall\\path.obj obj\\libc.obj", @@ -392,9 +399,14 @@ int main(int argc, char** argv) { if (!run_sync(core_tasks[i])) return 1; } + // The support library contains only driver-side helpers, never kernel policy. + if (!run_sync(nob_temp_sprintf( + "cl %s /MP /c /Foobj\\ddk\\ code\\ddk\\*.c && lib /nologo /OUT:bin\\ntlddk.lib obj\\ddk\\*.obj", + DRIVER_CFLAGS_LITERAL))) return 1; + nob_log(NOB_INFO, "--- Building Bus Drivers ---"); const char* pci_cmd = nob_temp_sprintf( - "cl %s /MP /c /Fo%s\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\%s.sys /IMPLIB:bin\\%s.lib /ENTRY:DriverEntry bin\\kernel.lib obj\\printf.obj obj\\libc.obj %s\\*.obj", + "cl %s /MP /c /Fo%s\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\%s.sys /IMPLIB:bin\\%s.lib /NOENTRY bin\\kernel.lib bin\\ntlddk.lib obj\\printf.obj obj\\libc.obj %s\\*.obj", PCI_BUS_DRIVER.cflags, PCI_BUS_DRIVER.obj_dir, PCI_BUS_DRIVER.sources, PCI_BUS_DRIVER.name, PCI_BUS_DRIVER.name, PCI_BUS_DRIVER.obj_dir ); @@ -406,9 +418,9 @@ int main(int argc, char** argv) { for (size_t i = 0; i < NOB_ARRAY_LEN(STANDALONE_DRIVERS); i++) { const Driver_Target* driver = &STANDALONE_DRIVERS[i]; const char* cmd = nob_temp_sprintf( - "cl %s /MP /c /Fo%s\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\%s.sys /ENTRY:DriverEntry bin\\kernel.lib %s obj\\libc.obj %s\\*.obj", + "cl %s /MP /c /Fo%s\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\%s.sys %s bin\\kernel.lib %s obj\\libc.obj %s\\*.obj", driver->cflags, driver->obj_dir, driver->sources, driver->name, - driver->libs ? driver->libs : "", driver->obj_dir + driver->entry_flag, driver->libs ? driver->libs : "", driver->obj_dir ); nob_da_append(&procs, run_async(cmd)); } @@ -425,7 +437,7 @@ int main(int argc, char** argv) { } } nob_sb_append_null(&lwip_files); - nob_da_append(&procs, run_async(nob_temp_sprintf("cl %s /MP /c /Foobj\\lwip\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\lwip.sys /ENTRY:DriverEntry bin\\kernel.lib obj\\libc.obj obj\\lwip\\*.obj", LwipCflags, lwip_files.items))); + nob_da_append(&procs, run_async(nob_temp_sprintf("cl %s /MP /c /Foobj\\lwip\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\lwip.sys /NOENTRY bin\\kernel.lib obj\\libc.obj obj\\lwip\\*.obj", LwipCflags, lwip_files.items))); } Nob_String_Builder ext4_sb = {0}, ext4_files = {0}; @@ -440,7 +452,7 @@ int main(int argc, char** argv) { } } nob_sb_append_null(&ext4_files); - nob_da_append(&procs, run_async(nob_temp_sprintf("cl %s /MP /c /Foobj\\ext4\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\ext4.sys /ENTRY:DriverEntry bin\\kernel.lib obj\\libc.obj obj\\ext4\\*.obj", Ext4Cflags, ext4_files.items))); + nob_da_append(&procs, run_async(nob_temp_sprintf("cl %s /MP /c /Foobj\\ext4\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\ext4.sys /NOENTRY bin\\kernel.lib obj\\libc.obj obj\\ext4\\*.obj", Ext4Cflags, ext4_files.items))); } Nob_String_Builder ntfs3g_sb = {0}, ntfs3g_files = {0}; @@ -455,7 +467,7 @@ int main(int argc, char** argv) { } } nob_sb_append_null(&ntfs3g_files); - nob_da_append(&procs, run_async(nob_temp_sprintf("cl %s /MP /c /Foobj\\ntfs3g\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\ntfs.sys /ENTRY:DriverEntry bin\\kernel.lib obj\\printf.obj obj\\libc.obj obj\\ntfs3g\\*.obj", Ntfs3gCflags, ntfs3g_files.items))); + nob_da_append(&procs, run_async(nob_temp_sprintf("cl %s /MP /c /Foobj\\ntfs3g\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\ntfs.sys /NOENTRY bin\\kernel.lib obj\\printf.obj obj\\libc.obj obj\\ntfs3g\\*.obj", Ntfs3gCflags, ntfs3g_files.items))); } @@ -537,6 +549,8 @@ int main(int argc, char** argv) { }; for (size_t i = 0; i < NOB_ARRAY_LEN(apps); i++) { + if (!with_tests && strncmp(apps[i][0], "tests\\", 6) == 0) + continue; const char* appCflags = strcmp(apps[i][3], "/ENTRY:PosixStart") == 0 ? PosixUserCflags : UserCflags; @@ -565,7 +579,7 @@ int main(int argc, char** argv) { mmd_dir(VHD_PART2, "apps"); mmd_dir(VHD_PART2, "system"); mmd_dir(VHD_PART2, "drivers"); - mmd_dir(VHD_PART2, "tests"); + if (with_tests) mmd_dir(VHD_PART2, "tests"); // The ESP is only the boot anchor; the configured system volume carries the OS payload. if (!mcopy_file("bin\\bootx64.efi", VHD_PART1, "EFI/BOOT/bootx64.efi")) return 1; @@ -684,7 +698,7 @@ int main(int argc, char** argv) { "bin\\surface_multithread_commit.exe", "bin\\surface_cross_process_smoke.exe", }; - if (!mcopy_files_to_dir(VHD_PART2, "tests/", test_files, NOB_ARRAY_LEN(test_files))) return 1; + if (with_tests && !mcopy_files_to_dir(VHD_PART2, "tests/", test_files, NOB_ARRAY_LEN(test_files))) return 1; const char* optional_app_files[3]; size_t optional_app_count = 0; diff --git a/code/ddk/pci_caps.c b/code/ddk/pci_caps.c new file mode 100644 index 0000000..4c06b2b --- /dev/null +++ b/code/ddk/pci_caps.c @@ -0,0 +1,34 @@ +// Pure DDK support library routines; no kernel or module-wide state. + +#include + +KSTATUS NtlPciFindCapability(const NTL_PCI_CONFIG_READER* reader, + uint8_t firstOffset, + uint8_t capabilityId, + uint8_t* outOffset) { + if (!reader || !reader->Read32 || !outOffset || !capabilityId) + return NTLSTATUS_INVALID_PARAMETER; + + *outOffset = 0; + uint8_t current = firstOffset; + uint64_t visited = 0; + + while (current) { + if (current < 0x40u || (current & 3u) != 0u) + return NTLSTATUS_DEVICE_ERROR; + + uint64_t bit = 1ull << (current / 4u - 16u); + if (visited & bit) + return NTLSTATUS_DEVICE_ERROR; + visited |= bit; + + uint32_t header = reader->Read32(reader->Context, current); + if ((uint8_t)header == capabilityId) { + *outOffset = current; + return NTLSTATUS_SUCCESS; + } + current = (uint8_t)((header >> 8) & 0xFCu); + } + + return NTLSTATUS_NOT_FOUND; +} diff --git a/code/docs/services.md b/code/docs/services.md deleted file mode 100644 index e7c440f..0000000 --- a/code/docs/services.md +++ /dev/null @@ -1,222 +0,0 @@ -# Native service architecture - -This document defines the intended user-mode service model for nt-lite. It is an architectural contract, not a description of every detail of the current implementation. Code should move toward this model without introducing temporary service boundaries that are not meant to survive. - -## Process roles - -`init.exe` is the kernel-selected bootstrap user process and user-mode service supervisor. Its process identifier is deliberately not an ABI: it happens to be allocated early today, but code, configuration, diagnostics, and personalities must not assume a particular PID for init. - -`init.exe` is not a UNIX init process: it does not imply a kernel process hierarchy, it does not adopt orphaned processes, and it does not provide reparenting semantics. Holding a process handle for supervision does not make the supervised process a child in a UNIX sense. - -The intended user-mode system plane has four roles: - -- `init.exe` owns service lifecycle and authority delegation. -- `rssrv.exe` is the critical native system server. -- Session-owned infrastructure such as `dwm.exe` is noncritical and owns bulky policy that should not make RSSRV monolithic. -- Subsystem-specific personality servers, such as `posixsrv.exe`, are optional and noncritical to the native system. - -Do not split native input, resource naming, display authority, or console ownership into one service process per subsystem merely to isolate components. nt-lite is a hybrid kernel. RSSRV is intentionally a substantial native server, but it is not the desktop compositor: the desktop window manager and composition engine belong in a separate `dwm.exe` process so window-tree, z-order, damage, decoration, and composition policy can evolve without expanding RSSRV into the whole graphical environment. - -## Kernel and RSSRV ownership - -The kernel owns mechanisms that require kernel authority or hardware access. The bootstrap service manager receives explicit manager capabilities for the native resource plane and delegates attenuated input, display, volume, and namespace authority to RSSRV; this authority must not depend on init's PID. This includes: - -- process and thread execution; -- virtual memory and sections; -- object handles and capabilities; -- ports and other IPC primitives; -- filesystem and storage drivers; -- physical input drivers and raw input-device objects; -- display miniports, low-level display devices, framebuffer or VRAM mappings, and hardware presentation primitives; -- kernel surfaces and other graphics primitives whose implementation belongs below user-mode policy. - -RSSRV owns shared native user-mode policy and system-visible logical resources. Its intended responsibilities include: - -- the global native object namespace; -- namespace directories, bindings, enumeration, and symbolic links; -- publication of mounted volumes and other native resources; -- logical keyboard and mouse ownership above raw device input; -- keyboard state and layout policy where it does not belong in the hardware driver; -- logical pointer state, input focus, capture, and routing; -- native console objects, console buffers, console input, and active-console policy; -- interactive-session state; -- the session's display metadata, modeset policy, and transitional console authority; -- authority and routing used by the session desktop/window manager, without owning the window tree or compositor. - -RSSRV owns the logical object and its policy; it does not replace the kernel driver or hardware object. For example, a keyboard driver remains a kernel component, while RSSRV consumes normalized key events and owns the logical keyboard state exposed to native user mode. - -RSSRV should remain internally modular even though its native resource facilities share one process and failure domain. Separate namespace, volume, input, console, session, and display components are preferred over separate user-mode services. Desktop/window/composition policy is the exception: it has enough independent bulk and failure behavior to live in `dwm.exe`. - -The implemented RSSRV foundation uses one root `RSSRV_STATE` and passes explicit component state to worker threads instead of relying on subsystem globals. The namespace tree lives in `namespace.c`, mounted-volume publication in `volume.c`, resource protocol/listener handling in `resource_service.c`, Session 0 policy in `session.c`, and `rssrv.c` is limited to bootstrap, service control, and component lifetime. RSSRV receives attenuated kernel input-manager and display-manager capabilities in dedicated input/display component state rather than relying on ambient process privilege. - -Logical input is now an RSSRV-owned facility. The kernel input manager exposes a capability-scoped stream of normalized physical key make/break and relative mouse events. RSSRV consumes that stream, owns key-down/modifier/lock state, performs the initial keyboard-layout character mapping, and owns logical pointer position and mouse-button/wheel state. Session 0 owns routing policy above that logical state: one active target, keyboard focus, ordinary pointer focus, and optional pointer capture. Keyboard and pointer dispatch snapshot the selected target before calling it, so target-specific queues do not share or race the raw logical-input queue. Session 0 owns logical pointer state and routing, while DWM mirrors that authoritative position to the visible hardware cursor through its separately delegated display-manager capability. RSSRV publishes the raw logical listener at `/Services/Input`; normal native applications and the current POSIX/Win32 personality bindings may consume that service through the `NtlInput*` client API, while the console receives a separately routed focused-input queue. The old global input syscalls remain a transitional native ABI for existing callers and contract tests, but they are no longer the canonical interactive input path and should be deleted after those callers migrate. - -RSSRV owns logical typematic timing for the Session 0 input path. Repeated physical make events are treated as idempotent there so PS/2 hardware repeat and USB HID share one logical repeat policy. The legacy kernel-global input path retains its existing kernel repeat timer only for legacy consumers that have not moved to RSSRV routing. - -## Desktop window manager - -`dwm.exe` is the Session 0 desktop window manager and compositor. It is not a personality server and it is not a replacement for RSSRV. RSSRV remains the authority broker for the interactive session: it owns Session 0, logical input routing, console ownership, and the namespace objects through which the session is named. init owns bootstrap delegation of the display-manager capability and splits its rights between RSSRV and DWM. - -DWM owns graphical policy that would otherwise make RSSRV monolithic: - -- the desktop and window tree; -- z-order and visibility; -- window bounds and decorations; -- dirty-region aggregation and composition scheduling; -- composition of client surfaces into the desktop surface; -- later animation and graphical effects. - -DWM must not receive ambient display privilege. init delegates an attenuated display-manager capability carrying query, physical-present, and cursor rights only; RSSRV receives the disjoint mode-setting and transitional-console authority it still needs. Mode-setting remains outside DWM unless a later design gives it a concrete reason to own that policy. A DWM crash must not take RSSRV down; native namespace, input, console, and session authority remain alive even if the graphical desktop disappears. DWM becomes eligible for `restart=on-failure` only after its session reconstruction protocol is defined, and then it is subject to the same three-restarts-in-any-sliding-five-minute-window rule as every other restartable managed process. - -DWM also owns visible pointer presentation policy independently from RSSRV input routing. The native desktop model must support a free cursor, a cursor confined to a desktop/window rectangle, and a relative-pointer lock suitable for games. Relative lock hides and pins the hardware cursor while preserving RSSRV's relative mouse deltas for the captured input target; it must not synthesize motion by repeatedly warping the logical pointer. Cursor visibility is an explicit DWM policy bit rather than a side effect of focus or capture. RSSRV pointer capture chooses the input recipient, while DWM confinement/relative-lock controls only presentation. The later desktop/window protocol will authorize focused clients to request these transitions without granting them DisplayManager cursor authority. - -## Personality servers - -A personality server exists only for state or policy that is specific to one application personality and must be shared between processes. - -Personality servers are not the owners of generic hardware or native session resources. They consume native facilities exposed by the kernel and RSSRV. - -`posixsrv.exe` may eventually own POSIX-specific shared state such as: - -- POSIX credentials and supplementary groups; -- POSIX process-group and session identifiers; -- cross-process POSIX signal state and routing; -- the synthetic POSIX filesystem metadata overlay; -- POSIX FIFO metadata; -- passwd and group database state. - -POSIX does not cause the kernel to acquire a UNIX process hierarchy. A POSIX launch relationship may be tracked by the POSIX subsystem when required by an exposed API, but there is no reparenting to a distinguished process and no orphan adoption. In particular, POSIX must not infer special semantics from whatever PID the kernel assigned to `init.exe`. - -A Win32 personality server should not be created merely for symmetry. If shared Win32-only state eventually requires a server, `winsrv.exe` is the preferred role name. Native console, input, session, and display ownership remains in RSSRV rather than moving into a Win32 server. - -## Service discovery - -The numeric kernel system-service registry is bootstrap infrastructure, not the general service namespace. - -`SYS_SERVICE_RESOURCE` exists so processes can find RSSRV before a higher-level namespace is available. New ordinary services should not receive additional numeric kernel service identifiers merely for discovery. - -Once RSSRV is available, named services and logical system resources should be published through the RSSRV namespace. A future namespace may contain entries such as: - -```text -/ -├── Device -│ ├── Keyboard0 -│ ├── Mouse0 -│ ├── Display0 -│ └── HarddiskVolume1 -├── Services -│ ├── Input -│ ├── Console -│ └── Posix -├── Sessions -│ └── 0 -│ ├── Console -│ └── Desktop -├── SystemRoot -> /Device/HarddiskVolume1 -└── Console -> /Sessions/0/Console -``` - -The current namespace foundation materializes `/Device`, `/Services`, and `/Sessions` as real RSSRV namespace directories. `/SystemRoot` is a real RSSRV symbolic link to the independently published `/Device/HarddiskVolumeN` object for the bootstrap system volume. `/Device/Display0` is a query-only display-manager child capability: init delegates mode-setting and transitional console authority to RSSRV while physical-present and cursor authority are delegated separately to DWM. `/Services/Input` and `/Services/Console` are bound RSSRV listener capabilities for the raw logical-input and native-console protocols. Session 0 is materialized as `/Sessions/0`, with `/Sessions/0/Desktop` reserved for the userspace desktop/window-manager object and `/Sessions/0/Console` bound to the current console listener. `/Console` is a symbolic link to `/Sessions/0/Console`. Namespace directories are enumerable through the resource protocol; native shell `dir` and tab completion use that enumeration rather than synthesizing names from filesystem paths. - -Names added later are not ABI until code deliberately makes them so. The important rule is that RSSRV is the canonical named namespace after bootstrap. - -## Service configuration and supervision - -`init.exe` owns service lifecycle. The mandatory `system/services.ini` file, packaged next to `settings.ini`, defines stable service policy. The kernel does not parse this file. `init.exe` reads it directly through the bootstrap system-volume capability before RSSRV starts, so locating RSSRV never depends on RSSRV itself. - -The configuration uses case-sensitive `[service.]` sections. Every service section must define all of these keys: - -```ini -[service.rssrv] -type=resource -image=system/rssrv.exe -start=auto -critical=true -restart=never -ready_timeout_ms=5000 -stop_timeout_ms=5000 -requires= -``` - -The parser is deliberately strict. Unknown or duplicate keys, duplicate service identifiers, malformed values, missing required keys, missing dependencies, dependency cycles, and an automatic service depending on a disabled service are fatal configuration errors. The file has no includes, quoting language, environment expansion, continuation lines, or implicit fallback descriptor. A missing or malformed `services.ini` is fatal rather than causing init to fall back to a compiled duplicate of service policy. - -`type` selects an audited service adapter whose bootstrap authority remains compiled into init. Configuration is not a capability language. In particular, the resource adapter owns the restricted security context and startup capabilities required by RSSRV; `services.ini` cannot grant arbitrary privileges or handles. The generic adapter is intentionally narrow: the service receives only its private service-control listener, runs under the shared unprivileged service context, exports exactly one public Port listener in READY, and init publishes that listener at `/Services/` through RSSRV. Automatic generic services must be noncritical, `restart=never`, and explicitly depend on RSSRV. `posixsrv.exe` is the first consumer of this adapter. - -Exactly one resource service must be configured. While resource-server reconstruction is unsupported it must be automatic, critical, dependency-free, and `restart=never`. A future implementation may relax only the restart restriction once recovery semantics exist. - -Managed services use the parsed descriptor and a runtime record. The runtime record owns the current process handle, control connection, and lifecycle state. Service dependencies are resolved to descriptor indices and dependency-first startup order is computed while validating the configuration. - -The private service-control READY protocol is generic. A READY notification reports a status and the number of transferred export handles; the meaning and expected count of those handles come from the compiled service adapter. RSSRV exports its init-only administrative resource listener plus the Session 0 control listener delegated onward to DWM. DWM exports one desktop listener for init to publish at `/Sessions/0/Desktop`. A generic service exports one public listener for `/Services/`. The protocol does not encode service-specific READY flags. - -The configured interactive session process is not a managed service. It is launched and observed separately from the service table. `dwm.exe`, by contrast, is an explicit noncritical `[service.dwm]`: it is launched by init in dependency order after RSSRV, receives no ambient security privilege, and exports no machine-global endpoint. Its initial bootstrap owns the visible hardware cursor; later compositor/window protocol work remains session policy inside that same service process. - -The service manager should remain small. Restart accounting, additional startup capabilities, and recovery policy should be added only where they have a concrete consumer. - -A service is expected to move through a small set of states such as: - -```text -stopped -> starting -> ready -> stopping -> stopped - \-> failed -``` - -A service becomes ready only after its authoritative state and exported capabilities are usable. Process creation alone is not readiness. - -A normal requested stop is not a crash and must not consume restart budget. - -## Automatic restart policy - -Automatic restart is opt-in for each service. A service must not be marked restartable until its authoritative state, exported capabilities, and dependent clients have a defined recovery model. - -When automatic restart is enabled for a failed or crashed service, `init.exe` applies a sliding five-minute restart window: - -1. Remove recorded automatic-restart timestamps that are at least five minutes old. -2. If three automatic restarts remain in the window, suppress another automatic restart and leave the service in the failed state. -3. Otherwise record the new restart attempt and restart the service. - -This permits at most three automatic restart attempts in any sliding five-minute interval. It is not a fixed five-minute bucket and it does not reset on wall-clock boundaries. - -A restart attempt is counted when `init.exe` commits to an automatic restart after a failure, not merely when the failed process is observed. Startup failure of the replacement still consumes that attempt. - -Dependency-driven recovery caused by a service failure counts against the failed service's restart budget. A deliberate stop, shutdown, or explicit administrative start does not count as an automatic restart. - -The default policy is no automatic restart until reconstruction semantics are defined. In particular, RSSRV is critical and must not be blindly restarted as though its namespace and session state survived. If RSSRV is ever made restartable, recovery must explicitly rebuild the native service plane and any user session state that cannot survive the loss of RSSRV. - -The three-restarts-in-five-minutes rule is an invariant of `restart=on-failure`, not a configurable knob. The current shipped service set contains no automatically restartable service, so init does not yet need runtime restart accounting. RSSRV is `restart=never`; an unexpected RSSRV exit is a terminal critical-service failure. The interactive session process is not a service and its separate restart behavior is outside this service restart budget. - -## Failure domains - -RSSRV is a critical native service because it is intended to own authoritative namespace and session state. Loss of RSSRV is therefore a native service-plane failure, not an ordinary personality failure. If a critical service reaches an unrecoverable failed state, init emits a fatal diagnostic and exits; the kernel already treats unexpected init termination as fatal. - -Personality servers are noncritical to the native system. Failure of `posixsrv.exe`, for example, may invalidate POSIX shared state and require affected POSIX processes to fail or be terminated, but it must not make native RSSRV facilities unavailable. - -Clients must not silently treat a newly started service instance as the same stateful server unless the protocol explicitly supports reconstruction. A restarted service instance has a new generation of authoritative state unless a future design specifies otherwise. - -## Design constraints - -When extending this model: - -- keep hardware mechanism in the kernel and shared native user-mode policy in RSSRV; -- do not create a service merely because a subsystem can be isolated behind IPC; -- do not duplicate a native resource owner inside multiple personality servers; -- keep the numeric kernel service registry bootstrap-only; -- keep service authority capability-based rather than identity checks against process names; -- define recovery semantics before enabling automatic restart; -- prefer one canonical path to a service or resource and remove transitional aliases once the real model works. - -## Native console ownership - -RSSRV owns the one logical native console used before multi-session support exists. The console is an RSSRV component, not a separate service process. It is published as `/Services/Console` so native clients and personality libraries use one canonical console protocol. - -The console component currently serializes output, cursor movement, fills, explicit glyph drawing, clear operations, and logical keyboard input. It owns a monotonically increasing content generation and a focused key queue populated by Session 0 routing. The input service remains available for applications that need the raw logical keyboard or pointer stream rather than console semantics. - -The kernel text console remains a transitional rendering backend. RSSRV reaches it only through the delegated display-manager capability and `SYS_DISPLAY_MANAGER_RIGHT_CONSOLE`; RSSRV does not receive `SYS_PRIV_CONSOLE_ACCESS`. The capability-scoped console bridge exists so normal user applications can migrate away from ambient console syscalls before the kernel text-buffer implementation is removed or reduced to early-boot/panic use. - -Normal native and personality console code should use `NtlConsole*`. The old `SysWriteConsole`, `SysGetConsoleInfo`, `SysSetConsoleCursor`, fill, clear, and direct glyph syscalls remain supported temporarily for bootstrapping, diagnostics, and regression tests. Their existence is not a commitment to keep two console APIs. Once DWM session/window composition can replace the kernel's interactive text-console path, remaining ordinary callers should move and the stale ambient syscalls should be deleted. - -Session 0 now exists as explicit RSSRV state. The console registers a stable in-process input target with that session and is the initial active target, keyboard focus, and pointer focus. Pointer capture is represented separately so the later userspace window manager can capture pointer delivery without changing the raw input service or console protocol. The console owns its own focused key queue; `/Services/Input` remains a raw logical observer queue rather than competing with console reads for the same records. - -`/Services/Console` names the process-independent console protocol, `/Sessions/0/Console` names the Session 0 instance, and `/Console` is the convenience symbolic link to the current session console. `/Sessions/0/Desktop` is currently only a namespace directory; the later DWM phase will replace that placeholder with the first real desktop policy object. - -Display authority is split explicitly at bootstrap. RSSRV retains mode-setting and transitional console rights, while init delegates physical-present and hardware-cursor rights to the noncritical DWM service. The published `/Device/Display0` capability remains intentionally query-only. Neither RSSRV nor ordinary applications need ambient `SYS_PRIV_DISPLAY_ACCESS` for the normal path. - -Kernel surfaces are now shareable capability-backed pixel storage rather than process-private presentation objects. A surface may have multiple independent VAD mappings in one or more processes; mapping rights distinguish read-only compositor access from client write access. `SysCommitSurface` records clipped damage, increments a content generation, and signals the waitable surface object without touching physical scanout. Surface wakeups are deliberately coalescing: consumers use the generation to detect skipped commits, and `LastDamageBounds` describes only the most recent commit rather than an acknowledged damage queue. The later DWM window protocol will carry the compositor's authoritative dirty-region submission. `SysPresentDisplayManagerSurface` is the capability-scoped physical-present path intended for DWM. The old ambient `SysPresentSurface` API and its handle right are deleted. Ordinary clients can create, map, share, wait on, and commit surfaces but cannot touch physical scanout. `SysPresentDisplayManagerSurface` is the only surface-to-scanout path and requires an explicitly delegated DisplayManager PRESENT capability. diff --git a/code/drivers/audio/hda/hda.c b/code/drivers/audio/hda/hda.c index 809e758..8c4955d 100644 --- a/code/drivers/audio/hda/hda.c +++ b/code/drivers/audio/hda/hda.c @@ -1,10 +1,10 @@ +#include // Intel High Definition Audio PCI attachment and controller lifetime management. #include "hda.h" #include -const NTL_DDK* gHdaDdk; static DRIVER_OBJECT gHdaDriver; static HDA_CONTROLLER* gHdaControllers[HDA_MAX_CONTROLLERS]; static uint32_t gHdaControllerCount; @@ -157,7 +157,7 @@ static KSTATUS HdaConfigureInterrupt(HDA_CONTROLLER* controller) { return NTLSTATUS_SUCCESS; } - gHdaDdk->Debug->Print( + DbgPrint( "HDA: MSI setup failed status=0x%x; attempting explicit INTx fallback\n", status); controller->UsingMsi = false; @@ -182,7 +182,7 @@ static KSTATUS HdaWaitForControllerInterruptWorkIdle(HDA_CONTROLLER* controller) if (controller->IsrActive == 0 && controller->Dpc.Inserted == 0 && controller->DpcActive == 0) return NTLSTATUS_SUCCESS; - gHdaDdk->Timer->Stall(10); + KeStallExecution(10); } return NTLSTATUS_TIMEOUT; } @@ -268,7 +268,7 @@ static KSTATUS HdaStartDevice(HDA_CONTROLLER* controller) { return status; } if (!KSUCCESS(status)) { - gHdaDdk->Debug->Print( + DbgPrint( "HDA: no analog endpoint registered status=0x%x\n", status); } @@ -379,14 +379,9 @@ static KSTATUS HdaAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDevice } // Registers the Intel HDA transport driver with the I/O manager. -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { - (void)init; - if (!ddk || !ddk->Debug || !ddk->Debug->Print || !ddk->Dma || !ddk->Timer || - !ddk->Timer->QueryTicks || !ddk->Timer->Stall || !ddk->Audio || - !ddk->Scheduler || !ddk->Scheduler->DelayThread) - return NTLSTATUS_INVALID_PARAMETER; +static KSTATUS DriverInitialize(NTL_MODULE* module) { + (void)module; - gHdaDdk = ddk; gHdaControllerCount = 0; memset(gHdaControllers, 0, sizeof(gHdaControllers)); @@ -395,9 +390,8 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { gHdaDriver.IdTable = &gHdaIdTable; gHdaDriver.DriverAddDevice = HdaAddDevice; gHdaDriver.MajorFunction[IRP_MJ_PNP] = HdaDispatchPnp; - - KSTATUS status = IoRegisterDriverObject(&gHdaDriver); - if (!KSUCCESS(status)) - gHdaDdk->Debug->Print("HDA: IoRegisterDriverObject failed status=0x%x\n", status); - return status; + return NTLSTATUS_SUCCESS; } + +static DRIVER_OBJECT* const gModuleIoDrivers[] = { &gHdaDriver }; +NTL_DECLARE_IO_MODULE(DriverInitialize, gModuleIoDrivers); diff --git a/code/drivers/audio/hda/hda.h b/code/drivers/audio/hda/hda.h index 4621c1c..796e2f4 100644 --- a/code/drivers/audio/hda/hda.h +++ b/code/drivers/audio/hda/hda.h @@ -300,8 +300,6 @@ struct HDA_CONTROLLER { uint64_t ControllerInterruptCount; }; -extern const NTL_DDK* gHdaDdk; - // Returns one byte from the controller register aperture. uint8_t HdaRead8(const HDA_CONTROLLER* controller, uint32_t offset); diff --git a/code/drivers/audio/hda/hda_codec.c b/code/drivers/audio/hda/hda_codec.c index 7a7f7ca..f3a5c9f 100644 --- a/code/drivers/audio/hda/hda_codec.c +++ b/code/drivers/audio/hda/hda_codec.c @@ -281,7 +281,7 @@ static KSTATUS HdaCodecPowerD0(HDA_CODEC_STATE* state, if ((powerState & HDA_POWER_ACTUAL_MASK) == (HDA_POWER_STATE_D0 << HDA_POWER_ACTUAL_SHIFT)) return NTLSTATUS_SUCCESS; - gHdaDdk->Timer->Stall(10); + KeStallExecution(10); } return NTLSTATUS_TIMEOUT; } @@ -583,7 +583,7 @@ static KSTATUS HdaCodecEnumerateAll(HDA_CODEC_STATE* state) { continue; } lastStatus = status; - gHdaDdk->Debug->Print( + DbgPrint( "HDA: codec %u topology discovery failed status=0x%x\n", codecAddress, status); @@ -1176,7 +1176,7 @@ static void HdaCodecRefreshEndpointsLocked(HDA_CODEC_STATE* state) { if (endpoint->Available != available) { endpoint->Available = available; if (endpoint->KernelEndpoint) - gHdaDdk->Audio->SetEndpointAvailable(endpoint->KernelEndpoint, + IoAudioSetEndpointAvailable(endpoint->KernelEndpoint, available ? 1u : 0u); } if (!endpoint->Running || endpoint->Starting || @@ -1210,13 +1210,13 @@ static void HdaCodecRefreshEndpointsLocked(HDA_CODEC_STATE* state) { uintptr_t flags = KeAcquireSpinlock(&state->CallbackLock); endpoint->Running = false; KeReleaseSpinlock(&state->CallbackLock, flags); - gHdaDdk->Debug->Print( + DbgPrint( "HDA: jack reroute failed kind=%u route=%u status=0x%x\n", endpoint->Kind, selectedRoute, status); if (endpoint->KernelEndpoint) { - gHdaDdk->Audio->ReportEndpointFailure(endpoint->KernelEndpoint, + IoAudioReportEndpointFailure(endpoint->KernelEndpoint, status, 0); } @@ -1306,7 +1306,7 @@ static void HdaCodecWorkThread(uintptr_t argument) { static void HdaCodecPollThread(uintptr_t argument) { HDA_CODEC_STATE* state = (HDA_CODEC_STATE*)argument; while (state->PollRunning) { - (void)gHdaDdk->Scheduler->DelayThread(HDA_JACK_POLL_INTERVAL_MS); + (void)KeDelayThread(HDA_JACK_POLL_INTERVAL_MS); if (!state->PollRunning) break; uintptr_t flags = KeAcquireSpinlock(&state->CallbackLock); @@ -1355,7 +1355,7 @@ static void HdaCodecPeriodComplete(void* context, endpoint = state->ActiveEndpoint->KernelEndpoint; KeReleaseSpinlock(&state->CallbackLock, flags); if (endpoint) { - gHdaDdk->Audio->NotifyPeriod(endpoint, + IoAudioNotifyPeriod(endpoint, completedPeriodIndex, presentedFrames, finePositionFrames, @@ -1453,7 +1453,7 @@ static void HdaCodecTransportFailure(void* context, KSTATUS failureStatus) { KeReleaseSpinlock(&state->CallbackLock, flags); for (uint8_t index = 0; index < state->EndpointCount; index++) { if (state->Endpoints[index].KernelEndpoint) { - gHdaDdk->Audio->ReportEndpointFailure( + IoAudioReportEndpointFailure( state->Endpoints[index].KernelEndpoint, failureStatus, 0); @@ -1747,7 +1747,7 @@ static KSTATUS HdaCodecRegisterEndpoints(HDA_CODEC_STATE* state) { } } - status = gHdaDdk->Audio->RegisterEndpoint(®istration, + status = IoAudioRegisterEndpoint(®istration, &endpoint->KernelEndpoint); if (!KSUCCESS(status)) return status; @@ -1779,7 +1779,7 @@ static void HdaCodecStopWorkers(HDA_CODEC_STATE* state) { // Discovers codec topology and registers usable analog render endpoints. KSTATUS HdaCodecInitialize(HDA_CONTROLLER* controller) { - if (!controller || !gHdaDdk || !gHdaDdk->Audio || !gHdaDdk->Scheduler) + if (!controller) return NTLSTATUS_INVALID_PARAMETER; HDA_CODEC_STATE* state = (HDA_CODEC_STATE*)MmAllocatePoolTag( sizeof(HDA_CODEC_STATE), @@ -1801,7 +1801,7 @@ KSTATUS HdaCodecInitialize(HDA_CONTROLLER* controller) { goto failed; status = HdaCodecBuildRoutes(state); if (!KSUCCESS(status)) { - gHdaDdk->Debug->Print( + DbgPrint( "HDA: no usable 48000 Hz, stereo, 16-bit analog converter-to-pin route\n"); goto failed; } @@ -1873,7 +1873,7 @@ void HdaCodecStop(HDA_CONTROLLER* controller, IO_AUDIO_REMOVAL_MODE mode) { HDA_LOGICAL_ENDPOINT* endpoint = &state->Endpoints[index - 1u]; if (!endpoint->KernelEndpoint) continue; - gHdaDdk->Audio->UnregisterEndpoint(endpoint->KernelEndpoint, mode); + IoAudioUnregisterEndpoint(endpoint->KernelEndpoint, mode); endpoint->KernelEndpoint = (IO_AUDIO_ENDPOINT*)0; } diff --git a/code/drivers/audio/hda/hda_transport.c b/code/drivers/audio/hda/hda_transport.c index 1d9ce4a..4baa59e 100644 --- a/code/drivers/audio/hda/hda_transport.c +++ b/code/drivers/audio/hda/hda_transport.c @@ -16,15 +16,12 @@ static __forceinline void HdaMemoryBarrier(void) { // Returns the controller's monotonic microsecond counter. static uint64_t HdaQueryTicks(void) { - if (!gHdaDdk || !gHdaDdk->Timer || !gHdaDdk->Timer->QueryTicks) - return 0; - return gHdaDdk->Timer->QueryTicks(); + return KeQueryTicks(); } // Delays for a bounded controller settling interval. static void HdaStall(uint32_t microseconds) { - if (gHdaDdk && gHdaDdk->Timer && gHdaDdk->Timer->Stall) - gHdaDdk->Timer->Stall(microseconds); + KeStallExecution(microseconds); } // Returns one byte from the controller register aperture. @@ -170,7 +167,7 @@ static uint8_t HdaRingSizeSelector(uint16_t count) { // Releases all DMA and synchronization resources owned by one controller. void HdaReleaseDma(HDA_CONTROLLER* controller) { - if (!controller || !gHdaDdk || !gHdaDdk->Dma) + if (!controller) return; if (controller->StreamMutex) { @@ -190,12 +187,12 @@ void HdaReleaseDma(HDA_CONTROLLER* controller) { controller->RecoveryFinished = (EVENT*)0; } - gHdaDdk->Dma->FreeCommonBuffer(controller->Output.BdlDma); + IoDmaFreeCommonBuffer(controller->Output.BdlDma); for (uint32_t ringIndex = 0; ringIndex < HDA_MAX_LOGICAL_ENDPOINTS; ringIndex++) - gHdaDdk->Dma->FreeCommonBuffer(controller->Output.Rings[ringIndex].Dma); - gHdaDdk->Dma->FreeCommonBuffer(controller->PositionDma); - gHdaDdk->Dma->FreeCommonBuffer(controller->RirbDma); - gHdaDdk->Dma->FreeCommonBuffer(controller->CorbDma); + IoDmaFreeCommonBuffer(controller->Output.Rings[ringIndex].Dma); + IoDmaFreeCommonBuffer(controller->PositionDma); + IoDmaFreeCommonBuffer(controller->RirbDma); + IoDmaFreeCommonBuffer(controller->CorbDma); controller->Output.BdlDma = (IO_DMA_BUFFER*)0; for (uint32_t ringIndex = 0; ringIndex < HDA_MAX_LOGICAL_ENDPOINTS; ringIndex++) { controller->Output.Rings[ringIndex].Dma = (IO_DMA_BUFFER*)0; @@ -211,14 +208,14 @@ void HdaReleaseDma(HDA_CONTROLLER* controller) { controller->Corb = (uint32_t*)0; if (controller->DmaAdapter) { - gHdaDdk->Dma->PutAdapter(controller->DmaAdapter); + IoDmaPutAdapter(controller->DmaAdapter); controller->DmaAdapter = (IO_DMA_ADAPTER*)0; } } // Allocates the transport and output DMA resources for one controller. KSTATUS HdaAllocateDma(HDA_CONTROLLER* controller) { - if (!controller || !controller->Fdo || !gHdaDdk || !gHdaDdk->Dma) + if (!controller || !controller->Fdo) return NTLSTATUS_INVALID_PARAMETER; IO_DMA_REQUIREMENTS requirements; @@ -229,7 +226,7 @@ KSTATUS HdaAllocateDma(HDA_CONTROLLER* controller) { requirements.Alignment = 128u; requirements.MaximumSegments = 1u; - KSTATUS status = gHdaDdk->Dma->GetAdapter(controller->Fdo, + KSTATUS status = IoDmaGetAdapter(controller->Fdo, &requirements, &controller->DmaAdapter); if (!KSUCCESS(status)) @@ -248,7 +245,7 @@ KSTATUS HdaAllocateDma(HDA_CONTROLLER* controller) { return status; } - status = gHdaDdk->Dma->AllocateCommonBuffer(controller->DmaAdapter, + status = IoDmaAllocateCommonBuffer(controller->DmaAdapter, (size_t)controller->CorbEntryCount * sizeof(uint32_t), 128u, &controller->CorbDma); @@ -256,7 +253,7 @@ KSTATUS HdaAllocateDma(HDA_CONTROLLER* controller) { HdaReleaseDma(controller); return status; } - status = gHdaDdk->Dma->AllocateCommonBuffer(controller->DmaAdapter, + status = IoDmaAllocateCommonBuffer(controller->DmaAdapter, (size_t)controller->RirbEntryCount * sizeof(HDA_RIRB_ENTRY), 128u, &controller->RirbDma); @@ -266,7 +263,7 @@ KSTATUS HdaAllocateDma(HDA_CONTROLLER* controller) { } // The position buffer is optional because LPIB is the supported fallback. - status = gHdaDdk->Dma->AllocateCommonBuffer(controller->DmaAdapter, + status = IoDmaAllocateCommonBuffer(controller->DmaAdapter, (size_t)controller->TotalStreamCount * sizeof(uint64_t), 128u, &controller->PositionDma); @@ -274,7 +271,7 @@ KSTATUS HdaAllocateDma(HDA_CONTROLLER* controller) { controller->PositionDma = (IO_DMA_BUFFER*)0; for (uint32_t ringIndex = 0; ringIndex < HDA_MAX_LOGICAL_ENDPOINTS; ringIndex++) { - status = gHdaDdk->Dma->AllocateCommonBuffer( + status = IoDmaAllocateCommonBuffer( controller->DmaAdapter, HDA_OUTPUT_BUFFER_BYTES, 128u, @@ -284,7 +281,7 @@ KSTATUS HdaAllocateDma(HDA_CONTROLLER* controller) { return status; } } - status = gHdaDdk->Dma->AllocateCommonBuffer(controller->DmaAdapter, + status = IoDmaAllocateCommonBuffer(controller->DmaAdapter, HDA_OUTPUT_PERIOD_COUNT * sizeof(HDA_BDL_ENTRY), 128u, &controller->Output.BdlDma); @@ -293,27 +290,27 @@ KSTATUS HdaAllocateDma(HDA_CONTROLLER* controller) { return status; } - controller->Corb = (uint32_t*)gHdaDdk->Dma->GetCommonBufferAddress(controller->CorbDma); - controller->Rirb = (HDA_RIRB_ENTRY*)gHdaDdk->Dma->GetCommonBufferAddress(controller->RirbDma); - controller->CorbDeviceAddress = gHdaDdk->Dma->GetCommonBufferDeviceAddress(controller->CorbDma); - controller->RirbDeviceAddress = gHdaDdk->Dma->GetCommonBufferDeviceAddress(controller->RirbDma); + controller->Corb = (uint32_t*)IoDmaGetCommonBufferAddress(controller->CorbDma); + controller->Rirb = (HDA_RIRB_ENTRY*)IoDmaGetCommonBufferAddress(controller->RirbDma); + controller->CorbDeviceAddress = IoDmaGetCommonBufferDeviceAddress(controller->CorbDma); + controller->RirbDeviceAddress = IoDmaGetCommonBufferDeviceAddress(controller->RirbDma); if (controller->PositionDma) { controller->PositionBuffer = (volatile uint64_t*) - gHdaDdk->Dma->GetCommonBufferAddress(controller->PositionDma); + IoDmaGetCommonBufferAddress(controller->PositionDma); controller->PositionDeviceAddress = - gHdaDdk->Dma->GetCommonBufferDeviceAddress(controller->PositionDma); + IoDmaGetCommonBufferDeviceAddress(controller->PositionDma); } for (uint32_t ringIndex = 0; ringIndex < HDA_MAX_LOGICAL_ENDPOINTS; ringIndex++) { controller->Output.Rings[ringIndex].Buffer = (uint8_t*) - gHdaDdk->Dma->GetCommonBufferAddress(controller->Output.Rings[ringIndex].Dma); + IoDmaGetCommonBufferAddress(controller->Output.Rings[ringIndex].Dma); controller->Output.Rings[ringIndex].DeviceAddress = - gHdaDdk->Dma->GetCommonBufferDeviceAddress( + IoDmaGetCommonBufferDeviceAddress( controller->Output.Rings[ringIndex].Dma); } controller->Output.Bdl = (HDA_BDL_ENTRY*) - gHdaDdk->Dma->GetCommonBufferAddress(controller->Output.BdlDma); + IoDmaGetCommonBufferAddress(controller->Output.BdlDma); controller->Output.BdlDeviceAddress = - gHdaDdk->Dma->GetCommonBufferDeviceAddress(controller->Output.BdlDma); + IoDmaGetCommonBufferDeviceAddress(controller->Output.BdlDma); memset(controller->Corb, 0, (size_t)controller->CorbEntryCount * sizeof(uint32_t)); memset(controller->Rirb, 0, (size_t)controller->RirbEntryCount * sizeof(HDA_RIRB_ENTRY)); @@ -502,7 +499,7 @@ static KSTATUS HdaResetCorbReadPointer(HDA_CONTROLLER* controller) { static KSTATUS HdaReportInitializationFailure(HDA_CONTROLLER* controller, const char* stage, KSTATUS status) { - gHdaDdk->Debug->Print( + DbgPrint( "HDA: init stage=%s failed status=0x%x GCTL=0x%08x STATESTS=0x%04x " "CORB=%02x/%02x/%04x/%04x RIRB=%02x/%02x/%04x SDCTL=0x%06x\n", stage, @@ -1381,7 +1378,7 @@ static void HdaRecoveryWorker(uintptr_t argument) { if (!KSUCCESS(status) && status != NTLSTATUS_DEVICE_REMOVED) { if (callbacks.TransportFailure) callbacks.TransportFailure(callbackContext, status); - gHdaDdk->Debug->Print("HDA: recovery failed status=0x%x\n", status); + DbgPrint("HDA: recovery failed status=0x%x\n", status); } if (queueDpc) (void)KeInsertQueueDpc(&controller->Dpc, (void*)0, (void*)0); diff --git a/code/drivers/bus/pci/pci.c b/code/drivers/bus/pci/pci.c index e25276e..217578c 100644 --- a/code/drivers/bus/pci/pci.c +++ b/code/drivers/bus/pci/pci.c @@ -1,3 +1,4 @@ +#include // PCI bus driver. This file owns PCI enumeration, PDO creation, BAR decoding, and explicit MMIO mappings for function drivers. #include "pci.h" @@ -9,11 +10,11 @@ #include #include +#include #include int _fltused; // MSVC floating-point helper anchor required by printf.obj -static const NTL_DDK* gDdk; static DRIVER_OBJECT gPciDriver; static uint32_t gPciDeviceCount; @@ -65,14 +66,14 @@ static bool PciIsPdo(DEVICE_OBJECT* pdo, PCI_PDO_EXTENSION** outExt) { // Implements the raw Read32 operation. static uint32_t PciRawRead32(uint8_t bus, uint8_t dev, uint8_t fn, uint16_t offset) { - if (!gDdk || !gDdk->Platform || !gDdk->Platform->PciRead32 || offset > 0xFCu) return 0xFFFFFFFFu; - return gDdk->Platform->PciRead32(0, bus, dev, fn, offset & 0xFCu); + if (offset > 0xFCu) return 0xFFFFFFFFu; + return KePciReadConfig32(0, bus, dev, fn, offset & 0xFCu); } // Implements the raw Write32 operation. static void PciRawWrite32(uint8_t bus, uint8_t dev, uint8_t fn, uint16_t offset, uint32_t value) { - if (!gDdk || !gDdk->Platform || !gDdk->Platform->PciWrite32 || offset > 0xFCu) return; - gDdk->Platform->PciWrite32(0, bus, dev, fn, offset & 0xFCu, value); + if (offset > 0xFCu) return; + KePciWriteConfig32(0, bus, dev, fn, offset & 0xFCu, value); } // Implements the raw Read8 operation. @@ -386,7 +387,7 @@ static KSTATUS PciCreatePdo(uint8_t bus, uint8_t dev, uint8_t fn) { } gPciDeviceCount++; - gDdk->Debug->Print("pci: %v ven=%04x dev=%04x class=%02x%02x%02x irq=%u\n", + DbgPrint("pci: %v ven=%04x dev=%04x class=%02x%02x%02x irq=%u\n", pdo->Name, info.VendorId, info.DeviceId, @@ -411,7 +412,7 @@ static void PciEnumerateBus(void) { for (uint8_t fn = 0; fn < fnLimit; fn++) { KSTATUS st = PciCreatePdo((uint8_t)bus, (uint8_t)dev, fn); if (!KSUCCESS(st) && st != NTLSTATUS_NOT_FOUND) { - gDdk->Debug->Print("pci: failed to create PDO for %02x:%02x.%u st=0x%x\n", + DbgPrint("pci: failed to create PDO for %02x:%02x.%u st=0x%x\n", bus, dev, fn, st); } } @@ -536,7 +537,7 @@ __declspec(dllexport) KSTATUS PciMapBar(DEVICE_OBJECT* pdo, uint8_t index, uint6 return st; for (uint64_t off = 0; off < mapLength; off += NTL_DDK_PAGE_SIZE) { - st = gDdk->Memory->MapPage(virtBase + (uintptr_t)off, physBase + off, flags, NtlDdkMapFailIfPresent); + st = MmDdkMapPage(virtBase + (uintptr_t)off, physBase + off, flags, NtlDdkMapFailIfPresent); if (!KSUCCESS(st)) return st; } @@ -578,25 +579,26 @@ __declspec(dllexport) KSTATUS PciGetInterrupt(DEVICE_OBJECT* pdo, uint8_t* outIr return NTLSTATUS_SUCCESS; } -// Returns capability. +// Reads configuration through the PCI provider bound to this PDO. +static uint32_t PciCapabilityRead32(void* context, uint16_t offset) { + const PCI_PDO_EXTENSION* ext = (const PCI_PDO_EXTENSION*)context; + return PciRawRead32(ext->Info.Address.Bus, + ext->Info.Address.Device, + ext->Info.Address.Function, + offset); +} + +// Finds one PCI capability using the shared DDK parser. static uint8_t PciFindCapability(PCI_PDO_EXTENSION* ext, uint8_t capId) { if (!ext || !ext->CapabilityOffset) return 0; - uint8_t cap = ext->CapabilityOffset & 0xFCu; - for (uint32_t guard = 0; cap && guard < 48u; guard++) { - uint32_t hdr = PciRawRead32(ext->Info.Address.Bus, - ext->Info.Address.Device, - ext->Info.Address.Function, - cap); - uint8_t id = (uint8_t)(hdr & 0xFFu); - uint8_t next = (uint8_t)((hdr >> 8) & 0xFCu); - if (id == capId) - return cap; - cap = next; - } - - return 0; + const NTL_PCI_CONFIG_READER reader = { ext, PciCapabilityRead32 }; + uint8_t offset = 0; + if (!KSUCCESS(NtlPciFindCapability(&reader, ext->CapabilityOffset & 0xFCu, + capId, &offset))) + return 0; + return offset; } // Enables msi. @@ -619,7 +621,7 @@ __declspec(dllexport) KSTATUS PciEnableMsi(DEVICE_OBJECT* pdo, uint8_t irqSlot) bool is64 = (control & PCI_MSI_CTRL_64BIT) != 0; bool pvm = (control & PCI_MSI_CTRL_PVM) != 0; NTL_DDK_MSI_MESSAGE message; - KSTATUS messageStatus = gDdk->Interrupt->ComposeMsiMessage(irqSlot, &message); + KSTATUS messageStatus = KeIrqComposeMsiMessage(irqSlot, &message); if (!KSUCCESS(messageStatus)) return messageStatus; @@ -643,7 +645,7 @@ __declspec(dllexport) KSTATUS PciEnableMsi(DEVICE_OBJECT* pdo, uint8_t irqSlot) cmd |= PCI_COMMAND_INTX_DISABLE; PciRawWrite16(bus, dev, fn, PCI_REG_COMMAND_STATUS, cmd); - gDdk->Debug->Print("pci: MSI enabled %02x:%02x.%u cap=0x%02x vector=0x%02x irqslot=%u %s\n", + DbgPrint("pci: MSI enabled %02x:%02x.%u cap=0x%02x vector=0x%02x irqslot=%u %s\n", bus, dev, fn, cap, (unsigned)(message.Data & 0xFFu), irqSlot, is64 ? "64-bit" : "32-bit"); return NTLSTATUS_SUCCESS; } @@ -658,7 +660,7 @@ __declspec(dllexport) KSTATUS PciEnableMsiAuto(DEVICE_OBJECT* pdo, uint8_t* outI return NTLSTATUS_NOT_SUPPORTED; uint8_t irq = 0xFFu; - KSTATUS st = gDdk->Interrupt->AllocateMsiVector(&irq); + KSTATUS st = KeIrqAllocateMsiVector(&irq); if (!KSUCCESS(st)) return st; @@ -670,21 +672,19 @@ __declspec(dllexport) KSTATUS PciEnableMsiAuto(DEVICE_OBJECT* pdo, uint8_t* outI return NTLSTATUS_SUCCESS; } -// Implements the driver Entry operation. -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { - (void)init; - if (!ddk || !ddk->Debug || !ddk->Interrupt || !ddk->Memory || !ddk->Platform) return NTLSTATUS_INVALID_PARAMETER; - gDdk = ddk; +static KSTATUS DriverInitialize(NTL_MODULE* module) { + (void)module; + gPciDeviceCount = 0; IoInitializeDriverObject(&gPciDriver, SYS_SV_LITERAL("pci")); gPciDriver.MajorFunction[IRP_MJ_PNP] = PciDispatchPnp; - KSTATUS st = IoRegisterDriverObject(&gPciDriver); - if (!KSUCCESS(st)) return st; - - gDdk->Debug->Print("pci: DriverEntry ABI=%u\n", PciDriverAbiVersion()); + DbgPrint("pci: module ABI=%u\n", PciDriverAbiVersion()); PciEnumerateBus(); - gDdk->Debug->Print("pci: enumeration complete, devices=%u\n", gPciDeviceCount); + DbgPrint("pci: enumeration complete, devices=%u\n", gPciDeviceCount); return NTLSTATUS_SUCCESS; } + +static DRIVER_OBJECT* const gModuleIoDrivers[] = { &gPciDriver }; +NTL_DECLARE_IO_MODULE(DriverInitialize, gModuleIoDrivers); diff --git a/code/drivers/bus/usb/usb.c b/code/drivers/bus/usb/usb.c index 5439b52..95ec816 100644 --- a/code/drivers/bus/usb/usb.c +++ b/code/drivers/bus/usb/usb.c @@ -1,9 +1,9 @@ +#include // USB driver package entry point. This file registers the USB bus, class, hub, HID, storage, and xHCI drivers. #include "usb.h" DRIVER_OBJECT gXhciDriver; -const NTL_DDK* gUsbDdk; static const IO_DEVICE_ID gXhciIds[] = { { IoBusPci, { "PCI\\CC_0C0330", sizeof("PCI\\CC_0C0330") - 1 } }, @@ -31,15 +31,12 @@ static KSTATUS UsbInitializeXhciDriver(void) { gXhciDriver.IdTable = &gXhciIdTable; gXhciDriver.DriverAddDevice = XhciAddDevice; gXhciDriver.MajorFunction[IRP_MJ_PNP] = XhciDispatchPnp; - return IoRegisterDriverObject(&gXhciDriver); + return NTLSTATUS_SUCCESS; } -// Implements the driver Entry operation. -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { - (void)init; - if (!ddk || !ddk->Dma || !ddk->Timer) return NTLSTATUS_INVALID_PARAMETER; - DbgPrint("usb: driver loading\n"); - gUsbDdk = ddk; +static KSTATUS DriverInitialize(NTL_MODULE* module) { + (void)module; + DbgPrint("usb: driver loading phase2-xecp-20261023\n"); KSTATUS st = UsbInitializeBusModel(); if (!KSUCCESS(st)) { @@ -80,3 +77,12 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { DbgPrint("usb: driver initialized\n"); return NTLSTATUS_SUCCESS; } + +extern DRIVER_OBJECT gUsbBusDriver; +extern DRIVER_OBJECT gUsbCompositeDriver; +extern DRIVER_OBJECT gUsbHidDriver; +extern DRIVER_OBJECT gUsbStorageDriver; +extern DRIVER_OBJECT gUsbHubDriver; + +static DRIVER_OBJECT* const gModuleIoDrivers[] = { &gXhciDriver, &gUsbBusDriver, &gUsbCompositeDriver, &gUsbHidDriver, &gUsbStorageDriver, &gUsbHubDriver }; +NTL_DECLARE_IO_MODULE(DriverInitialize, gModuleIoDrivers); diff --git a/code/drivers/bus/usb/usb.h b/code/drivers/bus/usb/usb.h index 9c170b0..fcf5169 100644 --- a/code/drivers/bus/usb/usb.h +++ b/code/drivers/bus/usb/usb.h @@ -112,6 +112,7 @@ static __inline void UsbTrace(const char* fmt, ...) { (void)fmt; } #define XHCI_TRB_TYPE_DISABLE_SLOT 10u #define XHCI_TRB_TYPE_ADDRESS_DEVICE 11u #define XHCI_TRB_TYPE_CONFIGURE_ENDPOINT 12u +#define XHCI_TRB_TYPE_STOP_ENDPOINT 15u #define XHCI_CC_SUCCESS 1u #define XHCI_CC_SHORT_PACKET 13u @@ -129,6 +130,9 @@ static __inline void UsbTrace(const char* fmt, ...) { (void)fmt; } #define XHCI_CONTEXT_ENTRIES(n) ((uint32_t)(n) << 27) #define XHCI_SLOT_SPEED(s) ((uint32_t)(s) << 20) +#define XHCI_SLOT_HUB (1u << 26) +#define XHCI_SLOT_PORT_COUNT(n) ((uint32_t)(n) << 24) +#define XHCI_SLOT_TT_THINK(n) (((uint32_t)(n) & 3u) << 16) #define XHCI_SLOT_ROOT_PORT(p) ((uint32_t)(p) << 16) #define XHCI_EP_TYPE_CONTROL (4u << 3) #define XHCI_EP_TYPE_BULK_OUT (2u << 3) @@ -409,6 +413,8 @@ struct USB_HCD { DEVICE_OBJECT* ControllerDevice; DEVICE_OBJECT* RootHubPdo; void* RootHubContext; + SPINLOCK RootHubLock; + bool RootScanRequested; uint8_t ControllerIndex; uint8_t MaxRootPorts; }; @@ -674,6 +680,8 @@ typedef struct XHCI_DEVICE { uint8_t DeviceProtocol; uint8_t ConfigurationValue; bool Configured; + uint8_t HubPortCount; + uint16_t HubCharacteristics; USB_DEVICE* UsbDevice; DEVICE_OBJECT* UsbPdo; DEVICE_OBJECT* ParentDeviceObject; @@ -786,7 +794,6 @@ typedef struct XHCI_WAIT_TARGET { typedef struct XHCI_CONTROLLER { uint32_t Magic; - const NTL_DDK* Ddk; DEVICE_OBJECT* Fdo; DEVICE_OBJECT* Pdo; DEVICE_OBJECT* LowerDevice; @@ -854,6 +861,7 @@ typedef struct XHCI_CONTROLLER { volatile bool InterruptsEnabled; volatile uint32_t InterruptCount; volatile uint32_t EventDpcCount; + volatile uint32_t EventDpcBudgetHits; SPINLOCK EventLock; SPINLOCK WaitLock; SPINLOCK CommandLock; @@ -871,7 +879,6 @@ extern DRIVER_OBJECT gUsbCompositeDriver; extern DRIVER_OBJECT gUsbHidDriver; extern DRIVER_OBJECT gUsbHubDriver; extern DRIVER_OBJECT gXhciDriver; -extern const NTL_DDK* gUsbDdk; KSTATUS UsbInitializeBusModel(void); KSTATUS UsbInitializeCompositeDriver(void); @@ -919,9 +926,9 @@ uint32_t XhciRead32(volatile uint8_t* base, uint32_t reg); void XhciWrite32(volatile uint8_t* base, uint32_t reg, uint32_t value); uint64_t XhciRead64(volatile uint8_t* base, uint32_t reg); void XhciWrite64(volatile uint8_t* base, uint32_t reg, uint64_t value); -void UsbDelayUs(const NTL_DDK* ddk, uint32_t us); -bool UsbWaitSet32(const NTL_DDK* ddk, volatile uint8_t* base, uint32_t reg, uint32_t mask, uint32_t timeoutUs); -bool UsbWaitClear32(const NTL_DDK* ddk, volatile uint8_t* base, uint32_t reg, uint32_t mask, uint32_t timeoutUs); +void UsbDelayUs(uint32_t us); +bool UsbWaitSet32( volatile uint8_t* base, uint32_t reg, uint32_t mask, uint32_t timeoutUs); +bool UsbWaitClear32( volatile uint8_t* base, uint32_t reg, uint32_t mask, uint32_t timeoutUs); void* UsbDmaAllocPages(XHCI_CONTROLLER* xhci, size_t pages, uint64_t* outDeviceAddress, @@ -948,7 +955,6 @@ void XhciLogPortStatus(XHCI_CONTROLLER* xhci); void XhciPowerRootPorts(XHCI_CONTROLLER* xhci); void XhciAckInterrupter0(XHCI_CONTROLLER* xhci); void XhciUpdateErdp(XHCI_CONTROLLER* xhci); -bool XhciDrainEventRing(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target); bool XhciDrainEventRingLocked(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target); bool XhciWaitTargetComplete(const XHCI_WAIT_TARGET* target); void XhciRegisterWaitTarget(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target); @@ -993,7 +999,6 @@ void XhciProcessBootKeyboardReport(XHCI_DEVICE* dev, const uint8_t* report, uint void XhciProcessBootMouseReport(XHCI_DEVICE* dev, const uint8_t* report, uint16_t len); KSTATUS XhciArmHidInterruptIn(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev); void XhciArmAllHid(XHCI_CONTROLLER* xhci); -void XhciHandleAsyncTransferEvent(XHCI_CONTROLLER* xhci, uint64_t trbDeviceAddress, uint32_t completionCode, uint8_t slotId, uint8_t endpointId, uint32_t residue); KSTATUS XhciStartHidInterrupts(XHCI_CONTROLLER* xhci); KSTATUS XhciSetHidBootProtocol(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev); KSTATUS XhciAllocateHidMemory(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev); @@ -1002,6 +1007,8 @@ KSTATUS UsbHidAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObje KSTATUS UsbHidDispatchPnp(DEVICE_OBJECT* device, IRP* irp); void XhciInitialiseTransferRing(XHCI_TRB* ring, uint64_t ringDeviceAddress, uint32_t trbCount, uint8_t cycle); KSTATUS XhciConfigurePipeEndpoint(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, USB_PIPE* pipe); +KSTATUS XhciConfigureHubContext(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, uint8_t portCount, uint16_t characteristics); +void XhciBuildConfigureSlotContext(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, uint32_t* slot, uint8_t endpointId); KSTATUS XhciBulkTransfer(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, uint8_t endpointId, XHCI_TRB* ring, uint64_t ringDeviceAddress, uint32_t* enqueue, uint8_t* cycle, uint64_t bufferDeviceAddress, uint32_t length, uint32_t* outActual); KSTATUS UsbStorageBulkIn(USB_STORAGE_EXTENSION* storage, uint64_t deviceAddress, uint32_t length, uint32_t* outActual); KSTATUS UsbStorageBulkOut(USB_STORAGE_EXTENSION* storage, uint64_t deviceAddress, uint32_t length, uint32_t* outActual); diff --git a/code/drivers/bus/usb/usb_bus.c b/code/drivers/bus/usb/usb_bus.c index 188dafd..bfb69a3 100644 --- a/code/drivers/bus/usb/usb_bus.c +++ b/code/drivers/bus/usb/usb_bus.c @@ -69,8 +69,8 @@ void* UsbAllocateDmaBuffer(USB_DEVICE* dev, // Frees a DMA buffer allocated for a USB class driver. void UsbFreeDmaBuffer(IO_DMA_BUFFER* buffer) { - if (buffer && gUsbDdk && gUsbDdk->Dma) - gUsbDdk->Dma->FreeCommonBuffer(buffer); + if (buffer) + IoDmaFreeCommonBuffer(buffer); } // Creates device. @@ -275,9 +275,6 @@ KSTATUS UsbInitializeBusModel(void) { gUsbBusDriver.MajorFunction[IRP_MJ_PNP] = UsbDispatchPdoPnp; gUsbBusDriver.MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = UsbDispatchInternalDeviceControl; - KSTATUS st = IoRegisterDriverObject(&gUsbBusDriver); - if (!KSUCCESS(st)) return st; - gUsbBusModelReady = true; return NTLSTATUS_SUCCESS; } @@ -290,6 +287,7 @@ void UsbInitializeHcd(USB_HCD* hcd, if (!hcd) return; memset(hcd, 0, sizeof(*hcd)); hcd->Operations = ops; + hcd->RootHubLock = (SPINLOCK)SPINLOCK_INIT; hcd->ControllerContext = controllerContext; hcd->DriverObject = &gUsbBusDriver; hcd->ControllerIndex = controllerIndex ? controllerIndex : ++gUsbNextControllerIndex; diff --git a/code/drivers/bus/usb/usb_composite.c b/code/drivers/bus/usb/usb_composite.c index cad8bbb..3cfdc63 100644 --- a/code/drivers/bus/usb/usb_composite.c +++ b/code/drivers/bus/usb/usb_composite.c @@ -152,5 +152,5 @@ KSTATUS UsbInitializeCompositeDriver(void) { gUsbCompositeDriver.DriverAddDevice = UsbCompositeAddDevice; gUsbCompositeDriver.MajorFunction[IRP_MJ_PNP] = UsbCompositeDispatchPnp; gUsbCompositeDriver.MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = UsbDispatchInternalDeviceControl; - return IoRegisterDriverObject(&gUsbCompositeDriver); + return NTLSTATUS_SUCCESS; } diff --git a/code/drivers/bus/usb/usb_core.c b/code/drivers/bus/usb/usb_core.c index af656c1..dde48a5 100644 --- a/code/drivers/bus/usb/usb_core.c +++ b/code/drivers/bus/usb/usb_core.c @@ -11,6 +11,8 @@ KSTATUS XhciSetConfiguration(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, setup.wIndex = 0; setup.wLength = 0; + DbgPrint("usb-stage: slot=%u SET_CONFIGURATION begin value=%u\n", + dev->SlotId, configurationValue); KSTATUS st = XhciControlNoData(xhci, dev, &setup); if (!KSUCCESS(st)) { DbgPrint("usb: SET_CONFIGURATION slot=%u value=%u failed st=0x%x\n", @@ -20,6 +22,8 @@ KSTATUS XhciSetConfiguration(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, dev->ConfigurationValue = configurationValue; dev->Configured = true; + DbgPrint("usb-stage: slot=%u SET_CONFIGURATION done value=%u\n", + dev->SlotId, configurationValue); UsbTrace("usb: SET_CONFIGURATION slot=%u value=%u complete\n", dev->SlotId, configurationValue); return NTLSTATUS_SUCCESS; @@ -220,6 +224,7 @@ KSTATUS XhciDiscoverAndSetConfiguration(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, USB_CONFIGURATION_DESCRIPTOR config; memset(&config, 0, sizeof(config)); uint16_t totalLength = 0; + DbgPrint("usb-stage: slot=%u GET_CONFIGURATION begin\n", dev->SlotId); KSTATUS st = XhciGetConfigurationDescriptor(xhci, dev, 0, &config, &totalLength); if (!KSUCCESS(st)) { DbgPrint("usb: slot=%u configuration descriptor read failed st=0x%x\n", @@ -227,6 +232,8 @@ KSTATUS XhciDiscoverAndSetConfiguration(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, return st; } + DbgPrint("usb-stage: slot=%u GET_CONFIGURATION done total=%u value=%u\n", + dev->SlotId, totalLength, config.bConfigurationValue); XhciLogConfigurationTree(xhci, dev, &config, totalLength); if (config.bConfigurationValue == 0) { @@ -239,6 +246,7 @@ KSTATUS XhciDiscoverAndSetConfiguration(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, if (!KSUCCESS(st)) return st; if (!dev->UsbPdo) { + DbgPrint("usb-stage: slot=%u PDO creation begin\n", dev->SlotId); st = UsbCreateDevicePdo(&xhci->Hcd, dev, dev->ParentDeviceObject ? dev->ParentDeviceObject : (xhci->Hcd.RootHubPdo ? xhci->Hcd.RootHubPdo : xhci->Hcd.ControllerDevice)); @@ -246,6 +254,7 @@ KSTATUS XhciDiscoverAndSetConfiguration(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, DbgPrint("usb: create device PDO slot=%u failed st=0x%x\n", dev->SlotId, st); return st; } + DbgPrint("usb-stage: slot=%u PDO creation done\n", dev->SlotId); } return NTLSTATUS_SUCCESS; @@ -286,8 +295,14 @@ static KSTATUS XhciEnumeratePathEx(XHCI_CONTROLLER* xhci, if (!xhci || !path || path->RootPort == 0) return NTLSTATUS_INVALID_PARAMETER; + DbgPrint("usb-stage: hcd=%02x:%02x.%u root=%u hub-slot=%u hub-port=%u enable-slot begin\n", + xhci->Bus, xhci->Dev, xhci->Fn, path->RootPort, + path->BehindHub ? path->ParentHubSlot : 0, + path->BehindHub ? path->ParentHubPort : 0); uint8_t slotId = 0; KSTATUS st = XhciEnableSlot(xhci, &slotId); + DbgPrint("usb-stage: hcd=%02x:%02x.%u enable-slot done st=0x%x slot=%u\n", + xhci->Bus, xhci->Dev, xhci->Fn, st, slotId); if (!KSUCCESS(st)) return st; XHCI_DEVICE* dev = &xhci->Devices[slotId]; @@ -307,7 +322,11 @@ static KSTATUS XhciEnumeratePathEx(XHCI_CONTROLLER* xhci, XhciBuildDefaultControlContext(xhci, dev); + DbgPrint("usb-stage: hcd=%02x:%02x.%u slot=%u address-device begin speed=%u route=%05x\n", + xhci->Bus, xhci->Dev, xhci->Fn, slotId, speed, path->RouteString); st = XhciAddressDevice(xhci, dev); + DbgPrint("usb-stage: hcd=%02x:%02x.%u slot=%u address-device done st=0x%x\n", + xhci->Bus, xhci->Dev, xhci->Fn, slotId, st); if (!KSUCCESS(st)) { XhciRollbackEnumeratedSlot(xhci, slotId); return st; @@ -316,7 +335,11 @@ static KSTATUS XhciEnumeratePathEx(XHCI_CONTROLLER* xhci, USB_DEVICE_DESCRIPTOR desc; memset(&desc, 0, sizeof(desc)); + DbgPrint("usb-stage: hcd=%02x:%02x.%u slot=%u get-device-descriptor begin\n", + xhci->Bus, xhci->Dev, xhci->Fn, slotId); st = XhciGetDeviceDescriptorTimeout(xhci, dev, &desc, USB_XHCI_ENUM_CONTROL_TIMEOUT_US); + DbgPrint("usb-stage: hcd=%02x:%02x.%u slot=%u get-device-descriptor done st=0x%x\n", + xhci->Bus, xhci->Dev, xhci->Fn, slotId, st); if (!KSUCCESS(st)) { DbgPrint("usb: port %u slot %u descriptor read failed st=0x%x\n", logicalPort, slotId, st); diff --git a/code/drivers/bus/usb/usb_hid.c b/code/drivers/bus/usb/usb_hid.c index 378f1ba..1005d4d 100644 --- a/code/drivers/bus/usb/usb_hid.c +++ b/code/drivers/bus/usb/usb_hid.c @@ -213,9 +213,9 @@ KSTATUS XhciAllocateHidMemory(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { dev->HidRing = (XHCI_TRB*)UsbDmaAllocPages(xhci, 1, &dev->HidRingDeviceAddress, &dev->HidRingDma); dev->HidBuffer = (uint8_t*)UsbDmaAllocPages(xhci, 1, &dev->HidBufferDeviceAddress, &dev->HidBufferDma); if (!dev->HidRing || !dev->HidBuffer) { - if (gUsbDdk && gUsbDdk->Dma) { - gUsbDdk->Dma->FreeCommonBuffer(dev->HidBufferDma); - gUsbDdk->Dma->FreeCommonBuffer(dev->HidRingDma); + { + IoDmaFreeCommonBuffer(dev->HidBufferDma); + IoDmaFreeCommonBuffer(dev->HidRingDma); } dev->HidBufferDma = (IO_DMA_BUFFER*)0; dev->HidRingDma = (IO_DMA_BUFFER*)0; @@ -261,10 +261,7 @@ KSTATUS XhciConfigureHidEndpoint(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { icc[0] = 0; icc[1] = (1u << 0) | (1u << dci); - slot[0] = XHCI_CONTEXT_ENTRIES(dci) | XHCI_SLOT_SPEED(dev->Speed); - slot[1] = XHCI_SLOT_ROOT_PORT(dev->Port); - slot[2] = 0; - slot[3] = 0; + XhciBuildConfigureSlotContext(xhci, dev, slot, dci); uint16_t maxPacket = dev->HidMaxPacket & 0x07FFu; if (maxPacket == 0) maxPacket = dev->HidKeyboard ? 8u : 4u; @@ -442,8 +439,10 @@ static void UsbHidInterruptComplete(USB_URB* urb, void* context) { return; ext->InterruptPending = false; - if (ext->SurpriseRemoved || ext->Removed || !ext->Started) + if (ext->SurpriseRemoved || ext->Removed || !ext->Started) { + IoReleaseRemoveLock(&ext->RemoveLock, (IRP*)0); return; + } if (KSUCCESS(urb->Status)) { uint16_t length = (uint16_t)urb->ActualLength; @@ -468,6 +467,7 @@ static void UsbHidInterruptComplete(USB_URB* urb, void* context) { if (!ext->SurpriseRemoved && !ext->Removed) (void)UsbHidArmInterrupt(ext); + IoReleaseRemoveLock(&ext->RemoveLock, (IRP*)0); } // Implements the hid Send Boot Protocol operation. @@ -523,6 +523,11 @@ static KSTATUS UsbHidArmInterrupt(USB_HID_EXTENSION* ext) { if (ext->InterruptPending) return NTLSTATUS_SUCCESS; + // One in-flight callback owns a remove reference through dispatch or cancel. + KSTATUS refStatus = IoAcquireRemoveLock(&ext->RemoveLock, (IRP*)0); + if (!KSUCCESS(refStatus)) + return refStatus; + ext->InterruptPending = true; memset(ext->ReportBuffer, 0, ext->ReportLength); memset(&ext->InterruptUrb, 0, sizeof(ext->InterruptUrb)); ext->InterruptUrb.Function = UrbFunctionInterruptTransfer; @@ -536,12 +541,13 @@ static KSTATUS UsbHidArmInterrupt(USB_HID_EXTENSION* ext) { ext->InterruptUrb.CompleteContext = ext; KSTATUS st = UsbSubmitUrbAsync(ext->LowerDevice, &ext->InterruptUrb); - if (st != NTLSTATUS_PENDING && !KSUCCESS(st)) - return st; - if (ext->InterruptUrb.Status != NTLSTATUS_PENDING && ext->InterruptUrb.Status != NTLSTATUS_SUCCESS) - return ext->InterruptUrb.Status; - - ext->InterruptPending = true; + // The USB PDO returns PENDING even when the HCD rejected the URB and + // completed its IRP synchronously. Inspect the URB's actual status. + if (st != NTLSTATUS_PENDING || ext->InterruptUrb.Status != NTLSTATUS_PENDING) { + ext->InterruptPending = false; + IoReleaseRemoveLock(&ext->RemoveLock, (IRP*)0); + return st == NTLSTATUS_PENDING ? ext->InterruptUrb.Status : st; + } return NTLSTATUS_SUCCESS; } @@ -616,8 +622,6 @@ static KSTATUS UsbHidStartDevice(DEVICE_OBJECT* device, USB_HID_EXTENSION* ext) ext->LastModifiers = 0; ext->LastButtons = 0; - KeInputRegisterDevice(ext->Keyboard ? "usb-hid-keyboard" : (ext->AbsoluteMouse ? "usb-hid-tablet" : "usb-hid-mouse"), 0, &ext->InputDeviceId); - ext->Started = true; device->PnpState = DevicePnpStarted; if (ext->InterfacePdo) @@ -627,9 +631,21 @@ static KSTATUS UsbHidStartDevice(DEVICE_OBJECT* device, USB_HID_EXTENSION* ext) (XHCI_CONTROLLER*)ext->Device->Hcd->ControllerContext : (XHCI_CONTROLLER*)0; st = UsbHidArmInterrupt(ext); - if (!KSUCCESS(st)) + if (!KSUCCESS(st)) { + ext->Started = false; + device->PnpState = DevicePnpStopped; + if (ext->InterfacePdo) + ext->InterfacePdo->PnpState = DevicePnpStopped; + if (ext->InterruptPipe) { + ext->InterruptPipe->Open = false; + UsbDereferencePipe(ext->InterruptPipe); + ext->InterruptPipe = (USB_PIPE*)0; + } + UsbHidReleaseDma(ext); return st; + } + KeInputRegisterDevice(ext->Keyboard ? "usb-hid-keyboard" : (ext->AbsoluteMouse ? "usb-hid-tablet" : "usb-hid-mouse"), 0, &ext->InputDeviceId); if (xhci) xhci->HidDeviceCount++; @@ -679,8 +695,13 @@ KSTATUS UsbHidDispatchPnp(DEVICE_OBJECT* device, IRP* irp) { if (ext) { ext->SurpriseRemoved = true; ext->Started = false; - if (ext->InterruptPending) - (void)UsbCancelUrb(&ext->InterruptUrb); + if (ext->InterruptPending) { + st = UsbCancelUrb(&ext->InterruptUrb); + if (!KSUCCESS(st)) { + IoCompleteRequest(irp, st, 0); + return NTLSTATUS_PENDING; + } + } } return IoForwardIrpToLower(device, irp); @@ -689,13 +710,15 @@ KSTATUS UsbHidDispatchPnp(DEVICE_OBJECT* device, IRP* irp) { if (ext) { ext->Removed = true; ext->Started = false; - if (ext->InterruptPending) - (void)UsbCancelUrb(&ext->InterruptUrb); - if (ext->InterruptPipe) { - ext->InterruptPipe->Open = false; - UsbDereferencePipe(ext->InterruptPipe); - ext->InterruptPipe = (USB_PIPE*)0; + if (ext->InterruptPending) { + st = UsbCancelUrb(&ext->InterruptUrb); + if (!KSUCCESS(st)) { + IoCompleteRequest(irp, st, 0); + return NTLSTATUS_PENDING; + } } + if (ext->InterruptPipe) + ext->InterruptPipe->Open = false; if (ext->Device && ext->Device->Hcd) { XHCI_CONTROLLER* xhci = (XHCI_CONTROLLER*)ext->Device->Hcd->ControllerContext; if (xhci && xhci->HidDeviceCount) @@ -706,6 +729,10 @@ KSTATUS UsbHidDispatchPnp(DEVICE_OBJECT* device, IRP* irp) { IoDestroyRemoveLock(&ext->RemoveLock); ext->RemoveLockReady = false; } + if (ext->InterruptPipe) { + UsbDereferencePipe(ext->InterruptPipe); + ext->InterruptPipe = (USB_PIPE*)0; + } UsbHidReleaseDma(ext); } @@ -751,8 +778,16 @@ KSTATUS UsbHidAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObje bool bootMouse = pdoExt->InterfaceDescriptor.bInterfaceSubClass == USB_HID_SUBCLASS_BOOT && pdoExt->InterfaceDescriptor.bInterfaceProtocol == USB_HID_PROTOCOL_MOUSE; bool qemuTablet = UsbHidIsQemuAbsoluteTabletPdo(pdoExt, ep); - if (!bootKeyboard && !bootMouse && !qemuTablet) - return NTLSTATUS_INVALID_PARAMETER; + if (!bootKeyboard && !bootMouse && !qemuTablet) { + // A nonboot HID interface requires its report descriptor to be parsed. + // Avoid treating opaque reports as boot mouse packets. + DbgPrint("usb: HID slot=%u if=%u subclass=%u protocol=%u requires report parser\n", + pdoExt->Device->SlotId, + pdoExt->InterfaceDescriptor.bInterfaceNumber, + pdoExt->InterfaceDescriptor.bInterfaceSubClass, + pdoExt->InterfaceDescriptor.bInterfaceProtocol); + return NTLSTATUS_NOT_SUPPORTED; + } DEVICE_OBJECT* fdo = (DEVICE_OBJECT*)0; KSTATUS st = IoCreateDevice(driver, sizeof(USB_HID_EXTENSION), &fdo); @@ -799,5 +834,5 @@ KSTATUS UsbInitializeHidDriver(void) { gUsbHidDriver.IdTable = &gUsbHidIdTable; gUsbHidDriver.DriverAddDevice = UsbHidAddDevice; gUsbHidDriver.MajorFunction[IRP_MJ_PNP] = UsbHidDispatchPnp; - return IoRegisterDriverObject(&gUsbHidDriver); + return NTLSTATUS_SUCCESS; } diff --git a/code/drivers/bus/usb/usb_hub.c b/code/drivers/bus/usb/usb_hub.c index 669a96a..e27046b 100644 --- a/code/drivers/bus/usb/usb_hub.c +++ b/code/drivers/bus/usb/usb_hub.c @@ -99,7 +99,7 @@ static KSTATUS UsbHubEnumerateRootPortWithRetries(USB_HUB_EXTENSION* hub, DbgPrint("usb-hub: root port %u enumeration attempt %u failed st=0x%x; retrying\n", port, attempt, last); - UsbDelayUs(xhci->Ddk, 50000); + UsbDelayUs(50000); } if (UsbHubRootPortConnected(xhci, port)) { @@ -126,7 +126,7 @@ static KSTATUS UsbHubEnumerateConnectedRootPort(USB_HUB_EXTENSION* hub, hub->RootPortSlot[port] = USB_HUB_PORT_ENUMERATING; hub->RootPortState[port] = UsbHubPortConnectedDebounce; - UsbDelayUs(xhci->Ddk, 30000u); + UsbDelayUs(30000u); if (!UsbHubRootPortConnected(xhci, port)) { UsbHubClearRootPortFailure(hub, port); hub->RootPortSlot[port] = 0; @@ -393,13 +393,21 @@ static KSTATUS UsbHubResetExternalPort(USB_HUB_EXTENSION* hub, uint8_t port, uin USB_HUB_PORT_STATUS ps; memset(&ps, 0, sizeof(ps)); + bool resetComplete = false; for (uint32_t i = 0; i < 500u; i++) { - UsbDelayUs(((XHCI_CONTROLLER*)hub->Hcd->ControllerContext)->Ddk, 1000u); + UsbDelayUs(1000u); st = UsbHubGetPortStatus(hub, port, &ps); if (!KSUCCESS(st)) return st; - if (ps.Change & USB_HUB_PORT_CHANGE_RESET) + if (ps.Change & USB_HUB_PORT_CHANGE_RESET) { + resetComplete = true; break; + } + } + if (!resetComplete) { + DbgPrint("usb-hub: slot=%u port=%u reset timed out status=%04x change=%04x\n", + hub->Device ? hub->Device->SlotId : 0, port, ps.Status, ps.Change); + return NTLSTATUS_TIMEOUT; } (void)UsbHubClearPortFeature(hub, port, USB_HUB_PORT_FEAT_C_RESET); @@ -464,7 +472,7 @@ static void UsbHubHandleExternalPortChange(USB_HUB_EXTENSION* hub, uint8_t port) hub->ChildSlot[port] = USB_HUB_PORT_ENUMERATING; hub->ChildPortState[port] = UsbHubPortConnectedDebounce; - UsbDelayUs(((XHCI_CONTROLLER*)hub->Hcd->ControllerContext)->Ddk, 30000u); + UsbDelayUs(30000u); uint8_t speed = 0; hub->ChildPortState[port] = UsbHubPortResetting; @@ -478,8 +486,12 @@ static void UsbHubHandleExternalPortChange(USB_HUB_EXTENSION* hub, uint8_t port) } hub->ChildPortState[port] = UsbHubPortEnumerating; + DbgPrint("usb-stage: hub-slot=%u port=%u child enumeration begin speed=%u\n", + hub->Device ? hub->Device->SlotId : 0, port, speed); uint8_t slot = 0; st = XhciEnumerateHubPortEx((XHCI_CONTROLLER*)hub->Hcd->ControllerContext, hub, port, speed, &slot); + DbgPrint("usb-stage: hub-slot=%u port=%u child enumeration done st=0x%x slot=%u\n", + hub->Device ? hub->Device->SlotId : 0, port, st, slot); if (KSUCCESS(st)) { hub->ChildSlot[port] = slot; hub->ChildPortState[port] = UsbHubPortChildPresent; @@ -580,6 +592,12 @@ static bool UsbHubTakeInitialRootScan(USB_HUB_EXTENSION* hub) { // Implements the hub Worker operation. static void UsbHubWorker(void* arg) { USB_HUB_EXTENSION* hub = (USB_HUB_EXTENSION*)arg; + if (hub && hub->Kind == UsbHubKindRoot && hub->Hcd) { + XHCI_CONTROLLER* xhci = (XHCI_CONTROLLER*)hub->Hcd->ControllerContext; + if (xhci) + DbgPrint("usb-stage: hcd=%02x:%02x.%u root worker entered ports=%u\n", + xhci->Bus, xhci->Dev, xhci->Fn, hub->PortCount); + } while (hub && hub->WorkerRunning) { KSTATUS st = KeWaitSemaphore(hub->WorkerSemaphore, KE_WAIT_INFINITE); if (!KSUCCESS(st)) @@ -588,8 +606,11 @@ static void UsbHubWorker(void* arg) { break; if (hub->Kind == UsbHubKindRoot) { - if (UsbHubTakeInitialRootScan(hub)) - (void)UsbHubDoRootPortScan(hub->Hcd); + if (UsbHubTakeInitialRootScan(hub)) { + KSTATUS scanStatus = UsbHubDoRootPortScan(hub->Hcd); + if (!KSUCCESS(scanStatus)) + DbgPrint("usb-stage: root scan aborted st=0x%x\n", scanStatus); + } uint8_t port = 0; while (UsbHubPopPendingRootPort(hub, &port)) @@ -657,6 +678,12 @@ static KSTATUS UsbHubStartWorker(USB_HUB_EXTENSION* hub) { UsbHubStopWorker(hub); return status; } + if (hub->Kind == UsbHubKindRoot && hub->Hcd) { + XHCI_CONTROLLER* xhci = (XHCI_CONTROLLER*)hub->Hcd->ControllerContext; + if (xhci) + DbgPrint("usb-stage: hcd=%02x:%02x.%u root worker created\n", + xhci->Bus, xhci->Dev, xhci->Fn); + } return NTLSTATUS_SUCCESS; } @@ -675,6 +702,8 @@ static KSTATUS UsbHubDoRootPortScan(USB_HCD* hcd) { if (!xhci || !xhci->Op) return NTLSTATUS_INVALID_PARAMETER; + DbgPrint("usb-stage: hcd=%02x:%02x.%u root scan begin ports=%u\n", + xhci->Bus, xhci->Dev, xhci->Fn, hub->PortCount); for (uint8_t port = 1; port <= hub->PortCount; port++) { uint32_t portsc = XhciRead32(xhci->Op, XhciPortReg(port)); if (!(portsc & XHCI_PORTSC_CCS)) @@ -686,9 +715,13 @@ static KSTATUS UsbHubDoRootPortScan(USB_HCD* hcd) { continue; } + DbgPrint("usb-stage: hcd=%02x:%02x.%u root-port=%u begin portsc=%08x\n", + xhci->Bus, xhci->Dev, xhci->Fn, port, portsc); attempted++; uint8_t slot = 0; KSTATUS st = UsbHubEnumerateConnectedRootPort(hub, xhci, port, &slot); + DbgPrint("usb-stage: hcd=%02x:%02x.%u root-port=%u done st=0x%x slot=%u\n", + xhci->Bus, xhci->Dev, xhci->Fn, port, st, slot); if (KSUCCESS(st)) { hub->RootPortSlot[port] = slot; hub->RootPortState[port] = UsbHubPortChildPresent; @@ -699,27 +732,45 @@ static KSTATUS UsbHubDoRootPortScan(USB_HCD* hcd) { } } - DbgPrint("usb-hub: root-port scan complete; attempted=%u configured=%u\n", attempted, configured); + DbgPrint("usb-hub: hcd=%02x:%02x.%u root-port scan complete; attempted=%u configured=%u\n", + xhci->Bus, xhci->Dev, xhci->Fn, attempted, configured); + uintptr_t flags = KeAcquireSpinlock(&hub->PendingLock); hub->InitialScanComplete = true; hub->InitialScanInProgress = false; + KeReleaseSpinlock(&hub->PendingLock, flags); return NTLSTATUS_SUCCESS; } -// Implements the hub Schedule Root Scan operation. -KSTATUS UsbHubScheduleRootScan(USB_HCD* hcd) { - if (!hcd || !hcd->RootHubContext) - return NTLSTATUS_INVALID_PARAMETER; - USB_HUB_EXTENSION* hub = (USB_HUB_EXTENSION*)hcd->RootHubContext; - if (hub->Kind != UsbHubKindRoot || !hub->Started) - return NTLSTATUS_OBJECT_DEAD; +static SEMAPHORE* UsbHubQueueInitialScan(USB_HUB_EXTENSION* hub) { + if (!hub || !hub->Started || !hub->WorkerSemaphore) + return (SEMAPHORE*)0; uintptr_t flags = KeAcquireSpinlock(&hub->PendingLock); - if (!hub->InitialScanComplete && !hub->InitialScanInProgress) + bool pending = !hub->InitialScanComplete && !hub->InitialScanInProgress; + if (pending) hub->InitialScanPending = true; KeReleaseSpinlock(&hub->PendingLock, flags); - if (hub->WorkerSemaphore) - KeSignalSemaphore(hub->WorkerSemaphore); + if (!pending) + return (SEMAPHORE*)0; + + ObReferenceObject(&hub->WorkerSemaphore->Header); + return hub->WorkerSemaphore; +} + +KSTATUS UsbHubScheduleRootScan(USB_HCD* hcd) { + if (!hcd || !hcd->RootHubPdo) + return NTLSTATUS_INVALID_PARAMETER; + + uintptr_t flags = KeAcquireSpinlock(&hcd->RootHubLock); + hcd->RootScanRequested = true; + SEMAPHORE* wake = UsbHubQueueInitialScan((USB_HUB_EXTENSION*)hcd->RootHubContext); + KeReleaseSpinlock(&hcd->RootHubLock, flags); + + if (wake) { + KeSignalSemaphore(wake); + ObDereferenceObject(&wake->Header); + } return NTLSTATUS_SUCCESS; } @@ -800,8 +851,10 @@ static void UsbHubExternalChangeComplete(USB_URB* urb, void* context) { return; hub->ChangeUrbPending = false; - if (!KSUCCESS(urb->Status) || hub->Removed || hub->SurpriseRemoved) + if (!KSUCCESS(urb->Status) || hub->Removed || hub->SurpriseRemoved) { + IoReleaseRemoveLock(&hub->RemoveLock, (IRP*)0); return; + } uint32_t actual = urb->ActualLength; if (actual > USB_HUB_CHANGE_BITMAP_BYTES) @@ -813,6 +866,7 @@ static void UsbHubExternalChangeComplete(USB_URB* urb, void* context) { if (byteIndex < actual && (hub->ChangeBitmap[byteIndex] & (1u << bit))) UsbHubQueueExternalPortChange(hub, port); } + IoReleaseRemoveLock(&hub->RemoveLock, (IRP*)0); } // Implements the hub Arm External Interrupt operation. @@ -820,6 +874,10 @@ static void UsbHubArmExternalInterrupt(USB_HUB_EXTENSION* hub) { if (!hub || !hub->InterruptPipe || !hub->ChangeBitmap || hub->ChangeUrbPending) return; + KSTATUS refStatus = IoAcquireRemoveLock(&hub->RemoveLock, (IRP*)0); + if (!KSUCCESS(refStatus)) + return; + hub->ChangeUrbPending = true; memset(hub->ChangeBitmap, 0, USB_HUB_CHANGE_BITMAP_BYTES); memset(&hub->ChangeUrb, 0, sizeof(hub->ChangeUrb)); hub->ChangeUrb.Function = UrbFunctionInterruptTransfer; @@ -835,11 +893,12 @@ static void UsbHubArmExternalInterrupt(USB_HUB_EXTENSION* hub) { hub->ChangeUrb.CompleteContext = hub; KSTATUS st = UsbSubmitUrbAsync(hub->LowerDevice, &hub->ChangeUrb); - if (st == NTLSTATUS_PENDING) { - hub->ChangeUrbPending = true; - } else if (!KSUCCESS(st)) { + if (st != NTLSTATUS_PENDING || hub->ChangeUrb.Status != NTLSTATUS_PENDING) { + hub->ChangeUrbPending = false; + IoReleaseRemoveLock(&hub->RemoveLock, (IRP*)0); DbgPrint("usb-hub: slot=%u interrupt arm failed st=0x%x\n", - hub->Device ? hub->Device->SlotId : 0, st); + hub->Device ? hub->Device->SlotId : 0, + st == NTLSTATUS_PENDING ? hub->ChangeUrb.Status : st); } } @@ -853,12 +912,30 @@ static KSTATUS UsbHubStartRootDevice(DEVICE_OBJECT* device, USB_HUB_EXTENSION* h if (!KSUCCESS(st)) return st; - hub->Started = true; device->PnpState = DevicePnpStarted; if (hub->HubPdo) hub->HubPdo->PnpState = DevicePnpStarted; - DbgPrint("usb-hub: root hub started ports=%u\n", hub->PortCount); + uintptr_t flags = KeAcquireSpinlock(&hub->Hcd->RootHubLock); + hub->Started = true; + SEMAPHORE* wake = hub->Hcd->RootScanRequested ? UsbHubQueueInitialScan(hub) : (SEMAPHORE*)0; + KeReleaseSpinlock(&hub->Hcd->RootHubLock, flags); + + XHCI_CONTROLLER* xhci = (XHCI_CONTROLLER*)hub->Hcd->ControllerContext; + if (xhci) + DbgPrint("usb-hub: hcd=%02x:%02x.%u root hub started ports=%u scan=%s\n", + xhci->Bus, xhci->Dev, xhci->Fn, hub->PortCount, + wake ? "queued" : "not-queued"); + + // Publish the startup checkpoint before waking enumeration. A bad port or + // an interrupt storm can otherwise hide which hub started the work. + if (wake) { + KeSignalSemaphore(wake); + ObDereferenceObject(&wake->Header); + if (xhci) + DbgPrint("usb-stage: hcd=%02x:%02x.%u initial scan wake returned\n", + xhci->Bus, xhci->Dev, xhci->Fn); + } return NTLSTATUS_SUCCESS; } @@ -888,10 +965,24 @@ static KSTATUS UsbHubStartExternalDevice(DEVICE_OBJECT* device, USB_HUB_EXTENSIO } hub->PowerOnToPowerGoodMs = desc.bPwrOn2PwrGood ? (uint8_t)(desc.bPwrOn2PwrGood * 2u) : 100u; + // xHCI needs the hub's Slot Context before routing low/full-speed child + // transfers through a high-speed transaction translator. + XHCI_CONTROLLER* xhci = hub->Hcd ? (XHCI_CONTROLLER*)hub->Hcd->ControllerContext : (XHCI_CONTROLLER*)0; + XHCI_DEVICE* xdev = xhci && hub->Device->SlotId <= XHCI_MAX_TRACKED_SLOTS + ? &xhci->Devices[hub->Device->SlotId] : (XHCI_DEVICE*)0; + if (!xdev || !xdev->Allocated) { + UsbHubReleaseDma(hub); + return NTLSTATUS_DEVICE_ERROR; + } + st = XhciConfigureHubContext(xhci, xdev, hub->PortCount, desc.wHubCharacteristics); + if (!KSUCCESS(st)) { + UsbHubReleaseDma(hub); + return st; + } + for (uint8_t port = 1; port <= hub->PortCount; port++) (void)UsbHubSetPortFeature(hub, port, USB_HUB_PORT_FEAT_POWER); - UsbDelayUs(((XHCI_CONTROLLER*)hub->Hcd->ControllerContext)->Ddk, - (uint32_t)hub->PowerOnToPowerGoodMs * 1000u); + UsbDelayUs((uint32_t)hub->PowerOnToPowerGoodMs * 1000u); st = UsbHubStartWorker(hub); if (!KSUCCESS(st)) { @@ -971,12 +1062,22 @@ KSTATUS UsbHubDispatchPnp(DEVICE_OBJECT* device, IRP* irp) { device->PnpState = DevicePnpSurpriseRemoved; if (hub) { hub->SurpriseRemoved = true; + if (hub->Kind == UsbHubKindRoot && hub->Hcd) { + uintptr_t flags = KeAcquireSpinlock(&hub->Hcd->RootHubLock); + if (hub->Hcd->RootHubContext == hub) + hub->Hcd->RootHubContext = (void*)0; + hub->Started = false; + KeReleaseSpinlock(&hub->Hcd->RootHubLock, flags); + } hub->WorkerRunning = false; - if (hub->ChangeUrbPending) - (void)UsbCancelUrb(&hub->ChangeUrb); - if (hub->WorkerSemaphore) - KeSignalSemaphore(hub->WorkerSemaphore); UsbHubStopWorker(hub); + if (hub->ChangeUrbPending) { + st = UsbCancelUrb(&hub->ChangeUrb); + if (!KSUCCESS(st)) { + IoCompleteRequest(irp, st, 0); + return NTLSTATUS_PENDING; + } + } // Remove downstream PDOs owned by this hub FDO. The explicit // slot arrays are still used as the hardware lookup table; the @@ -1000,23 +1101,34 @@ KSTATUS UsbHubDispatchPnp(DEVICE_OBJECT* device, IRP* irp) { device->PnpState = DevicePnpDeleted; if (hub) { hub->Removed = true; - hub->WorkerRunning = false; - if (hub->ChangeUrbPending) - (void)UsbCancelUrb(&hub->ChangeUrb); - (void)UsbRemoveDeviceObjectChildren(device, false); - if (hub->InterruptPipe) { - hub->InterruptPipe->Open = false; - UsbDereferencePipe(hub->InterruptPipe); - hub->InterruptPipe = (USB_PIPE*)0; + if (hub->Kind == UsbHubKindRoot && hub->Hcd) { + uintptr_t flags = KeAcquireSpinlock(&hub->Hcd->RootHubLock); + if (hub->Hcd->RootHubContext == hub) + hub->Hcd->RootHubContext = (void*)0; + hub->Started = false; + KeReleaseSpinlock(&hub->Hcd->RootHubLock, flags); } + hub->WorkerRunning = false; UsbHubStopWorker(hub); - if (hub->Kind == UsbHubKindRoot && hub->Hcd && hub->Hcd->RootHubContext == hub) - hub->Hcd->RootHubContext = (void*)0; + if (hub->ChangeUrbPending) { + st = UsbCancelUrb(&hub->ChangeUrb); + if (!KSUCCESS(st)) { + IoCompleteRequest(irp, st, 0); + return NTLSTATUS_PENDING; + } + } + (void)UsbRemoveDeviceObjectChildren(device, false); + if (hub->InterruptPipe) + hub->InterruptPipe->Open = false; if (hub->RemoveLockReady) { IoReleaseRemoveLockAndWait(&hub->RemoveLock, irp); IoDestroyRemoveLock(&hub->RemoveLock); hub->RemoveLockReady = false; } + if (hub->InterruptPipe) { + UsbDereferencePipe(hub->InterruptPipe); + hub->InterruptPipe = (USB_PIPE*)0; + } UsbHubReleaseDma(hub); } @@ -1118,7 +1230,9 @@ KSTATUS UsbHubAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObje } if (kind == UsbHubKindRoot) { + uintptr_t flags = KeAcquireSpinlock(&hub->Hcd->RootHubLock); hub->Hcd->RootHubContext = hub; + KeReleaseSpinlock(&hub->Hcd->RootHubLock, flags); DbgPrint("usb-hub: root hub FDO attached\n"); } else { DbgPrint("usb-hub: external hub FDO attached slot=%u ep=%02x\n", @@ -1135,5 +1249,5 @@ KSTATUS UsbInitializeHubDriver(void) { gUsbHubDriver.IdTable = &gUsbHubIdTable; gUsbHubDriver.DriverAddDevice = UsbHubAddDevice; gUsbHubDriver.MajorFunction[IRP_MJ_PNP] = UsbHubDispatchPnp; - return IoRegisterDriverObject(&gUsbHubDriver); + return NTLSTATUS_SUCCESS; } diff --git a/code/drivers/bus/usb/usb_storage.c b/code/drivers/bus/usb/usb_storage.c index 0bf217a..ac03df4 100644 --- a/code/drivers/bus/usb/usb_storage.c +++ b/code/drivers/bus/usb/usb_storage.c @@ -347,7 +347,7 @@ KSTATUS UsbStorageTestUnitReady(USB_STORAGE_EXTENSION* storage) { if (KSUCCESS(st)) return NTLSTATUS_SUCCESS; last = st; - UsbDelayUs(xhci->Ddk, 10000u); + UsbDelayUs(10000u); } return last; @@ -468,7 +468,7 @@ void UsbStorageAcquireIo(USB_STORAGE_EXTENSION* storage) { if (!storage) return; XHCI_CONTROLLER* xhci = storage->Device ? (XHCI_CONTROLLER*)storage->Device->Hcd->ControllerContext : (XHCI_CONTROLLER*)0; while (NtlAtomicCompareExchange32(&storage->IoBusy, 1, 0) != 0) { - if (xhci) UsbDelayUs(xhci->Ddk, 10); + if (xhci) UsbDelayUs(10); } } @@ -584,10 +584,10 @@ KSTATUS XhciAllocateMassStorageMemory(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { dev->MassOutRing = (XHCI_TRB*)UsbDmaAllocPages(xhci, 1, &dev->MassOutRingDeviceAddress, &dev->MassOutRingDma); dev->MassBuffer = (uint8_t*)UsbDmaAllocPages(xhci, USB_STORAGE_DMA_PAGES, &dev->MassBufferDeviceAddress, &dev->MassBufferDma); if (!dev->MassInRing || !dev->MassOutRing || !dev->MassBuffer) { - if (gUsbDdk && gUsbDdk->Dma) { - gUsbDdk->Dma->FreeCommonBuffer(dev->MassBufferDma); - gUsbDdk->Dma->FreeCommonBuffer(dev->MassOutRingDma); - gUsbDdk->Dma->FreeCommonBuffer(dev->MassInRingDma); + { + IoDmaFreeCommonBuffer(dev->MassBufferDma); + IoDmaFreeCommonBuffer(dev->MassOutRingDma); + IoDmaFreeCommonBuffer(dev->MassInRingDma); } dev->MassBufferDma = (IO_DMA_BUFFER*)0; dev->MassOutRingDma = (IO_DMA_BUFFER*)0; @@ -1059,5 +1059,5 @@ KSTATUS UsbInitializeStorageDriver(void) { gUsbStorageDriver.MajorFunction[IRP_MJ_WRITE] = UsbStorageDispatchReadWrite; gUsbStorageDriver.MajorFunction[IRP_MJ_FLUSH_BUFFERS] = UsbStorageDispatchFlush; gUsbStorageDriver.MajorFunction[IRP_MJ_PNP] = UsbStorageDispatchPnp; - return IoRegisterDriverObject(&gUsbStorageDriver); + return NTLSTATUS_SUCCESS; } diff --git a/code/drivers/bus/usb/usb_xhci.c b/code/drivers/bus/usb/usb_xhci.c index a62ca5a..f5aa3cd 100644 --- a/code/drivers/bus/usb/usb_xhci.c +++ b/code/drivers/bus/usb/usb_xhci.c @@ -48,27 +48,22 @@ void XhciWrite64(volatile uint8_t* base, uint32_t reg, uint64_t value) { } // Implements the delay Us operation. -void UsbDelayUs(const NTL_DDK* ddk, uint32_t us) { - if (ddk && ddk->Timer && ddk->Timer->Stall) ddk->Timer->Stall(us); - else { - for (uint32_t i = 0; i < us * 32u; i++) _mm_pause(); - } +void UsbDelayUs(uint32_t us) { + KeStallExecution(us); } -bool UsbWaitSet32(const NTL_DDK* ddk, volatile uint8_t* base, uint32_t reg, - uint32_t mask, uint32_t timeoutUs) { +bool UsbWaitSet32(volatile uint8_t* base, uint32_t reg, uint32_t mask, uint32_t timeoutUs) { for (uint32_t i = 0; i < timeoutUs; i++) { if ((XhciRead32(base, reg) & mask) == mask) return true; - UsbDelayUs(ddk, 1); + UsbDelayUs(1); } return false; } -bool UsbWaitClear32(const NTL_DDK* ddk, volatile uint8_t* base, uint32_t reg, - uint32_t mask, uint32_t timeoutUs) { +bool UsbWaitClear32(volatile uint8_t* base, uint32_t reg, uint32_t mask, uint32_t timeoutUs) { for (uint32_t i = 0; i < timeoutUs; i++) { if ((XhciRead32(base, reg) & mask) == 0) return true; - UsbDelayUs(ddk, 1); + UsbDelayUs(1); } return false; } @@ -83,7 +78,7 @@ void* UsbDmaAllocPages(XHCI_CONTROLLER* xhci, return (void*)0; IO_DMA_BUFFER* buffer = (IO_DMA_BUFFER*)0; - KSTATUS status = xhci->Ddk->Dma->AllocateCommonBuffer(xhci->DmaAdapter, + KSTATUS status = IoDmaAllocateCommonBuffer(xhci->DmaAdapter, pages * (size_t)PAGE_SIZE, PAGE_SIZE, &buffer); @@ -93,14 +88,14 @@ void* UsbDmaAllocPages(XHCI_CONTROLLER* xhci, return (void*)0; } - *outDeviceAddress = xhci->Ddk->Dma->GetCommonBufferDeviceAddress(buffer); + *outDeviceAddress = IoDmaGetCommonBufferDeviceAddress(buffer); *outBuffer = buffer; - return xhci->Ddk->Dma->GetCommonBufferAddress(buffer); + return IoDmaGetCommonBufferAddress(buffer); } // Releases all DMA allocations owned by one xHCI slot. void XhciReleaseDeviceDma(XHCI_DEVICE* dev) { - if (!dev || !gUsbDdk || !gUsbDdk->Dma) + if (!dev) return; for (uint32_t endpoint = 1; endpoint < 32; endpoint++) { @@ -110,15 +105,15 @@ void XhciReleaseDeviceDma(XHCI_DEVICE* dev) { UsbDereferencePipe(pipe); } - gUsbDdk->Dma->FreeCommonBuffer(dev->MassBufferDma); - gUsbDdk->Dma->FreeCommonBuffer(dev->HidBufferDma); - gUsbDdk->Dma->FreeCommonBuffer(dev->DescriptorDma); - gUsbDdk->Dma->FreeCommonBuffer(dev->MassOutRingDma); - gUsbDdk->Dma->FreeCommonBuffer(dev->MassInRingDma); - gUsbDdk->Dma->FreeCommonBuffer(dev->HidRingDma); - gUsbDdk->Dma->FreeCommonBuffer(dev->Ep0RingDma); - gUsbDdk->Dma->FreeCommonBuffer(dev->DeviceContextDma); - gUsbDdk->Dma->FreeCommonBuffer(dev->InputContextDma); + IoDmaFreeCommonBuffer(dev->MassBufferDma); + IoDmaFreeCommonBuffer(dev->HidBufferDma); + IoDmaFreeCommonBuffer(dev->DescriptorDma); + IoDmaFreeCommonBuffer(dev->MassOutRingDma); + IoDmaFreeCommonBuffer(dev->MassInRingDma); + IoDmaFreeCommonBuffer(dev->HidRingDma); + IoDmaFreeCommonBuffer(dev->Ep0RingDma); + IoDmaFreeCommonBuffer(dev->DeviceContextDma); + IoDmaFreeCommonBuffer(dev->InputContextDma); dev->MassBufferDma = (IO_DMA_BUFFER*)0; dev->HidBufferDma = (IO_DMA_BUFFER*)0; @@ -143,22 +138,22 @@ void XhciReleaseDeviceDma(XHCI_DEVICE* dev) { // Releases controller-wide xHCI DMA allocations. static void XhciReleaseControllerDma(XHCI_CONTROLLER* xhci) { - if (!xhci || !xhci->Ddk || !xhci->Ddk->Dma) + if (!xhci) return; for (uint32_t slot = 1; slot <= XHCI_MAX_TRACKED_SLOTS; slot++) XhciReleaseDeviceDma(&xhci->Devices[slot]); for (uint16_t i = 0; i < XHCI_MAX_SCRATCHPADS; i++) { - xhci->Ddk->Dma->FreeCommonBuffer(xhci->ScratchpadDma[i]); + IoDmaFreeCommonBuffer(xhci->ScratchpadDma[i]); xhci->ScratchpadDma[i] = (IO_DMA_BUFFER*)0; xhci->ScratchpadDeviceAddress[i] = 0; } - xhci->Ddk->Dma->FreeCommonBuffer(xhci->ScratchpadArrayDma); - xhci->Ddk->Dma->FreeCommonBuffer(xhci->ErstDma); - xhci->Ddk->Dma->FreeCommonBuffer(xhci->EventRingDma); - xhci->Ddk->Dma->FreeCommonBuffer(xhci->CommandRingDma); - xhci->Ddk->Dma->FreeCommonBuffer(xhci->DcbaaDma); + IoDmaFreeCommonBuffer(xhci->ScratchpadArrayDma); + IoDmaFreeCommonBuffer(xhci->ErstDma); + IoDmaFreeCommonBuffer(xhci->EventRingDma); + IoDmaFreeCommonBuffer(xhci->CommandRingDma); + IoDmaFreeCommonBuffer(xhci->DcbaaDma); xhci->ScratchpadArrayDma = (IO_DMA_BUFFER*)0; xhci->ErstDma = (IO_DMA_BUFFER*)0; xhci->EventRingDma = (IO_DMA_BUFFER*)0; @@ -170,7 +165,7 @@ static void XhciReleaseControllerDma(XHCI_CONTROLLER* xhci) { xhci->CommandRing = (XHCI_TRB*)0; xhci->Dcbaa = (uint64_t*)0; - xhci->Ddk->Dma->PutAdapter(xhci->DmaAdapter); + IoDmaPutAdapter(xhci->DmaAdapter); xhci->DmaAdapter = (IO_DMA_ADAPTER*)0; } @@ -220,6 +215,76 @@ uint32_t* XhciInputEndpointContext(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, return (uint32_t*)(dev->InputContext + (uint32_t)(endpointId + 1u) * xhci->ContextBytes); } +// Reconstructs the addressed device topology when configuring an endpoint or hub. +// Configure Endpoint must not discard route, TT, or hub attributes on the slot. +void XhciBuildConfigureSlotContext(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, + uint32_t* slot, uint8_t endpointId) { + if (!xhci || !dev || !slot) + return; + memset(slot, 0, xhci->ContextBytes); + + uint8_t lastContext = endpointId; + if (dev->DeviceContext) { + uint32_t previous = ((const uint32_t*)dev->DeviceContext)[0]; + uint8_t previousLast = (uint8_t)((previous >> 27) & 31u); + if (previousLast > lastContext) + lastContext = previousLast; + } + uint8_t rootPort = dev->Path.RootPort ? dev->Path.RootPort : dev->Port; + slot[0] = XHCI_CONTEXT_ENTRIES(lastContext) | XHCI_SLOT_SPEED(dev->Speed) | + (dev->Path.RouteString & 0xfffffu); + slot[1] = XHCI_SLOT_ROOT_PORT(rootPort); + if (dev->Path.BehindHub && dev->Speed <= 2u && dev->Path.ParentHubSlot) { + slot[2] = (uint32_t)dev->Path.ParentHubSlot | + ((uint32_t)dev->Path.ParentHubPort << 8); + } + if (dev->HubPortCount) { + slot[0] |= XHCI_SLOT_HUB; + slot[1] |= XHCI_SLOT_PORT_COUNT(dev->HubPortCount); + if (dev->Speed == 3u) + slot[2] |= XHCI_SLOT_TT_THINK(dev->HubCharacteristics >> 5); + } +} + +// Publishes an external hub's topology to xHCI before its child ports are used. +KSTATUS XhciConfigureHubContext(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, + uint8_t portCount, uint16_t characteristics) { + if (!xhci || !dev || !dev->Addressed || !dev->InputContext || + !dev->DeviceContext || !portCount) + return NTLSTATUS_INVALID_PARAMETER; + + uint8_t oldPortCount = dev->HubPortCount; + uint16_t oldCharacteristics = dev->HubCharacteristics; + dev->HubPortCount = portCount; + dev->HubCharacteristics = characteristics; + + memset(dev->InputContext, 0, PAGE_SIZE); + uint32_t* icc = XhciInputControlContext(xhci, dev); + uint32_t* slot = XhciInputSlotContext(xhci, dev); + // Update only the slot context; existing endpoint contexts remain active. + icc[1] = 1u; + XhciBuildConfigureSlotContext(xhci, dev, slot, 1u); + + XHCI_COMMAND_RESULT result; + memset(&result, 0, sizeof(result)); + DbgPrint("usb-stage: hcd=%02x:%02x.%u hub-slot=%u configure-context begin ports=%u tt=%u speed=%u\n", + xhci->Bus, xhci->Dev, xhci->Fn, dev->SlotId, + portCount, (characteristics >> 5) & 3u, dev->Speed); + KSTATUS st = XhciSubmitCommand(xhci, dev->InputContextDeviceAddress, 0, + (XHCI_TRB_TYPE_CONFIGURE_ENDPOINT << XHCI_TRB_TYPE_SHIFT) | + ((uint32_t)dev->SlotId << 24), + &result); + if (!KSUCCESS(st) || result.CompletionCode != XHCI_CC_SUCCESS) { + DbgPrint("usb-stage: hub-slot=%u configure-context failed st=0x%x cc=%u\n", + dev->SlotId, st, result.CompletionCode); + dev->HubPortCount = oldPortCount; + dev->HubCharacteristics = oldCharacteristics; + return KSUCCESS(st) ? NTLSTATUS_DEVICE_ERROR : st; + } + DbgPrint("usb-stage: hub-slot=%u configure-context done\n", dev->SlotId); + return NTLSTATUS_SUCCESS; +} + // Implements the ep Max Packet operation. uint32_t XhciEpMaxPacket(uint8_t speed) { switch (speed) { @@ -254,6 +319,15 @@ const char* XhciPortSpeedName(uint32_t speed) { } } +// Advances an xHCI extended-capability offset using the relative DWORD link. +static uint32_t XhciNextExtendedCapabilityDword(uint32_t current, uint8_t next, + uint32_t mappedBytes) { + if (!next) return 0; + uint64_t following = (uint64_t)current + next; + if (following * 4u + sizeof(uint32_t) > mappedBytes) return 0; + return (uint32_t)following; +} + // Parses supported Protocols. void XhciParseSupportedProtocols(XHCI_CONTROLLER* xhci) { memset(xhci->PortProtocolMajor, 0, sizeof(xhci->PortProtocolMajor)); @@ -297,8 +371,18 @@ void XhciParseSupportedProtocols(XHCI_CONTROLLER* xhci) { } } - xecp = next; + xecp = XhciNextExtendedCapabilityDword(xecp, next, xhci->MmioBytes); } + + uint32_t usb2Ports = 0; + uint32_t usb3Ports = 0; + for (uint32_t port = 1; port <= xhci->MaxPorts; port++) { + if (xhci->PortProtocolMajor[port] == 2u) usb2Ports++; + else if (xhci->PortProtocolMajor[port] >= 3u) usb3Ports++; + } + DbgPrint("usb: xHCI hcd=%02x:%02x.%u protocol ports usb2=%u usb3=%u unknown=%u\n", + xhci->Bus, xhci->Dev, xhci->Fn, usb2Ports, usb3Ports, + (uint32_t)xhci->MaxPorts - usb2Ports - usb3Ports); } // Implements the port Protocol Major operation. @@ -385,7 +469,7 @@ void XhciPowerRootPorts(XHCI_CONTROLLER* xhci) { } if (powered) { - UsbDelayUs(xhci->Ddk, 20000u); + UsbDelayUs(20000u); DbgPrint("usb: xHCI powered %u previously-off root port(s)\n", powered); } } @@ -465,12 +549,43 @@ 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; - bool processed = false; +// A completion leaves EventLock with its class-device rundown reference held. +// Client callbacks can rearm transfers or acquire unrelated PnP locks. +#define XHCI_COMPLETION_BATCH 16u - while (true) { +typedef struct XHCI_ASYNC_COMPLETION { + USB_URB* Urb; + IRP* Irp; + KSTATUS Status; + uint32_t ActualLength; +} XHCI_ASYNC_COMPLETION; + +// Claims an asynchronous TD exactly once while the event lock is owned. +static bool XhciClaimAsyncCompletion(XHCI_CONTROLLER* xhci, uint64_t trbAddress, + uint32_t completionCode, uint8_t slotId, + uint8_t endpointId, uint32_t residue, + XHCI_ASYNC_COMPLETION* completion); + +// Executes callbacks outside the event lock; class remove locks own their lifetime. +static void XhciDispatchAsyncCompletion(const XHCI_ASYNC_COMPLETION* completed) { + USB_URB* urb = completed->Urb; + if (urb->Complete) + urb->Complete(urb, urb->CompleteContext); + if (completed->Irp) + IoCompleteRequest(completed->Irp, completed->Status, + KSUCCESS(completed->Status) ? completed->ActualLength : 0); +} + +// Drains a bounded batch while the event lock is held. +static bool XhciDrainEventRing(XHCI_CONTROLLER* xhci, + XHCI_ASYNC_COMPLETION* completions, uint32_t* completionCount, + uint32_t* changedPorts) { + bool processed = false; + *completionCount = 0; + + for (uint32_t drained = 0; drained < XHCI_EVENT_TRBS; drained++) { + if (*completionCount == XHCI_COMPLETION_BATCH) + break; XHCI_TRB* ev = &xhci->EventRing[xhci->EventDequeue]; uint32_t control = ev->Control; uint8_t cycle = (uint8_t)(control & XHCI_TRB_CYCLE); @@ -495,15 +610,15 @@ bool XhciDrainEventRing(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target) { UsbTrace("usb: xHCI transfer event trb=0x%016llX cc=%u slot=%u ep=%u residue=%u\n", (unsigned long long)param, cc, slotId, epId, residue); } - XHCI_WAIT_TARGET* waiter = XhciClaimTransferWaiter(xhci, param, slotId, epId, cc, residue); - if (!waiter) - XhciHandleAsyncTransferEvent(xhci, param, cc, slotId, epId, residue); + if (!waiter && XhciClaimAsyncCompletion(xhci, param, cc, slotId, epId, residue, + &completions[*completionCount])) + (*completionCount)++; } else if (type == XHCI_TRB_TYPE_PORT_SC) { uint32_t portId = ev->ParameterLo >> 24; UsbTrace("usb: xHCI port-status-change event port=%u cc=%u\n", portId, cc); - if (portId && portId <= xhci->MaxPorts) - UsbHubQueueRootPortChange(&xhci->Hcd, (uint8_t)portId); + if (portId && portId <= xhci->MaxPorts && portId <= USB_ROOT_HUB_MAX_PORTS) + changedPorts[portId / 32u] |= 1u << (portId & 31u); } else { DbgPrint("usb: xHCI event type=%u cc=%u p=0x%016llX status=%08x ctrl=%08x\n", type, cc, (unsigned long long)param, ev->Status, ev->Control); @@ -521,12 +636,27 @@ bool XhciDrainEventRing(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target) { return processed; } -// Implements the drain Event Ring Locked operation. +// Serializes event-ring ownership without calling into class drivers under the lock. bool XhciDrainEventRingLocked(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target) { + (void)target; if (!xhci) return false; + XHCI_ASYNC_COMPLETION completions[XHCI_COMPLETION_BATCH]; + uint32_t changedPorts[8] = { 0 }; + uint32_t count = 0; uintptr_t flags = KeAcquireSpinlock(&xhci->EventLock); - bool processed = XhciDrainEventRing(xhci, target); + bool processed = XhciDrainEventRing(xhci, completions, &count, changedPorts); KeReleaseSpinlock(&xhci->EventLock, flags); + + // Hub wakeups can take scheduler locks; keep them outside EventLock too. + for (uint32_t word = 0; word < 8; word++) { + uint32_t bits = changedPorts[word]; + for (uint32_t bit = 0; bit < 32; bit++) { + if (bits & (1u << bit)) + UsbHubQueueRootPortChange(&xhci->Hcd, (uint8_t)(word * 32u + bit)); + } + } + for (uint32_t i = 0; i < count; i++) + XhciDispatchAsyncCompletion(&completions[i]); return processed; } @@ -591,7 +721,7 @@ KSTATUS XhciWaitForArmedTarget(XHCI_CONTROLLER* xhci, XHCI_WAIT_TARGET* target, KeInsertQueueDpc(&xhci->EventDpc, (void*)0, (void*)0); } - UsbDelayUs(xhci->Ddk, 1); + UsbDelayUs(1); } XhciUnregisterWaitTarget(xhci, target); @@ -617,9 +747,17 @@ void XhciEventDpcRoutine(KDPC* dpc, void* context, return; xhci->EventDpcCount++; - while (XhciDrainEventRingLocked(xhci, NULL)) { - // Keep draining until the software dequeue catches up to the producer. + // Processing is bounded even if connected hubs continuously report changes. + // Updating ERDP clears EHB; remaining events cause the interrupter to + // assert again, allowing other DPCs and scheduled threads to make progress. + for (uint32_t pass = 0; pass < 4u; pass++) { + if (!XhciDrainEventRingLocked(xhci, NULL)) + return; } + uint32_t hits = ++xhci->EventDpcBudgetHits; + if ((hits & (hits - 1u)) == 0u) + DbgPrint("usb: xHCI hcd=%02x:%02x.%u event DPC budget reached count=%u\n", + xhci->Bus, xhci->Dev, xhci->Fn, hits); } #define XHCI_CONTROLLER_MAGIC 0x58484349u // XHCI @@ -700,7 +838,7 @@ KSTATUS XhciBiosHandoff(XHCI_CONTROLLER* xhci) { ok = true; break; } - UsbDelayUs(xhci->Ddk, 1); + UsbDelayUs(1); } if (!ok) { DbgPrint("usb: xHCI BIOS handoff timed out; continuing anyway\n"); @@ -712,7 +850,7 @@ KSTATUS XhciBiosHandoff(XHCI_CONTROLLER* xhci) { return NTLSTATUS_SUCCESS; } - xecp = next; + xecp = XhciNextExtendedCapabilityDword(xecp, next, xhci->MmioBytes); } return NTLSTATUS_SUCCESS; @@ -723,7 +861,7 @@ KSTATUS XhciStopAndReset(XHCI_CONTROLLER* xhci) { uint32_t cmd = XhciRead32(xhci->Op, XHCI_OP_USBCMD); if (cmd & XHCI_USBCMD_RS) { XhciWrite32(xhci->Op, XHCI_OP_USBCMD, cmd & ~XHCI_USBCMD_RS); - if (!UsbWaitSet32(xhci->Ddk, xhci->Op, XHCI_OP_USBSTS, + if (!UsbWaitSet32(xhci->Op, XHCI_OP_USBSTS, XHCI_USBSTS_HCH, 1000000u)) { DbgPrint("usb: xHCI stop timeout usbsts=%08x\n", XhciRead32(xhci->Op, XHCI_OP_USBSTS)); @@ -732,14 +870,14 @@ KSTATUS XhciStopAndReset(XHCI_CONTROLLER* xhci) { } XhciWrite32(xhci->Op, XHCI_OP_USBCMD, XHCI_USBCMD_HCRST); - if (!UsbWaitClear32(xhci->Ddk, xhci->Op, XHCI_OP_USBCMD, + if (!UsbWaitClear32(xhci->Op, XHCI_OP_USBCMD, XHCI_USBCMD_HCRST, 1000000u)) { DbgPrint("usb: xHCI reset timeout usbcmd=%08x\n", XhciRead32(xhci->Op, XHCI_OP_USBCMD)); return NTLSTATUS_TIMEOUT; } - if (!UsbWaitClear32(xhci->Ddk, xhci->Op, XHCI_OP_USBSTS, + if (!UsbWaitClear32(xhci->Op, XHCI_OP_USBSTS, XHCI_USBSTS_CNR, 1000000u)) { DbgPrint("usb: xHCI controller-not-ready timeout usbsts=%08x\n", XhciRead32(xhci->Op, XHCI_OP_USBSTS)); @@ -769,8 +907,7 @@ static void XhciHaltForDmaRelease(XHCI_CONTROLLER* xhci) { uint32_t command = XhciRead32(xhci->Op, XHCI_OP_USBCMD); command &= ~(XHCI_USBCMD_RS | XHCI_USBCMD_INTE); XhciWrite32(xhci->Op, XHCI_OP_USBCMD, command); - if (!UsbWaitSet32(xhci->Ddk, - xhci->Op, + if (!UsbWaitSet32(xhci->Op, XHCI_OP_USBSTS, XHCI_USBSTS_HCH, 1000000u)) @@ -1048,7 +1185,7 @@ KSTATUS XhciStart(XHCI_CONTROLLER* xhci) { cmd |= XHCI_USBCMD_RS; XhciWrite32(xhci->Op, XHCI_OP_USBCMD, cmd); - if (!UsbWaitClear32(xhci->Ddk, xhci->Op, XHCI_OP_USBSTS, + if (!UsbWaitClear32(xhci->Op, XHCI_OP_USBSTS, XHCI_USBSTS_HCH, 1000000u)) { DbgPrint("usb: xHCI start timeout usbsts=%08x\n", XhciRead32(xhci->Op, XHCI_OP_USBSTS)); @@ -1099,6 +1236,8 @@ KSTATUS XhciResetPort(XHCI_CONTROLLER* xhci, uint8_t port, uint8_t* outSpeed) { uint32_t speed = (portsc & XHCI_PORTSC_SPEED_MASK) >> XHCI_PORTSC_SPEED_SHIFT; uint8_t protocol = XhciPortProtocolMajor(xhci, port, portsc); + DbgPrint("usb-stage: hcd=%02x:%02x.%u root-port=%u reset begin proto=%u speed=%u portsc=%08x\n", + xhci->Bus, xhci->Dev, xhci->Fn, port, protocol, speed, portsc); XhciClearPortChanges(xhci, port); portsc = XhciRead32(xhci->Op, off); @@ -1120,7 +1259,7 @@ KSTATUS XhciResetPort(XHCI_CONTROLLER* xhci, uint8_t port, uint8_t* outSpeed) { break; } XhciDrainEventRingLocked(xhci, NULL); - UsbDelayUs(xhci->Ddk, 1); + UsbDelayUs(1); } XhciClearPortChanges(xhci, port); @@ -1133,6 +1272,8 @@ KSTATUS XhciResetPort(XHCI_CONTROLLER* xhci, uint8_t port, uint8_t* outSpeed) { (portsc & XHCI_PORTSC_PED) ? 1u : 0u, protocol, XhciPortSpeedName(speed), speed); + DbgPrint("usb-stage: hcd=%02x:%02x.%u root-port=%u superspeed ready=%u portsc=%08x\n", + xhci->Bus, xhci->Dev, xhci->Fn, port, enabled ? 1u : 0u, portsc); if (!enabled || !(portsc & XHCI_PORTSC_CCS) || !(portsc & XHCI_PORTSC_PED)) return NTLSTATUS_DEVICE_ERROR; return NTLSTATUS_SUCCESS; @@ -1149,11 +1290,12 @@ KSTATUS XhciResetPort(XHCI_CONTROLLER* xhci, uint8_t port, uint8_t* outSpeed) { break; } XhciDrainEventRingLocked(xhci, NULL); - UsbDelayUs(xhci->Ddk, 1); + UsbDelayUs(1); } if (!resetDone) { - DbgPrint("usb: xHCI port %u reset timeout portsc=%08x\n", port, portsc); + DbgPrint("usb: xHCI hcd=%02x:%02x.%u port %u reset timeout portsc=%08x\n", + xhci->Bus, xhci->Dev, xhci->Fn, port, portsc); return NTLSTATUS_TIMEOUT; } @@ -1167,6 +1309,8 @@ KSTATUS XhciResetPort(XHCI_CONTROLLER* xhci, uint8_t port, uint8_t* outSpeed) { (portsc & XHCI_PORTSC_PED) ? 1u : 0u, protocol, XhciPortSpeedName(speed), speed); + DbgPrint("usb-stage: hcd=%02x:%02x.%u root-port=%u reset done portsc=%08x speed=%u\n", + xhci->Bus, xhci->Dev, xhci->Fn, port, portsc, speed); if (!(portsc & XHCI_PORTSC_CCS) || !(portsc & XHCI_PORTSC_PED)) return NTLSTATUS_DEVICE_ERROR; @@ -1674,10 +1818,7 @@ KSTATUS XhciConfigurePipeEndpoint(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, USB_P icc[0] = 0; icc[1] = (1u << 0) | (1u << dci); - slot[0] = XHCI_CONTEXT_ENTRIES(dci) | XHCI_SLOT_SPEED(dev->Speed); - slot[1] = XHCI_SLOT_ROOT_PORT(dev->Port); - slot[2] = 0; - slot[3] = 0; + XhciBuildConfigureSlotContext(xhci, dev, slot, dci); uint16_t maxPacket = pipe->MaxPacketSize & 0x07FFu; if (maxPacket == 0) maxPacket = 8u; @@ -1692,7 +1833,7 @@ KSTATUS XhciConfigurePipeEndpoint(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, USB_P } else if (pipe->Type == UsbPipeBulk) { endpointType = XHCI_EP_TYPE_BULK_OUT; } else { - xhci->Ddk->Dma->FreeCommonBuffer(ctx->RingDma); + IoDmaFreeCommonBuffer(ctx->RingDma); MmFreePool(ctx); return NTLSTATUS_NOT_SUPPORTED; } @@ -1715,7 +1856,7 @@ KSTATUS XhciConfigurePipeEndpoint(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev, USB_P if (!KSUCCESS(st) || result.CompletionCode != XHCI_CC_SUCCESS) { DbgPrint("usb: Configure pipe endpoint failed slot=%u ep=%u type=%u st=0x%x cc=%u\n", dev->SlotId, dci, pipe->Type, st, result.CompletionCode); - xhci->Ddk->Dma->FreeCommonBuffer(ctx->RingDma); + IoDmaFreeCommonBuffer(ctx->RingDma); MmFreePool(ctx); return KSUCCESS(st) ? NTLSTATUS_DEVICE_ERROR : st; } @@ -1740,8 +1881,15 @@ static KSTATUS XhciHcdSubmitInterruptUrb(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev XHCI_PIPE_CONTEXT* ctx = (XHCI_PIPE_CONTEXT*)urb->Pipe->HcdPipeContext; if (!ctx || !ctx->Configured) return NTLSTATUS_DEVICE_ERROR; - if (ctx->ActiveTransfer) + uintptr_t flags = KeAcquireSpinlock(&xhci->EventLock); + if (ctx->ActiveTransfer) { + KeReleaseSpinlock(&xhci->EventLock, flags); return NTLSTATUS_QUEUE_FULL; + } + if (urb->Pipe->Halted) { + KeReleaseSpinlock(&xhci->EventLock, flags); + return NTLSTATUS_OBJECT_DEAD; + } uint64_t trbDeviceAddress = 0; st = XhciRingAppend(ctx->Ring, @@ -1755,7 +1903,10 @@ static KSTATUS XhciHcdSubmitInterruptUrb(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev XHCI_TRB_ISP | XHCI_TRB_IOC, &trbDeviceAddress); - if (!KSUCCESS(st)) return st; + if (!KSUCCESS(st)) { + KeReleaseSpinlock(&xhci->EventLock, flags); + return st; + } ctx->ActiveTransfer = true; ctx->ActiveTrbDeviceAddress = trbDeviceAddress; @@ -1768,58 +1919,51 @@ static KSTATUS XhciHcdSubmitInterruptUrb(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev XhciBarrier(); XhciWrite32(xhci->Doorbells, (uint32_t)dev->SlotId * 4u, ctx->EndpointId); + KeReleaseSpinlock(&xhci->EventLock, flags); return NTLSTATUS_PENDING; } -void XhciHandleAsyncTransferEvent(XHCI_CONTROLLER* xhci, uint64_t trbDeviceAddress, - uint32_t completionCode, uint8_t slotId, - uint8_t endpointId, uint32_t residue) { - if (!xhci || slotId == 0 || slotId > XHCI_MAX_TRACKED_SLOTS || endpointId >= 32) - return; +// Completes ownership of an interrupt URB before its callback is dispatched. +static bool XhciClaimAsyncCompletion(XHCI_CONTROLLER* xhci, uint64_t trbDeviceAddress, + uint32_t completionCode, uint8_t slotId, + uint8_t endpointId, uint32_t residue, + XHCI_ASYNC_COMPLETION* completed) { + if (!xhci || !completed || slotId == 0 || slotId > XHCI_MAX_TRACKED_SLOTS || endpointId >= 32) + return false; XHCI_DEVICE* dev = &xhci->Devices[slotId]; USB_PIPE* pipe = dev->EndpointPipes[endpointId]; if (!pipe || !pipe->HcdPipeContext) - return; + return false; XHCI_PIPE_CONTEXT* ctx = (XHCI_PIPE_CONTEXT*)pipe->HcdPipeContext; if (!ctx->ActiveTransfer || ctx->ActiveTrbDeviceAddress != trbDeviceAddress) - return; + return false; USB_URB* urb = ctx->ActiveUrb; ctx->ActiveTransfer = false; ctx->ActiveTrbDeviceAddress = 0; ctx->ActiveUrb = (USB_URB*)0; - if (!urb) - return; + return false; KSTATUS st = NTLSTATUS_SUCCESS; - if (urb->Cancelled) { + if (urb->Cancelled) st = NTLSTATUS_CANCELLED; - } else if (completionCode != XHCI_CC_SUCCESS && - completionCode != XHCI_CC_SHORT_PACKET) { + else if (completionCode != XHCI_CC_SUCCESS && completionCode != XHCI_CC_SHORT_PACKET) st = NTLSTATUS_DEVICE_ERROR; - } - - uint32_t actual = 0; - if (KSUCCESS(st)) { - actual = (residue <= urb->Length) ? (urb->Length - residue) : 0; - } + uint32_t actual = KSUCCESS(st) && residue <= urb->Length ? urb->Length - residue : 0; urb->Pending = false; urb->Status = st; urb->ActualLength = actual; urb->HcdCompletionCode = completionCode; - - IRP* completeIrp = urb->Irp; + completed->Urb = urb; + completed->Irp = urb->Irp; + completed->Status = st; + completed->ActualLength = actual; urb->Irp = (IRP*)0; - - if (urb->Complete) - urb->Complete(urb, urb->CompleteContext); - - if (completeIrp) - IoCompleteRequest(completeIrp, st, KSUCCESS(st) ? actual : 0); + return true; } // Implements the hcd Submit Bulk Urb operation. @@ -1905,13 +2049,59 @@ static KSTATUS XhciHcdSubmitUrb(USB_HCD* hcd, USB_URB* urb) { return st; } -// Implements the hcd Cancel Urb operation. static KSTATUS XhciHcdCancelUrb(USB_HCD* hcd, USB_URB* urb) { - (void)hcd; - if (!urb) return NTLSTATUS_INVALID_PARAMETER; + if (!hcd || !urb || !urb->Device || !urb->Pipe) + return NTLSTATUS_INVALID_PARAMETER; + + XHCI_CONTROLLER* xhci = (XHCI_CONTROLLER*)hcd->ControllerContext; + XHCI_DEVICE* dev = (XHCI_DEVICE*)urb->Device->HcdDeviceContext; + if (!xhci || !dev || dev->SlotId == 0 || !urb->Pipe->HcdPipeContext) + return NTLSTATUS_INVALID_PARAMETER; + + XHCI_PIPE_CONTEXT* ctx = (XHCI_PIPE_CONTEXT*)urb->Pipe->HcdPipeContext; + uintptr_t flags = KeAcquireSpinlock(&xhci->EventLock); urb->Cancelled = true; - if (!urb->Pending) - urb->Status = NTLSTATUS_CANCELLED; + bool active = ctx->ActiveTransfer && ctx->ActiveUrb == urb; + KeReleaseSpinlock(&xhci->EventLock, flags); + if (!active) + return NTLSTATUS_SUCCESS; + + XHCI_COMMAND_RESULT result; + memset(&result, 0, sizeof(result)); + KSTATUS st = XhciSubmitCommand(xhci, 0, 0, + (XHCI_TRB_TYPE_STOP_ENDPOINT << XHCI_TRB_TYPE_SHIFT) | + ((uint32_t)ctx->EndpointId << 16) | + ((uint32_t)dev->SlotId << 24), + &result); + + flags = KeAcquireSpinlock(&xhci->EventLock); + if (!ctx->ActiveTransfer || ctx->ActiveUrb != urb) { + KeReleaseSpinlock(&xhci->EventLock, flags); + return NTLSTATUS_SUCCESS; + } + if (!KSUCCESS(st) || result.CompletionCode != XHCI_CC_SUCCESS) { + KeReleaseSpinlock(&xhci->EventLock, flags); + DbgPrint("usb: xHCI STOP_ENDPOINT failed slot=%u ep=%u st=0x%x cc=%u\n", + dev->SlotId, ctx->EndpointId, st, result.CompletionCode); + return KSUCCESS(st) ? NTLSTATUS_DEVICE_ERROR : st; + } + + ctx->ActiveTransfer = false; + ctx->ActiveTrbDeviceAddress = 0; + ctx->ActiveUrb = (USB_URB*)0; + urb->Pipe->Halted = true; + urb->Pending = false; + urb->Status = NTLSTATUS_CANCELLED; + urb->ActualLength = 0; + urb->HcdCompletionCode = result.CompletionCode; + + IRP* completeIrp = urb->Irp; + urb->Irp = (IRP*)0; + KeReleaseSpinlock(&xhci->EventLock, flags); + if (urb->Complete) + urb->Complete(urb, urb->CompleteContext); + if (completeIrp) + IoCompleteRequest(completeIrp, NTLSTATUS_CANCELLED, 0); return NTLSTATUS_SUCCESS; } @@ -1933,8 +2123,7 @@ static void XhciHcdDestroyPipe(USB_HCD* hcd, USB_PIPE* pipe) { if (!pipe || !pipe->HcdPipeContext) return; XHCI_PIPE_CONTEXT* ctx = (XHCI_PIPE_CONTEXT*)pipe->HcdPipeContext; - if (gUsbDdk && gUsbDdk->Dma) - gUsbDdk->Dma->FreeCommonBuffer(ctx->RingDma); + IoDmaFreeCommonBuffer(ctx->RingDma); ctx->RingDma = (IO_DMA_BUFFER*)0; ctx->Ring = (XHCI_TRB*)0; pipe->HcdPipeContext = (void*)0; @@ -2109,7 +2298,7 @@ KSTATUS XhciStartController(XHCI_CONTROLLER* xhci) { dmaRequirements.Boundary = 0x10000u; dmaRequirements.MaximumSegmentLength = 0x10000u; dmaRequirements.MaximumSegments = XHCI_PIPE_RING_TRBS - 1u; - st = xhci->Ddk->Dma->GetAdapter(xhci->Fdo, + st = IoDmaGetAdapter(xhci->Fdo, &dmaRequirements, &xhci->DmaAdapter); if (!KSUCCESS(st)) { @@ -2165,7 +2354,7 @@ KSTATUS XhciStartController(XHCI_CONTROLLER* xhci) { if (!KSUCCESS(st)) DbgPrint("usb: root hub async scan scheduling failed st=0x%x\n", st); else - DbgPrint("usb: root hub initial scan queued asynchronously\n"); + DbgPrint("usb: root hub initial scan requested\n"); DbgPrint("usb: xHCI host controller initialised; HID boot input active=%u mass-storage will arrive through hotplug\n", xhci->HidDeviceCount); @@ -2247,7 +2436,7 @@ KSTATUS XhciAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObject XHCI_CONTROLLER* xhci = (XHCI_CONTROLLER*)fdo->DeviceExtension; memset(xhci, 0, sizeof(*xhci)); xhci->Magic = XHCI_CONTROLLER_MAGIC; - xhci->Ddk = gUsbDdk; + xhci->Fdo = fdo; xhci->Pdo = physicalDeviceObject; xhci->Irq = 0xFFu; diff --git a/code/drivers/display/vmwaresvga/vmwaresvga.c b/code/drivers/display/vmwaresvga/vmwaresvga.c index 8ff0276..1080c6a 100644 --- a/code/drivers/display/vmwaresvga/vmwaresvga.c +++ b/code/drivers/display/vmwaresvga/vmwaresvga.c @@ -1,10 +1,9 @@ +#include // VMware SVGA-II PCI display provider. This file owns adapter setup, FIFO command submission, framebuffer presentation, and cursor integration. #include #include "vmwaresvga.h" -const NTL_DDK* gVmwSvgaDdk; - static DRIVER_OBJECT gVmwSvgaDriver; static const IO_DEVICE_ID gVmwSvgaIds[] = { @@ -1500,8 +1499,10 @@ static KSTATUS VmwSvgaStartDevice(VMWSVGA_DEVICE* v) { st = KeDisplaySetPrimary(&v->DisplayDevice); KeDisplaySuspendConsole(false); - if (!KSUCCESS(st)) + if (!KSUCCESS(st)) { + (void)KeDisplayUnregisterDevice(&v->DisplayDevice); return st; + } v->Started = true; v->Fdo->PnpState = DevicePnpStarted; @@ -1534,11 +1535,15 @@ KSTATUS VmwSvgaDispatchPnp(DEVICE_OBJECT* device, IRP* irp) { return NTLSTATUS_SUCCESS; case IRP_MN_SURPRISE_REMOVAL: + if (v->DisplayDevice.Ops) + (void)KeDisplayUnregisterDevice(&v->DisplayDevice); v->SurpriseRemoved = true; device->PnpState = DevicePnpSurpriseRemoved; return NTLSTATUS_SUCCESS; case IRP_MN_REMOVE_DEVICE: + if (v->DisplayDevice.Ops) + (void)KeDisplayUnregisterDevice(&v->DisplayDevice); v->Removed = true; device->PnpState = DevicePnpDeleted; if (v->Backbuffer) { @@ -1588,23 +1593,18 @@ KSTATUS VmwSvgaAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObj return NTLSTATUS_SUCCESS; } -// Implements the driver Entry operation. -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { - (void)init; - if (!ddk) - return NTLSTATUS_INVALID_PARAMETER; +static KSTATUS DriverInitialize(NTL_MODULE* module) { + (void)module; DbgPrint("VMWSVGA: driver loading...\n"); - gVmwSvgaDdk = ddk; memset(&gVmwSvgaDriver, 0, sizeof(gVmwSvgaDriver)); IoInitializeDriverObject(&gVmwSvgaDriver, SYS_SV_LITERAL("vmwaresvga")); gVmwSvgaDriver.IdTable = &gVmwSvgaIdTable; gVmwSvgaDriver.DriverAddDevice = VmwSvgaAddDevice; gVmwSvgaDriver.MajorFunction[IRP_MJ_PNP] = VmwSvgaDispatchPnp; - - KSTATUS st = IoRegisterDriverObject(&gVmwSvgaDriver); - if (!KSUCCESS(st)) - DbgPrint("VMWSVGA: IoRegisterDriverObject failed: 0x%x\n", st); - return st; + return NTLSTATUS_SUCCESS; } + +static DRIVER_OBJECT* const gModuleIoDrivers[] = { &gVmwSvgaDriver }; +NTL_DECLARE_IO_MODULE(DriverInitialize, gModuleIoDrivers); diff --git a/code/drivers/display/vmwaresvga/vmwaresvga.h b/code/drivers/display/vmwaresvga/vmwaresvga.h index 90b1eb1..80c52ed 100644 --- a/code/drivers/display/vmwaresvga/vmwaresvga.h +++ b/code/drivers/display/vmwaresvga/vmwaresvga.h @@ -210,7 +210,5 @@ typedef struct VMWSVGA_DEVICE { DISPLAY_DEVICE DisplayDevice; } VMWSVGA_DEVICE; -extern const NTL_DDK* gVmwSvgaDdk; - KSTATUS VmwSvgaAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObject); KSTATUS VmwSvgaDispatchPnp(DEVICE_OBJECT* device, IRP* irp); diff --git a/code/drivers/drivers.ini b/code/drivers/drivers.ini index 4439655..6916492 100644 --- a/code/drivers/drivers.ini +++ b/code/drivers/drivers.ini @@ -1,17 +1,90 @@ -[boot] -pci.sys -hda.sys -ps2.sys -cereal.sys -ahci.sys -ata.sys -usb.sys -fat.sys -ntfs.sys -ext4.sys +; drivers.ini -[runtime] -lwip.sys -vmwaresvga.sys -rtl8139.sys -rtl816x.sys +[class.bus] + +[class.storage] +requires=bus + +[class.filesystem] +after=storage + +[class.usb-host] +requires=bus + +[class.network-adapter] +requires=bus + +[class.network-stack] +after=network-adapter + +[driver.pci] +image=pci.sys +start=boot +class=bus +critical=true + +[driver.hda] +image=hda.sys +start=boot +requires=pci + +[driver.ps2] +image=ps2.sys +start=boot + +[driver.cereal] +image=cereal.sys +start=boot + +[driver.ahci] +image=ahci.sys +start=boot +class=storage + +[driver.ata] +image=ata.sys +start=boot +class=storage + +[driver.usb] +image=usb.sys +start=boot +class=usb-host + +[driver.fat] +image=fat.sys +start=boot +class=filesystem +after=usb + +[driver.ntfs] +image=ntfs.sys +start=boot +class=filesystem +after=usb + +[driver.ext4] +image=ext4.sys +start=boot +class=filesystem +after=usb + +[driver.lwip] +image=lwip.sys +start=runtime +class=network-stack + +[driver.vmwaresvga] +image=vmwaresvga.sys +start=runtime +requires=pci + +[driver.rtl8139] +image=rtl8139.sys +start=runtime +class=network-adapter + +[driver.rtl816x] +image=rtl816x.sys +start=runtime +class=network-adapter diff --git a/code/drivers/fs/ext4/ext4_driver.c b/code/drivers/fs/ext4/ext4_driver.c index 3ce0f44..1acc36b 100644 --- a/code/drivers/fs/ext4/ext4_driver.c +++ b/code/drivers/fs/ext4/ext4_driver.c @@ -1,3 +1,4 @@ +#include /* * NTLite ext2/ext3/ext4 read/write filesystem driver backed by lwext4. * @@ -1154,12 +1155,9 @@ static void Ext4PartitionChange(const IO_PARTITION_INFO* partition, } } -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { +static KSTATUS DriverInitialize(NTL_MODULE* module) { + (void)module; DbgPrint("EXT4: Init\n"); - if (!ddk) - return NTLSTATUS_INVALID_PARAMETER; - if (init) - init->Name = "ext4"; Ext4ListInit(&gExt4Mounts); gExt4MountedCount = 0; @@ -1177,3 +1175,5 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { DbgPrint("EXT4: Init complete; mounted %u volume(s).\n", gExt4MountedCount); return NTLSTATUS_SUCCESS; } + +NTL_DECLARE_MODULE(DriverInitialize); diff --git a/code/drivers/fs/fat/fat_driver.c b/code/drivers/fs/fat/fat_driver.c index 6016b95..cb88112 100644 --- a/code/drivers/fs/fat/fat_driver.c +++ b/code/drivers/fs/fat/fat_driver.c @@ -1,3 +1,4 @@ +#include // FAT mount discovery, BPB validation, and driver lifetime. #include @@ -275,11 +276,10 @@ static void FatPartitionChange(const IO_PARTITION_INFO* partition, } // Initializes FAT and subscribes to the authoritative kernel partition stream. -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { +static KSTATUS DriverInitialize(NTL_MODULE* module) { + (void)module; DbgPrint("FAT: Init\n"); - if (!ddk) - return NTLSTATUS_INVALID_PARAMETER; - (void)init; + FatCacheInvalidate(); LIST_INIT(&gFatMounts); @@ -297,3 +297,5 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { DbgPrint("FAT: Init complete; mounted %u volume(s).\n", gMountedCount); return NTLSTATUS_SUCCESS; } + +NTL_DECLARE_MODULE(DriverInitialize); diff --git a/code/drivers/fs/ntfs/ntfs.c b/code/drivers/fs/ntfs/ntfs.c index 64bd36a..91d57da 100644 --- a/code/drivers/fs/ntfs/ntfs.c +++ b/code/drivers/fs/ntfs/ntfs.c @@ -1,3 +1,4 @@ +#include // 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_VOLUME_PROVIDER ABI. @@ -1597,11 +1598,10 @@ static void NtfsPartitionChange(const IO_PARTITION_INFO* partition, } // Initializes NTFS and subscribes to the authoritative kernel partition stream. -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { +static KSTATUS DriverInitialize(NTL_MODULE* module) { + (void)module; DbgPrint("NTFS: Init\n"); - if (!ddk) - return NTLSTATUS_INVALID_PARAMETER; - (void)init; + LIST_INIT(&gNtfsMounts); gMountedCount = 0; @@ -1618,3 +1618,5 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { DbgPrint("NTFS: Init complete; mounted %u volume(s).\n", gMountedCount); return NTLSTATUS_SUCCESS; } + +NTL_DECLARE_MODULE(DriverInitialize); diff --git a/code/drivers/fs/ntfs/ntfs3g/ntfs3g_driver.c b/code/drivers/fs/ntfs/ntfs3g/ntfs3g_driver.c index a789ec3..f6c29af 100644 --- a/code/drivers/fs/ntfs/ntfs3g/ntfs3g_driver.c +++ b/code/drivers/fs/ntfs/ntfs3g/ntfs3g_driver.c @@ -1,3 +1,4 @@ +#include /* NTFS-3G filesystem provider for NTLite. */ #include "config.h" @@ -1137,12 +1138,9 @@ static void Ntfs3gPartitionChange(const IO_PARTITION_INFO* partition, } } -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { +static KSTATUS DriverInitialize(NTL_MODULE* module) { + (void)module; DbgPrint("NTFS3G: Init (libntfs-3g replacement driver)\n"); - if (!ddk) - return NTLSTATUS_INVALID_PARAMETER; - if (init) - init->Name = "ntfs"; Ntfs3gListInit(&gNtfs3gMounts); gNtfs3gMountedCount = 0; Ntfs3gEnsureMutexes(); @@ -1152,3 +1150,5 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { DbgPrint("NTFS3G: Init complete; mounted %u volume(s).\n", gNtfs3gMountedCount); return NTLSTATUS_SUCCESS; } + +NTL_DECLARE_MODULE(DriverInitialize); diff --git a/code/drivers/input/ps2/ps2.c b/code/drivers/input/ps2/ps2.c index 658f62c..8973a89 100644 --- a/code/drivers/input/ps2/ps2.c +++ b/code/drivers/input/ps2/ps2.c @@ -1,3 +1,4 @@ +#include // PS/2 input driver package entry point. This file registers keyboard and mouse devices. #include "ps2.h" @@ -14,6 +15,14 @@ static volatile int gMousePacketIndex; static PS2_MOUSE_PACKET gMouseDpcQueue[MOUSE_DPC_QUEUE_SIZE]; static volatile int gMouseDpcHead; static volatile int gMouseDpcTail; +static volatile uint32_t gKeyboardIrqCount; +static volatile uint32_t gKeyboardByteCount; +static volatile uint32_t gKeyboardEventCount; +static volatile uint32_t gMouseIrqCount; +static volatile uint32_t gMouseByteCount; +static volatile uint32_t gMousePacketCount; +static volatile uint32_t gKeyboardForeignIrqCount; +static volatile uint32_t gMouseForeignIrqCount; // Implements the ps2 Set1 To Key operation. static INPUT_KEY Ps2Set1ToKey(uint8_t make, bool extended) { @@ -137,6 +146,10 @@ static void Ps2KeyboardDpcRoutine(KDPC* dpc, void* context, void* systemArgument INPUT_KEY_EVENT event = gKeyboardDpcQueue[gKeyboardDpcHead]; gKeyboardDpcHead = (gKeyboardDpcHead + 1) & (int)KEYBOARD_DPC_QUEUE_MASK; KeInputSubmitKey(&event); + uint32_t n = ++gKeyboardEventCount; + if ((n & (n - 1u)) == 0u) + DbgPrint("ps2: keyboard progress irq=%u raw=%u events=%u foreign=%u\n", + gKeyboardIrqCount, gKeyboardByteCount, n, gKeyboardForeignIrqCount); } } @@ -162,7 +175,15 @@ static void Ps2QueueKeyboardEvent(INPUT_KEY key, bool pressed) { // Implements the ps2 Keyboard Isr operation. static void Ps2KeyboardIsr(void) { + ++gKeyboardIrqCount; + uint8_t status = __inbyte(PS2_STAT); + // The 8042 output buffer is shared; never consume AUX bytes as scancodes. + if (!(status & PS2_STAT_OUT_FULL) || (status & PS2_STAT_AUX_DATA)) { + ++gKeyboardForeignIrqCount; + return; + } uint8_t sc = __inbyte(PS2_DATA); + ++gKeyboardByteCount; if (sc == SC1_EXT_PREFIX) { gKeyboardNextExtended = true; return; @@ -197,12 +218,23 @@ static void Ps2MouseDpcRoutine(KDPC* dpc, void* context, void* systemArgument1, .buttons = packet.buttons, }; KeInputSubmitMouse(&event); + uint32_t n = ++gMousePacketCount; + if ((n & (n - 1u)) == 0u) + DbgPrint("ps2: mouse progress irq=%u raw=%u packets=%u foreign=%u\n", + gMouseIrqCount, gMouseByteCount, n, gMouseForeignIrqCount); } } // Implements the ps2 Mouse Isr operation. static void Ps2MouseIsr(void) { + ++gMouseIrqCount; + uint8_t status = __inbyte(PS2_STAT); + if (!(status & PS2_STAT_OUT_FULL) || !(status & PS2_STAT_AUX_DATA)) { + ++gMouseForeignIrqCount; + return; + } uint8_t data = __inbyte(PS2_DATA); + ++gMouseByteCount; if (gMousePacketIndex == 0 && !(data & 0x08u)) return; @@ -281,10 +313,9 @@ static void Ps2MouseInitialize(void) { DbgPrint("ps2: mouse enable did not ACK\n"); } -// Implements the driver Entry operation. -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { - (void)ddk; - (void)init; +static KSTATUS DriverInitialize(NTL_MODULE* module) { + (void)module; + uint32_t keyboardId = 0, mouseId = 0; KeInputRegisterDevice("ps2-keyboard", 0, &keyboardId); @@ -308,3 +339,5 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { DbgPrint("ps2: initialized keyboard=%u mouse=%u\n", keyboardId, mouseId); return NTLSTATUS_SUCCESS; } + +NTL_DECLARE_MODULE(DriverInitialize); diff --git a/code/drivers/input/ps2/ps2.h b/code/drivers/input/ps2/ps2.h index b068fa5..9ef59f3 100644 --- a/code/drivers/input/ps2/ps2.h +++ b/code/drivers/input/ps2/ps2.h @@ -19,6 +19,7 @@ #define PS2_STAT_OUT_FULL 0x01u #define PS2_STAT_IN_FULL 0x02u +#define PS2_STAT_AUX_DATA 0x20u #define PS2_IRQ_KEYBOARD 1u #define PS2_IRQ_MOUSE 12u diff --git a/code/drivers/input/serial/serial.c b/code/drivers/input/serial/serial.c index cffebea..5c55c29 100644 --- a/code/drivers/input/serial/serial.c +++ b/code/drivers/input/serial/serial.c @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include @@ -339,10 +339,9 @@ static KSTATUS SerialConfigureCom1(void) { return NTLSTATUS_DEVICE_ERROR; } -// DriverEntry registers COM1 as an input source. -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { - (void)ddk; - (void)init; +// Initializes the optional COM1 input source. +static KSTATUS SerialInitialize(NTL_MODULE* module) { + (void)module; if (!SerialProbeCom1()) { DbgPrint("serial: COM1 not present; input driver disabled\n"); @@ -374,3 +373,5 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { DbgPrint("serial: initialized COM1 input device=%u irq=%u\n", deviceId, SERIAL_COM1_IRQ); return NTLSTATUS_SUCCESS; } + +NTL_DECLARE_MODULE(SerialInitialize); diff --git a/code/drivers/net/lwip/README.md b/code/drivers/net/lwip/README.md index 633852b..1538ab0 100644 --- a/code/drivers/net/lwip/README.md +++ b/code/drivers/net/lwip/README.md @@ -20,6 +20,6 @@ Build notes: Load order: -`drivers.ini` should load `lwip.sys` before NIC drivers so the provider is -ready when the NIC registers. The kernel also supports provider-after-device -registration, but provider-first keeps boot logs cleaner. +`drivers.ini` v2 uses `class.network-stack` with `after=network-adapter`. +NIC modules initialize first; lwIP attaches to already registered adapters. +The stack also initializes normally when no physical NIC is present. diff --git a/code/drivers/net/lwip/lwip_stack.c b/code/drivers/net/lwip/lwip_stack.c index 86c50ca..13910e9 100644 --- a/code/drivers/net/lwip/lwip_stack.c +++ b/code/drivers/net/lwip/lwip_stack.c @@ -1,3 +1,4 @@ +#include // lwIP stack provider glue. This file bridges the kernel network-device model into lwIP. #include @@ -111,7 +112,6 @@ typedef struct LWIP_PROVIDER_SOCKET { static LWIP_BINDING* gBindings[LWIP_MAX_NETIFS]; static volatile int gLwipInitialized = 0; -static const NTL_DDK* gLwipDdk; static KSTATUS LwipProviderAttachDevice(NET_DEVICE* device); static KSTATUS LwipProviderReceiveFrame(NET_DEVICE* device, const void* frame, size_t length); @@ -770,21 +770,15 @@ static void LwipInitDone(void* arg) { // Delays the current provider thread through the DDK scheduler table. static KSTATUS LwipDelayThread(uint64_t milliseconds) { - if (!gLwipDdk || !gLwipDdk->Scheduler || !gLwipDdk->Scheduler->DelayThread) - return NTLSTATUS_INVALID_PARAMETER; - return gLwipDdk->Scheduler->DelayThread(milliseconds); + return KeDelayThread(milliseconds); } // Implements the stack Initialize operation. -static KSTATUS LwipStackInitialize(const NTL_DDK* ddk) { +static KSTATUS LwipStackInitialize(void) { if (gLwipInitialized) return NTLSTATUS_SUCCESS; - if (!ddk || !ddk->Timer || !ddk->Timer->QueryTicks || - !ddk->Scheduler || !ddk->Scheduler->DelayThread) - return NTLSTATUS_INVALID_PARAMETER; - gLwipDdk = ddk; - LwipPortSetTimerQuery(ddk->Timer->QueryTicks); + LwipPortSetTimerQuery(KeQueryTicks); SEMAPHORE* done = 0; KSTATUS st = KeSemaphoreCreate(0, 1, &done); @@ -1822,9 +1816,9 @@ static KSTATUS LwipDispatchInternalDeviceControl(DEVICE_OBJECT* device, IRP* irp return LwipDispatchSocketRequest(request); } -// Implements the driver Entry operation. -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { - (void)init; +static KSTATUS DriverInitialize(NTL_MODULE* module) { + (void)module; + DbgPrint("lwIP: driver loading...\n"); LWIP_NETIF_CONFIG defaultConfig; @@ -1835,7 +1829,7 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { return st; } - st = LwipStackInitialize(ddk); + st = LwipStackInitialize(); if (!KSUCCESS(st)) { DbgPrint("lwIP: initialization failed: 0x%x\n", st); return st; @@ -1844,12 +1838,6 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { IoInitializeDriverObject(&gLwipDriver, SYS_SV_LITERAL("lwip-stack")); gLwipDriver.MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = LwipDispatchInternalDeviceControl; - st = IoRegisterDriverObject(&gLwipDriver); - if (!KSUCCESS(st)) { - DbgPrint("lwIP: driver object registration failed: 0x%x\n", st); - return st; - } - st = IoCreateDevice(&gLwipDriver, 0, &gLwipDevice); if (!KSUCCESS(st)) { DbgPrint("lwIP: stack device creation failed: 0x%x\n", st); @@ -1867,3 +1855,6 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { DbgPrint("lwIP: driver initialized\n"); return NTLSTATUS_SUCCESS; } + +static DRIVER_OBJECT* const gModuleIoDrivers[] = { &gLwipDriver }; +NTL_DECLARE_IO_MODULE(DriverInitialize, gModuleIoDrivers); diff --git a/code/drivers/net/rtl8139/rtl8139.c b/code/drivers/net/rtl8139/rtl8139.c index 5d58811..d107380 100644 --- a/code/drivers/net/rtl8139/rtl8139.c +++ b/code/drivers/net/rtl8139/rtl8139.c @@ -1,8 +1,8 @@ +#include // Realtek RTL8139 network driver. This file owns PCI attachment, MMIO setup, RX/TX rings, and net-device registration. #include "rtl8139.h" -const NTL_DDK* gRtl8139Ddk; static DRIVER_OBJECT gRtl8139Driver; static RTL8139_DEVICE* gRtl8139Devices[RTL8139_MAX_DEVICES]; static uint32_t gRtl8139DeviceCount; @@ -69,8 +69,7 @@ static KSTATUS Rtl8139Reset(RTL8139_DEVICE* r) { for (uint32_t i = 0; i < 1000; i++) { if ((Rtl8139Read8(r, RTL8139_REG_CMD) & RTL8139_CMD_RST) == 0) return NTLSTATUS_SUCCESS; - if (gRtl8139Ddk->Timer->Stall) - gRtl8139Ddk->Timer->Stall(1000); + KeStallExecution(1000); } return NTLSTATUS_TIMEOUT; } @@ -100,17 +99,17 @@ static void Rtl8139StopDma(RTL8139_DEVICE* r) { // Releases the common buffers and adapter owned by one RTL8139 device. static void Rtl8139ReleaseDma(RTL8139_DEVICE* r) { - if (!r || !gRtl8139Ddk || !gRtl8139Ddk->Dma) + if (!r) return; - gRtl8139Ddk->Dma->FreeCommonBuffer(r->TxDma); - gRtl8139Ddk->Dma->FreeCommonBuffer(r->RxDma); + IoDmaFreeCommonBuffer(r->TxDma); + IoDmaFreeCommonBuffer(r->RxDma); r->TxDma = (IO_DMA_BUFFER*)0; r->RxDma = (IO_DMA_BUFFER*)0; r->TxBuffers = (uint8_t*)0; r->RxBuffer = (uint8_t*)0; r->TxDeviceAddress = 0; r->RxDeviceAddress = 0; - gRtl8139Ddk->Dma->PutAdapter(r->DmaAdapter); + IoDmaPutAdapter(r->DmaAdapter); r->DmaAdapter = (IO_DMA_ADAPTER*)0; } @@ -123,13 +122,13 @@ static KSTATUS Rtl8139AllocateDma(RTL8139_DEVICE* r) { requirements.MaximumSegmentLength = RTL8139_TX_BUF_SIZE; requirements.MaximumSegments = 1; - KSTATUS status = gRtl8139Ddk->Dma->GetAdapter(r->Fdo, + KSTATUS status = IoDmaGetAdapter(r->Fdo, &requirements, &r->DmaAdapter); if (!KSUCCESS(status)) return status; - status = gRtl8139Ddk->Dma->AllocateCommonBuffer(r->DmaAdapter, + status = IoDmaAllocateCommonBuffer(r->DmaAdapter, RTL8139_RX_ALLOC_SIZE, PAGE_SIZE, &r->RxDma); @@ -138,7 +137,7 @@ static KSTATUS Rtl8139AllocateDma(RTL8139_DEVICE* r) { return status; } - status = gRtl8139Ddk->Dma->AllocateCommonBuffer(r->DmaAdapter, + status = IoDmaAllocateCommonBuffer(r->DmaAdapter, RTL8139_TX_SLOTS * RTL8139_TX_BUF_SIZE, PAGE_SIZE, &r->TxDma); @@ -147,10 +146,10 @@ static KSTATUS Rtl8139AllocateDma(RTL8139_DEVICE* r) { return status; } - r->RxBuffer = (uint8_t*)gRtl8139Ddk->Dma->GetCommonBufferAddress(r->RxDma); - r->TxBuffers = (uint8_t*)gRtl8139Ddk->Dma->GetCommonBufferAddress(r->TxDma); - r->RxDeviceAddress = gRtl8139Ddk->Dma->GetCommonBufferDeviceAddress(r->RxDma); - r->TxDeviceAddress = gRtl8139Ddk->Dma->GetCommonBufferDeviceAddress(r->TxDma); + r->RxBuffer = (uint8_t*)IoDmaGetCommonBufferAddress(r->RxDma); + r->TxBuffers = (uint8_t*)IoDmaGetCommonBufferAddress(r->TxDma); + r->RxDeviceAddress = IoDmaGetCommonBufferDeviceAddress(r->RxDma); + r->TxDeviceAddress = IoDmaGetCommonBufferDeviceAddress(r->TxDma); DbgPrint("RTL8139: rx=0x%016llX tx=0x%016llX\n", (unsigned long long)r->RxDeviceAddress, @@ -603,14 +602,11 @@ KSTATUS Rtl8139AddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObj return NTLSTATUS_SUCCESS; } -// Implements the driver Entry operation. -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { - (void)init; - if (!ddk || !ddk->Dma || !ddk->Timer) - return NTLSTATUS_INVALID_PARAMETER; +static KSTATUS DriverInitialize(NTL_MODULE* module) { + (void)module; DbgPrint("RTL8139: driver loading...\n"); - gRtl8139Ddk = ddk; + gRtl8139DeviceCount = 0; memset(&gRtl8139Driver, 0, sizeof(gRtl8139Driver)); @@ -618,9 +614,8 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { gRtl8139Driver.IdTable = &gRtl8139IdTable; gRtl8139Driver.DriverAddDevice = Rtl8139AddDevice; gRtl8139Driver.MajorFunction[IRP_MJ_PNP] = Rtl8139DispatchPnp; - - KSTATUS st = IoRegisterDriverObject(&gRtl8139Driver); - if (!KSUCCESS(st)) - DbgPrint("RTL8139: IoRegisterDriverObject failed: 0x%x\n", st); - return st; + return NTLSTATUS_SUCCESS; } + +static DRIVER_OBJECT* const gModuleIoDrivers[] = { &gRtl8139Driver }; +NTL_DECLARE_IO_MODULE(DriverInitialize, gModuleIoDrivers); diff --git a/code/drivers/net/rtl8139/rtl8139.h b/code/drivers/net/rtl8139/rtl8139.h index a6f8d86..fd5f1c6 100644 --- a/code/drivers/net/rtl8139/rtl8139.h +++ b/code/drivers/net/rtl8139/rtl8139.h @@ -133,7 +133,5 @@ typedef struct RTL8139_DEVICE { NET_DEVICE NetDevice; } RTL8139_DEVICE; -extern const NTL_DDK* gRtl8139Ddk; - KSTATUS Rtl8139AddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObject); KSTATUS Rtl8139DispatchPnp(DEVICE_OBJECT* device, IRP* irp); diff --git a/code/drivers/net/rtl816x/rtl816x.c b/code/drivers/net/rtl816x/rtl816x.c index c912323..cd075a0 100644 --- a/code/drivers/net/rtl816x/rtl816x.c +++ b/code/drivers/net/rtl816x/rtl816x.c @@ -1,8 +1,8 @@ +#include // Realtek RTL816x network driver. This file owns PCI attachment, descriptor rings, and net-device registration. #include "rtl816x.h" -const NTL_DDK* gRtl816xDdk; static DRIVER_OBJECT gRtl816xDriver; static RTL816X_DEVICE* gRtl816xDevices[RTL816X_MAX_DEVICES]; static uint32_t gRtl816xDeviceCount; @@ -70,8 +70,7 @@ static KSTATUS RtlReset(RTL816X_DEVICE* r) { for (uint32_t i = 0; i < 1000; i++) { if ((RtlRead8(r, RTL_REG_CHIP_CMD) & RTL_CMD_RESET) == 0) return NTLSTATUS_SUCCESS; - if (gRtl816xDdk->Timer->Stall) - gRtl816xDdk->Timer->Stall(1000); + KeStallExecution(1000); } return NTLSTATUS_TIMEOUT; } @@ -101,12 +100,12 @@ static void RtlStopDma(RTL816X_DEVICE* r) { // Releases the common buffers and adapter owned by one RTL816x device. static void RtlReleaseDma(RTL816X_DEVICE* r) { - if (!r || !gRtl816xDdk || !gRtl816xDdk->Dma) + if (!r) return; - gRtl816xDdk->Dma->FreeCommonBuffer(r->TxBuffersDma); - gRtl816xDdk->Dma->FreeCommonBuffer(r->RxBuffersDma); - gRtl816xDdk->Dma->FreeCommonBuffer(r->TxRingDma); - gRtl816xDdk->Dma->FreeCommonBuffer(r->RxRingDma); + IoDmaFreeCommonBuffer(r->TxBuffersDma); + IoDmaFreeCommonBuffer(r->RxBuffersDma); + IoDmaFreeCommonBuffer(r->TxRingDma); + IoDmaFreeCommonBuffer(r->RxRingDma); r->TxBuffersDma = (IO_DMA_BUFFER*)0; r->RxBuffersDma = (IO_DMA_BUFFER*)0; r->TxRingDma = (IO_DMA_BUFFER*)0; @@ -119,7 +118,7 @@ static void RtlReleaseDma(RTL816X_DEVICE* r) { r->RxBuffersDeviceAddress = 0; r->TxRingDeviceAddress = 0; r->RxRingDeviceAddress = 0; - gRtl816xDdk->Dma->PutAdapter(r->DmaAdapter); + IoDmaPutAdapter(r->DmaAdapter); r->DmaAdapter = (IO_DMA_ADAPTER*)0; } @@ -132,41 +131,41 @@ static KSTATUS RtlAllocateDma(RTL816X_DEVICE* r) { requirements.MaximumSegmentLength = RTL_BUF_SIZE; requirements.MaximumSegments = RTL_TX_COUNT; - KSTATUS status = gRtl816xDdk->Dma->GetAdapter(r->Fdo, + KSTATUS status = IoDmaGetAdapter(r->Fdo, &requirements, &r->DmaAdapter); if (!KSUCCESS(status)) return status; - status = gRtl816xDdk->Dma->AllocateCommonBuffer(r->DmaAdapter, + status = IoDmaAllocateCommonBuffer(r->DmaAdapter, PAGE_SIZE * RTL_RING_PAGES, PAGE_SIZE, &r->RxRingDma); if (!KSUCCESS(status)) goto fail; - status = gRtl816xDdk->Dma->AllocateCommonBuffer(r->DmaAdapter, + status = IoDmaAllocateCommonBuffer(r->DmaAdapter, PAGE_SIZE * RTL_RING_PAGES, PAGE_SIZE, &r->TxRingDma); if (!KSUCCESS(status)) goto fail; - status = gRtl816xDdk->Dma->AllocateCommonBuffer(r->DmaAdapter, + status = IoDmaAllocateCommonBuffer(r->DmaAdapter, PAGE_SIZE * RTL_RXBUF_PAGES, PAGE_SIZE, &r->RxBuffersDma); if (!KSUCCESS(status)) goto fail; - status = gRtl816xDdk->Dma->AllocateCommonBuffer(r->DmaAdapter, + status = IoDmaAllocateCommonBuffer(r->DmaAdapter, PAGE_SIZE * RTL_TXBUF_PAGES, PAGE_SIZE, &r->TxBuffersDma); if (!KSUCCESS(status)) goto fail; - r->RxRing = (RTL_DESC*)gRtl816xDdk->Dma->GetCommonBufferAddress(r->RxRingDma); - r->TxRing = (RTL_DESC*)gRtl816xDdk->Dma->GetCommonBufferAddress(r->TxRingDma); - r->RxBuffers = (uint8_t*)gRtl816xDdk->Dma->GetCommonBufferAddress(r->RxBuffersDma); - r->TxBuffers = (uint8_t*)gRtl816xDdk->Dma->GetCommonBufferAddress(r->TxBuffersDma); - r->RxRingDeviceAddress = gRtl816xDdk->Dma->GetCommonBufferDeviceAddress(r->RxRingDma); - r->TxRingDeviceAddress = gRtl816xDdk->Dma->GetCommonBufferDeviceAddress(r->TxRingDma); - r->RxBuffersDeviceAddress = gRtl816xDdk->Dma->GetCommonBufferDeviceAddress(r->RxBuffersDma); - r->TxBuffersDeviceAddress = gRtl816xDdk->Dma->GetCommonBufferDeviceAddress(r->TxBuffersDma); + r->RxRing = (RTL_DESC*)IoDmaGetCommonBufferAddress(r->RxRingDma); + r->TxRing = (RTL_DESC*)IoDmaGetCommonBufferAddress(r->TxRingDma); + r->RxBuffers = (uint8_t*)IoDmaGetCommonBufferAddress(r->RxBuffersDma); + r->TxBuffers = (uint8_t*)IoDmaGetCommonBufferAddress(r->TxBuffersDma); + r->RxRingDeviceAddress = IoDmaGetCommonBufferDeviceAddress(r->RxRingDma); + r->TxRingDeviceAddress = IoDmaGetCommonBufferDeviceAddress(r->TxRingDma); + r->RxBuffersDeviceAddress = IoDmaGetCommonBufferDeviceAddress(r->RxBuffersDma); + r->TxBuffersDeviceAddress = IoDmaGetCommonBufferDeviceAddress(r->TxBuffersDma); DbgPrint("RTL816X: rxRing=0x%016llX txRing=0x%016llX rxBuf=0x%016llX txBuf=0x%016llX\n", (unsigned long long)r->RxRingDeviceAddress, @@ -667,14 +666,11 @@ KSTATUS Rtl816xAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObj return NTLSTATUS_SUCCESS; } -// Implements the driver Entry operation. -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { - (void)init; - if (!ddk || !ddk->Dma || !ddk->Timer) - return NTLSTATUS_INVALID_PARAMETER; +static KSTATUS DriverInitialize(NTL_MODULE* module) { + (void)module; DbgPrint("RTL816X: driver loading...\n"); - gRtl816xDdk = ddk; + gRtl816xDeviceCount = 0; memset(&gRtl816xDriver, 0, sizeof(gRtl816xDriver)); @@ -682,9 +678,8 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { gRtl816xDriver.IdTable = &gRtl816xIdTable; gRtl816xDriver.DriverAddDevice = Rtl816xAddDevice; gRtl816xDriver.MajorFunction[IRP_MJ_PNP] = Rtl816xDispatchPnp; - - KSTATUS st = IoRegisterDriverObject(&gRtl816xDriver); - if (!KSUCCESS(st)) - DbgPrint("RTL816X: IoRegisterDriverObject failed: 0x%x\n", st); - return st; + return NTLSTATUS_SUCCESS; } + +static DRIVER_OBJECT* const gModuleIoDrivers[] = { &gRtl816xDriver }; +NTL_DECLARE_IO_MODULE(DriverInitialize, gModuleIoDrivers); diff --git a/code/drivers/net/rtl816x/rtl816x.h b/code/drivers/net/rtl816x/rtl816x.h index f84c30e..945d149 100644 --- a/code/drivers/net/rtl816x/rtl816x.h +++ b/code/drivers/net/rtl816x/rtl816x.h @@ -151,7 +151,5 @@ typedef struct RTL816X_DEVICE { NET_DEVICE NetDevice; } RTL816X_DEVICE; -extern const NTL_DDK* gRtl816xDdk; - KSTATUS Rtl816xAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObject); KSTATUS Rtl816xDispatchPnp(DEVICE_OBJECT* device, IRP* irp); diff --git a/code/drivers/storage/ahci/ahci.c b/code/drivers/storage/ahci/ahci.c index 0ee47ea..0c9a920 100644 --- a/code/drivers/storage/ahci/ahci.c +++ b/code/drivers/storage/ahci/ahci.c @@ -1,8 +1,8 @@ +#include // AHCI storage driver. This file owns controller attachment, drive discovery, command submission, and worker-driven IRP completion. #include "ahci.h" -const NTL_DDK* gAhciDdk; DRIVER_OBJECT g_driver; static AHCI_CONTROLLER* g_controllers[MAX_AHCI_CONTROLLERS]; @@ -102,13 +102,13 @@ static void AhciReleaseDriveRuntime(AHCI_DRIVE* drv) { // Releases the DMA buffers owned by one AHCI drive. static void AhciReleaseDriveDma(AHCI_DRIVE* drv) { - if (!drv || !gAhciDdk || !gAhciDdk->Dma) + if (!drv) return; if (drv->port && drv->controller && !drv->surpriseRemoved && !drv->controller->surpriseRemoved) port_stop(drv->port); - gAhciDdk->Dma->FreeCommonBuffer(drv->bounceDma); - gAhciDdk->Dma->FreeCommonBuffer(drv->structDma); + IoDmaFreeCommonBuffer(drv->bounceDma); + IoDmaFreeCommonBuffer(drv->structDma); drv->bounceDma = (IO_DMA_BUFFER*)0; drv->structDma = (IO_DMA_BUFFER*)0; drv->bounceDeviceAddress = 0; @@ -122,16 +122,16 @@ static KSTATUS port_init_memory(AHCI_DRIVE* drv) { if (!drv || !drv->controller || !drv->controller->dmaAdapter) return NTLSTATUS_INVALID_PARAMETER; - KSTATUS status = gAhciDdk->Dma->AllocateCommonBuffer(drv->controller->dmaAdapter, + KSTATUS status = IoDmaAllocateCommonBuffer(drv->controller->dmaAdapter, PORT_PAGES_STRUCT * PAGE_SIZE, PAGE_SIZE, &drv->structDma); if (!KSUCCESS(status)) return status; - drv->structVirt = gAhciDdk->Dma->GetCommonBufferAddress(drv->structDma); - drv->structDeviceAddress = gAhciDdk->Dma->GetCommonBufferDeviceAddress(drv->structDma); + drv->structVirt = IoDmaGetCommonBufferAddress(drv->structDma); + drv->structDeviceAddress = IoDmaGetCommonBufferDeviceAddress(drv->structDma); - status = gAhciDdk->Dma->AllocateCommonBuffer(drv->controller->dmaAdapter, + status = IoDmaAllocateCommonBuffer(drv->controller->dmaAdapter, BOUNCE_BYTES, PAGE_SIZE, &drv->bounceDma); @@ -139,8 +139,8 @@ static KSTATUS port_init_memory(AHCI_DRIVE* drv) { AhciReleaseDriveDma(drv); return status; } - drv->bounceVirt = gAhciDdk->Dma->GetCommonBufferAddress(drv->bounceDma); - drv->bounceDeviceAddress = gAhciDdk->Dma->GetCommonBufferDeviceAddress(drv->bounceDma); + drv->bounceVirt = IoDmaGetCommonBufferAddress(drv->bounceDma); + drv->bounceDeviceAddress = IoDmaGetCommonBufferDeviceAddress(drv->bounceDma); HBA_PORT* port = drv->port; port_stop(port); @@ -701,7 +701,7 @@ static KSTATUS AhciDispatchControllerPnp(DEVICE_OBJECT* device, IRP* irp) { } } AhciUnregisterController(ctl); - gAhciDdk->Dma->PutAdapter(ctl->dmaAdapter); + IoDmaPutAdapter(ctl->dmaAdapter); ctl->dmaAdapter = (IO_DMA_ADAPTER*)0; IoDetachDevice(device); IoDeleteDevice(device); @@ -940,7 +940,7 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) { dmaRequirements.Alignment = 2; dmaRequirements.MaximumSegmentLength = 1u << 22; dmaRequirements.MaximumSegments = PORT_PRDT_COUNT; - st = gAhciDdk->Dma->GetAdapter(ctl->fdo, &dmaRequirements, &ctl->dmaAdapter); + st = IoDmaGetAdapter(ctl->fdo, &dmaRequirements, &ctl->dmaAdapter); if (!KSUCCESS(st)) { DbgPrint("AHCI: DMA adapter creation failed (0x%x)\n", st); return st; @@ -949,7 +949,7 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) { st = AhciRegisterController(ctl); if (!KSUCCESS(st)) { DbgPrint("AHCI: controller registry full (0x%x)\n", st); - gAhciDdk->Dma->PutAdapter(ctl->dmaAdapter); + IoDmaPutAdapter(ctl->dmaAdapter); ctl->dmaAdapter = (IO_DMA_ADAPTER*)0; return st; } @@ -1159,14 +1159,11 @@ static KSTATUS AhciAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDevic return NTLSTATUS_SUCCESS; } -// Implements the driver Entry operation. -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { - (void)init; - if (!ddk || !ddk->Memory || !ddk->Memory->PhysToVirt || !ddk->Dma) - return NTLSTATUS_INVALID_PARAMETER; +static KSTATUS DriverInitialize(NTL_MODULE* module) { + (void)module; DbgPrint("AHCI: driver loading...\n"); - gAhciDdk = ddk; + g_controllerCount = 0; g_nextDiskIndex = 0; @@ -1178,9 +1175,8 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { g_driver.MajorFunction[IRP_MJ_WRITE] = AhciDispatchReadWrite; g_driver.MajorFunction[IRP_MJ_FLUSH_BUFFERS] = AhciDispatchFlush; g_driver.MajorFunction[IRP_MJ_PNP] = AhciDispatchPnp; - - KSTATUS st = IoRegisterDriverObject(&g_driver); - if (!KSUCCESS(st)) - DbgPrint("AHCI: IoRegisterDriverObject failed: 0x%x\n", st); - return st; + return NTLSTATUS_SUCCESS; } + +static DRIVER_OBJECT* const gModuleIoDrivers[] = { &g_driver }; +NTL_DECLARE_IO_MODULE(DriverInitialize, gModuleIoDrivers); diff --git a/code/drivers/storage/ahci/ahci.h b/code/drivers/storage/ahci/ahci.h index ee311dd..08613c3 100644 --- a/code/drivers/storage/ahci/ahci.h +++ b/code/drivers/storage/ahci/ahci.h @@ -385,7 +385,6 @@ struct AHCI_CONTROLLER { bool removed; }; -extern const NTL_DDK* gAhciDdk; extern DRIVER_OBJECT g_driver; KSTATUS ahci_map_mmio(uint64_t physBase, uint64_t length); diff --git a/code/drivers/storage/ahci/ahci_port.c b/code/drivers/storage/ahci/ahci_port.c index cc6199a..b912bcf 100644 --- a/code/drivers/storage/ahci/ahci_port.c +++ b/code/drivers/storage/ahci/ahci_port.c @@ -10,9 +10,9 @@ KSTATUS ahci_map_mmio(uint64_t physBase, uint64_t length) { uint64_t pageEnd = (physBase + length + PAGE_SIZE - 1u) & ~(uint64_t)(PAGE_SIZE - 1u); int pageIndex = 0; for (uint64_t phys = pageBase; phys < pageEnd; phys += PAGE_SIZE, pageIndex++) { - uintptr_t virt = (uintptr_t)gAhciDdk->Memory->PhysToVirt(phys); + uintptr_t virt = (uintptr_t)MmDdkPhysToVirt(phys); uint64_t flags = PAGE_PRESENT | PAGE_WRITE | PAGE_CACHE_UC; - KSTATUS st = gAhciDdk->Memory->MapPage(virt, + KSTATUS st = MmDdkMapPage(virt, phys, flags, NtlDdkMapReplaceExisting); diff --git a/code/drivers/storage/ata/ata.c b/code/drivers/storage/ata/ata.c index 1816e40..ce52b6c 100644 --- a/code/drivers/storage/ata/ata.c +++ b/code/drivers/storage/ata/ata.c @@ -1,8 +1,8 @@ +#include // ATA compatibility storage driver. This file owns legacy IDE probing and PIO disk access for older virtual hardware. #include "ata.h" -const NTL_DDK* gAtaDdk; static DRIVER_OBJECT gAtaDriver; static ATA_CONTROLLER* gAtaControllers[ATA_MAX_CONTROLLERS]; static uint32_t gAtaControllerCount; @@ -56,12 +56,12 @@ static void AtaDelay400ns(ATA_CHANNEL* ch) { // Returns the monotonic DDK timer in microseconds. static uint64_t AtaQueryTicks(void) { - return gAtaDdk->Timer->QueryTicks(); + return KeQueryTicks(); } // Delays briefly between ATA status samples without involving the scheduler. static void AtaPollDelay(void) { - gAtaDdk->Timer->Stall(ATA_POLL_INTERVAL_US); + KeStallExecution(ATA_POLL_INTERVAL_US); } // Returns whether a status byte represents an unimplemented/floating legacy channel. @@ -125,9 +125,9 @@ static KSTATUS AtaResetChannel(ATA_CHANNEL* ch) { DbgPrint("ATA: channel %u reset begin status=0x%02x\n", ch->Index, before); AtaDeviceControl(ch, ATA_DEVICE_CONTROL_NIEN | ATA_DEVICE_CONTROL_SRST); - gAtaDdk->Timer->Stall(ATA_RESET_ASSERT_US); + KeStallExecution(ATA_RESET_ASSERT_US); AtaDeviceControl(ch, ATA_DEVICE_CONTROL_NIEN); - gAtaDdk->Timer->Stall(ATA_RESET_SETTLE_US); + KeStallExecution(ATA_RESET_SETTLE_US); uint8_t finalStatus = AtaAltStatus(ch); if (AtaStatusIsAbsent(finalStatus)) { @@ -922,14 +922,11 @@ KSTATUS AtaAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObject) return NTLSTATUS_SUCCESS; } -// Implements the driver Entry operation. -KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { - (void)init; - if (!ddk || !ddk->Timer || !ddk->Timer->QueryTicks || !ddk->Timer->Stall) - return NTLSTATUS_INVALID_PARAMETER; +static KSTATUS DriverInitialize(NTL_MODULE* module) { + (void)module; DbgPrint("ATA: driver loading...\n"); - gAtaDdk = ddk; + gAtaControllerCount = 0; memset(&gAtaDriver, 0, sizeof(gAtaDriver)); @@ -940,9 +937,8 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { gAtaDriver.MajorFunction[IRP_MJ_WRITE] = AtaDispatchReadWrite; gAtaDriver.MajorFunction[IRP_MJ_FLUSH_BUFFERS] = AtaDispatchFlush; gAtaDriver.MajorFunction[IRP_MJ_PNP] = AtaDispatchPnp; - - KSTATUS st = IoRegisterDriverObject(&gAtaDriver); - if (!KSUCCESS(st)) - DbgPrint("ATA: IoRegisterDriverObject failed: 0x%x\n", st); - return st; + return NTLSTATUS_SUCCESS; } + +static DRIVER_OBJECT* const gModuleIoDrivers[] = { &gAtaDriver }; +NTL_DECLARE_IO_MODULE(DriverInitialize, gModuleIoDrivers); diff --git a/code/drivers/storage/ata/ata.h b/code/drivers/storage/ata/ata.h index 7634f7f..bd4c812 100644 --- a/code/drivers/storage/ata/ata.h +++ b/code/drivers/storage/ata/ata.h @@ -133,8 +133,6 @@ typedef struct ATA_CONTROLLER { uint8_t DeviceCount; } ATA_CONTROLLER; -extern const NTL_DDK* gAtaDdk; - KSTATUS AtaAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObject); KSTATUS AtaDispatchPnp(DEVICE_OBJECT* device, IRP* irp); KSTATUS AtaDispatchReadWrite(DEVICE_OBJECT* device, IRP* irp); diff --git a/code/kernel/dm_config.c b/code/kernel/dm_config.c new file mode 100644 index 0000000..53e75b5 --- /dev/null +++ b/code/kernel/dm_config.c @@ -0,0 +1,422 @@ +// Strict drivers.ini v2 parsing, validation and dependency evaluation. + +#include +#include + +#define DM_DRIVER_IMAGE_SET 1u +#define DM_DRIVER_START_SET 2u +#define DM_DRIVER_CLASS_SET 4u +#define DM_DRIVER_CRITICAL_SET 8u +#define DM_DRIVER_REQUIRES_SET 16u +#define DM_DRIVER_AFTER_SET 32u +#define DM_CLASS_REQUIRES_SET 1u +#define DM_CLASS_AFTER_SET 2u + +static bool DmFail(char* error, size_t capacity, const char* message) { + if (error && capacity) { + size_t i = 0; + while (message[i] && i + 1 < capacity) { + error[i] = message[i]; + ++i; + } + error[i] = '\0'; + } + return false; +} + +static bool DmSpace(char c) { + return c == ' ' || c == '\t' || c == '\r'; +} + +static void DmTrim(const char** begin, const char** end) { + while (*begin < *end && DmSpace(**begin)) ++*begin; + while (*end > *begin && DmSpace((*end)[-1])) --*end; +} + +static bool DmEqual(const char* begin, const char* end, const char* value) { + while (begin < end && *value && *begin == *value) { + ++begin; + ++value; + } + return begin == end && !*value; +} + +static bool DmIdentifier(const char* begin, const char* end) { + if (begin == end || (size_t)(end - begin) >= DM_ID_MAX) return false; + for (const char* p = begin; p < end; ++p) { + char c = *p; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '.' || c == '-' || c == '_')) return false; + } + return true; +} + +// PE import module names are compared without ASCII case distinctions. +static bool DmImageEqual(const char* a, const char* b) { + while (*a && *b) { + char x = *a++; + char y = *b++; + if (x >= 'A' && x <= 'Z') x = (char)(x + ('a' - 'A')); + if (y >= 'A' && y <= 'Z') y = (char)(y + ('a' - 'A')); + if (x != y) return false; + } + return !*a && !*b; +} + +static bool DmCopy(char* out, size_t capacity, const char* begin, const char* end) { + size_t size = (size_t)(end - begin); + if (!size || size >= capacity) return false; + memcpy(out, begin, size); + out[size] = '\0'; + return true; +} + +static int DmFindDriver(const DM_CONFIG* config, const char* id) { + for (uint32_t i = 0; i < config->DriverCount; ++i) + if (!strcmp(config->Drivers[i].Id, id)) return (int)i; + return -1; +} + +static int DmFindClass(const DM_CONFIG* config, const char* id) { + for (uint32_t i = 0; i < config->ClassCount; ++i) + if (!strcmp(config->Classes[i].Id, id)) return (int)i; + return -1; +} + +static bool DmParseEdges(DM_EDGES* edges, const char* begin, const char* end, + DM_DEPENDENCY_KIND kind) { + if (begin == end) return true; + while (begin < end) { + const char* finish = begin; + while (finish < end && *finish != ',') ++finish; + const char* tokenEnd = finish; + DmTrim(&begin, &tokenEnd); + if (!DmIdentifier(begin, tokenEnd) || edges->Count == DM_MAX_EDGES) return false; + for (uint32_t i = 0; i < edges->Count; ++i) { + if (edges->Items[i].Kind == kind && + DmEqual(begin, tokenEnd, edges->Items[i].Id)) return false; + } + DM_DEPENDENCY* edge = &edges->Items[edges->Count++]; + edge->Kind = kind; + if (!DmCopy(edge->Id, sizeof(edge->Id), begin, tokenEnd)) return false; + if (finish == end) return true; + begin = finish + 1; + DmTrim(&begin, &end); + if (begin == end) return false; + } + return true; +} + +// Validates references and boot-phase satisfiability without flattening class alternatives. +static bool DmValidateEdges(const DM_CONFIG* config, const DM_EDGES* edges, + const DM_DRIVER_CONFIG* source, bool required) { + for (uint32_t i = 0; i < edges->Count; ++i) { + const DM_DEPENDENCY* edge = &edges->Items[i]; + if (edge->Kind == DmDependencyDriver) { + int target = DmFindDriver(config, edge->Id); + if (target < 0) return false; + if (source && source->Phase == DmStartBoot && + config->Drivers[target].Phase != DmStartBoot) return false; + } else { + if (DmFindClass(config, edge->Id) < 0) return false; + uint32_t bootProviders = 0; + for (uint32_t j = 0; j < config->DriverCount; ++j) { + if (strcmp(config->Drivers[j].Class, edge->Id)) continue; + if (config->Drivers[j].Phase == DmStartBoot) ++bootProviders; + } + if (required && source && source->Phase == DmStartBoot && !bootProviders) + return false; + } + } + return true; +} + +static bool DmEdgesOverlap(const DM_EDGES* requires, const DM_EDGES* after) { + for (uint32_t i = 0; i < requires->Count; ++i) + for (uint32_t j = 0; j < after->Count; ++j) + if (requires->Items[i].Kind == after->Items[j].Kind && + !strcmp(requires->Items[i].Id, after->Items[j].Id)) return true; + return false; +} + +static bool DmVisitClass(uint32_t index, uint32_t count, + uint8_t color[DM_MAX_CLASSES], + uint8_t graph[DM_MAX_CLASSES][DM_MAX_CLASSES]) { + color[index] = 1; + for (uint32_t j = 0; j < count; ++j) { + if (!graph[index][j]) continue; + if (color[j] == 1 || + (color[j] == 0 && !DmVisitClass(j, count, color, graph))) return false; + } + color[index] = 2; + return true; +} + +bool DmConfigParse(DM_CONFIG* config, const char* bytes, size_t size, + char* error, size_t errorCapacity) { + if (!config || !bytes || size == 0 || size > DM_CONFIG_MAX_BYTES) + return DmFail(error, errorCapacity, "invalid configuration size"); + memset(config, 0, sizeof(*config)); + DM_DRIVER_CONFIG* driver = NULL; + DM_CLASS_CONFIG* cls = NULL; + bool seenDriverSection = false; + size_t pos = 0; + while (pos < size) { + size_t line = pos; + while (pos < size && bytes[pos] != '\n') ++pos; + const char* begin = bytes + line; + const char* end = bytes + pos; + if (pos < size) ++pos; + DmTrim(&begin, &end); + if (begin == end || *begin == ';' || *begin == '#') continue; + if (*begin == '[') { + if (end[-1] != ']' || (size_t)(end - begin) < 4) + return DmFail(error, errorCapacity, "invalid section"); + const char* name = begin + 1; + const char* nameEnd = end - 1; + driver = NULL; + cls = NULL; + if ((size_t)(nameEnd - name) > 7 && !memcmp(name, "driver.", 7)) { + name += 7; + if (!DmIdentifier(name, nameEnd) || config->DriverCount == DM_MAX_DRIVERS) + return DmFail(error, errorCapacity, "invalid driver id or driver limit"); + driver = &config->Drivers[config->DriverCount++]; + seenDriverSection = true; + if (!DmCopy(driver->Id, sizeof(driver->Id), name, nameEnd)) return false; + for (uint32_t i = 0; i + 1 < config->DriverCount; ++i) + if (!strcmp(config->Drivers[i].Id, driver->Id)) + return DmFail(error, errorCapacity, "duplicate driver id"); + } else if ((size_t)(nameEnd - name) > 6 && !memcmp(name, "class.", 6)) { + name += 6; + if (!DmIdentifier(name, nameEnd) || config->ClassCount == DM_MAX_CLASSES) + return DmFail(error, errorCapacity, "invalid class id or class limit"); + if (seenDriverSection) + return DmFail(error, errorCapacity, "class declared after driver"); + cls = &config->Classes[config->ClassCount++]; + if (!DmCopy(cls->Id, sizeof(cls->Id), name, nameEnd)) return false; + for (uint32_t i = 0; i + 1 < config->ClassCount; ++i) + if (!strcmp(config->Classes[i].Id, cls->Id)) + return DmFail(error, errorCapacity, "duplicate class id"); + } else return DmFail(error, errorCapacity, "unknown section"); + continue; + } + if (!driver && !cls) return DmFail(error, errorCapacity, "key outside section"); + const char* equal = begin; + while (equal < end && *equal != '=') ++equal; + if (equal == end) return DmFail(error, errorCapacity, "missing equals"); + const char* keyEnd = equal; + const char* value = equal + 1; + DmTrim(&begin, &keyEnd); + DmTrim(&value, &end); + for (const char* p = value; p < end; ++p) { + if (*p == ';' || *p == '#') { + end = p; + DmTrim(&value, &end); + break; + } + } + uint8_t mask = 0; + if (driver) { + if (DmEqual(begin, keyEnd, "image")) { + mask = DM_DRIVER_IMAGE_SET; + if (!DmCopy(driver->Image, sizeof(driver->Image), value, end)) + return DmFail(error, errorCapacity, "invalid image"); + for (const char* p = value; p < end; ++p) + if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9') || + *p == '-' || *p == '_' || *p == '.')) + return DmFail(error, errorCapacity, "invalid image filename"); + if (end - value < 5 || memcmp(end - 4, ".sys", 4)) + return DmFail(error, errorCapacity, "image must be a .sys file"); + } else if (DmEqual(begin, keyEnd, "start")) { + mask = DM_DRIVER_START_SET; + if (DmEqual(value, end, "boot")) driver->Phase = DmStartBoot; + else if (DmEqual(value, end, "runtime")) driver->Phase = DmStartRuntime; + else if (DmEqual(value, end, "disabled")) { + driver->Phase = DmStartDisabled; + driver->State = DmRunDisabled; + } + else return DmFail(error, errorCapacity, "invalid start phase"); + } else if (DmEqual(begin, keyEnd, "class")) { + mask = DM_DRIVER_CLASS_SET; + if (value != end && (!DmIdentifier(value, end) || + !DmCopy(driver->Class, sizeof(driver->Class), value, end))) + return DmFail(error, errorCapacity, "invalid class"); + } else if (DmEqual(begin, keyEnd, "requires")) { + mask = DM_DRIVER_REQUIRES_SET; + if (!DmParseEdges(&driver->Requires, value, end, DmDependencyDriver)) + return DmFail(error, errorCapacity, "invalid requires"); + } else if (DmEqual(begin, keyEnd, "after")) { + mask = DM_DRIVER_AFTER_SET; + if (!DmParseEdges(&driver->After, value, end, DmDependencyDriver)) + return DmFail(error, errorCapacity, "invalid after"); + } else if (DmEqual(begin, keyEnd, "critical")) { + mask = DM_DRIVER_CRITICAL_SET; + if (value == end) driver->Critical = false; + else if (DmEqual(value, end, "true")) driver->Critical = true; + else if (DmEqual(value, end, "false")) driver->Critical = false; + else return DmFail(error, errorCapacity, "invalid critical value"); + } else return DmFail(error, errorCapacity, "unknown driver key"); + if (driver->Present & mask) return DmFail(error, errorCapacity, "duplicate driver key"); + driver->Present |= mask; + } else { + if (DmEqual(begin, keyEnd, "requires")) { + mask = DM_CLASS_REQUIRES_SET; + if (!DmParseEdges(&cls->Requires, value, end, DmDependencyClass)) + return DmFail(error, errorCapacity, "invalid class requires"); + } else if (DmEqual(begin, keyEnd, "after")) { + mask = DM_CLASS_AFTER_SET; + if (!DmParseEdges(&cls->After, value, end, DmDependencyClass)) + return DmFail(error, errorCapacity, "invalid class after"); + } else return DmFail(error, errorCapacity, "unknown class key"); + if (cls->Present & mask) return DmFail(error, errorCapacity, "duplicate class key"); + cls->Present |= mask; + } + } + if (!config->DriverCount) return DmFail(error, errorCapacity, "no drivers"); + // Class graph is checked independently even when a class has no providers. + uint8_t classGraph[DM_MAX_CLASSES][DM_MAX_CLASSES] = {{0}}; + for (uint32_t i = 0; i < config->ClassCount; ++i) { + const DM_CLASS_CONFIG* cls = &config->Classes[i]; + if (DmEdgesOverlap(&cls->Requires, &cls->After)) + return DmFail(error, errorCapacity, "conflicting class dependency"); + const DM_EDGES* lists[] = { &cls->Requires, &cls->After }; + for (uint32_t k = 0; k < 2; ++k) + for (uint32_t j = 0; j < lists[k]->Count; ++j) { + int index = DmFindClass(config, lists[k]->Items[j].Id); + if (index < 0) return DmFail(error, errorCapacity, "unknown class dependency"); + classGraph[i][index] = 1; + } + } + uint8_t classColors[DM_MAX_CLASSES] = {0}; + for (uint32_t i = 0; i < config->ClassCount; ++i) + if (!classColors[i] && !DmVisitClass(i, config->ClassCount, + classColors, classGraph)) + return DmFail(error, errorCapacity, "class dependency cycle"); + for (uint32_t i = 0; i < config->DriverCount; ++i) { + DM_DRIVER_CONFIG* d = &config->Drivers[i]; + if ((d->Present & (DM_DRIVER_IMAGE_SET | DM_DRIVER_START_SET)) != + (DM_DRIVER_IMAGE_SET | DM_DRIVER_START_SET)) + return DmFail(error, errorCapacity, "driver needs image and start"); + for (uint32_t j = 0; j < i; ++j) + if (d->Phase != DmStartDisabled && + config->Drivers[j].Phase != DmStartDisabled && + DmImageEqual(config->Drivers[j].Image, d->Image)) + return DmFail(error, errorCapacity, "duplicate image"); + if (d->Class[0] && DmFindClass(config, d->Class) < 0) + return DmFail(error, errorCapacity, "undeclared driver class"); + if (DmEdgesOverlap(&d->Requires, &d->After)) + return DmFail(error, errorCapacity, "conflicting driver dependency"); + if (!DmValidateEdges(config, &d->Requires, d, true) || + !DmValidateEdges(config, &d->After, d, false)) + return DmFail(error, errorCapacity, "invalid driver dependency or phase"); + if (d->Class[0] && d->Phase != DmStartDisabled) { + DM_CLASS_CONFIG* c = &config->Classes[DmFindClass(config, d->Class)]; + if (!DmValidateEdges(config, &c->Requires, d, true) || + !DmValidateEdges(config, &c->After, d, false)) + return DmFail(error, errorCapacity, "invalid class dependency or phase"); + } + } + // An optimistic dry run models requires= as an OR over class providers and + // after= as a settlement barrier. It detects real combined deadlocks without + // rejecting a class merely because one alternative provider depends on it. + for (uint32_t phase = DmStartBoot; phase <= DmStartRuntime; ++phase) { + config->ActivePhase = (DM_START_PHASE)phase; + for (;;) { + bool progress = false; + for (uint32_t i = 0; i < config->DriverCount; ++i) { + DM_DRIVER_CONFIG* d = &config->Drivers[i]; + if (d->Phase != phase || d->State != DmRunWaiting) continue; + if (DmDriverCanStart(config, i)) { + d->State = DmRunStarted; + progress = true; + } else if (DmDriverIsBlocked(config, i)) { + d->State = DmRunBlocked; + progress = true; + } + } + if (!progress) break; + } + for (uint32_t i = 0; i < config->DriverCount; ++i) + if (config->Drivers[i].Phase == phase && + config->Drivers[i].State == DmRunWaiting) + return DmFail(error, errorCapacity, "unschedulable dependency cycle"); + } + for (uint32_t i = 0; i < config->DriverCount; ++i) + if (config->Drivers[i].Phase != DmStartDisabled) + config->Drivers[i].State = DmRunWaiting; + config->ActivePhase = DmStartBoot; + return true; +} + +bool DmClassSettled(const DM_CONFIG* config, const char* classId) { + for (uint32_t i = 0; i < config->DriverCount; ++i) { + const DM_DRIVER_CONFIG* driver = &config->Drivers[i]; + if (!strcmp(driver->Class, classId) && + (config->ActivePhase == DmStartRuntime || driver->Phase == DmStartBoot) && + driver->State == DmRunWaiting) return false; + } + return true; +} + +bool DmClassAvailable(const DM_CONFIG* config, const char* classId) { + for (uint32_t i = 0; i < config->DriverCount; ++i) { + const DM_DRIVER_CONFIG* driver = &config->Drivers[i]; + if (!strcmp(driver->Class, classId) && driver->State == DmRunStarted) return true; + } + return false; +} + +static bool DmEdgesReady(const DM_CONFIG* config, const DM_EDGES* edges, bool required) { + for (uint32_t i = 0; i < edges->Count; ++i) { + const DM_DEPENDENCY* edge = &edges->Items[i]; + if (edge->Kind == DmDependencyClass) { + if (required ? !DmClassAvailable(config, edge->Id) : + !DmClassSettled(config, edge->Id)) return false; + } else { + int index = DmFindDriver(config, edge->Id); + if (index < 0) return false; + DM_RUN_STATE state = config->Drivers[index].State; + if (required ? state != DmRunStarted : state == DmRunWaiting) return false; + } + } + return true; +} + +bool DmDriverCanStart(const DM_CONFIG* config, uint32_t index) { + if (!config || index >= config->DriverCount) return false; + const DM_DRIVER_CONFIG* driver = &config->Drivers[index]; + if (driver->State != DmRunWaiting || + !DmEdgesReady(config, &driver->Requires, true) || + !DmEdgesReady(config, &driver->After, false)) return false; + if (!driver->Class[0]) return true; + const DM_CLASS_CONFIG* cls = &config->Classes[DmFindClass(config, driver->Class)]; + return DmEdgesReady(config, &cls->Requires, true) && + DmEdgesReady(config, &cls->After, false); +} + +static bool DmEdgesImpossible(const DM_CONFIG* config, const DM_EDGES* edges) { + for (uint32_t i = 0; i < edges->Count; ++i) { + const DM_DEPENDENCY* edge = &edges->Items[i]; + if (edge->Kind == DmDependencyClass) { + if (DmClassSettled(config, edge->Id) && + !DmClassAvailable(config, edge->Id)) return true; + } else { + int index = DmFindDriver(config, edge->Id); + if (index < 0 || config->Drivers[index].State == DmRunFailed || + config->Drivers[index].State == DmRunBlocked || + config->Drivers[index].State == DmRunDisabled) return true; + } + } + return false; +} + +bool DmDriverIsBlocked(const DM_CONFIG* config, uint32_t index) { + if (!config || index >= config->DriverCount) return false; + const DM_DRIVER_CONFIG* driver = &config->Drivers[index]; + if (driver->State != DmRunWaiting) return false; + if (DmEdgesImpossible(config, &driver->Requires)) return true; + if (!driver->Class[0]) return false; + return DmEdgesImpossible(config, + &config->Classes[DmFindClass(config, driver->Class)].Requires); +} diff --git a/code/kernel/driver_module.c b/code/kernel/driver_module.c new file mode 100644 index 0000000..d1afd9e --- /dev/null +++ b/code/kernel/driver_module.c @@ -0,0 +1,226 @@ +// Driver module identity and versioned declarative module exports. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DM_NAME_MAX 64u + +typedef enum DM_MODULE_STATE { + DmModuleStarting, + DmModuleStarted, + DmModuleFailed, + DmModuleStopping, + DmModuleStopped, +} DM_MODULE_STATE; + +struct NTL_MODULE { + struct NTL_MODULE* Next; + char Name[DM_NAME_MAX]; + uintptr_t ImageBase; + DM_MODULE_STATE State; + NTL_MODULE_PREPARE_SHUTDOWN PrepareShutdown; + NTL_MODULE_COMMIT_SHUTDOWN CommitShutdown; +}; + +static NTL_MODULE* gDriverModules; + +static bool DmImageContains(uintptr_t base, size_t imageSize, uintptr_t ptr, size_t bytes) { + return ptr >= base && ptr - base <= imageSize && bytes <= imageSize - (ptr - base); +} + +__declspec(dllexport) const char* NtlModuleGetName(const NTL_MODULE* module) { + return module ? module->Name : (const char*)0; +} + +static bool DmValidImageString(uintptr_t base, size_t size, const char* text, + size_t maxLength) { + if (!DmImageContains(base, size, (uintptr_t)text, 1u)) return false; + for (size_t i = 0; i < maxLength; ++i) { + if (!DmImageContains(base, size, (uintptr_t)text + i, 1u)) return false; + if (!text[i]) + return i && SysSvIsUtf8Text(SysSvFromParts(text, i)); + } + return false; +} + +static bool DmValidIoDriver(uintptr_t imageBase, size_t imageSize, const DRIVER_OBJECT* driver) { + if (!DmImageContains(imageBase, imageSize, (uintptr_t)driver, sizeof(*driver)) || + driver->Header.Type != ObjectTypeDriver || + driver->Header.LifetimeState != ObjectAlive || + !driver->Name.Length || + !DmImageContains(imageBase, imageSize, (uintptr_t)driver->Name.Data, + driver->Name.Length) || + !SysSvIsUtf8Text(driver->Name) || + (driver->DriverAddDevice && + !DmImageContains(imageBase, imageSize, (uintptr_t)driver->DriverAddDevice, 1u)) || + (driver->DriverUnload && + !DmImageContains(imageBase, imageSize, (uintptr_t)driver->DriverUnload, 1u))) + return false; + + for (uint32_t i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i) { + if (driver->MajorFunction[i] && + !DmImageContains(imageBase, imageSize, + (uintptr_t)driver->MajorFunction[i], 1u)) + return false; + } + + if (!driver->IdTable) return true; + if (!driver->DriverAddDevice || + !DmImageContains(imageBase, imageSize, (uintptr_t)driver->IdTable, + sizeof(*driver->IdTable)) || + !driver->IdTable->Count || driver->IdTable->Count > 32u || + !DmImageContains(imageBase, imageSize, (uintptr_t)driver->IdTable->Ids, + driver->IdTable->Count * sizeof(IO_DEVICE_ID))) + return false; + + for (size_t i = 0; i < driver->IdTable->Count; ++i) { + const IO_DEVICE_ID* id = &driver->IdTable->Ids[i]; + if ((id->BusType != IoBusPci && id->BusType != IoBusUsb && + id->BusType != IoBusUnknown) || + !id->Id.Length || + !DmImageContains(imageBase, imageSize, (uintptr_t)id->Id.Data, + id->Id.Length) || + !SysSvIsUtf8Text(id->Id)) + return false; + } + return true; +} + +KSTATUS DmStartLoadedDriver(const char* name, uintptr_t imageBase) { + if (!name || !*name || !imageBase) + return NTLSTATUS_INVALID_PARAMETER; + + size_t length = 0; + while (length < DM_NAME_MAX && name[length]) + length++; + if (!length || length == DM_NAME_MAX) + return NTLSTATUS_INVALID_PARAMETER; + + for (NTL_MODULE* it = gDriverModules; it; it = it->Next) { + if (strcmp(it->Name, name) == 0 || it->ImageBase == imageBase) + return NTLSTATUS_ALREADY_EXISTS; + } + + size_t imageSize = LdrGetImageSize(imageBase); + if (!imageSize) + return NTLSTATUS_INVALID_IMAGE_FORMAT; + + const NTL_MODULE_EXPORT* manifest = + (const NTL_MODULE_EXPORT*)LdrGetImageExport(imageBase, NTL_DDK_MODULE_EXPORT_NAME); + NTL_MODULE_INITIALIZE initialize = (NTL_MODULE_INITIALIZE)0; + + if (!manifest) return NTLSTATUS_INVALID_IMAGE_FORMAT; + { + if (!DmImageContains(imageBase, imageSize, (uintptr_t)manifest, sizeof(*manifest)) || + manifest->Signature != NTL_DDK_MODULE_SIGNATURE || + manifest->Version != NTL_DDK_MODULE_ABI_VERSION || + manifest->Size != sizeof(*manifest) || manifest->Flags != 0u || + manifest->Reserved != 0u || manifest->Reserved2 != 0u || + (manifest->Initialize && !DmImageContains(imageBase, imageSize, + (uintptr_t)manifest->Initialize, 1u)) || + (manifest->PrepareShutdown && !DmImageContains(imageBase, imageSize, + (uintptr_t)manifest->PrepareShutdown, 1u)) || + (manifest->CommitShutdown && !DmImageContains(imageBase, imageSize, + (uintptr_t)manifest->CommitShutdown, 1u))) + return NTLSTATUS_INVALID_IMAGE_FORMAT; + if ((manifest->DeviceCount && !manifest->Devices) || + manifest->DeviceCount > 64u || + (manifest->DeviceCount && + !DmImageContains(imageBase, imageSize, (uintptr_t)manifest->Devices, + (size_t)manifest->DeviceCount * sizeof(NTL_DEVICE_DESC)))) + return NTLSTATUS_INVALID_IMAGE_FORMAT; + for (uint32_t i = 0; i < manifest->DeviceCount; ++i) { + const NTL_DEVICE_DESC* d = &manifest->Devices[i]; + if (d->Size != sizeof(*d) || d->Flags || !d->Name || !d->Start || + !DmValidImageString(imageBase, imageSize, d->Name, 64u) || + !DmImageContains(imageBase, imageSize, (uintptr_t)d->Start, 1u) || + (d->Stop && !DmImageContains(imageBase, imageSize, (uintptr_t)d->Stop, 1u)) || + (d->CancelStart && !DmImageContains(imageBase, imageSize, + (uintptr_t)d->CancelStart, 1u)) || + !d->MatchCount || d->MatchCount > 32u || + !DmImageContains(imageBase, imageSize, (uintptr_t)d->Matches, + (size_t)d->MatchCount * sizeof(NTL_DEVICE_MATCH))) + return NTLSTATUS_INVALID_IMAGE_FORMAT; + for (uint32_t j = 0; j < d->MatchCount; ++j) { + const NTL_DEVICE_MATCH* m = &d->Matches[j]; + if ((m->Bus != NtlBusPci && m->Bus != NtlBusUsb) || + !DmValidImageString(imageBase, imageSize, m->Id, 96u)) + return NTLSTATUS_INVALID_IMAGE_FORMAT; + } + } + if ((manifest->IoDriverCount && !manifest->IoDrivers) || + manifest->IoDriverCount > 64u || + (manifest->IoDriverCount && + !DmImageContains(imageBase, imageSize, (uintptr_t)manifest->IoDrivers, + (size_t)manifest->IoDriverCount * sizeof(*manifest->IoDrivers)))) + return NTLSTATUS_INVALID_IMAGE_FORMAT; + for (uint32_t i = 0; i < manifest->IoDriverCount; ++i) { + if (!DmImageContains(imageBase, imageSize, + (uintptr_t)manifest->IoDrivers[i], sizeof(DRIVER_OBJECT))) + return NTLSTATUS_INVALID_IMAGE_FORMAT; + } + initialize = manifest->Initialize; + } + + NTL_MODULE* module = (NTL_MODULE*)MmAllocatePool(sizeof(*module)); + if (!module) + return NTLSTATUS_NO_MEMORY; + + memset(module, 0, sizeof(*module)); + memcpy(module->Name, name, length); + module->ImageBase = imageBase; + module->State = DmModuleStarting; + module->PrepareShutdown = manifest->PrepareShutdown; + module->CommitShutdown = manifest->CommitShutdown; + module->Next = gDriverModules; + gDriverModules = module; + + KSTATUS status = initialize ? initialize(module) : NTLSTATUS_SUCCESS; + if (!KSUCCESS(status) && !KFAILED(status)) + status = NTLSTATUS_INVALID_PARAMETER; + + if (KSUCCESS(status) && manifest) { + for (uint32_t i = 0; i < manifest->IoDriverCount; ++i) { + if (!DmValidIoDriver(imageBase, imageSize, manifest->IoDrivers[i])) { + status = NTLSTATUS_INVALID_IMAGE_FORMAT; + break; + } + } + } + if (KSUCCESS(status)) + status = IoPnpPublishModule(module, manifest->Devices, manifest->DeviceCount, + manifest->IoDrivers, manifest->IoDriverCount); + module->State = KSUCCESS(status) ? DmModuleStarted : DmModuleFailed; + return status; +} + +KSTATUS DmPrepareShutdown(void) { + KSTATUS firstError = NTLSTATUS_SUCCESS; + for (NTL_MODULE* module = gDriverModules; module; module = module->Next) { + if (module->State != DmModuleStarted) continue; + module->State = DmModuleStopping; + if (!module->PrepareShutdown) continue; + KSTATUS st = module->PrepareShutdown(module); + if (!KSUCCESS(st)) { + DbgPrint("shutdown: prepare %s failed 0x%x\n", module->Name, st); + if (KSUCCESS(firstError)) firstError = st; + } + } + return firstError; +} + +void DmCommitShutdown(void) { + for (NTL_MODULE* module = gDriverModules; module; module = module->Next) { + if (module->State != DmModuleStopping) continue; + if (module->CommitShutdown) module->CommitShutdown(module); + module->State = DmModuleStopped; + } +} diff --git a/code/kernel/hal/hal_apic.c b/code/kernel/hal/hal_apic.c index b119b84..de2ad68 100644 --- a/code/kernel/hal/hal_apic.c +++ b/code/kernel/hal/hal_apic.c @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -34,6 +35,12 @@ static volatile uint32_t* gIoapic = (volatile uint32_t*)0; static uint32_t gBspLapicId = 0; static uint8_t gX2ApicSupported = 0; static uint8_t gX2ApicEnabled = 0; +static uint32_t gIoapicGsiBase = 0; +static uint32_t gIoapicRedirectionCount = 0; +static uint32_t gIsaGsi[16]; +static uint16_t gIsaFlags[16]; +static uint16_t gSciIrq = UINT16_MAX; +static bool gIsaOverridesInitialized = false; #define IOAPIC_IDX 0x00 #define IOAPIC_DATA 0x10 @@ -188,29 +195,86 @@ KSTATUS HalApicComposeMsi(uint8_t vector, HAL_MSI_MESSAGE* messageOut) { return NTLSTATUS_SUCCESS; } +// Initializes the identity ISA map before processing MADT source overrides. +static void HalApicInitializeIsaMap(void) { + if (gIsaOverridesInitialized) return; + for (uint32_t irq = 0; irq < 16; ++irq) { + gIsaGsi[irq] = irq; + gIsaFlags[irq] = 0; + } + gIsaOverridesInitialized = true; +} + +// Records the global-system-interrupt base of the currently supported IOAPIC. +void HalApicSetGsiBase(uint32_t gsiBase) { gIoapicGsiBase = gsiBase; } + +// Preserves MADT interrupt-source overrides for legacy ISA devices. +void HalApicSetIsaOverride(uint8_t irq, uint32_t gsi, uint16_t flags) { + if (irq >= 16) return; + HalApicInitializeIsaMap(); + gIsaGsi[irq] = gsi; + gIsaFlags[irq] = flags; +} + +// Marks the FADT SCI as a low-active, level-triggered interrupt by default. +void HalApicSetSciIrq(uint16_t irq) { gSciIrq = irq; } + +// Resolves one logical IRQ to a physical pin on the selected IOAPIC. +static bool HalApicResolveIrq(uint8_t irq, uint32_t* pinOut, uint32_t* flagsOut) { + HalApicInitializeIsaMap(); + uint32_t gsi = irq < 16 ? gIsaGsi[irq] : irq; + if (gsi < gIoapicGsiBase || gsi - gIoapicGsiBase >= gIoapicRedirectionCount) + return false; + uint32_t flags = 0; + if (irq == gSciIrq) flags = (1u << 13) | (1u << 15); + if (irq < 16) { + uint16_t override = gIsaFlags[irq]; + // MADT 00 is bus-conformant; 11 selects active low or level triggered. + if ((override & 3u) == 3u) flags |= (1u << 13); + else if ((override & 3u) == 1u) flags &= ~(1u << 13); + if (((override >> 2) & 3u) == 3u) flags |= (1u << 15); + else if (((override >> 2) & 3u) == 1u) flags &= ~(1u << 15); + } + *pinOut = gsi - gIoapicGsiBase; + *flagsOut = flags; + return true; +} + +// Reports whether a platform IRQ can be routed by the current IOAPIC. +bool HalApicCanRouteIrq(uint8_t irq) { + uint32_t pin, flags; + return irq < 24 && HalApicResolveIrq(irq, &pin, &flags); +} + // Implements the apic Route Irq operation. void HalApicRouteIrq(uint8_t irq) { // Full initial setup of the redirection entry. // Sets the vector and clears the mask bit in one write. - uint32_t vector = IRQ_VECTOR_BASE + irq; // e.g. IRQ 1 -> vector 0x21 - IoapicWrite(IOREDTBL_HI(irq), (gBspLapicId & 0xFFu) << 24); // external IRQs target the BSP - IoapicWrite(IOREDTBL_LO(irq), vector); // unmasked (bit 16 = 0), edge, fixed + uint32_t pin, flags; + if (!HalApicResolveIrq(irq, &pin, &flags)) return; + uint32_t vector = IRQ_VECTOR_BASE + irq; + IoapicWrite(IOREDTBL_HI(pin), (gBspLapicId & 0xFFu) << 24); + IoapicWrite(IOREDTBL_LO(pin), vector | flags); } // Implements the apic Mask Irq operation. void HalApicMaskIrq(uint8_t irq) { - uint32_t lo = IoapicRead(IOREDTBL_LO(irq)); + uint32_t pin, flags; + if (!HalApicResolveIrq(irq, &pin, &flags)) return; + uint32_t lo = IoapicRead(IOREDTBL_LO(pin)); lo |= (1 << 16); - IoapicWrite(IOREDTBL_LO(irq), lo); + IoapicWrite(IOREDTBL_LO(pin), lo); } // Implements the apic Unmask Irq operation. void HalApicUnmaskIrq(uint8_t irq) { - uint32_t lo = IoapicRead(IOREDTBL_LO(irq)); - lo &= ~(1 << 16); // clear mask bit - lo = (lo & ~0xFF) | (IRQ_VECTOR_BASE + irq); - IoapicWrite(IOREDTBL_HI(irq), (gBspLapicId & 0xFFu) << 24); - IoapicWrite(IOREDTBL_LO(irq), lo); + uint32_t pin, flags; + if (!HalApicResolveIrq(irq, &pin, &flags)) return; + uint32_t lo = IoapicRead(IOREDTBL_LO(pin)); + lo = (lo & ~(0xFFu | (1u << 16) | (1u << 13) | (1u << 15))) | + (IRQ_VECTOR_BASE + irq) | flags; + IoapicWrite(IOREDTBL_HI(pin), (gBspLapicId & 0xFFu) << 24); + IoapicWrite(IOREDTBL_LO(pin), lo); } // Implements the apic Send Eoi operation. @@ -240,8 +304,9 @@ void HalApicInitialize(uintptr_t lapicPhys, uintptr_t ioapicPhys) { // Mask all IOAPIC lines initially. uint32_t maxRedir = (IoapicRead(1) >> 16) & 0xFF; - for (uint8_t i = 0; i <= maxRedir; i++) { - IoapicWrite(IOREDTBL_LO(i), (1 << 16) | (IRQ_VECTOR_BASE + i)); + gIoapicRedirectionCount = maxRedir + 1u; + for (uint32_t i = 0; i <= maxRedir; i++) { + IoapicWrite(IOREDTBL_LO(i), (1u << 16) | IRQ_VECTOR_BASE); IoapicWrite(IOREDTBL_HI(i), (gBspLapicId & 0xFFu) << 24); } } diff --git a/code/kernel/hal/hal_main.c b/code/kernel/hal/hal_main.c index a54d04d..f6a8397 100644 --- a/code/kernel/hal/hal_main.c +++ b/code/kernel/hal/hal_main.c @@ -122,6 +122,12 @@ typedef struct ACPI_FIXED_REGISTER { static ACPI_FIXED_REGISTER gPm1aCnt; static ACPI_FIXED_REGISTER gPm1bCnt; +static ACPI_FIXED_REGISTER gPm1aStatus; +static ACPI_FIXED_REGISTER gPm1bStatus; +static ACPI_FIXED_REGISTER gPm1aEnable; +static ACPI_FIXED_REGISTER gPm1bEnable; +static uint16_t gSciIrq = 0; +static bool gFixedPowerButtonArmed = false; static ACPI_FIXED_REGISTER gSleepControl; static uint32_t gLegacyPm1aPort = 0; static uint32_t gLegacyPm1bPort = 0; @@ -172,6 +178,14 @@ void* HalPhysicalToVirtual(uint64_t phys) { // 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_SCI_INT_OFFSET 46u +#define FADT_PM1A_EVT_BLK_OFFSET 56u +#define FADT_PM1B_EVT_BLK_OFFSET 60u +#define FADT_PM1_EVT_LEN_OFFSET 88u +#define FADT_X_PM1A_EVT_BLK_OFFSET 148u +#define FADT_X_PM1B_EVT_BLK_OFFSET 160u +#define FADT_FLAG_POWER_BUTTON (1u << 4) +#define ACPI_PM1_POWER_BUTTON_BIT (1u << 8) #define FADT_DSDT_OFFSET 40u #define FADT_SMI_CMD_OFFSET 48u #define FADT_ACPI_ENABLE_OFFSET 52u @@ -184,6 +198,8 @@ void* HalPhysicalToVirtual(uint64_t phys) { #define FADT_X_PM1A_CNT_BLK_OFFSET 172u #define FADT_X_PM1B_CNT_BLK_OFFSET 184u #define FADT_SLEEP_CONTROL_OFFSET 244u +#define FADT_RESET_REG_OFFSET 116u +#define FADT_RESET_VALUE_OFFSET 128u #define FADT_FLAG_HW_REDUCED_ACPI (1u << 20) #define INTEL_PCH_SMI_EN_OFFSET 0x30u #define INTEL_PCH_SMI_ON_SLP_EN (1u << 4) @@ -198,6 +214,9 @@ typedef struct { } ACPI_GAS; #pragma pack(pop) +static ACPI_FIXED_REGISTER gResetRegister; +static uint8_t gResetValue; + // Implements the acpi Read U32 operation. static uint32_t AcpiReadU32(const void* table, uint32_t tableLen, uint32_t off) { uint32_t value = 0; @@ -291,6 +310,77 @@ static void AcpiWriteFixedRegister(const ACPI_FIXED_REGISTER* reg, uint32_t valu else *(volatile uint32_t*)address = value; } +// Selects a PM1 event block as two adjacent equal-width status and enable registers. +static void AcpiConfigurePm1EventBlock(const void* fadt, uint32_t len, + uint32_t legacyOffset, uint32_t gasOffset, + uint8_t halfLength, ACPI_FIXED_REGISTER* status, + ACPI_FIXED_REGISTER* enable) { + ACPI_FIXED_REGISTER block = {0}; + uint32_t legacy = AcpiReadU32(fadt, len, legacyOffset); + if (legacy && legacy <= 0xFFFFu) { + block.AddressSpaceId = 1; + block.Address = legacy; + } + ACPI_FIXED_REGISTER extended = {0}; + if (AcpiReadGasRegister(fadt, len, gasOffset, &extended)) block = extended; + if (!block.Address || (block.AddressSpaceId == 1 && + block.Address + halfLength * 2u - 1u > 0xFFFFu)) return; + block.WidthBytes = halfLength; + *status = block; + block.Address += halfLength; + *enable = block; +} + +// Reports the fixed-feature power-button interrupt supported by this HAL. +static KSTATUS AcpiQueryFixedButtonIrq(uint8_t* irqOut) { + if (!irqOut) return NTLSTATUS_INVALID_PARAMETER; + // A FADT control-method power button requires AML evaluation, not PM1 polling. + if (gHwReducedAcpi || (gFadtFlags & FADT_FLAG_POWER_BUTTON) || + !gSciIrq || gSciIrq >= 16u || !gPm1aStatus.Address || !gPm1aEnable.Address || + !HalApicCanRouteIrq((uint8_t)gSciIrq)) + return NTLSTATUS_NOT_SUPPORTED; + *irqOut = (uint8_t)gSciIrq; + return NTLSTATUS_SUCCESS; +} + +// Discards any stale press then enables the PM1 fixed power-button SCI. +static KSTATUS AcpiArmFixedButton(void) { + uint8_t irq; + KSTATUS status = AcpiQueryFixedButtonIrq(&irq); + if (!KSUCCESS(status)) return status; + // No SCI_EN means firmware still owns ACPI events. Never steal ownership + // through SMI_CMD just to install a power-button handler during boot. + if (!(AcpiReadFixedRegister(&gPm1aCnt) & 1u)) + return NTLSTATUS_NOT_SUPPORTED; + AcpiWriteFixedRegister(&gPm1aStatus, ACPI_PM1_POWER_BUTTON_BIT); + if (gPm1bStatus.Address) + AcpiWriteFixedRegister(&gPm1bStatus, ACPI_PM1_POWER_BUTTON_BIT); + uint32_t enabled = AcpiReadFixedRegister(&gPm1aEnable); + AcpiWriteFixedRegister(&gPm1aEnable, enabled | ACPI_PM1_POWER_BUTTON_BIT); + if (gPm1bEnable.Address) { + enabled = AcpiReadFixedRegister(&gPm1bEnable); + AcpiWriteFixedRegister(&gPm1bEnable, enabled | ACPI_PM1_POWER_BUTTON_BIT); + } + gFixedPowerButtonArmed = true; + return NTLSTATUS_SUCCESS; +} + +// Acknowledges the fixed-button source only; the SCI may carry unrelated events. +static bool AcpiConsumeFixedButton(void) { + if (!gFixedPowerButtonArmed) return false; + bool pressed = false; + if (AcpiReadFixedRegister(&gPm1aStatus) & ACPI_PM1_POWER_BUTTON_BIT) { + AcpiWriteFixedRegister(&gPm1aStatus, ACPI_PM1_POWER_BUTTON_BIT); + pressed = true; + } + if (gPm1bStatus.Address && + (AcpiReadFixedRegister(&gPm1bStatus) & ACPI_PM1_POWER_BUTTON_BIT)) { + AcpiWriteFixedRegister(&gPm1bStatus, ACPI_PM1_POWER_BUTTON_BIT); + pressed = true; + } + return pressed; +} + // Detects whether PM1 control is using the standard Intel PCH PMBASE layout. static void AcpiDetectIntelPchPmBase(void) { gIntelPchPmBase = 0; @@ -490,10 +580,20 @@ static void ParseFadt(const ACPISDTHeader* header) { memset(&gPm1aCnt, 0, sizeof(gPm1aCnt)); memset(&gPm1bCnt, 0, sizeof(gPm1bCnt)); + memset(&gPm1aStatus, 0, sizeof(gPm1aStatus)); + memset(&gPm1bStatus, 0, sizeof(gPm1bStatus)); + memset(&gPm1aEnable, 0, sizeof(gPm1aEnable)); + memset(&gPm1bEnable, 0, sizeof(gPm1bEnable)); + gSciIrq = 0; + if (len >= FADT_SCI_INT_OFFSET + sizeof(gSciIrq)) + memcpy(&gSciIrq, (const uint8_t*)fadt + FADT_SCI_INT_OFFSET, sizeof(gSciIrq)); memset(&gSleepControl, 0, sizeof(gSleepControl)); gLegacyPm1aPort = 0; gLegacyPm1bPort = 0; gFadtFlags = AcpiReadU32(fadt, len, FADT_FLAGS_OFFSET); + if (AcpiReadByteGasRegister(fadt, len, FADT_RESET_REG_OFFSET, &gResetRegister) && + FADT_RESET_VALUE_OFFSET < len) + gResetValue = *((const uint8_t*)fadt + FADT_RESET_VALUE_OFFSET); gHwReducedAcpi = (gFadtFlags & FADT_FLAG_HW_REDUCED_ACPI) != 0; uint8_t pm1CntLength = FADT_PM1_CNT_LEN_OFFSET < len @@ -528,6 +628,16 @@ static void ParseFadt(const ACPISDTHeader* header) { if (gHwReducedAcpi) (void)AcpiReadByteGasRegister(fadt, len, FADT_SLEEP_CONTROL_OFFSET, &gSleepControl); + uint8_t eventLength = FADT_PM1_EVT_LEN_OFFSET < len ? + *((const uint8_t*)fadt + FADT_PM1_EVT_LEN_OFFSET) : 0u; + if (!gHwReducedAcpi && (eventLength == 4u || eventLength == 8u)) { + uint8_t half = eventLength / 2u; + AcpiConfigurePm1EventBlock(fadt, len, FADT_PM1A_EVT_BLK_OFFSET, + FADT_X_PM1A_EVT_BLK_OFFSET, half, &gPm1aStatus, &gPm1aEnable); + AcpiConfigurePm1EventBlock(fadt, len, FADT_PM1B_EVT_BLK_OFFSET, + FADT_X_PM1B_EVT_BLK_OFFSET, half, &gPm1bStatus, &gPm1bEnable); + } + gSmiCommandPort = AcpiReadU32(fadt, len, FADT_SMI_CMD_OFFSET); gAcpiEnableValue = FADT_ACPI_ENABLE_OFFSET < len ? *((const uint8_t*)fadt + FADT_ACPI_ENABLE_OFFSET) @@ -599,6 +709,15 @@ static void ParseMadt(MADT* madt) { HalAddCpuRecord(uid, lapicId, flags); } else if (type == 1 && len >= 12) { // I/O APIC entry gIoapicPhys = *(uint32_t*)(p + 4); + uint32_t base; + memcpy(&base, p + 8, sizeof(base)); + HalApicSetGsiBase(base); + } else if (type == 2 && len >= 10 && p[2] == 0) { // ISA source override + uint32_t gsi; + uint16_t flags; + memcpy(&gsi, p + 4, sizeof(gsi)); + memcpy(&flags, p + 8, sizeof(flags)); + HalApicSetIsaOverride(p[3], gsi, flags); } else if (type == 5 && len >= 12) { // Local APIC Address Override gLapicPhys = (uintptr_t)*(uint64_t*)(p + 4); } else if (type == 9 && len >= 16) { // Processor Local x2APIC @@ -796,6 +915,19 @@ static void HalShutdown(void) { HalStopProcessor(); } +static void HalReset(void) { + (void)HalDisableInterrupts(); + if (gResetRegister.Address && gResetValue) + AcpiWriteFixedRegister(&gResetRegister, gResetValue); + if (gApi.Timer.Stall) gApi.Timer.Stall(100000u); + __outbyte(0xCF9u, 0x06u); + if (gApi.Timer.Stall) gApi.Timer.Stall(100000u); + __outbyte(0x64u, 0xFEu); + if (gApi.Timer.Stall) gApi.Timer.Stall(100000u); + HalDbgPrint("HAL: reset failed\n"); + HalStopProcessor(); +} + static KSTATUS HalQueryProcessorTopology(HAL_PROCESSOR_TOPOLOGY* out) { if (!out) return NTLSTATUS_INVALID_PARAMETER; @@ -863,6 +995,7 @@ HAL_ENTRY KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) { HalDbgPrint("HAL: LAPIC=%016llx IOAPIC=%016llx HPET=%016llx\n", gLapicPhys, gIoapicPhys, gHpetPhys); + HalApicSetSciIrq(gSciIrq); HalApicInitialize(gLapicPhys, gIoapicPhys); HalDbgPrint("HAL: local APIC mode=%s BSP_ID=%u\n", HalApicUsingX2Apic() ? "x2APIC" : "xAPIC", @@ -874,6 +1007,10 @@ HAL_ENTRY KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) { HalRtcInitialize(&gApi.Rtc); HalPciInitialize(&gApi.Pci); gApi.Power.Shutdown = HalShutdown; + gApi.Power.Reset = HalReset; + gApi.Power.QueryFixedButtonIrq = AcpiQueryFixedButtonIrq; + gApi.Power.ArmFixedButton = AcpiArmFixedButton; + gApi.Power.ConsumeFixedButton = AcpiConsumeFixedButton; AcpiDetectIntelPchPmBase(); gInitialised = 1; diff --git a/code/kernel/include/kernel/dm_config.h b/code/kernel/include/kernel/dm_config.h new file mode 100644 index 0000000..b9362a1 --- /dev/null +++ b/code/kernel/include/kernel/dm_config.h @@ -0,0 +1,85 @@ +// Kernel driver configuration and dependency graph contract. + +#pragma once + +#include +#include +#include + +#define DM_MAX_DRIVERS 64u +#define DM_MAX_CLASSES 32u +#define DM_MAX_EDGES 16u +#define DM_ID_MAX 64u +#define DM_IMAGE_MAX 64u +#define DM_CONFIG_MAX_BYTES (64u * 1024u) + +typedef enum DM_START_PHASE { + DmStartBoot, + DmStartRuntime, + DmStartDisabled, +} DM_START_PHASE; + +typedef enum DM_DEPENDENCY_KIND { + DmDependencyClass, + DmDependencyDriver, +} DM_DEPENDENCY_KIND; + +typedef struct DM_DEPENDENCY { + DM_DEPENDENCY_KIND Kind; + char Id[DM_ID_MAX]; +} DM_DEPENDENCY; + +typedef struct DM_EDGES { + DM_DEPENDENCY Items[DM_MAX_EDGES]; + uint32_t Count; +} DM_EDGES; + +typedef enum DM_RUN_STATE { + DmRunWaiting, + DmRunStarted, + DmRunFailed, + DmRunBlocked, + DmRunDisabled, +} DM_RUN_STATE; + +typedef struct DM_DRIVER_CONFIG { + char Id[DM_ID_MAX]; + char Image[DM_IMAGE_MAX]; + char Class[DM_ID_MAX]; + DM_EDGES Requires; + DM_EDGES After; + DM_START_PHASE Phase; + DM_RUN_STATE State; + bool Critical; + uint8_t Present; +} DM_DRIVER_CONFIG; + +typedef struct DM_CLASS_CONFIG { + char Id[DM_ID_MAX]; + DM_EDGES Requires; + DM_EDGES After; + uint8_t Present; +} DM_CLASS_CONFIG; + +typedef struct DM_CONFIG { + DM_DRIVER_CONFIG Drivers[DM_MAX_DRIVERS]; + DM_CLASS_CONFIG Classes[DM_MAX_CLASSES]; + uint32_t DriverCount; + uint32_t ClassCount; + DM_START_PHASE ActivePhase; +} DM_CONFIG; + +// Parses and validates a complete immutable configuration before any driver starts. +// Empty optional values and omitted optional keys have identical semantics. +bool DmConfigParse(DM_CONFIG* config, const char* bytes, size_t size, + char* error, size_t errorCapacity); + +// Returns true once every configured provider in this class has reached a terminal state. +bool DmClassSettled(const DM_CONFIG* config, const char* classId); +bool DmClassAvailable(const DM_CONFIG* config, const char* classId); + +// Tests the current requirements/ordering gates for a single waiting module. +bool DmDriverCanStart(const DM_CONFIG* config, uint32_t index); + +// Returns true once a waiting driver's required dependency cannot become available. +bool DmDriverIsBlocked(const DM_CONFIG* config, uint32_t index); diff --git a/code/kernel/include/kernel/driver_module.h b/code/kernel/include/kernel/driver_module.h new file mode 100644 index 0000000..1a6fbb7 --- /dev/null +++ b/code/kernel/include/kernel/driver_module.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + + +KSTATUS DmStartLoadedDriver(const char* name, uintptr_t imageBase); +KSTATUS DmPrepareShutdown(void); +void DmCommitShutdown(void); diff --git a/code/kernel/include/kernel/hal/hal_api.h b/code/kernel/include/kernel/hal/hal_api.h index 9140466..9688f14 100644 --- a/code/kernel/include/kernel/hal/hal_api.h +++ b/code/kernel/include/kernel/hal/hal_api.h @@ -185,9 +185,14 @@ typedef struct HAL_INTERRUPT_CONTROLLER { } HAL_INTERRUPT_CONTROLLER; typedef struct HAL_POWER { + // ACPI fixed-feature button events. Control-method devices require an AML owner. + KSTATUS (*QueryFixedButtonIrq)(uint8_t* irqOut); + KSTATUS (*ArmFixedButton)(void); + bool (*ConsumeFixedButton)(void); // ACPI S5 soft-off. Disables interrupts, writes SLP_EN to PM1a/b_CNT, // and halts if firmware doesn't respond. Does not return. void (*Shutdown)(void); + void (*Reset)(void); } HAL_POWER; typedef struct HAL_API { @@ -226,7 +231,7 @@ typedef struct HAL_INIT_PARAMS { } HAL_INIT_PARAMS; // Increment whenever the kernel/HAL table ABI changes. -#define HAL_API_VERSION 2u +#define HAL_API_VERSION 4u // Returns the HAL table. Processor interrupt-state operations are valid before HalInitialize(). HAL_ENTRY const HAL_API* HalGetApi(void); diff --git a/code/kernel/include/kernel/hal/hal_apic.h b/code/kernel/include/kernel/hal/hal_apic.h index 418503b..70b5548 100644 --- a/code/kernel/include/kernel/hal/hal_apic.h +++ b/code/kernel/include/kernel/hal/hal_apic.h @@ -7,6 +7,10 @@ #include void HalApicRouteIrq(uint8_t irq); +void HalApicSetGsiBase(uint32_t gsiBase); +void HalApicSetIsaOverride(uint8_t irq, uint32_t gsi, uint16_t flags); +void HalApicSetSciIrq(uint16_t irq); +bool HalApicCanRouteIrq(uint8_t irq); void HalApicMaskIrq(uint8_t irq); void HalApicUnmaskIrq(uint8_t irq); void HalApicSendEoi(void); diff --git a/code/kernel/include/kernel/io_ddk.h b/code/kernel/include/kernel/io_ddk.h index 398de0f..c382207 100644 --- a/code/kernel/include/kernel/io_ddk.h +++ b/code/kernel/include/kernel/io_ddk.h @@ -1,4 +1,4 @@ -// Kernel DDK-table provider declarations. +// Kernel driver service initialization. #pragma once #include @@ -8,8 +8,5 @@ struct HAL_API; typedef struct HAL_API HAL_API; -// Initializes the kernel-owned DDK table from platform services. -void IoInitializeDdk(const HAL_API* hal, uintptr_t physicalWindowBase); -// Returns the kernel-owned DDK table passed to driver entrypoints. -const NTL_DDK* IoGetDdk(void); +void IoInitializeDdk(const HAL_API* hal, uintptr_t physicalWindowBase); diff --git a/code/kernel/include/kernel/io_dma.h b/code/kernel/include/kernel/io_dma.h index 4556a5b..a9dc066 100644 --- a/code/kernel/include/kernel/io_dma.h +++ b/code/kernel/include/kernel/io_dma.h @@ -4,6 +4,85 @@ #include +typedef struct NTL_DDK_DMA { + // Returns the one adapter and requirement set owned by a device object. + KSTATUS (*GetAdapter)(struct DEVICE_OBJECT* device, + const IO_DMA_REQUIREMENTS* requirements, + IO_DMA_ADAPTER** outAdapter); + + // Releases one caller-owned adapter reference. + void (*PutAdapter)(IO_DMA_ADAPTER* adapter); + + // Allocates coherent memory with stable CPU and base device addresses. The + // caller must quiesce every device reference before releasing the buffer. + KSTATUS (*AllocateCommonBuffer)(IO_DMA_ADAPTER* adapter, + size_t size, + size_t alignment, + IO_DMA_BUFFER** outBuffer); + + // Releases a coherent common buffer. + void (*FreeCommonBuffer)(IO_DMA_BUFFER* buffer); + + // Returns the stable CPU address of a common buffer. + void* (*GetCommonBufferAddress)(const IO_DMA_BUFFER* buffer); + + // Returns the stable device address of a common buffer. + uint64_t (*GetCommonBufferDeviceAddress)(const IO_DMA_BUFFER* buffer); + + // Returns the caller-requested size of a common buffer. + size_t (*GetCommonBufferSize)(const IO_DMA_BUFFER* buffer); + + // Stabilizes a kernel range for one or more streaming mappings. Pinning + // preserves the pages, but does not transfer ownership to a device. + KSTATUS (*PinKernelMemory)(void* address, + size_t size, + IO_DMA_DIRECTION direction, + MM_PINNED_MEMORY** outMemory); + + // Stabilizes a process-owned user range for one or more mappings. The + // descriptor retains the process and range until the final unpin. + KSTATUS (*PinUserMemory)(struct PROCESS* process, + void* address, + size_t size, + IO_DMA_DIRECTION direction, + MM_PINNED_MEMORY** outMemory); + + // Releases one pinned-memory reference after all mappings leave. + void (*UnpinMemory)(MM_PINNED_MEMORY* memory); + + // Maps a pinned subrange and creates a constrained segment list. A new + // mapping begins CPU-owned and requires external operation serialization. + KSTATUS (*MapPinnedMemory)(IO_DMA_ADAPTER* adapter, + MM_PINNED_MEMORY* memory, + size_t offset, + size_t length, + IO_DMA_DIRECTION direction, + uint32_t flags, + IO_DMA_MAPPING** outMapping); + + // Releases a mapping and performs a final receive-side bounce copy only + // when ownership was transferred to the device and not returned to the CPU. + void (*UnmapMemory)(IO_DMA_MAPPING* mapping); + + // Transfers mapping ownership from the CPU to the device. Streaming DMA + // must not begin before this operation succeeds. + KSTATUS (*SynchronizeForDevice)(IO_DMA_MAPPING* mapping); + + // Transfers mapping ownership from the device to the CPU. CPU readers must + // not consume receive data before this operation succeeds. + KSTATUS (*SynchronizeForCpu)(IO_DMA_MAPPING* mapping); + + // Returns the immutable segment count owned by a mapping. + uint32_t (*GetSegmentCount)(const IO_DMA_MAPPING* mapping); + + // Returns the immutable segment array owned by a mapping. + const IO_DMA_SEGMENT* (*GetSegments)(const IO_DMA_MAPPING* mapping); + + // Copies current DMA counters into caller-owned storage. + void (*QueryStats)(IO_DMA_STATS* outStats); +} NTL_DDK_DMA; + + struct DEVICE_OBJECT; // Returns the kernel-owned DDK DMA table. diff --git a/code/kernel/include/kernel/io_pnp.h b/code/kernel/include/kernel/io_pnp.h new file mode 100644 index 0000000..c9848b5 --- /dev/null +++ b/code/kernel/include/kernel/io_pnp.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +struct DEVICE_OBJECT; +struct NTL_MODULE; +struct DRIVER_OBJECT; + +KSTATUS IoPnpInitialize(void); +KSTATUS IoPnpPublishModule(struct NTL_MODULE* module, + const NTL_DEVICE_DESC* descriptors, uint32_t count, + struct DRIVER_OBJECT* const* ioDrivers, uint32_t ioCount); +KSTATUS IoPnpPhysicalDeviceArrived(struct DEVICE_OBJECT* pdo); +void IoPnpPhysicalDevicePublished(struct DEVICE_OBJECT* pdo); +void IoPnpPhysicalDeviceRemoved(struct DEVICE_OBJECT* pdo); +KSTATUS IoPnpWaitForIdle(uint64_t timeoutMs); +KSTATUS IoPnpShutdown(uint64_t timeoutMs); diff --git a/code/kernel/include/kernel/ke_audio.h b/code/kernel/include/kernel/ke_audio.h index 447ab16..6f5b0e9 100644 --- a/code/kernel/include/kernel/ke_audio.h +++ b/code/kernel/include/kernel/ke_audio.h @@ -5,6 +5,34 @@ #include #include #include + +typedef struct NTL_DDK_AUDIO { + // Registers one endpoint and returns a provider-owned reference valid until unregister returns. + KSTATUS (*RegisterEndpoint)(const IO_AUDIO_ENDPOINT_REGISTRATION* registration, + IO_AUDIO_ENDPOINT** outEndpoint); + + // Stops the endpoint, removes it from enumeration, and consumes the provider-owned reference. + void (*UnregisterEndpoint)(IO_AUDIO_ENDPOINT* endpoint, IO_AUDIO_REMOVAL_MODE mode); + + // Reports one completed hardware period from ISR/DPC completion processing. + // presentedFrames, hardwarePositionFrames, and interruptCount must be monotonic for the + // endpoint lifetime. timestampTicks uses the DDK monotonic microsecond source. + void (*NotifyPeriod)(IO_AUDIO_ENDPOINT* endpoint, + uint32_t completedPeriodIndex, + uint64_t presentedFrames, + uint64_t hardwarePositionFrames, + uint64_t interruptCount, + uint64_t timestampTicks); + + // Updates jack-derived endpoint availability without changing endpoint identity. + void (*SetEndpointAvailable)(IO_AUDIO_ENDPOINT* endpoint, bool available); + + // Fails an endpoint and wakes every stream owner. + void (*ReportEndpointFailure)(IO_AUDIO_ENDPOINT* endpoint, + KSTATUS status, + uint32_t flags); +} NTL_DDK_AUDIO; + #include #include diff --git a/code/kernel/include/kernel/ke_named_queue.h b/code/kernel/include/kernel/ke_named_queue.h new file mode 100644 index 0000000..90982b0 --- /dev/null +++ b/code/kernel/include/kernel/ke_named_queue.h @@ -0,0 +1,12 @@ +// Named queue registry hooks shared by process handle cleanup and syscalls. + +#pragma once + +#include +#include + +struct PROCESS; +struct OBJECT; + +// Removes registry entries owned by the given process handle/object pair. +void KeNamedQueueUnpublishHandle(struct PROCESS* process, SYS_HANDLE handle, struct OBJECT* object); diff --git a/code/kernel/include/kernel/ke_power.h b/code/kernel/include/kernel/ke_power.h new file mode 100644 index 0000000..c49da69 --- /dev/null +++ b/code/kernel/include/kernel/ke_power.h @@ -0,0 +1,18 @@ +#pragma once +#include +#include + +struct PROCESS; +struct EVENT; + +struct HAL_API; +KSTATUS KePowerInitialize(void); +void KePowerInitializeButton(const struct HAL_API* hal); +void KePowerSetInitProcess(struct PROCESS* init); +KSTATUS KePowerRequest(uint32_t action); +KSTATUS KePowerRequestHardware(uint32_t action); +KSTATUS KePowerGetRequest(struct PROCESS* caller, uint32_t* actionOut); +KSTATUS KePowerComplete(struct PROCESS* caller, uint32_t action); +struct EVENT* KePowerRequestEvent(struct PROCESS* caller); +struct EVENT* KePowerCompletionEvent(void); +uint32_t KePowerCommittedAction(void); diff --git a/code/kernel/include/kernel/ke_selftest.h b/code/kernel/include/kernel/ke_selftest.h index 7fb76fd..02b9c3b 100644 --- a/code/kernel/include/kernel/ke_selftest.h +++ b/code/kernel/include/kernel/ke_selftest.h @@ -1,10 +1,6 @@ -// Kernel ke selftest declarations shared by implementation files and kernel clients. +// Debug-only bootstrap checks and focused kernel diagnostics. #pragma once -// ke_selftest.h - kernel bring-up and regression self-tests. -#include - -void KiRunCoreHardeningSelfTests(void); -void KiRunEarlyKernelSelfTests(void); -void KiRunKernelSelfTests(const HAL_API* hal); +void KiCheckBootState(void); +void KiRunKernelSelfTests(void); diff --git a/code/kernel/include/kernel/ke_smp.h b/code/kernel/include/kernel/ke_smp.h index b1d6a35..2d64c14 100644 --- a/code/kernel/include/kernel/ke_smp.h +++ b/code/kernel/include/kernel/ke_smp.h @@ -19,8 +19,3 @@ KSTATUS KeAdmitApplicationProcessorsToScheduler(void); // KeStartApplicationProcessors calls this after MADT enumeration; it is also // safe to call again after failed AP startup disables a CPU record. void KeBuildProcessorTopology(void); - -// Run explicit AP-affine kernel workers that exercise the global scheduler, -// reschedule IPIs, yield, and event wait/wake. Normal kernel/user/driver -// threads remain boot-processor-affine unless their affinity is explicitly widened. -KSTATUS KeRunApplicationProcessorSchedulerSelfTests(void); diff --git a/code/kernel/include/kernel/ke_system_service.h b/code/kernel/include/kernel/ke_system_service.h index bcac900..bbd4aa2 100644 --- a/code/kernel/include/kernel/ke_system_service.h +++ b/code/kernel/include/kernel/ke_system_service.h @@ -4,7 +4,7 @@ #include #include -#include +#include struct PORT; @@ -20,5 +20,9 @@ KSTATUS KeSystemServiceReference(SYS_SERVICE_ID serviceId, struct PORT** listene // Removes every service entry that refers to a listener being destroyed. void KeSystemServiceUnregisterListener(struct PORT* listener); -// Returns the security-context claim required to register one service identity. -SYS_SERVICE_CLAIMS KeSystemServiceRequiredClaim(SYS_SERVICE_ID serviceId); +// Creates one kernel-issued, typed authority for a numeric registry identity. +// Caller owns the returned object reference and may issue attenuated handles. +KSTATUS KeSystemServiceCreateRegistrationAuthority(SYS_SERVICE_ID serviceId, OBJECT** authorityOut); + +// Reads the bound identity from a referenced, type-checked authority. +KSTATUS KeSystemServiceRegistrationIdentity(const OBJECT* authority, SYS_SERVICE_ID* serviceIdOut); diff --git a/code/kernel/include/kernel/ke_userenv.h b/code/kernel/include/kernel/ke_userenv.h index c1854ed..ebd19f5 100644 --- a/code/kernel/include/kernel/ke_userenv.h +++ b/code/kernel/include/kernel/ke_userenv.h @@ -11,25 +11,31 @@ #define SYS_PEB_MAGIC 0x30424550u // "PEB0" #define SYS_TEB_MAGIC 0x30424554u // "TEB0" -#define SYS_ENV_VERSION 4u +#define SYS_ENV_VERSION 5u #ifndef SYS_USER_ENV_TYPES_DEFINED #define SYS_USER_ENV_TYPES_DEFINED 1 -#define SYS_STARTUP_CAP_SYSTEM_VOLUME 1u -#define SYS_STARTUP_CAP_SERVICE_CONTROL 2u -#define SYS_STARTUP_CAP_SYSTEM_VOLUME_NAMESPACE 3u -#define SYS_STARTUP_CAP_VOLUME_MANAGER 4u -#define SYS_STARTUP_CAP_INPUT_MANAGER 5u -#define SYS_STARTUP_CAP_DISPLAY_MANAGER 6u -#define SYS_STARTUP_CAP_SESSION_CONTROL 7u #define SYS_STARTUP_CAPABILITY_MAX 32u typedef struct SYS_STARTUP_CAPABILITY { - uint32_t Tag; + char Name[SYS_STARTUP_CAPABILITY_NAME_MAX + 1u]; SYS_HANDLE Handle; } SYS_STARTUP_CAPABILITY; +// Kernel-side validation of canonical process startup capability names. +static inline int SysStartupCapabilityNameValid(const char name[SYS_STARTUP_CAPABILITY_NAME_MAX + 1u]) { + if (!name || !name[0]) return 0; + int terminated = 0; + for (uint32_t i = 0; i <= SYS_STARTUP_CAPABILITY_NAME_MAX; ++i) { + char c = name[i]; + if (!c) { terminated = 1; continue; } + if (terminated || !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.')) return 0; + } + return terminated; +} + typedef struct SYS_PEB { uint32_t Magic; uint32_t Version; diff --git a/code/kernel/include/kernel/ldr_image.h b/code/kernel/include/kernel/ldr_image.h index 4504124..6bbebe9 100644 --- a/code/kernel/include/kernel/ldr_image.h +++ b/code/kernel/include/kernel/ldr_image.h @@ -20,6 +20,9 @@ KSTATUS LdrLoadImageFileForProcess(struct PROCESS* process, struct FILE_OBJECT* size_t LdrGetImageSize(uintptr_t base); size_t LdrGetMappedImageSize(struct PROCESS* process, uintptr_t base); +// Look up a named export in one already-mapped image. +void* LdrGetImageExport(uintptr_t imageBase, const char* functionName); + // Look up a named export in a loaded module. // 'moduleName' must match a registered kernel module name such as "hal.dll". void* LdrGetExport(const char* moduleName, const char* functionName); @@ -40,4 +43,5 @@ KSTATUS LdrProtectImageForProcess(struct PROCESS* process, uintptr_t imageBase, struct BOOT_INFO; // forward declaration KSTATUS LdrRegisterModulesFromBootInfo(const struct BOOT_INFO* bootInfo); KSTATUS LdrRegisterModule(const char* name, uintptr_t base); +KSTATUS LdrSetModuleExportsAvailable(const char* name, bool available); const char* LdrFindKernelModuleByAddress(uintptr_t address, uintptr_t* baseOut, size_t* sizeOut); diff --git a/code/kernel/include/kernel/mm_file_cache.h b/code/kernel/include/kernel/mm_file_cache.h index 2fdeadc..4dbd62c 100644 --- a/code/kernel/include/kernel/mm_file_cache.h +++ b/code/kernel/include/kernel/mm_file_cache.h @@ -72,6 +72,7 @@ KSTATUS MmFileCacheWrite(FILE_OBJECT* file, uint64_t offset, const void* buf, // Flushes dirty cached pages for one file. KSTATUS MmFileCacheFlushFile(FILE_OBJECT* file); +KSTATUS MmFileCacheFlushAll(void); // Drops cached clean pages overlapping the byte range. Dirty pages are flushed // by callers before destructive metadata operations. diff --git a/code/kernel/include/kernel/se_security.h b/code/kernel/include/kernel/se_security.h index 60f6246..adb847d 100644 --- a/code/kernel/include/kernel/se_security.h +++ b/code/kernel/include/kernel/se_security.h @@ -13,8 +13,6 @@ typedef struct SECURITY_CONTEXT { uint32_t SecurityId; SYS_PRIVILEGES EffectivePrivileges; SYS_PRIVILEGES DelegablePrivileges; - SYS_SERVICE_CLAIMS ServiceClaims; - SYS_SERVICE_CLAIMS DelegableServiceClaims; uint32_t Flags; } SECURITY_CONTEXT; @@ -24,19 +22,14 @@ KSTATUS SeInitializeSecurity(void); // Returns the bootstrap root context. The returned pointer is owned by the security subsystem. SECURITY_CONTEXT* SeGetRootSecurityContext(void); -// Derives one immutable child context without allowing privilege or service-claim amplification. +// Derives one immutable child context without allowing ambient privilege amplification. KSTATUS SeDeriveSecurityContext(SECURITY_CONTEXT* source, SYS_PRIVILEGES effectivePrivileges, SYS_PRIVILEGES delegablePrivileges, - SYS_SERVICE_CLAIMS serviceClaims, - SYS_SERVICE_CLAIMS delegableServiceClaims, SECURITY_CONTEXT** contextOut); // Returns whether one process currently carries all requested ambient privileges. bool SeProcessHasPrivilege(const struct PROCESS* process, SYS_PRIVILEGES privileges); -// Returns whether one security context carries all requested service claims. -bool SeContextHasServiceClaims(const SECURITY_CONTEXT* context, SYS_SERVICE_CLAIMS claims); - // Copies one immutable security context into the public query layout. void SeQuerySecurityContext(const SECURITY_CONTEXT* context, SYS_SECURITY_CONTEXT_INFO* infoOut); diff --git a/code/kernel/include/kernel/syscall_nr.h b/code/kernel/include/kernel/syscall_nr.h index d74a317..4585e23 100644 --- a/code/kernel/include/kernel/syscall_nr.h +++ b/code/kernel/include/kernel/syscall_nr.h @@ -193,4 +193,9 @@ #define SYS_GET_SOCKET_OPTION 160 #define SYS_POLL 161 -#define SYS_MAX 162 +#define SYS_OPEN_POWER_EVENT 162 +#define SYS_REQUEST_SYSTEM_POWER 163 +#define SYS_QUERY_SYSTEM_POWER 164 +#define SYS_COMPLETE_SYSTEM_POWER 165 + +#define SYS_MAX 166 diff --git a/code/kernel/io_ddk.c b/code/kernel/io_ddk.c index 852d3a7..33f7667 100644 --- a/code/kernel/io_ddk.c +++ b/code/kernel/io_ddk.c @@ -1,135 +1,142 @@ -// Kernel DDK-table provider implementation. - -#include #include #include #include #include #include -#include #include -#include #include -static const HAL_API* gIoDdkHal; -static uintptr_t gIoDdkPhysicalWindowBase; +static const HAL_API* gHal; +static uintptr_t gPhysicalWindowBase; -// Returns the kernel direct-map alias for a physical address. -static void* IoDdkPhysToVirt(uint64_t physicalAddress) { - if (!gIoDdkPhysicalWindowBase || physicalAddress > UINTPTR_MAX - gIoDdkPhysicalWindowBase) - return (void*)0; - return (void*)(gIoDdkPhysicalWindowBase + (uintptr_t)physicalAddress); +void IoInitializeDdk(const HAL_API* hal, uintptr_t physicalWindowBase) { + gHal = hal; + gPhysicalWindowBase = physicalWindowBase; } -// Maps one kernel page through the DDK memory surface. -static KSTATUS IoDdkMapPage(uintptr_t virtualAddress, - uint64_t physicalAddress, - uint64_t pageFlags, - NTL_DDK_MAP_MODE mode) { - MM_PTE_MAP_MODE kernelMode = MmPteMapFailIfPresent; - if (mode == NtlDdkMapReplaceExisting) - kernelMode = MmPteMapReplaceExisting; - return MmPteMapPage(virtualAddress, physicalAddress, pageFlags, kernelMode); +__declspec(dllexport) uint64_t KeQueryTicks(void) { + return gHal && gHal->Timer.QueryTicks ? gHal->Timer.QueryTicks() : 0; } -// Reads one aligned PCI configuration DWORD through the platform HAL. -static uint32_t IoDdkPciRead32(uint16_t segment, - uint8_t bus, - uint8_t device, - uint8_t function, - uint16_t offset) { - if (!gIoDdkHal || !gIoDdkHal->Pci.Read32) - return 0xFFFFFFFFu; - return gIoDdkHal->Pci.Read32(segment, bus, device, function, offset); +__declspec(dllexport) void KeStallExecution(uint32_t microseconds) { + if (gHal && gHal->Timer.Stall) gHal->Timer.Stall(microseconds); } -// Writes one aligned PCI configuration DWORD through the platform HAL. -static void IoDdkPciWrite32(uint16_t segment, - uint8_t bus, - uint8_t device, - uint8_t function, - uint16_t offset, - uint32_t value) { - if (!gIoDdkHal || !gIoDdkHal->Pci.Write32) - return; - gIoDdkHal->Pci.Write32(segment, bus, device, function, offset, value); +__declspec(dllexport) uint32_t KePciReadConfig32(uint16_t segment, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset) { + return gHal && gHal->Pci.Read32 ? gHal->Pci.Read32(segment, bus, device, function, offset) : UINT32_MAX; } -// Allocates one kernel IRQ slot for MSI delivery. -static KSTATUS IoDdkAllocateMsiVector(uint8_t* outIrq) { - return KeIrqAllocateMsiVector(outIrq); +__declspec(dllexport) void KePciWriteConfig32(uint16_t segment, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint32_t value) { + if (gHal && gHal->Pci.Write32) gHal->Pci.Write32(segment, bus, device, function, offset, value); } - -// Composes one platform MSI message for an allocated nt-lite IRQ slot. -static KSTATUS IoDdkComposeMsiMessage(uint8_t irq, NTL_DDK_MSI_MESSAGE* messageOut) { - if (!gIoDdkHal || !messageOut || irq >= 24u) +__declspec(dllexport) KSTATUS KeIrqComposeMsiMessage(uint8_t irq, NTL_DDK_MSI_MESSAGE* out) { + if (!gHal || !out || irq >= 24u || !gHal->InterruptController.ComposeMsi) return NTLSTATUS_INVALID_PARAMETER; - HAL_MSI_MESSAGE message; - KSTATUS status = gIoDdkHal->InterruptController.ComposeMsi((uint8_t)(0x20u + irq), &message); - if (!KSUCCESS(status)) - return status; - - messageOut->Address = message.Address; - messageOut->Data = message.Data; - messageOut->Reserved = 0; + KSTATUS status = gHal->InterruptController.ComposeMsi((uint8_t)(0x20u + irq), &message); + if (!KSUCCESS(status)) return status; + out->Address = message.Address; + out->Data = message.Data; + out->Reserved = 0; return NTLSTATUS_SUCCESS; } -static const NTL_DDK_DEBUG gIoDdkDebug = { - .Print = DbgPrint, - .GetFlags = KeGetDebugFlags, -}; - -static const NTL_DDK_INTERRUPT gIoDdkInterrupt = { - .AllocateMsiVector = IoDdkAllocateMsiVector, - .ComposeMsiMessage = IoDdkComposeMsiMessage, -}; - -static const NTL_DDK_MEMORY gIoDdkMemory = { - .PhysToVirt = IoDdkPhysToVirt, - .MapPage = IoDdkMapPage, -}; - -static const NTL_DDK_PLATFORM gIoDdkPlatform = { - .PciRead32 = IoDdkPciRead32, - .PciWrite32 = IoDdkPciWrite32, -}; - -static const NTL_DDK_SCHEDULER gIoDdkScheduler = { - .DelayThread = KeDelayThread, - .Yield = KeYield, -}; - -static NTL_DDK_TIMER gIoDdkTimer = { - .QueryTicks = 0, - .Stall = 0, -}; - -static NTL_DDK gIoDdk = { - .Version = NTL_DDK_VERSION, - .Debug = &gIoDdkDebug, - .Audio = (const NTL_DDK_AUDIO*)0, - .Dma = (const NTL_DDK_DMA*)0, - .Interrupt = &gIoDdkInterrupt, - .Memory = &gIoDdkMemory, - .Platform = &gIoDdkPlatform, - .Scheduler = &gIoDdkScheduler, - .Timer = &gIoDdkTimer, -}; - -// Initializes the kernel-owned DDK table from platform services. -void IoInitializeDdk(const HAL_API* hal, uintptr_t physicalWindowBase) { - gIoDdkHal = hal; - gIoDdkPhysicalWindowBase = physicalWindowBase; - gIoDdk.Audio = KeAudioGetDdk(); - gIoDdk.Dma = IoGetDmaDdk(); - gIoDdkTimer.QueryTicks = hal ? hal->Timer.QueryTicks : 0; - gIoDdkTimer.Stall = hal ? hal->Timer.Stall : 0; +__declspec(dllexport) void* MmDdkPhysToVirt(uint64_t physicalAddress) { + if (!gPhysicalWindowBase || physicalAddress > UINTPTR_MAX - gPhysicalWindowBase) + return (void*)0; + return (void*)(gPhysicalWindowBase + (uintptr_t)physicalAddress); } -// Returns the kernel-owned DDK table passed to driver entrypoints. -const NTL_DDK* IoGetDdk(void) { - return &gIoDdk; +__declspec(dllexport) KSTATUS MmDdkMapPage(uintptr_t virtualAddress, uint64_t physicalAddress, uint64_t flags, NTL_DDK_MAP_MODE mode) { + return MmPteMapPage(virtualAddress, physicalAddress, flags, + mode == NtlDdkMapReplaceExisting ? MmPteMapReplaceExisting : MmPteMapFailIfPresent); +} + +__declspec(dllexport) KSTATUS IoDmaGetAdapter(struct DEVICE_OBJECT* device, const IO_DMA_REQUIREMENTS* requirements, IO_DMA_ADAPTER** outAdapter) { + return IoGetDmaDdk()->GetAdapter(device, requirements, outAdapter); +} + +__declspec(dllexport) void IoDmaPutAdapter(IO_DMA_ADAPTER* adapter) { + IoGetDmaDdk()->PutAdapter(adapter); +} + +__declspec(dllexport) KSTATUS IoDmaAllocateCommonBuffer(IO_DMA_ADAPTER* adapter, size_t size, size_t alignment, IO_DMA_BUFFER** outBuffer) { + return IoGetDmaDdk()->AllocateCommonBuffer(adapter, size, alignment, outBuffer); +} + +__declspec(dllexport) void IoDmaFreeCommonBuffer(IO_DMA_BUFFER* buffer) { + IoGetDmaDdk()->FreeCommonBuffer(buffer); +} + +__declspec(dllexport) void* IoDmaGetCommonBufferAddress(const IO_DMA_BUFFER* buffer) { + return IoGetDmaDdk()->GetCommonBufferAddress(buffer); +} + +__declspec(dllexport) uint64_t IoDmaGetCommonBufferDeviceAddress(const IO_DMA_BUFFER* buffer) { + return IoGetDmaDdk()->GetCommonBufferDeviceAddress(buffer); +} + +__declspec(dllexport) size_t IoDmaGetCommonBufferSize(const IO_DMA_BUFFER* buffer) { + return IoGetDmaDdk()->GetCommonBufferSize(buffer); +} + +__declspec(dllexport) KSTATUS IoDmaPinKernelMemory(void* address, size_t size, IO_DMA_DIRECTION direction, MM_PINNED_MEMORY** outMemory) { + return IoGetDmaDdk()->PinKernelMemory(address, size, direction, outMemory); +} + +__declspec(dllexport) KSTATUS IoDmaPinUserMemory(struct PROCESS* process, void* address, size_t size, IO_DMA_DIRECTION direction, MM_PINNED_MEMORY** outMemory) { + return IoGetDmaDdk()->PinUserMemory(process, address, size, direction, outMemory); +} + +__declspec(dllexport) void IoDmaUnpinMemory(MM_PINNED_MEMORY* memory) { + IoGetDmaDdk()->UnpinMemory(memory); +} + +__declspec(dllexport) KSTATUS IoDmaMapPinnedMemory(IO_DMA_ADAPTER* adapter, MM_PINNED_MEMORY* memory, size_t offset, size_t length, IO_DMA_DIRECTION direction, uint32_t flags, IO_DMA_MAPPING** outMapping) { + return IoGetDmaDdk()->MapPinnedMemory(adapter, memory, offset, length, direction, flags, outMapping); +} + +__declspec(dllexport) void IoDmaUnmapMemory(IO_DMA_MAPPING* mapping) { + IoGetDmaDdk()->UnmapMemory(mapping); +} + +__declspec(dllexport) KSTATUS IoDmaSynchronizeForDevice(IO_DMA_MAPPING* mapping) { + return IoGetDmaDdk()->SynchronizeForDevice(mapping); +} + +__declspec(dllexport) KSTATUS IoDmaSynchronizeForCpu(IO_DMA_MAPPING* mapping) { + return IoGetDmaDdk()->SynchronizeForCpu(mapping); +} + +__declspec(dllexport) uint32_t IoDmaGetSegmentCount(const IO_DMA_MAPPING* mapping) { + return IoGetDmaDdk()->GetSegmentCount(mapping); +} + +__declspec(dllexport) const IO_DMA_SEGMENT* IoDmaGetSegments(const IO_DMA_MAPPING* mapping) { + return IoGetDmaDdk()->GetSegments(mapping); +} + +__declspec(dllexport) void IoDmaQueryStats(IO_DMA_STATS* outStats) { + IoGetDmaDdk()->QueryStats(outStats); +} + +__declspec(dllexport) KSTATUS IoAudioRegisterEndpoint(const IO_AUDIO_ENDPOINT_REGISTRATION* registration, IO_AUDIO_ENDPOINT** outEndpoint) { + return KeAudioGetDdk()->RegisterEndpoint(registration, outEndpoint); +} + +__declspec(dllexport) void IoAudioUnregisterEndpoint(IO_AUDIO_ENDPOINT* endpoint, IO_AUDIO_REMOVAL_MODE mode) { + KeAudioGetDdk()->UnregisterEndpoint(endpoint, mode); +} + +__declspec(dllexport) void IoAudioNotifyPeriod(IO_AUDIO_ENDPOINT* endpoint, uint32_t completedPeriodIndex, uint64_t presentedFrames, uint64_t hardwarePositionFrames, uint64_t interruptCount, uint64_t timestampTicks) { + KeAudioGetDdk()->NotifyPeriod(endpoint, completedPeriodIndex, presentedFrames, hardwarePositionFrames, interruptCount, timestampTicks); +} + +__declspec(dllexport) void IoAudioSetEndpointAvailable(IO_AUDIO_ENDPOINT* endpoint, bool available) { + KeAudioGetDdk()->SetEndpointAvailable(endpoint, available); +} + +__declspec(dllexport) void IoAudioReportEndpointFailure(IO_AUDIO_ENDPOINT* endpoint, KSTATUS status, uint32_t flags) { + KeAudioGetDdk()->ReportEndpointFailure(endpoint, status, flags); } diff --git a/code/kernel/io_device.c b/code/kernel/io_device.c index 26995f3..08e9d3f 100644 --- a/code/kernel/io_device.c +++ b/code/kernel/io_device.c @@ -11,72 +11,7 @@ #include #include #include - -#define MAX_DRIVER_OBJECTS 64 -#define MAX_PDO_OBJECTS 128 - -// Implements the sv Or Unnamed operation. -static SYS_STRING_VIEW IoSvOrUnnamed(SYS_STRING_VIEW name) { - return SysSvIsEmpty(name) ? SYS_SV_LITERAL("") : name; -} - -// Implements the bus Type Name operation. -static const char* IoBusTypeName(IO_BUS_TYPE busType) { - switch (busType) { - case IoBusPci: return "pci"; - case IoBusUsb: return "usb"; - default: return "unknown"; - } -} - -// Driver object registry / PDO AddDevice binding. - -static DRIVER_OBJECT* gDriverObjects[MAX_DRIVER_OBJECTS]; -static uint32_t gDriverObjectCount; -static DEVICE_OBJECT* gPhysicalDevices[MAX_PDO_OBJECTS]; -static uint32_t gPhysicalDeviceCount; - -// Implements the driver Matches Pdo operation. -static bool IoDriverMatchesPdo(const DRIVER_OBJECT* driver, const DEVICE_OBJECT* pdo) { - if (!driver || !driver->IdTable || !pdo || pdo->Type != DeviceObjectPhysical) - return false; - - for (size_t d = 0; d < driver->IdTable->Count; d++) { - const IO_DEVICE_ID* want = &driver->IdTable->Ids[d]; - if (want->BusType != IoBusUnknown && want->BusType != pdo->u.Pdo.BusType) - continue; - for (uint8_t p = 0; p < pdo->u.Pdo.IdCount; p++) { - if (SysSvEqual(want->Id, pdo->u.Pdo.Ids[p].Id)) - return true; - } - } - return false; -} - -// Implements the try Add Device For Driver operation. -static bool IoTryAddDeviceForDriver(DRIVER_OBJECT* driver, DEVICE_OBJECT* pdo) { - if (!driver || !pdo || !driver->DriverAddDevice) - return false; - if (!IoDriverMatchesPdo(driver, pdo)) - return false; - - KSTATUS st = driver->DriverAddDevice(driver, pdo); - if (KSUCCESS(st)) { - DbgPrint("io: AddDevice driver=%v pdo=%v bus=%s\n", - IoSvOrUnnamed(driver->Name), IoSvOrUnnamed(pdo->Name), IoBusTypeName(pdo->u.Pdo.BusType)); - - KSTATUS start = IoSendPnpIrpSync(pdo, IRP_MN_START_DEVICE); - if (!KSUCCESS(start) && start != NTLSTATUS_NOT_SUPPORTED) { - DbgPrint("io: START_DEVICE failed driver=%v pdo=%v st=0x%x\n", - IoSvOrUnnamed(driver->Name), IoSvOrUnnamed(pdo->Name), start); - } - return true; - } else { - DbgPrint("io: AddDevice failed driver=%v pdo=%v st=0x%x\n", - IoSvOrUnnamed(driver->Name), IoSvOrUnnamed(pdo->Name), st); - return true; - } -} +#include __declspec(dllexport) void IoInitializeDriverObject(DRIVER_OBJECT* driver, SYS_STRING_VIEW name) { @@ -86,42 +21,12 @@ __declspec(dllexport) void IoInitializeDriverObject(DRIVER_OBJECT* driver, driver->Name = name; } -// Registers driver Object. -__declspec(dllexport) KSTATUS IoRegisterDriverObject(DRIVER_OBJECT* driver) { - if (!driver) return NTLSTATUS_INVALID_PARAMETER; - if (!SysSvIsUtf8Text(driver->Name)) return NTLSTATUS_INVALID_PARAMETER; - if (gDriverObjectCount >= MAX_DRIVER_OBJECTS) - return NTLSTATUS_NO_MEMORY; - - if (driver->Header.Type != ObjectTypeDriver || - driver->Header.LifetimeState != ObjectAlive) { - ObInitializeObjectHeader(&driver->Header, ObjectTypeDriver); - } - - gDriverObjects[gDriverObjectCount++] = driver; - DbgPrint("io: registered driver object %v\n", IoSvOrUnnamed(driver->Name)); - - for (uint32_t i = 0; i < gPhysicalDeviceCount; i++) - (void)IoTryAddDeviceForDriver(driver, gPhysicalDevices[i]); - - return NTLSTATUS_SUCCESS; -} - // Device object lifetime. static void IoDeviceDestroy(OBJECT* object) { DEVICE_OBJECT* dev = (DEVICE_OBJECT*)object; IoDmaDetachDevice(dev); - for (uint32_t i = 0; i < gPhysicalDeviceCount; i++) { - if (gPhysicalDevices[i] == dev) { - gPhysicalDevices[i] = gPhysicalDevices[gPhysicalDeviceCount - 1u]; - gPhysicalDevices[gPhysicalDeviceCount - 1u] = (DEVICE_OBJECT*)0; - gPhysicalDeviceCount--; - break; - } - } - if (dev->LowerDevice) IoDetachDevice(dev); @@ -232,27 +137,11 @@ __declspec(dllexport) KSTATUS IoRegisterPhysicalDevice(DEVICE_OBJECT* pdo) { if (!pdo || pdo->Type != DeviceObjectPhysical) return NTLSTATUS_INVALID_PARAMETER; - for (uint32_t i = 0; i < gPhysicalDeviceCount; i++) { - if (gPhysicalDevices[i] == pdo) - return NTLSTATUS_ALREADY_EXISTS; - } + KSTATUS status = IoPnpPhysicalDeviceArrived(pdo); + if (!KSUCCESS(status)) return status; - if (gPhysicalDeviceCount >= MAX_PDO_OBJECTS) - return NTLSTATUS_NO_MEMORY; + IoPnpPhysicalDevicePublished(pdo); - gPhysicalDevices[gPhysicalDeviceCount++] = pdo; - - bool anyMatched = false; - for (uint32_t i = 0; i < gDriverObjectCount; i++) { - if (IoTryAddDeviceForDriver(gDriverObjects[i], pdo)) - anyMatched = true; - } - - if (!anyMatched && pdo->u.Pdo.BusType == IoBusUsb) { - SYS_STRING_VIEW id = pdo->u.Pdo.IdCount ? pdo->u.Pdo.Ids[0].Id : SYS_SV_LITERAL(""); - DbgPrint("io: no function driver for pdo=%v id=%v bus=%s\n", - IoSvOrUnnamed(pdo->Name), id, IoBusTypeName(pdo->u.Pdo.BusType)); - } return NTLSTATUS_SUCCESS; } @@ -280,24 +169,6 @@ __declspec(dllexport) void IoDereferenceDeviceObject(DEVICE_OBJECT* device) { if (device) ObDereferenceObject(&device->Header); } -// Releases physical Device Registry Entry. -static void IoRemovePhysicalDeviceRegistryEntry(DEVICE_OBJECT* device) { - if (!device || device->Type != DeviceObjectPhysical) - return; - - for (uint32_t i = 0; i < gPhysicalDeviceCount; i++) { - if (gPhysicalDevices[i] == device) { - for (uint32_t j = i + 1; j < gPhysicalDeviceCount; j++) - gPhysicalDevices[j - 1] = gPhysicalDevices[j]; - gPhysicalDeviceCount--; - if (gPhysicalDeviceCount < MAX_PDO_OBJECTS) - gPhysicalDevices[gPhysicalDeviceCount] = (DEVICE_OBJECT*)0; - return; - } - } -} - -// Implements the unlink Device From Parent operation. static void IoUnlinkDeviceFromParent(DEVICE_OBJECT* device) { if (!device || !device->Parent) return; @@ -333,9 +204,11 @@ __declspec(dllexport) void IoDeleteDevice(DEVICE_OBJECT* device) { device->PnpState = DevicePnpDeleted; KeReleaseSpinlock(&device->Lock, flags); - IoDmaDetachDevice(device); + if (device->Type == DeviceObjectPhysical) + IoPnpPhysicalDeviceRemoved(device); + else + IoDmaDetachDevice(device); IoUnlinkDeviceFromParent(device); - IoRemovePhysicalDeviceRegistryEntry(device); IoDereferenceDeviceObject(device); } diff --git a/code/kernel/io_pnp.c b/code/kernel/io_pnp.c new file mode 100644 index 0000000..02b85af --- /dev/null +++ b/code/kernel/io_pnp.c @@ -0,0 +1,594 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PNP_MAX_DESCRIPTORS 64u +#define PNP_MAX_DEVICES 128u +#define PNP_WORKERS 2u + +typedef enum PNP_BIND_STATE { + PnpUnbound, + PnpReserved, + PnpStarting, + PnpPending, + PnpCancelling, + PnpStarted, + PnpFailed, + PnpCleaning, + PnpStopping, +} PNP_BIND_STATE; + +typedef struct PNP_DRIVER { + const NTL_DEVICE_DESC* Descriptor; + DRIVER_OBJECT* IoDriver; + struct NTL_MODULE* Module; +} PNP_DRIVER; + +typedef struct PNP_NODE { + DEVICE_OBJECT* Pdo; + DEVICE_OBJECT* ParentPdo; + NTL_DEVICE* Binding; + const PNP_DRIVER* Driver; + PNP_BIND_STATE State; + bool Removed; + bool Published; + bool CancelIssued; +} PNP_NODE; + +struct NTL_DEVICE_RESOURCE { + NTL_DEVICE_RESOURCE* Next; + NTL_DEVICE* Owner; + NTL_DEVICE_RESOURCE_RELEASE Release; + void* Context; +}; + +struct NTL_DEVICE { + SPINLOCK Lock; + PNP_NODE* Node; + void* Context; + NTL_DEVICE_RESOURCE* Resources; + KSTATUS CompletionStatus; + bool CompletionSet; + bool Closing; +}; + +static SPINLOCK gPnpLock = SPINLOCK_INIT; +static PNP_DRIVER gPnpDrivers[PNP_MAX_DESCRIPTORS]; +static uint32_t gPnpDriverCount; +static PNP_NODE* gPnpNodes[PNP_MAX_DEVICES]; +static uint32_t gPnpNodeCount; +static EVENT* gPnpReady; +static EVENT* gPnpChanged; +static bool gPnpStarted; +static bool gPnpShutdown; +static KWORK_ITEM gPnpWakeItem; + +static void IoPnpSignal(void* unused) { + (void)unused; + for (uint32_t i = 0; i < PNP_WORKERS; ++i) + KeSignalEvent(gPnpReady); + KeSignalEvent(gPnpChanged); +} + +static void IoPnpWake(void) { + if (gPnpStarted) + (void)KeQueueWorkItem(&gPnpWakeItem); +} + +static bool IoPnpMatches(const NTL_DEVICE_DESC* descriptor, const DEVICE_OBJECT* pdo) { + for (uint32_t i = 0; i < descriptor->MatchCount; ++i) { + const NTL_DEVICE_MATCH* match = &descriptor->Matches[i]; + if ((uint32_t)match->Bus != (uint32_t)pdo->u.Pdo.BusType) + continue; + size_t length = strlen(match->Id); + for (uint8_t j = 0; j < pdo->u.Pdo.IdCount; ++j) { + SYS_STRING_VIEW id = pdo->u.Pdo.Ids[j].Id; + if (id.Length == length && !memcmp(id.Data, match->Id, length)) + return true; + } + } + return false; +} + +static bool IoPnpMatchesIoDriver(const DRIVER_OBJECT* driver, const DEVICE_OBJECT* pdo) { + if (!driver->IdTable || !driver->DriverAddDevice) return false; + for (size_t i = 0; i < driver->IdTable->Count; ++i) { + const IO_DEVICE_ID* wanted = &driver->IdTable->Ids[i]; + if (wanted->BusType != IoBusUnknown && wanted->BusType != pdo->u.Pdo.BusType) + continue; + for (uint8_t j = 0; j < pdo->u.Pdo.IdCount; ++j) + if (SysSvEqual(wanted->Id, pdo->u.Pdo.Ids[j].Id)) return true; + } + return false; +} + +static const PNP_DRIVER* IoPnpFindDriver(const DEVICE_OBJECT* pdo) { + for (uint32_t i = 0; i < gPnpDriverCount; ++i) { + if ((gPnpDrivers[i].Descriptor && IoPnpMatches(gPnpDrivers[i].Descriptor, pdo)) || + (gPnpDrivers[i].IoDriver && IoPnpMatchesIoDriver(gPnpDrivers[i].IoDriver, pdo))) + return &gPnpDrivers[i]; + } + return (const PNP_DRIVER*)0; +} + +static PNP_NODE* IoPnpFindNode(const DEVICE_OBJECT* pdo) { + for (uint32_t i = 0; i < gPnpNodeCount; ++i) + if (gPnpNodes[i]->Pdo == pdo) + return gPnpNodes[i]; + return (PNP_NODE*)0; +} + +static DEVICE_OBJECT* IoPnpParentPdo(const DEVICE_OBJECT* pdo) { + const DEVICE_OBJECT* parent = pdo->Parent; + for (uint32_t i = 0; parent && i < 64u; ++i) { + if (parent->Type == DeviceObjectPhysical) + return (DEVICE_OBJECT*)parent; + parent = parent->LowerDevice ? parent->LowerDevice : parent->Parent; + } + return (DEVICE_OBJECT*)0; +} + +static bool IoPnpHasChildren(const DEVICE_OBJECT* parent) { + for (uint32_t i = 0; i < gPnpNodeCount; ++i) { + const DEVICE_OBJECT* ancestor = gPnpNodes[i]->ParentPdo; + for (uint32_t depth = 0; ancestor && depth < PNP_MAX_DEVICES; ++depth) { + if (ancestor == parent) return true; + const PNP_NODE* node = IoPnpFindNode(ancestor); + ancestor = node ? node->ParentPdo : (const DEVICE_OBJECT*)0; + } + } + return false; +} + +static void IoPnpReleaseResources(NTL_DEVICE* device) { + uintptr_t flags = KeAcquireSpinlock(&device->Lock); + device->Closing = true; + NTL_DEVICE_RESOURCE* resource = device->Resources; + device->Resources = (NTL_DEVICE_RESOURCE*)0; + KeReleaseSpinlock(&device->Lock, flags); + + while (resource) { + NTL_DEVICE_RESOURCE* next = resource->Next; + resource->Owner = (NTL_DEVICE*)0; + resource->Release(resource->Context); + MmFreePool(resource); + resource = next; + } +} + +static void IoPnpDispose(PNP_NODE* node) { + NTL_DEVICE* device = node->Binding; + if (device) { + if (node->State == PnpStopping) { + if (node->Driver->Descriptor && node->Driver->Descriptor->Stop) + node->Driver->Descriptor->Stop(device); + else if (node->Driver->IoDriver && !node->Pdo->DeletePending) + (void)IoSendPnpIrpSync(node->Pdo, IRP_MN_REMOVE_DEVICE); + } + IoPnpReleaseResources(device); + MmFreePool(device); + } + + uintptr_t flags = KeAcquireSpinlock(&gPnpLock); + for (uint32_t i = 0; i < gPnpNodeCount; ++i) { + if (gPnpNodes[i] == node) { + gPnpNodes[i] = gPnpNodes[--gPnpNodeCount]; + gPnpNodes[gPnpNodeCount] = (PNP_NODE*)0; + break; + } + } + KeReleaseSpinlock(&gPnpLock, flags); + IoDereferenceDeviceObject(node->Pdo); + MmFreePool(node); + IoPnpWake(); +} + +static void IoPnpStart(PNP_NODE* node) { + const NTL_DEVICE_DESC* desc = node->Driver->Descriptor; + size_t contextSize = desc ? desc->ContextSize : 0; + NTL_DEVICE* device = (NTL_DEVICE*)MmAllocatePool(sizeof(*device) + contextSize); + if (!device) { + uintptr_t flags = KeAcquireSpinlock(&gPnpLock); + node->State = PnpFailed; + KeReleaseSpinlock(&gPnpLock, flags); + IoPnpWake(); + return; + } + memset(device, 0, sizeof(*device) + contextSize); + device->Lock = (SPINLOCK)SPINLOCK_INIT; + device->Node = node; + device->Context = (void*)(device + 1); + + uintptr_t flags = KeAcquireSpinlock(&gPnpLock); + node->Binding = device; + node->State = PnpStarting; + KeReleaseSpinlock(&gPnpLock, flags); + + KSTATUS result; + if (desc) { + result = desc->Start(device); + } else { + DRIVER_OBJECT* io = node->Driver->IoDriver; + result = io->DriverAddDevice(io, node->Pdo); + if (KSUCCESS(result)) { + result = IoSendPnpIrpSync(node->Pdo, IRP_MN_START_DEVICE); + if (result == NTLSTATUS_NOT_SUPPORTED) result = NTLSTATUS_SUCCESS; + } + } + + flags = KeAcquireSpinlock(&gPnpLock); + if (device->CompletionSet) + result = device->CompletionStatus; + node->State = result == NTLSTATUS_PENDING ? PnpPending : + KSUCCESS(result) ? PnpStarted : PnpFailed; + KeReleaseSpinlock(&gPnpLock, flags); + if (KFAILED(result)) + DbgPrint("pnp: start failed (0x%x)\n", result); + IoPnpWake(); +} + +static void IoPnpWorker(void* ignored) { + (void)ignored; + for (;;) { + (void)KeWaitForEvent(gPnpReady, false); + for (;;) { + PNP_NODE* node = (PNP_NODE*)0; + bool stopping = false; + bool cleanup = false; + bool cancel = false; + uintptr_t flags = KeAcquireSpinlock(&gPnpLock); + for (uint32_t i = 0; i < gPnpNodeCount; ++i) { + PNP_NODE* candidate = gPnpNodes[i]; + if (candidate->State == PnpPending && candidate->Removed && + !candidate->CancelIssued && (candidate->Driver->Descriptor && candidate->Driver->Descriptor->CancelStart)) { + candidate->CancelIssued = true; + candidate->State = PnpCancelling; + node = candidate; + cancel = true; + break; + } + if (candidate->State == PnpFailed && candidate->Binding && !candidate->Removed) { + candidate->State = PnpCleaning; + node = candidate; + cleanup = true; + break; + } + if (gPnpShutdown && candidate->Removed && + IoPnpHasChildren(candidate->Pdo)) + continue; + if (candidate->Removed && candidate->State != PnpReserved && + candidate->State != PnpStarting && candidate->State != PnpPending && + candidate->State != PnpCancelling && candidate->State != PnpStopping && + candidate->State != PnpCleaning) { + candidate->State = PnpStopping; + node = candidate; + stopping = true; + break; + } + if (candidate->State != PnpUnbound || candidate->Removed || + gPnpShutdown || !candidate->Published) + continue; + const PNP_DRIVER* driver = IoPnpFindDriver(candidate->Pdo); + if (driver) { + candidate->Driver = driver; + candidate->State = PnpReserved; + node = candidate; + break; + } + } + KeReleaseSpinlock(&gPnpLock, flags); + if (!node) break; + KeSignalEvent(gPnpReady); + if (stopping) { + IoPnpDispose(node); + } else if (cancel) { + node->Driver->Descriptor->CancelStart(node->Binding); + uintptr_t update = KeAcquireSpinlock(&gPnpLock); + node->State = node->Binding->CompletionSet ? + (KSUCCESS(node->Binding->CompletionStatus) ? PnpStarted : PnpFailed) : + PnpPending; + KeReleaseSpinlock(&gPnpLock, update); + IoPnpWake(); + } else if (cleanup) { + NTL_DEVICE* device = node->Binding; + if (node->Driver->Descriptor && node->Driver->Descriptor->Stop) + node->Driver->Descriptor->Stop(device); + else if (node->Driver->IoDriver && node->Pdo->AttachedDevice) + (void)IoSendPnpIrpSync(node->Pdo, IRP_MN_REMOVE_DEVICE); + IoPnpReleaseResources(device); + uintptr_t update = KeAcquireSpinlock(&gPnpLock); + node->Binding = (NTL_DEVICE*)0; + node->State = PnpFailed; + KeReleaseSpinlock(&gPnpLock, update); + MmFreePool(device); + IoPnpWake(); + } else { + IoPnpStart(node); + } + } + } +} + +KSTATUS IoPnpInitialize(void) { + if (gPnpStarted) return NTLSTATUS_ALREADY_EXISTS; + KSTATUS st = KeEventCreate(EventAutoReset, false, &gPnpReady); + if (!KSUCCESS(st)) return st; + st = KeEventCreate(EventAutoReset, false, &gPnpChanged); + if (!KSUCCESS(st)) return st; + + for (uint32_t i = 0; i < PNP_WORKERS; ++i) { + const KE_THREAD_CREATE_PARAMETERS params = + KE_THREAD_CREATE_NAMED_NORMAL(PriorityNormal, "pnp.device"); + st = KeCreateKernelThread((uintptr_t)IoPnpWorker, 0, ¶ms, (THREAD**)0); + if (!KSUCCESS(st)) return st; + } + KeInitializeWorkItem(&gPnpWakeItem, IoPnpSignal, (void*)0); + gPnpStarted = true; + IoPnpWake(); + return NTLSTATUS_SUCCESS; +} + +KSTATUS IoPnpPublishModule(struct NTL_MODULE* module, + const NTL_DEVICE_DESC* descriptors, uint32_t count, + DRIVER_OBJECT* const* ioDrivers, uint32_t ioCount) { + if (!gPnpStarted || !module || (count && !descriptors) || (ioCount && !ioDrivers)) + return NTLSTATUS_INVALID_PARAMETER; + uintptr_t flags = KeAcquireSpinlock(&gPnpLock); + if (gPnpShutdown) { + KeReleaseSpinlock(&gPnpLock, flags); + return NTLSTATUS_DEVICE_REMOVED; + } + if (count > PNP_MAX_DESCRIPTORS - gPnpDriverCount || + ioCount > PNP_MAX_DESCRIPTORS - gPnpDriverCount - count) { + KeReleaseSpinlock(&gPnpLock, flags); + return NTLSTATUS_NO_MEMORY; + } + for (uint32_t i = 0; i < count; ++i) { + for (uint32_t j = 0; j < gPnpDriverCount; ++j) { + SYS_STRING_VIEW existing = gPnpDrivers[j].IoDriver ? + gPnpDrivers[j].IoDriver->Name : + SysSvFromParts(gPnpDrivers[j].Descriptor->Name, + strlen(gPnpDrivers[j].Descriptor->Name)); + SYS_STRING_VIEW incoming = SysSvFromParts(descriptors[i].Name, + strlen(descriptors[i].Name)); + if (SysSvEqual(existing, incoming)) { + KeReleaseSpinlock(&gPnpLock, flags); + return NTLSTATUS_ALREADY_EXISTS; + } + } + for (uint32_t j = i + 1; j < count; ++j) { + if (!strcmp(descriptors[i].Name, descriptors[j].Name)) { + KeReleaseSpinlock(&gPnpLock, flags); + return NTLSTATUS_ALREADY_EXISTS; + } + } + } + for (uint32_t i = 0; i < ioCount; ++i) { + DRIVER_OBJECT* driver = ioDrivers[i]; + for (uint32_t j = 0; j < gPnpDriverCount; ++j) { + SYS_STRING_VIEW existing = gPnpDrivers[j].IoDriver ? + gPnpDrivers[j].IoDriver->Name : + SysSvFromParts(gPnpDrivers[j].Descriptor->Name, + strlen(gPnpDrivers[j].Descriptor->Name)); + if (SysSvEqual(driver->Name, existing)) { + KeReleaseSpinlock(&gPnpLock, flags); + return NTLSTATUS_ALREADY_EXISTS; + } + } + for (uint32_t j = i + 1; j < ioCount; ++j) { + if (SysSvEqual(driver->Name, ioDrivers[j]->Name)) { + KeReleaseSpinlock(&gPnpLock, flags); + return NTLSTATUS_ALREADY_EXISTS; + } + } + for (uint32_t j = 0; j < count; ++j) { + if (SysSvEqual(driver->Name, SysSvFromParts(descriptors[j].Name, + strlen(descriptors[j].Name)))) { + KeReleaseSpinlock(&gPnpLock, flags); + return NTLSTATUS_ALREADY_EXISTS; + } + } + } + for (uint32_t i = 0; i < count; ++i) { + gPnpDrivers[gPnpDriverCount].Descriptor = &descriptors[i]; + gPnpDrivers[gPnpDriverCount].IoDriver = (DRIVER_OBJECT*)0; + gPnpDrivers[gPnpDriverCount].Module = module; + ++gPnpDriverCount; + } + for (uint32_t i = 0; i < ioCount; ++i) { + gPnpDrivers[gPnpDriverCount].Descriptor = (const NTL_DEVICE_DESC*)0; + gPnpDrivers[gPnpDriverCount].IoDriver = ioDrivers[i]; + gPnpDrivers[gPnpDriverCount].Module = module; + ++gPnpDriverCount; + } + KeReleaseSpinlock(&gPnpLock, flags); + IoPnpWake(); + return NTLSTATUS_SUCCESS; +} + +KSTATUS IoPnpPhysicalDeviceArrived(DEVICE_OBJECT* pdo) { + if (!pdo || pdo->Type != DeviceObjectPhysical) return NTLSTATUS_INVALID_PARAMETER; + if (!gPnpStarted) return NTLSTATUS_SUCCESS; + PNP_NODE* node = (PNP_NODE*)MmAllocatePool(sizeof(*node)); + if (!node) return NTLSTATUS_NO_MEMORY; + memset(node, 0, sizeof(*node)); + node->Pdo = pdo; + node->ParentPdo = IoPnpParentPdo(pdo); + IoReferenceDeviceObject(pdo); + uintptr_t flags = KeAcquireSpinlock(&gPnpLock); + if (gPnpShutdown) { + KeReleaseSpinlock(&gPnpLock, flags); + IoDereferenceDeviceObject(pdo); + MmFreePool(node); + return NTLSTATUS_DEVICE_REMOVED; + } + for (uint32_t i = 0; i < gPnpNodeCount; ++i) { + if (gPnpNodes[i]->Pdo == pdo) { + KeReleaseSpinlock(&gPnpLock, flags); + IoDereferenceDeviceObject(pdo); + MmFreePool(node); + return NTLSTATUS_ALREADY_EXISTS; + } + } + if (gPnpNodeCount == PNP_MAX_DEVICES) { + KeReleaseSpinlock(&gPnpLock, flags); + IoDereferenceDeviceObject(pdo); + MmFreePool(node); + return NTLSTATUS_NO_MEMORY; + } + gPnpNodes[gPnpNodeCount++] = node; + KeReleaseSpinlock(&gPnpLock, flags); + return NTLSTATUS_SUCCESS; +} + +void IoPnpPhysicalDevicePublished(DEVICE_OBJECT* pdo) { + if (!pdo || !gPnpStarted) return; + uintptr_t flags = KeAcquireSpinlock(&gPnpLock); + for (uint32_t i = 0; i < gPnpNodeCount; ++i) { + if (gPnpNodes[i]->Pdo == pdo) { + gPnpNodes[i]->Published = true; + break; + } + } + KeReleaseSpinlock(&gPnpLock, flags); + IoPnpWake(); +} + +void IoPnpPhysicalDeviceRemoved(DEVICE_OBJECT* pdo) { + if (!pdo || !gPnpStarted) return; + uintptr_t flags = KeAcquireSpinlock(&gPnpLock); + for (uint32_t i = 0; i < gPnpNodeCount; ++i) { + if (gPnpNodes[i]->Pdo == pdo) { + gPnpNodes[i]->Removed = true; + break; + } + } + KeReleaseSpinlock(&gPnpLock, flags); + IoPnpWake(); +} + +KSTATUS IoPnpWaitForIdle(uint64_t timeoutMs) { + if (!gPnpStarted) return NTLSTATUS_INVALID_PARAMETER; + uint64_t deadline = KeMonotonicDeadlineAfterMilliseconds(timeoutMs); + for (;;) { + bool idle = true; + uintptr_t flags = KeAcquireSpinlock(&gPnpLock); + for (uint32_t i = 0; i < gPnpNodeCount; ++i) { + PNP_NODE* node = gPnpNodes[i]; + if (node->State == PnpReserved || node->State == PnpStarting || + node->State == PnpPending || node->State == PnpCancelling || + node->State == PnpCleaning || + node->State == PnpStopping || + (node->State == PnpFailed && node->Binding) || + (node->Removed && node->State != PnpPending) || + (!gPnpShutdown && node->State == PnpUnbound && node->Published && + IoPnpFindDriver(node->Pdo))) { + idle = false; + break; + } + } + KeReleaseSpinlock(&gPnpLock, flags); + if (idle) return NTLSTATUS_SUCCESS; + uint64_t remaining = KeMonotonicMillisecondsUntil(deadline); + if (!remaining) return NTLSTATUS_TIMEOUT; + uint32_t index = 0; + KSTATUS st = KeWaitForMultipleEvents(&gPnpChanged, 1, false, remaining, &index); + if (st == NTLSTATUS_TIMEOUT) return NTLSTATUS_TIMEOUT; + if (!KSUCCESS(st)) return st; + } +} + +KSTATUS IoPnpShutdown(uint64_t timeoutMs) { + if (!gPnpStarted) return NTLSTATUS_INVALID_PARAMETER; + uintptr_t flags = KeAcquireSpinlock(&gPnpLock); + gPnpShutdown = true; + for (uint32_t i = 0; i < gPnpNodeCount; ++i) + gPnpNodes[i]->Removed = true; + KeReleaseSpinlock(&gPnpLock, flags); + IoPnpWake(); + return IoPnpWaitForIdle(timeoutMs); +} + +__declspec(dllexport) void* NtlDeviceContext(NTL_DEVICE* device) { + return device ? device->Context : (void*)0; +} + +__declspec(dllexport) const char* NtlDeviceId(NTL_DEVICE* device, uint32_t index) { + if (!device || index >= device->Node->Pdo->u.Pdo.IdCount) return (const char*)0; + return device->Node->Pdo->u.Pdo.Ids[index].Id.Data; +} + +__declspec(dllexport) NTL_BUS_TYPE NtlDeviceBus(NTL_DEVICE* device) { + return device ? (NTL_BUS_TYPE)device->Node->Pdo->u.Pdo.BusType : (NTL_BUS_TYPE)0; +} + +__declspec(dllexport) KSTATUS NtlDeviceCompleteStart(NTL_DEVICE* device, KSTATUS result) { + if (!device || result == NTLSTATUS_PENDING || !device->Node) return NTLSTATUS_INVALID_PARAMETER; + uintptr_t flags = KeAcquireSpinlock(&gPnpLock); + PNP_NODE* node = device->Node; + if (node->Binding != device || device->CompletionSet || + (node->State != PnpStarting && node->State != PnpPending && + node->State != PnpCancelling)) { + KeReleaseSpinlock(&gPnpLock, flags); + return NTLSTATUS_OBJECT_DEAD; + } + device->CompletionSet = true; + device->CompletionStatus = result; + if (node->State == PnpPending) { + node->State = KSUCCESS(result) ? PnpStarted : PnpFailed; + } + KeReleaseSpinlock(&gPnpLock, flags); + IoPnpWake(); + return NTLSTATUS_SUCCESS; +} + +__declspec(dllexport) KSTATUS NtlDeviceOwnResource(NTL_DEVICE* device, void* context, + NTL_DEVICE_RESOURCE_RELEASE release, + NTL_DEVICE_RESOURCE** out) { + if (!device || !release || !out) return NTLSTATUS_INVALID_PARAMETER; + *out = (NTL_DEVICE_RESOURCE*)0; + NTL_DEVICE_RESOURCE* resource = (NTL_DEVICE_RESOURCE*)MmAllocatePool(sizeof(*resource)); + if (!resource) return NTLSTATUS_NO_MEMORY; + resource->Owner = device; + resource->Context = context; + resource->Release = release; + uintptr_t flags = KeAcquireSpinlock(&device->Lock); + if (device->Closing) { + KeReleaseSpinlock(&device->Lock, flags); + MmFreePool(resource); + return NTLSTATUS_OBJECT_DEAD; + } + resource->Next = device->Resources; + device->Resources = resource; + KeReleaseSpinlock(&device->Lock, flags); + *out = resource; + return NTLSTATUS_SUCCESS; +} + +__declspec(dllexport) KSTATUS NtlDeviceReleaseResource(NTL_DEVICE_RESOURCE* resource) { + if (!resource || !resource->Owner) return NTLSTATUS_INVALID_PARAMETER; + NTL_DEVICE* device = resource->Owner; + uintptr_t flags = KeAcquireSpinlock(&device->Lock); + NTL_DEVICE_RESOURCE** link = &device->Resources; + while (*link && *link != resource) link = &(*link)->Next; + if (*link != resource) { + KeReleaseSpinlock(&device->Lock, flags); + return NTLSTATUS_OBJECT_DEAD; + } + *link = resource->Next; + resource->Owner = (NTL_DEVICE*)0; + KeReleaseSpinlock(&device->Lock, flags); + resource->Release(resource->Context); + MmFreePool(resource); + return NTLSTATUS_SUCCESS; +} diff --git a/code/kernel/ke_debug.c b/code/kernel/ke_debug.c index cc6ed55..af942fb 100644 --- a/code/kernel/ke_debug.c +++ b/code/kernel/ke_debug.c @@ -38,6 +38,10 @@ static size_t gDebugLogSize; static size_t gDebugLogWrite; static size_t gDebugLogUsed; static volatile int32_t gDebugLogFlushing; +static bool gDebugAtLineStart = true; +static bool gDebugClockReady; +static uint64_t gDebugClockOrigin; +static uint64_t gDebugLastTick; // Appends bytes to the in-memory debug log while its lock is held. static void KiDebugLogAppendLocked(const char* text, size_t len) { @@ -185,6 +189,30 @@ uint32_t KeGetDebugFlags(void) { return gDebugFlags; } +// Prefixes each logical debug line with elapsed monotonic seconds when available. +static size_t KiDebugTimestamp(char* buffer, size_t capacity) { + if (!gDebugHal || !gDebugHal->Timer.QueryTicks) + return 0; + + uint64_t tick = gDebugHal->Timer.QueryTicks(); + if (tick == 0) + return 0; + if (!gDebugClockReady) { + gDebugClockOrigin = tick; + gDebugLastTick = tick; + gDebugClockReady = true; + } + if (tick < gDebugLastTick) + tick = gDebugLastTick; + gDebugLastTick = tick; + + uint64_t elapsed = tick - gDebugClockOrigin; + int count = snprintf_(buffer, capacity, "[%6llu.%06llu] ", + (unsigned long long)(elapsed / 1000000ull), + (unsigned long long)(elapsed % 1000000ull)); + return count > 0 && (size_t)count < capacity ? (size_t)count : 0; +} + // Writes one formatted kernel diagnostic. __declspec(dllexport) void DbgPrint(const char* fmt, ...) { if (!gDebugEnabled || !fmt) @@ -202,23 +230,50 @@ __declspec(dllexport) void DbgPrint(const char* fmt, ...) { if (length >= sizeof(line)) length = sizeof(line) - 1u; } + if (length == 0) + return; bool panic = KiPanicIsActive(); if (panic && !KiPanicCurrentCpuOwnsReport()) KiPanicSecondaryHalt(); - if (!panic) { - uintptr_t lockFlags = KeAcquireSpinlock(&gDebugPrintLock); - if (!NtlAtomicLoad32(&gDebugLogFlushing)) - KiDebugLogAppendLocked(line, length); - KeReleaseSpinlock(&gDebugPrintLock, lockFlags); + uint32_t stream = panic ? HAL_OUTPUT_STREAM_PANIC : HAL_OUTPUT_STREAM_DEBUG; + uint32_t flags = panic ? HAL_OUTPUT_WRITE_EMERGENCY : 0; + uint32_t foreground = panic ? 0x00FF2222u : 0x00CCCCCCu; + uint32_t background = panic ? 0x00000000u : 0x00012456u; + + uintptr_t lockFlags = 0; + if (!panic) + lockFlags = KeAcquireSpinlock(&gDebugPrintLock); + + size_t offset = 0; + while (offset < length) { + size_t end = offset; + while (end < length && line[end] != '\n') + end++; + if (end < length) + end++; + + if (panic || gDebugAtLineStart) { + char prefix[48]; + size_t prefixLength = KiDebugTimestamp(prefix, sizeof(prefix)); + if (prefixLength) { + if (!panic && !NtlAtomicLoad32(&gDebugLogFlushing)) + KiDebugLogAppendLocked(prefix, prefixLength); + KiOutputWrite(stream, flags, foreground, background, prefix, prefixLength); + } + } + + if (!panic && !NtlAtomicLoad32(&gDebugLogFlushing)) + KiDebugLogAppendLocked(line + offset, end - offset); + KiOutputWrite(stream, flags, foreground, background, line + offset, end - offset); + if (!panic) + gDebugAtLineStart = line[end - 1u] == '\n'; + offset = end; } - KiOutputWrite(panic ? HAL_OUTPUT_STREAM_PANIC : HAL_OUTPUT_STREAM_DEBUG, - panic ? HAL_OUTPUT_WRITE_EMERGENCY : 0, - panic ? 0x00FF2222u : 0x00CCCCCCu, - panic ? 0x00000000u : 0x00012456u, - line, length); + if (!panic) + KeReleaseSpinlock(&gDebugPrintLock, lockFlags); } // Writes one debug-log segment and rejects successful zero-progress writes. diff --git a/code/kernel/ke_display.c b/code/kernel/ke_display.c index 9596fe1..2ed754b 100644 --- a/code/kernel/ke_display.c +++ b/code/kernel/ke_display.c @@ -1516,6 +1516,45 @@ DISPLAY_API KSTATUS KeDisplayRegisterDevice(DISPLAY_DEVICE* device) { return result; } +DISPLAY_API KSTATUS KeDisplayUnregisterDevice(DISPLAY_DEVICE* device) { + if (!device || device == &gBootFbDevice) + return NTLSTATUS_INVALID_PARAMETER; + + bool providerHeld = false; + KSTATUS status = KiDisplayAcquireProviderMutex(&providerHeld); + if (!KSUCCESS(status)) + return status; + + uintptr_t flags = KeAcquireSpinlock(&gDisplayLock); + DISPLAY_DEVICE** link = &gDisplayList; + while (*link && *link != device) + link = &(*link)->Next; + + if (!*link) { + KeReleaseSpinlock(&gDisplayLock, flags); + KiDisplayReleaseProviderMutex(providerHeld); + return NTLSTATUS_NOT_FOUND; + } + + *link = device->Next; + device->Next = (DISPLAY_DEVICE*)0; + if (gPrimaryDisplay == device) { + gPrimaryDisplay = (DISPLAY_DEVICE*)0; + gDisplayGeneration++; + gCursorDrawn = false; + gCursorUpdatePending = false; + gCursorAppliedGeneration = gCursorRequestGeneration; + gCursorCommittedX = DISPLAY_CURSOR_POS_INVALID; + gCursorCommittedY = DISPLAY_CURSOR_POS_INVALID; + gDirtyAll = false; + gDirtyRectCount = 0; + gDrawDepth = 0; + } + KeReleaseSpinlock(&gDisplayLock, flags); + KiDisplayReleaseProviderMutex(providerHeld); + return NTLSTATUS_SUCCESS; +} + // Implements the display Seed New Primary operation. static KSTATUS KiDisplaySeedNewPrimary(DISPLAY_DEVICE* oldDevice, DISPLAY_DEVICE* newDevice) { if (!oldDevice || !newDevice || oldDevice == newDevice) @@ -1570,6 +1609,11 @@ DISPLAY_API KSTATUS KeDisplaySetPrimary(DISPLAY_DEVICE* device) { const char* name = device->Name ? device->Name : "?"; + bool providerHeld = false; + KSTATUS result = KiDisplayAcquireProviderMutex(&providerHeld); + if (!KSUCCESS(result)) + return result; + uintptr_t flags = KeAcquireSpinlock(&gDisplayLock); bool found = false; for (DISPLAY_DEVICE* it = gDisplayList; it; it = it->Next) { @@ -1580,6 +1624,7 @@ DISPLAY_API KSTATUS KeDisplaySetPrimary(DISPLAY_DEVICE* device) { } if (!found) { KeReleaseSpinlock(&gDisplayLock, flags); + KiDisplayReleaseProviderMutex(providerHeld); return NTLSTATUS_NOT_FOUND; } @@ -1590,17 +1635,6 @@ DISPLAY_API KSTATUS KeDisplaySetPrimary(DISPLAY_DEVICE* device) { gCursorUpdatePending = gCursorVisible; KeReleaseSpinlock(&gDisplayLock, flags); - bool providerHeld = false; - KSTATUS result = KiDisplayAcquireProviderMutex(&providerHeld); - if (!KSUCCESS(result)) { - flags = KeAcquireSpinlock(&gDisplayLock); - gDisplayTransitionInProgress = false; - gDisplayGeneration++; - KeReleaseSpinlock(&gDisplayLock, flags); - KiDisplayQueueCursorWork(); - return result; - } - if (oldDevice && oldCursorDrawn && oldDevice->Ops && oldDevice->Ops->ShowCursor) (void)oldDevice->Ops->ShowCursor(oldDevice, false); diff --git a/code/kernel/ke_dpc.c b/code/kernel/ke_dpc.c index b2fe920..3e0c4c2 100644 --- a/code/kernel/ke_dpc.c +++ b/code/kernel/ke_dpc.c @@ -13,6 +13,9 @@ static SPINLOCK gDpcLock; static volatile int32_t gDpcInitialized = 0; static volatile int32_t gDpcActive = 0; +// Bound a single interrupt-exit dispatch so self-requeuing devices yield the CPU. +#define KE_DPC_DISPATCH_BUDGET 64u + // Initializes dpc System. void KeInitializeDpcSystem(void) { LIST_INIT(&gDpcQueue); @@ -83,7 +86,10 @@ void KiDrainDpcQueue(void) { if (oldIrql < DISPATCH_LEVEL) KeRaiseIrql(DISPATCH_LEVEL); - while (true) { + // The xHCI event routine already bounds its own work, but an IRQ stream + // can requeue it after every dispatch. Limit the whole drain, too. The + // pending queue is picked up at the next timer or device interrupt exit. + for (uint32_t dispatched = 0; dispatched < KE_DPC_DISPATCH_BUDGET; dispatched++) { uintptr_t flags = KeAcquireSpinlock(&gDpcLock); LIST_ENTRY* node = KeListPopHeadChecked(&gDpcQueue); if (!node) { diff --git a/code/kernel/ke_handle.c b/code/kernel/ke_handle.c index 1957f3d..79d869c 100644 --- a/code/kernel/ke_handle.c +++ b/code/kernel/ke_handle.c @@ -301,6 +301,9 @@ SYS_RIGHTS KeGetValidHandleRights(OBJECT_TYPE type) { return management | SYS_RIGHT_QUERY | SYS_DISPLAY_MANAGER_RIGHT_ENUMERATE | SYS_DISPLAY_MANAGER_RIGHT_MODESET | SYS_DISPLAY_MANAGER_RIGHT_PRESENT | SYS_DISPLAY_MANAGER_RIGHT_CURSOR | SYS_DISPLAY_MANAGER_RIGHT_CONSOLE; + case ObjectTypeServiceRegistration: + return management | SYS_RIGHT_QUERY | + SYS_SERVICE_REGISTRATION_RIGHT_REGISTER | SYS_SERVICE_REGISTRATION_RIGHT_UNREGISTER; case ObjectTypeDevice: case ObjectTypeDriver: case ObjectTypeUsbDevice: diff --git a/code/kernel/ke_power.c b/code/kernel/ke_power.c new file mode 100644 index 0000000..ee4edb0 --- /dev/null +++ b/code/kernel/ke_power.c @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static SPINLOCK gPowerLock = SPINLOCK_INIT; +static EVENT* gPowerRequest; +static EVENT* gPowerComplete; +static PROCESS* gInit; +static uint32_t gAction; +static bool gCommitted; +static const HAL_API* gButtonHal; +static uint32_t gButtonAction; +static KDPC gPowerButtonDpc; + +KSTATUS KePowerInitialize(void) { + if (gPowerRequest) return NTLSTATUS_ALREADY_EXISTS; + KSTATUS st = KeEventCreate(EventManualReset, false, &gPowerRequest); + if (!KSUCCESS(st)) return st; + st = KeEventCreate(EventManualReset, false, &gPowerComplete); + return st; +} + +// Delivers the physical action after interrupt-exit processing. +static void KiPowerButtonDpc(KDPC* dpc, void* context, void* arg1, void* arg2) { + (void)dpc; + (void)context; + (void)arg1; + (void)arg2; + (void)KePowerRequestHardware(gButtonAction); +} + +// The ACPI SCI handler only acknowledges the fixed event and queues normal work. +static void KiPowerButtonIsr(void) { + if (!gButtonHal || !gButtonHal->Power.ConsumeFixedButton || + !gButtonHal->Power.ConsumeFixedButton()) return; + (void)KeInsertQueueDpc(&gPowerButtonDpc, NULL, NULL); +} + +// Reads immutable boot-time button policy and attaches it to the fixed ACPI SCI. +void KePowerInitializeButton(const HAL_API* hal) { + char mode[24]; + KSTATUS status = KeGetSettingString("power", "button", mode, sizeof(mode)); + if (!KSUCCESS(status)) + KePanic(KE_PANIC_CODE_INIT, "power: missing [power] button, status=0x%x\n", status); + if (strcmp(mode, "ignore") == 0) { + DbgPrint("power: ACPI power button ignored by policy\n"); + return; + } + if (strcmp(mode, "shutdown") == 0) gButtonAction = SYS_POWER_ACTION_SHUTDOWN; + else if (strcmp(mode, "restart") == 0) gButtonAction = SYS_POWER_ACTION_RESTART; + else KePanic(KE_PANIC_CODE_INIT, "power: invalid [power] button=%s\n", mode); + + if (!hal || !hal->Power.QueryFixedButtonIrq || !hal->Power.ArmFixedButton || + !hal->Power.ConsumeFixedButton) { + DbgPrint("power: no ACPI fixed power button HAL support\n"); + return; + } + uint8_t irq = 0; + status = hal->Power.QueryFixedButtonIrq(&irq); + if (!KSUCCESS(status)) { + DbgPrint("power: ACPI fixed button unavailable (0x%x); AML button not yet supported\n", status); + return; + } + // The IOAPIC is still masked for this IRQ. Arm the firmware source first, + // then install the kernel handler and finally unmask via KeIrqRegister. + status = hal->Power.ArmFixedButton(); + if (!KSUCCESS(status)) { + DbgPrint("power: cannot arm fixed button IRQ %u, status=0x%x\n", irq, status); + return; + } + KeInitializeDpc(&gPowerButtonDpc, KiPowerButtonDpc, NULL); + gButtonHal = hal; + KeIrqRegister(irq, KiPowerButtonIsr); + DbgPrint("power: ACPI fixed button armed on IRQ %u, action=%s\n", irq, mode); +} + +void KePowerSetInitProcess(PROCESS* init) { + uintptr_t flags = KeAcquireSpinlock(&gPowerLock); + gInit = init; + KeReleaseSpinlock(&gPowerLock, flags); +} + +// Records one power transition under the coordinator lock. The hardware event +// may arrive before init exists; syscall-originated requests require init. +static KSTATUS KiPowerQueueRequest(uint32_t action, bool requireInit) { + if (action != SYS_POWER_ACTION_SHUTDOWN && action != SYS_POWER_ACTION_RESTART) + return NTLSTATUS_INVALID_PARAMETER; + uintptr_t flags = KeAcquireSpinlock(&gPowerLock); + KSTATUS status = NTLSTATUS_SUCCESS; + if (!gPowerRequest || (requireInit && !gInit)) status = NTLSTATUS_NOT_SUPPORTED; + else if (gAction) status = NTLSTATUS_ALREADY_EXISTS; + else gAction = action; + KeReleaseSpinlock(&gPowerLock, flags); + if (KSUCCESS(status)) KeSignalEvent(gPowerRequest); + return status; +} + +// Accepts a privileged explicit power request only after init exists. +KSTATUS KePowerRequest(uint32_t action) { return KiPowerQueueRequest(action, true); } + +// Latches a physical power-button request even during init startup. +KSTATUS KePowerRequestHardware(uint32_t action) { return KiPowerQueueRequest(action, false); } + +EVENT* KePowerRequestEvent(PROCESS* caller) { + uintptr_t flags = KeAcquireSpinlock(&gPowerLock); + EVENT* event = caller && caller == gInit ? gPowerRequest : (EVENT*)0; + KeReleaseSpinlock(&gPowerLock, flags); + return event; +} + +KSTATUS KePowerGetRequest(PROCESS* caller, uint32_t* actionOut) { + if (!actionOut) return NTLSTATUS_INVALID_PARAMETER; + uintptr_t flags = KeAcquireSpinlock(&gPowerLock); + KSTATUS st = caller && caller == gInit ? NTLSTATUS_SUCCESS : NTLSTATUS_ACCESS_DENIED; + if (KSUCCESS(st)) *actionOut = gAction; + KeReleaseSpinlock(&gPowerLock, flags); + return st; +} + +KSTATUS KePowerComplete(PROCESS* caller, uint32_t action) { + uintptr_t flags = KeAcquireSpinlock(&gPowerLock); + KSTATUS st = caller && caller == gInit ? NTLSTATUS_SUCCESS : NTLSTATUS_ACCESS_DENIED; + if (KSUCCESS(st) && (!gAction || action != gAction || gCommitted)) + st = NTLSTATUS_INVALID_PARAMETER; + if (KSUCCESS(st)) gCommitted = true; + KeReleaseSpinlock(&gPowerLock, flags); + if (KSUCCESS(st)) KeSignalEvent(gPowerComplete); + return st; +} + +EVENT* KePowerCompletionEvent(void) { return gPowerComplete; } + +uint32_t KePowerCommittedAction(void) { + uintptr_t flags = KeAcquireSpinlock(&gPowerLock); + uint32_t result = gCommitted ? gAction : 0; + KeReleaseSpinlock(&gPowerLock, flags); + return result; +} diff --git a/code/kernel/ke_process.c b/code/kernel/ke_process.c index 04302c3..871c4d9 100644 --- a/code/kernel/ke_process.c +++ b/code/kernel/ke_process.c @@ -629,7 +629,8 @@ KSTATUS KeExitProcess(PROCESS* process, int32_t exitCode) { // Begins termination of one process and wakes every sibling thread. KSTATUS KeTerminateProcess(PROCESS* process, int32_t exitCode) { if (!process) return NTLSTATUS_INVALID_PARAMETER; - if (KeIsProcessTerminationProtected(process)) return NTLSTATUS_ACCESS_DENIED; + if (KeIsProcessTerminationProtected(process) && KeGetCurrentProcess() != process) + return NTLSTATUS_ACCESS_DENIED; process->ExitCode = exitCode; NtlAtomicExchange32(&process->Terminating, 1); diff --git a/code/kernel/ke_selftest.c b/code/kernel/ke_selftest.c index 4120f32..caa122f 100644 --- a/code/kernel/ke_selftest.c +++ b/code/kernel/ke_selftest.c @@ -1,39 +1,20 @@ -// Kernel self-tests. This file runs bring-up regression checks before normal user workloads start. +// Focused debug-only checks for bootstrap state, APC waits, and copy-on-write. #include -#include -#include #include - -#include +#include #include #include #include #include -#include #include -#include -#include #include -#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include -// Focused APC wait-commit race self-test. User-mode APC return and async I/O -// behavior are covered by executable smoke tests; keep only the scheduler race -// that cannot be constructed through the public ABI. - -// Verifies that one APC normal routine executes in interruptible PASSIVE thread context. static void KiAssertApcNormalContext(const char* name) { KCPU* cpu = KeGetCurrentCpu(); if (KeGetCurrentIrql() != PASSIVE_LEVEL) @@ -108,486 +89,6 @@ static void KiRunApcWaitRaceSelfTest(void) { DbgPrint("APC wait-race selftest: PASS\n"); } -// Verifies that the PFN database tracks ownership, references, pins, and contiguous runs. -static void KiRunPfnDatabaseSelfTest(void) { - MM_PFN_STATS before; - MM_PFN_STATS after; - MmPfnQueryStats(&before); - - PHYSICAL_ADDRESS frame = MmPfnAllocate(MmPfnUseKernel); - if (!frame) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: failed to allocate kernel frame\n"); - - MM_PFN* pfn = MmPfnGet(frame); - if (!pfn || pfn->State != MmPfnStateActive || pfn->Usage != MmPfnUseKernel || pfn->RefCount != 1) - KePanic(KE_PANIC_CODE_SELFTEST, - "PFN selftest: allocated frame metadata invalid phys=%016llx\n", - (unsigned long long)frame); - - if (!KSUCCESS(MmPfnRetain(frame)) || pfn->RefCount != 2) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: retain failed ref=%u\n", pfn->RefCount); - if (!KSUCCESS(MmPfnRelease(frame)) || pfn->RefCount != 1) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: release failed ref=%u\n", pfn->RefCount); - - if (!KSUCCESS(MmPfnPin(frame)) || pfn->PinCount != 1) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: pin failed pin=%u\n", pfn->PinCount); - if (MmPfnFree(frame) != NTLSTATUS_ACCESS_DENIED) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: pinned frame was freed\n"); - if (!KSUCCESS(MmPfnUnpin(frame)) || pfn->PinCount != 0) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: unpin failed pin=%u\n", pfn->PinCount); - if (!KSUCCESS(MmPfnAddMapping(frame)) || pfn->MappedCount != 1) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: mapping add failed maps=%u\n", pfn->MappedCount); - if (MmPfnFree(frame) != NTLSTATUS_ACCESS_DENIED) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: mapped frame was freed\n"); - if (!KSUCCESS(MmPfnRemoveMapping(frame)) || pfn->MappedCount != 0) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: mapping remove failed maps=%u\n", pfn->MappedCount); - if (!KSUCCESS(MmPfnFree(frame))) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: free failed\n"); - - PHYSICAL_ADDRESS run = MmPfnAllocateContiguous(2, MmPfnUseDma); - if (!run) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: contiguous allocation failed\n"); - MM_PFN* p0 = MmPfnGet(run); - MM_PFN* p1 = MmPfnGet(run + PAGE_SIZE); - if (!p0 || !p1 || p0->Usage != MmPfnUseDma || p1->Usage != MmPfnUseDma) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: contiguous metadata invalid\n"); - MmPfnFree(run); - MmPfnFree(run + PAGE_SIZE); - - // Accounting buckets are part of the VM contract. A missing increment here - // usually means a new allocation path is using an anonymous usage class. - PHYSICAL_ADDRESS clean = MmPfnAllocate(MmPfnUseCleanFileCache); - PHYSICAL_ADDRESS image = MmPfnAllocate(MmPfnUseImage); - PHYSICAL_ADDRESS mapped = MmPfnAllocate(MmPfnUseMappedFile); - PHYSICAL_ADDRESS anon = MmPfnAllocate(MmPfnUseAnonymous); - PHYSICAL_ADDRESS cow = MmPfnAllocate(MmPfnUseCow); - PHYSICAL_ADDRESS surface = MmPfnAllocate(MmPfnUseSurface); - if (!clean || !image || !mapped || !anon || !cow || !surface) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: bucket allocation failed\n"); - - MM_PFN_STATS bucketStats; - MmPfnQueryStats(&bucketStats); - if (bucketStats.CleanFileCacheFrames < before.CleanFileCacheFrames + 1 || - bucketStats.ImageFrames < before.ImageFrames + 1 || - bucketStats.MappedFileFrames < before.MappedFileFrames + 1 || - bucketStats.AnonymousFrames < before.AnonymousFrames + 1 || - bucketStats.CowFrames < before.CowFrames + 1 || - bucketStats.SurfaceFrames < before.SurfaceFrames + 1) { - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: usage bucket accounting failed\n"); - } - - // Standby is the clean-page holding state used by later file-cache and image - // reclaim. It must not change the free count until the owning cache drops it. - if (!KSUCCESS(MmPfnMoveToStandby(clean))) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: clean standby transition failed\n"); - if (!KSUCCESS(MmPfnMoveToStandby(image))) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: image standby transition failed\n"); - - MM_PFN_STATS standbyStats; - MmPfnQueryStats(&standbyStats); - if (standbyStats.StandbyCleanFileCacheFrames < bucketStats.StandbyCleanFileCacheFrames + 1 || - standbyStats.StandbyImageFrames < bucketStats.StandbyImageFrames + 1 || - standbyStats.ReclaimableFrames < bucketStats.ReclaimableFrames + 2 || - standbyStats.FreeFrames != bucketStats.FreeFrames) { - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: standby accounting failed\n"); - } - - if (!KSUCCESS(MmPfnActivate(clean))) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: clean standby activation failed\n"); - if (!KSUCCESS(MmPfnActivate(image))) - KePanic(KE_PANIC_CODE_SELFTEST, "PFN selftest: image standby activation failed\n"); - - (void)MmPfnFree(clean); - (void)MmPfnFree(image); - (void)MmPfnFree(mapped); - (void)MmPfnFree(anon); - (void)MmPfnFree(cow); - (void)MmPfnFree(surface); - - MmPfnQueryStats(&after); - if (before.FreeFrames != after.FreeFrames) - KePanic(KE_PANIC_CODE_SELFTEST, - "PFN selftest: free count leak before=%llu after=%llu\n", - (unsigned long long)before.FreeFrames, - (unsigned long long)after.FreeFrames); - - DbgPrint("PFN selftest: metadata/refcount/pin/buckets/standby/contiguous PASS\n"); -} - -// Verifies common-buffer, direct mapping, bounce mapping, and teardown accounting. -static void KiRunDmaSelfTest(void) { - const NTL_DDK_DMA* dma = IoGetDmaDdk(); - if (!dma) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: DDK table missing\n"); - - IO_DMA_STATS before; - IO_DMA_STATS after; - dma->QueryStats(&before); - - DRIVER_OBJECT driver; - memset(&driver, 0, sizeof(driver)); - IoInitializeDriverObject(&driver, SYS_SV_LITERAL("dma-selftest")); - - DEVICE_OBJECT* device = (DEVICE_OBJECT*)0; - KSTATUS status = IoCreateDevice(&driver, 0, &device); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: device creation failed status=%u\n", status); - - IO_DMA_REQUIREMENTS requirements; - memset(&requirements, 0, sizeof(requirements)); - requirements.MaximumDeviceAddress = UINT64_MAX; - requirements.Alignment = PAGE_SIZE; - requirements.MaximumSegmentLength = PAGE_SIZE; - requirements.MaximumSegments = 4; - - IO_DMA_ADAPTER* adapter = (IO_DMA_ADAPTER*)0; - IO_DMA_BUFFER* buffer = (IO_DMA_BUFFER*)0; - MM_PINNED_MEMORY* pinned = (MM_PINNED_MEMORY*)0; - IO_DMA_MAPPING* mapping = (IO_DMA_MAPPING*)0; - - status = dma->GetAdapter(device, &requirements, &adapter); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: adapter creation failed status=%u\n", status); - - status = dma->AllocateCommonBuffer(adapter, PAGE_SIZE, PAGE_SIZE, &buffer); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: common buffer allocation failed status=%u\n", status); - - uint8_t* bytes = (uint8_t*)dma->GetCommonBufferAddress(buffer); - uint64_t deviceAddress = dma->GetCommonBufferDeviceAddress(buffer); - if (!bytes || !deviceAddress || (deviceAddress & (PAGE_SIZE - 1)) != 0 || - dma->GetCommonBufferSize(buffer) != PAGE_SIZE) - KePanic(KE_PANIC_CODE_SELFTEST, - "DMA selftest: common buffer metadata invalid address=0x%016llX\n", - (unsigned long long)deviceAddress); - - status = dma->PinKernelMemory(bytes, PAGE_SIZE, IoDmaBidirectional, &pinned); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: kernel pin failed status=%u\n", status); - status = dma->MapPinnedMemory(adapter, - pinned, - 0, - PAGE_SIZE, - IoDmaBidirectional, - IO_DMA_MAP_NO_BOUNCE, - &mapping); - if (!KSUCCESS(status) || dma->GetSegmentCount(mapping) != 1 || - dma->GetSegments(mapping)[0].DeviceAddress != deviceAddress) - KePanic(KE_PANIC_CODE_SELFTEST, - "DMA selftest: direct mapping failed status=%u segments=%u\n", - status, - dma->GetSegmentCount(mapping)); - dma->UnmapMemory(mapping); - mapping = (IO_DMA_MAPPING*)0; - dma->UnpinMemory(pinned); - pinned = (MM_PINNED_MEMORY*)0; - - for (size_t i = 0; i < 128; i++) - bytes[i + 1] = (uint8_t)(i ^ 0x5Au); - status = dma->PinKernelMemory(bytes + 1, 128, IoDmaToDevice, &pinned); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: unaligned kernel pin failed status=%u\n", status); - status = dma->MapPinnedMemory(adapter, pinned, 0, 128, IoDmaToDevice, 0, &mapping); - if (!KSUCCESS(status) || dma->GetSegmentCount(mapping) != 1 || - (dma->GetSegments(mapping)[0].DeviceAddress & (PAGE_SIZE - 1)) != 0 || - dma->GetSegments(mapping)[0].DeviceAddress == deviceAddress + 1) - KePanic(KE_PANIC_CODE_SELFTEST, - "DMA selftest: bounce mapping failed status=%u segments=%u\n", - status, - dma->GetSegmentCount(mapping)); - status = dma->SynchronizeForDevice(mapping); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: bounce synchronization failed status=%u\n", status); - dma->UnmapMemory(mapping); - mapping = (IO_DMA_MAPPING*)0; - dma->UnpinMemory(pinned); - pinned = (MM_PINNED_MEMORY*)0; - - memset(bytes + 1, 0, 128); - status = dma->PinKernelMemory(bytes + 1, 128, IoDmaFromDevice, &pinned); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: receive pin failed status=%u\n", status); - status = dma->MapPinnedMemory(adapter, pinned, 0, 128, IoDmaFromDevice, 0, &mapping); - if (!KSUCCESS(status) || dma->GetSegmentCount(mapping) != 1 || - dma->GetSegments(mapping)[0].DeviceAddress == deviceAddress + 1) - KePanic(KE_PANIC_CODE_SELFTEST, - "DMA selftest: receive bounce mapping failed status=%u segments=%u\n", - status, - dma->GetSegmentCount(mapping)); - status = dma->SynchronizeForDevice(mapping); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: receive handoff failed status=%u\n", status); - - uint64_t receiveDeviceAddress = dma->GetSegments(mapping)[0].DeviceAddress; - uint8_t* receiveDeviceAlias = (uint8_t*)MmPfnToVirtual(receiveDeviceAddress & PAGE_MASK); - if (!receiveDeviceAlias) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: receive device alias missing\n"); - receiveDeviceAlias += receiveDeviceAddress & (PAGE_SIZE - 1); - for (size_t i = 0; i < 128; i++) - receiveDeviceAlias[i] = (uint8_t)(i ^ 0xA5u); - - status = dma->SynchronizeForCpu(mapping); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: receive synchronization failed status=%u\n", status); - for (size_t i = 0; i < 128; i++) { - if (bytes[i + 1] != (uint8_t)(i ^ 0xA5u)) - KePanic(KE_PANIC_CODE_SELFTEST, - "DMA selftest: receive bounce copy mismatch offset=%llu\n", - (unsigned long long)i); - } - dma->UnmapMemory(mapping); - mapping = (IO_DMA_MAPPING*)0; - dma->UnpinMemory(pinned); - pinned = (MM_PINNED_MEMORY*)0; - - PROCESS* process = KeGetCurrentProcess(); - if (!process) - process = KeGetSystemProcess(); - if (!process) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: no process for user pinning\n"); - - void* userBase = (void*)0; - status = MmVadCreateCommitted(process, - PAGE_SIZE, - PAGE_SIZE, - PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX, - MM_VAD_ZERO, - MmVadGeneric, - &userBase); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: user VAD creation failed status=%u\n", status); - status = MmVadEnsureResident(process, userBase, PAGE_SIZE, PAGE_WRITE | PAGE_USER); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: user VAD materialization failed status=%u\n", status); - ((uint8_t*)userBase)[0] = 0xD5u; - - status = dma->PinUserMemory(process, userBase, PAGE_SIZE, IoDmaToDevice, &pinned); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: user pin failed status=%u\n", status); - status = MmVadDestroy(process, userBase); - if (status != NTLSTATUS_ACCESS_DENIED) - KePanic(KE_PANIC_CODE_SELFTEST, - "DMA selftest: pinned user VAD was destroyable status=%u\n", - status); - status = dma->MapPinnedMemory(adapter, - pinned, - 0, - PAGE_SIZE, - IoDmaToDevice, - IO_DMA_MAP_NO_BOUNCE, - &mapping); - if (!KSUCCESS(status) || dma->GetSegmentCount(mapping) != 1) - KePanic(KE_PANIC_CODE_SELFTEST, - "DMA selftest: user mapping failed status=%u segments=%u\n", - status, - dma->GetSegmentCount(mapping)); - status = dma->SynchronizeForDevice(mapping); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: user synchronization failed status=%u\n", status); - dma->UnmapMemory(mapping); - mapping = (IO_DMA_MAPPING*)0; - dma->UnpinMemory(pinned); - pinned = (MM_PINNED_MEMORY*)0; - status = MmVadDestroy(process, userBase); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: user VAD destroy failed status=%u\n", status); - - dma->FreeCommonBuffer(buffer); - dma->PutAdapter(adapter); - IoDeleteDevice(device); - - dma->QueryStats(&after); - if (after.AdaptersCreated != before.AdaptersCreated + 1 || - after.AdaptersDestroyed != before.AdaptersDestroyed + 1 || - after.AdaptersInUse != before.AdaptersInUse || - after.CommonBuffersAllocated < before.CommonBuffersAllocated + 3 || - after.CommonBuffersFreed < before.CommonBuffersFreed + 3 || - after.CommonBuffersInUse != before.CommonBuffersInUse || - after.CommonBufferBytesInUse != before.CommonBufferBytesInUse || - after.PinnedRangesInUse != before.PinnedRangesInUse || - after.PinnedPagesInUse != before.PinnedPagesInUse || - after.PinnedKernelRanges != before.PinnedKernelRanges + 3 || - after.PinnedUserRanges != before.PinnedUserRanges + 1 || - after.PinnedRangesReleased != before.PinnedRangesReleased + 4 || - after.StreamingMappings != before.StreamingMappings + 4 || - after.StreamingMappingsUnmapped != before.StreamingMappingsUnmapped + 4 || - after.StreamingMappingsInUse != before.StreamingMappingsInUse || - after.DirectMappings != before.DirectMappings + 2 || - after.BounceMappings != before.BounceMappings + 2 || - after.BounceBytesCopiedToDevice < before.BounceBytesCopiedToDevice + 128 || - after.BounceBytesCopiedToCpu < before.BounceBytesCopiedToCpu + 128) - KePanic(KE_PANIC_CODE_SELFTEST, "DMA selftest: accounting mismatch\n"); - - DbgPrint("DMA selftest: common/kernel-pin/user-pin/direct/send-bounce/receive-bounce/teardown PASS\n"); -} - -// Verifies the page-table manager invariants that the higher VMM will depend on. -static void KiRunMemoryMappingSelfTest(void) { - PHYSICAL_ADDRESS phys = 0; - uint64_t flags = 0; - KSTATUS s = MmPteQueryPageFor(MmPteQuerySelfTest, 0, &phys, &flags); - if (s != NTLSTATUS_NOT_FOUND) { - KePanic(KE_PANIC_CODE_SELFTEST, "MM selftest: virtual page zero is mapped phys=%016llx flags=%016llx status=%u\n", - (unsigned long long)phys, - (unsigned long long)flags, - (unsigned)s); - } - - uintptr_t testVa = 0xFFFFA00000000000ULL; - s = MmPteQueryPageFor(MmPteQuerySelfTest, testVa, &phys, &flags); - if (s != NTLSTATUS_NOT_FOUND) { - KePanic(KE_PANIC_CODE_SELFTEST, "MM selftest: test VA already mapped va=%016llx phys=%016llx flags=%016llx\n", - (unsigned long long)testVa, - (unsigned long long)phys, - (unsigned long long)flags); - } - - PHYSICAL_ADDRESS frame = MmPfnAllocate(MmPfnUseKernel); - if (!frame) - KePanic(KE_PANIC_CODE_SELFTEST, "MM selftest: failed to allocate test frame\n"); - - s = MmPteMapPage(testVa, frame, PAGE_PRESENT | PAGE_WRITE | PAGE_NX, MmPteMapFailIfPresent); - if (!KSUCCESS(s)) { - MmPfnFree(frame); - KePanic(KE_PANIC_CODE_SELFTEST, "MM selftest: MmPteMapPage failed status=%u\n", s); - } - - volatile uint64_t* probe = (volatile uint64_t*)testVa; - *probe = 0x1122334455667788ULL; - if (*probe != 0x1122334455667788ULL) - KePanic(KE_PANIC_CODE_SELFTEST, "MM selftest: mapped test page did not retain data\n"); - - s = MmPteQueryPageFor(MmPteQuerySelfTest, testVa, &phys, &flags); - if (!KSUCCESS(s) || phys != frame || !(flags & PAGE_WRITE)) - KePanic(KE_PANIC_CODE_SELFTEST, "MM selftest: query after map failed status=%u phys=%016llx frame=%016llx flags=%016llx\n", - s, - (unsigned long long)phys, - (unsigned long long)frame, - (unsigned long long)flags); - - s = MmPteProtectRange(testVa, PAGE_SIZE, PAGE_PRESENT | PAGE_NX); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "MM selftest: MmPteProtectRange failed status=%u\n", s); - - s = MmPteQueryPageFor(MmPteQuerySelfTest, testVa, &phys, &flags); - if (!KSUCCESS(s) || (flags & PAGE_WRITE)) - KePanic(KE_PANIC_CODE_SELFTEST, "MM selftest: read-only protect failed status=%u flags=%016llx\n", - s, - (unsigned long long)flags); - - s = MmPteUnmapPage(testVa); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "MM selftest: MmPteUnmapPage failed status=%u\n", s); - - s = MmPteQueryPageFor(MmPteQuerySelfTest, testVa, &phys, &flags); - if (s != NTLSTATUS_NOT_FOUND) - KePanic(KE_PANIC_CODE_SELFTEST, "MM selftest: test VA still mapped after unmap status=%u\n", s); - - MmPfnFree(frame); - 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(); - if (!process) - process = KeGetSystemProcess(); - if (!process) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: no process context\n"); - - size_t beforeVadCount = 0; - size_t beforeReserved = 0; - size_t beforeCommitted = 0; - MmVadQueryProcessMemory(process, &beforeVadCount, &beforeReserved, &beforeCommitted); - - void* base = (void*)0; - KSTATUS s = MmVadReserve(process, - PAGE_SIZE * 4, - PAGE_SIZE, - PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX, - MM_VAD_ZERO, - MmVadGeneric, - &base); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: reserve failed %u\n", s); - - uint8_t* p = (uint8_t*)base; - s = MmVadCommit(process, p, PAGE_SIZE, PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX, MM_VAD_ZERO); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: first commit failed %u\n", s); - - s = MmVadCommit(process, p + PAGE_SIZE * 2, PAGE_SIZE, PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX, MM_VAD_ZERO); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: sparse commit failed %u\n", s); - - void* queryBase = (void*)0; - size_t querySize = 0; - uint64_t queryFlags = 0; - s = MmVadQuery(process, p, &queryBase, &querySize, &queryFlags, (MM_VAD_TYPE*)0, (uint32_t*)0); - if (!KSUCCESS(s) || queryBase != p || querySize != PAGE_SIZE || !(queryFlags & PAGE_WRITE)) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: first query failed status=%u base=0x%016llX size=%llu flags=%016llx\n", - s, queryBase, (unsigned long long)querySize, (unsigned long long)queryFlags); - - s = MmPteQueryPageFor(MmPteQuerySelfTest, (uintptr_t)p, (PHYSICAL_ADDRESS*)0, (uint64_t*)0); - if (s != NTLSTATUS_NOT_FOUND) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: demand-zero page was mapped before access status=%u\n", s); - - s = MmVadEnsureResident(process, p, PAGE_SIZE, PAGE_WRITE | PAGE_USER); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: first demand-zero materialization failed %u\n", s); - s = MmVadEnsureResident(process, p + PAGE_SIZE * 2, PAGE_SIZE, PAGE_WRITE | PAGE_USER); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: sparse demand-zero materialization failed %u\n", s); - - s = MmVadQuery(process, p + PAGE_SIZE, &queryBase, &querySize, &queryFlags, (MM_VAD_TYPE*)0, (uint32_t*)0); - if (s != NTLSTATUS_NOT_FOUND) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: uncommitted hole queried as committed status=%u\n", s); - - p[0] = 0x5A; - p[PAGE_SIZE * 2] = 0xA5; - - s = MmVadProtect(process, p, PAGE_SIZE, 0); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: NOACCESS protect failed %u\n", s); - - s = MmPteQueryPageFor(MmPteQuerySelfTest, (uintptr_t)p, (PHYSICAL_ADDRESS*)0, (uint64_t*)0); - if (s != NTLSTATUS_NOT_FOUND) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: NOACCESS page still had a PTE status=%u\n", s); - - s = MmVadQuery(process, p, &queryBase, &querySize, &queryFlags, (MM_VAD_TYPE*)0, (uint32_t*)0); - if (!KSUCCESS(s) || queryFlags != 0) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: NOACCESS query failed status=%u flags=%016llx\n", - s, (unsigned long long)queryFlags); - - s = MmVadProtect(process, p, PAGE_SIZE, PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: remap after NOACCESS failed %u\n", s); - if (p[0] != 0x5A || p[PAGE_SIZE * 2] != 0xA5) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: protected page contents were lost\n"); - - size_t afterVadCount = 0; - size_t afterReserved = 0; - size_t afterCommitted = 0; - MmVadQueryProcessMemory(process, &afterVadCount, &afterReserved, &afterCommitted); - if (afterVadCount != beforeVadCount + 1 || afterCommitted != beforeCommitted + PAGE_SIZE * 2) - KePanic(KE_PANIC_CODE_SELFTEST, - "VAD selftest: accounting mismatch before=%llu after=%llu vads=%llu/%llu\n", - (unsigned long long)beforeCommitted, - (unsigned long long)afterCommitted, - (unsigned long long)beforeVadCount, - (unsigned long long)afterVadCount); - - s = MmVadDestroy(process, base); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: destroy failed %u\n", s); - - MmVadQueryProcessMemory(process, &afterVadCount, &afterReserved, &afterCommitted); - if (afterVadCount != beforeVadCount || afterCommitted != beforeCommitted) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD selftest: accounting leaked after destroy\n"); - - 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(); @@ -703,839 +204,21 @@ 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()) { - DbgPrint("VAD swap selftest: SKIP, swap is disabled\n"); - return; - } - - PROCESS* process = KeGetCurrentProcess(); - if (!process) - process = KeGetSystemProcess(); - if (!process) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD swap selftest: no process context\n"); - - void* base = (void*)0; - KSTATUS s = MmVadCreateCommitted(process, - PAGE_SIZE, - PAGE_SIZE, - PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX, - MM_VAD_ZERO, - MmVadGeneric, - &base); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD swap selftest: allocation failed %u\n", s); - - s = MmVadEnsureResident(process, base, PAGE_SIZE, PAGE_WRITE | PAGE_USER); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD swap selftest: materialization failed %u\n", s); - - uint8_t* p = (uint8_t*)base; - p[0] = 0xC1; - p[PAGE_SIZE - 1] = 0x5E; - - void* trimmed = (void*)0; - s = MmVadTrimOnePageForTest(process, &trimmed); - if (!KSUCCESS(s) || trimmed != base) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD swap selftest: trim failed status=%u trimmed=0x%016llX base=0x%016llX\n", s, trimmed, base); - - s = MmPteQueryPageFor(MmPteQuerySelfTest, (uintptr_t)base, (PHYSICAL_ADDRESS*)0, (uint64_t*)0); - if (s != NTLSTATUS_NOT_FOUND) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD swap selftest: swapped page still had a PTE status=%u\n", s); - - s = MmVadResolveUserPageFault(process, (uintptr_t)base, 0); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD swap selftest: page-in fault failed %u\n", s); - - if (p[0] != 0xC1 || p[PAGE_SIZE - 1] != 0x5E) - KePanic(KE_PANIC_CODE_SELFTEST, "VAD swap selftest: contents changed after page-in %02x/%02x\n", p[0], p[PAGE_SIZE - 1]); - - (void)MmVadDestroy(process, base); - DbgPrint("VAD swap selftest: private pageout/pagein PASS\n"); -} - -// String-view ABI self-test. -static void KiRunStringViewSelfTest(void) { - DbgPrint("STRINGVIEW selftest: begin\n"); - - SYS_STRING_VIEW hello = SYS_SV_LITERAL("Hello"); - if (hello.Length != 5 || !SysSvEqualLit(hello, "Hello")) - KePanic(KE_PANIC_CODE_SELFTEST, "STRINGVIEW selftest: literal/equality failed\n"); - - static const char embedded[] = { 'a', 'b', '\0', 'c', 'd' }; - SYS_STRING_VIEW emb = SysSvFromParts(embedded, sizeof(embedded)); - if (emb.Length != 5 || !SysSvContainsByte(emb, '\0')) - KePanic(KE_PANIC_CODE_SELFTEST, "STRINGVIEW selftest: embedded-NUL detection failed\n"); - - char copy[6]; - if (!SysSvCopyZ(copy, sizeof(copy), emb)) - KePanic(KE_PANIC_CODE_SELFTEST, "STRINGVIEW selftest: bounded copy failed\n"); - if (memcmp(copy, embedded, sizeof(embedded)) != 0 || copy[5] != '\0') - KePanic(KE_PANIC_CODE_SELFTEST, "STRINGVIEW selftest: bounded copy contents wrong\n"); - - if (SysSvCopyZ(copy, 4, emb)) - KePanic(KE_PANIC_CODE_SELFTEST, "STRINGVIEW selftest: undersized copy unexpectedly succeeded\n"); - - // Paths are string views at the ABI boundary, but provider path names do not - // accept embedded NUL bytes. This must fail before reaching any filesystem. - IO_VOLUME* volume = (IO_VOLUME*)0; - KSTATUS s = KeReferenceSystemRootVolume(&volume); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "STRINGVIEW selftest: system volume reference status=%u\n", s); - FILE_OBJECT* f = (FILE_OBJECT*)0; - s = IoOpenFileAtVolume(volume, SysSvFromParts("system/init.exe\0junk", 20), &f); - IoReleaseVolume(volume); - if (s != NTLSTATUS_INVALID_PARAMETER) - KePanic(KE_PANIC_CODE_SELFTEST, "STRINGVIEW selftest: embedded-NUL path status=%u\n", s); - - DbgPrint("STRINGVIEW selftest: sample view='%v' len=%llu\n", - hello, (uint64_t)hello.Length); - DbgPrint("STRINGVIEW selftest: PASS\n"); -} - -// Capability-relative native-path self-test. -static void KiRunVolumeRelativePathSelfTest(void) { - DbgPrint("VOLUME/PATH selftest: begin\n"); - - IO_VOLUME* volume = (IO_VOLUME*)0; - KSTATUS status = KeReferenceSystemRootVolume(&volume); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, "VOLUME/PATH selftest: system volume reference failed %u\n", status); - - FILE_OBJECT* file = (FILE_OBJECT*)0; - status = IoOpenFileAtVolume(volume, SYS_SV_LITERAL("system/init.exe"), &file); - if (!KSUCCESS(status) || !file) - KePanic(KE_PANIC_CODE_SELFTEST, "VOLUME/PATH selftest: direct open failed %u\n", status); - IoCloseFile(file); - - const SYS_STRING_VIEW invalidPaths[] = { - SYS_SV_LITERAL("/system/init.exe"), - SYS_SV_LITERAL("system\\init.exe"), - SYS_SV_LITERAL("system//init.exe"), - SYS_SV_LITERAL("system/./init.exe"), - SYS_SV_LITERAL("system/../init.exe"), - SYS_SV_LITERAL("system/init.exe/"), - }; - for (size_t i = 0; i < sizeof(invalidPaths) / sizeof(invalidPaths[0]); ++i) { - file = (FILE_OBJECT*)0; - status = IoOpenFileAtVolume(volume, invalidPaths[i], &file); - if (status != NTLSTATUS_INVALID_PARAMETER || file) - KePanic(KE_PANIC_CODE_SELFTEST, - "VOLUME/PATH selftest: invalid path %llu status=%u file=0x%016llX\n", - (uint64_t)i, status, (uint64_t)(uintptr_t)file); - } - - DIR_OBJECT* directory = (DIR_OBJECT*)0; - status = IoOpenDirAtVolume(volume, SYS_SV_LITERAL("system"), &directory); - if (!KSUCCESS(status) || !directory) - KePanic(KE_PANIC_CODE_SELFTEST, "VOLUME/PATH selftest: directory open failed %u\n", status); - - file = (FILE_OBJECT*)0; - status = IoOpenFileAtDirectory(directory, SYS_SV_LITERAL("init.exe"), &file); - if (!KSUCCESS(status) || !file) - KePanic(KE_PANIC_CODE_SELFTEST, "VOLUME/PATH selftest: directory-relative open failed %u\n", status); - IoCloseFile(file); - - file = (FILE_OBJECT*)0; - status = IoOpenFileAtDirectory(directory, SYS_SV_LITERAL("../init.exe"), &file); - if (status != NTLSTATUS_INVALID_PARAMETER || file) - KePanic(KE_PANIC_CODE_SELFTEST, "VOLUME/PATH selftest: parent traversal status=%u\n", status); - - IoCloseDir(directory); - IoReleaseVolume(volume); - DbgPrint("VOLUME/PATH selftest: PASS\n"); -} - -// Async file I/O self-test. - -typedef struct KI_FILE_ASYNC_SELFTEST_STATE { - EVENT* CompletionEvent; - FILE_OBJECT* File; - IO_STATUS_BLOCK Iosb; - KAPC Apc; - uint8_t* Buffer; - - volatile int ApcDelivered; - volatile KSTATUS ApcStatus; -} KI_FILE_ASYNC_SELFTEST_STATE; - -static void KiFileAsyncSelfTestKernelRoutine(KAPC* apc, - void** normalRoutine, - void** normalContext, - void** systemArgument1, - void** systemArgument2) { - DbgPrint("FILE/ASYNC selftest: kernel routine apc=0x%016llX iosb=0x%016llX status=%u irql=%u\n", - (uint64_t)apc, - (uint64_t)*systemArgument1, - (uint32_t)(uintptr_t)*systemArgument2, - KeGetCurrentIrql()); - - (void)normalRoutine; - (void)normalContext; -} - -static void KiFileAsyncSelfTestNormalRoutine(void* normalContext, - void* systemArgument1, - void* systemArgument2) { - KI_FILE_ASYNC_SELFTEST_STATE* st = - (KI_FILE_ASYNC_SELFTEST_STATE*)normalContext; - KSTATUS status = (KSTATUS)(uintptr_t)systemArgument2; - - DbgPrint("FILE/ASYNC selftest: APC delivered iosb=0x%016llX status=%u iosb=%u/%llu irql=%u\n", - (uint64_t)systemArgument1, - status, - st->Iosb.Status, - (uint64_t)st->Iosb.Information, - KeGetCurrentIrql()); - - if (KeGetCurrentIrql() != PASSIVE_LEVEL) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: APC ran above PASSIVE_LEVEL\n"); - - st->ApcDelivered++; - st->ApcStatus = status; -} - -// Runs async File Io Self Test. -static void KiRunAsyncFileIoSelfTest(void) { - DbgPrint("FILE/ASYNC selftest: begin\n"); - - KI_FILE_ASYNC_SELFTEST_STATE st; - memset(&st, 0, sizeof(st)); - st.Iosb.Status = NTLSTATUS_FAILURE; - - KSTATUS s = KeOpenSystemRootFile("system/init.exe", &st.File); - if (!KSUCCESS(s)) { - DbgPrint("FILE/ASYNC selftest: SKIP, system/init.exe open failed %u\n", s); - return; - } - - s = KeEventCreate(EventAutoReset, 0, &st.CompletionEvent); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: event create failed %u\n", s); - - st.Buffer = (uint8_t*)MmAllocatePool(512); - if (!st.Buffer) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: no buffer\n"); - memset(st.Buffer, 0xCC, 512); - - KeInitializeApc(&st.Apc, - KeGetCurrentThread(), - KiFileAsyncSelfTestKernelRoutine, - KiFileAsyncSelfTestNormalRoutine, - (void*)&st); - - DbgPrint("FILE/ASYNC selftest: submit read system/init.exe offset=0 size=512\n"); - IO_ASYNC_FILE_REQUEST_OBJECT* readReq = (IO_ASYNC_FILE_REQUEST_OBJECT*)0; - s = IoReadFileAsyncKernel(st.File, - st.Buffer, - 512, - 0, - &st.Iosb, - st.CompletionEvent, - &st.Apc, - (MM_LOCKED_USER_BUFFER*)0, - (MM_LOCKED_USER_BUFFER*)0, - &readReq); - if (s != NTLSTATUS_PENDING) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: submit returned %u\n", s); - - s = KeWaitForEvent(st.CompletionEvent, 1); - if (s != NTLSTATUS_SUCCESS && s != NTLSTATUS_KERNEL_APC) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: wait returned %u\n", s); - - if (st.Iosb.Status == NTLSTATUS_PENDING) { - s = KeWaitForEvent(st.CompletionEvent, 0); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: final event wait returned %u\n", s); - } - - if (st.ApcDelivered != 1) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: APC not delivered count=%d\n", st.ApcDelivered); - - if (!KSUCCESS(st.Iosb.Status) || !KSUCCESS(st.ApcStatus)) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: I/O failed iosb=%u apc=%u info=%llu\n", - st.Iosb.Status, st.ApcStatus, (uint64_t)st.Iosb.Information); - - if (st.Iosb.Information < 2 || st.Buffer[0] != 'M' || st.Buffer[1] != 'Z') - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: bad file header info=%llu bytes=%02X %02X\n", - (uint64_t)st.Iosb.Information, st.Buffer[0], st.Buffer[1]); - - DbgPrint("FILE/ASYNC selftest: first bytes %02X %02X %02X %02X info=%llu wait=%u\n", - st.Buffer[0], st.Buffer[1], st.Buffer[2], st.Buffer[3], - (uint64_t)st.Iosb.Information, s); - - if (readReq) - ObDereferenceObject(IoAsyncFileRequestObject(readReq)); - - MmFreePool(st.Buffer); - ObDereferenceObject(&st.CompletionEvent->Header); - IoCloseFile(st.File); - - // Exercise the async write path with an explicit-offset temporary file. - { - static const char payload[] = "async-file-write-selftest\n"; - char verify[sizeof(payload)]; - FILE_OBJECT* wf = (FILE_OBJECT*)0; - FILE_OBJECT* rf = (FILE_OBJECT*)0; - EVENT* wev = (EVENT*)0; - IO_STATUS_BLOCK wiosb; - size_t got = 0; - - memset(verify, 0, sizeof(verify)); - wiosb.Status = NTLSTATUS_FAILURE; - wiosb.Information = 0; - - (void)KeDeleteSystemRootFile("asyncio.tmp"); - s = KeCreateSystemRootFile("asyncio.tmp", &wf); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: create write temp failed %u\n", s); - - s = KeEventCreate(EventAutoReset, 0, &wev); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: write event create failed %u\n", s); - - DbgPrint("FILE/ASYNC selftest: submit write asyncio.tmp size=%llu\n", - (uint64_t)(sizeof(payload) - 1)); - - IO_ASYNC_FILE_REQUEST_OBJECT* writeReq = (IO_ASYNC_FILE_REQUEST_OBJECT*)0; - s = IoWriteFileAsyncKernel(wf, - payload, - sizeof(payload) - 1, - 0, - &wiosb, - wev, - (KAPC*)0, - (MM_LOCKED_USER_BUFFER*)0, - (MM_LOCKED_USER_BUFFER*)0, - &writeReq); - if (s != NTLSTATUS_PENDING) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: write submit returned %u\n", s); - - s = KeWaitForEvent(wev, 1); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: write wait returned %u\n", s); - - if (!KSUCCESS(wiosb.Status) || wiosb.Information != sizeof(payload) - 1) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: write failed status=%u info=%llu\n", - wiosb.Status, (uint64_t)wiosb.Information); - - if (writeReq) - ObDereferenceObject(IoAsyncFileRequestObject(writeReq)); - - IoCloseFile(wf); - wf = (FILE_OBJECT*)0; - - s = KeOpenSystemRootFile("asyncio.tmp", &rf); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: reopen write temp failed %u\n", s); - - s = IoReadFileAt(rf, 0, verify, sizeof(payload) - 1, &got); - if (!KSUCCESS(s) || got != sizeof(payload) - 1 || - // Implements the memcmp operation. - memcmp(verify, payload, sizeof(payload) - 1) != 0) { - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: verify failed status=%u got=%llu\n", - s, (uint64_t)got); - } - - IoCloseFile(rf); - ObDereferenceObject(&wev->Header); - s = KeDeleteSystemRootFile("asyncio.tmp"); - if (!KSUCCESS(s)) - KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: delete temp failed %u\n", s); - - DbgPrint("FILE/ASYNC selftest: async write verified\n"); - } - - DbgPrint("FILE/ASYNC selftest: PASS\n"); -} - -typedef struct KI_SCHEDULER_SELFTEST_STATE { - MUTEX* GateMutex; - MUTEX* LinkMutex; - EVENT* ChainReadyEvent; - EVENT* RealtimeReadyEvent; - EVENT* ChainFinishedEvent; - EVENT* RealtimeFinishedEvent; - volatile KSTATUS ChainAcquireStatus; - volatile KSTATUS RealtimeAcquireStatus; -} KI_SCHEDULER_SELFTEST_STATE; - -// Waits until one worker has entered the expected mutex wait state. -static KE_THREAD_SCHEDULER_SNAPSHOT KiSchedulerAwaitMutexWait(THREAD* thread, MUTEX* mutex) { - KE_THREAD_SCHEDULER_SNAPSHOT snapshot; - memset(&snapshot, 0, sizeof(snapshot)); - - for (uint32_t attempt = 0; attempt < 100; attempt++) { - KiSchedulerSnapshotThread(thread, &snapshot); - if (snapshot.State == ThreadWaiting && snapshot.WaitingMutex == mutex) - return snapshot; - - KSTATUS status = KeDelayThread(SCHED_TICK_MS); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: state wait failed status=%u\n", status); - } - - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: thread %u did not block on mutex=0x%016llX state=%u waiting=0x%016llX\n", - thread ? thread->ThreadId : 0, - (uint64_t)mutex, - (unsigned)snapshot.State, - (uint64_t)snapshot.WaitingMutex); -} - -typedef struct KI_SYNC_TIMEOUT_SELFTEST_STATE { - MUTEX* Mutex; - EVENT* ReadyEvent; - EVENT* FinishedEvent; - volatile KSTATUS MutexStatus; -} KI_SYNC_TIMEOUT_SELFTEST_STATE; - -// Waits on the held mutex long enough for the timeout engine to remove donation state. -static void KiSynchronizationTimeoutWorker(void* argument) { - KI_SYNC_TIMEOUT_SELFTEST_STATE* state = (KI_SYNC_TIMEOUT_SELFTEST_STATE*)argument; - KeSignalEvent(state->ReadyEvent); - - state->MutexStatus = KeAcquireMutex(state->Mutex, SCHED_TICK_MS * 20ULL); - if (KE_MUTEX_ACQUIRE_SUCCEEDED(state->MutexStatus)) - KeReleaseMutex(state->Mutex); - - KeSignalEvent(state->FinishedEvent); - KeExitThread(KeGetCurrentThread(), 0); -} - -// Verifies finite mutex and semaphore waits consume no object state after timeout. -static void KiRunSynchronizationTimeoutSelfTest(void) { - DbgPrint("SYNC/TIMEOUT selftest: begin\n"); - - KI_SYNC_TIMEOUT_SELFTEST_STATE state; - memset(&state, 0, sizeof(state)); - state.MutexStatus = NTLSTATUS_PENDING; - - KSTATUS status = KeMutexCreate(&state.Mutex); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: mutex create failed status=%u\n", status); - - status = KeEventCreate(EventAutoReset, 0, &state.ReadyEvent); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: ready event create failed status=%u\n", status); - - status = KeEventCreate(EventAutoReset, 0, &state.FinishedEvent); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: finished event create failed status=%u\n", status); - - status = KeAcquireMutex(state.Mutex, KE_WAIT_INFINITE); - if (status != NTLSTATUS_SUCCESS) - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: owner acquire failed status=%u\n", status); - - THREAD* worker = (THREAD*)0; - const KE_THREAD_CREATE_PARAMETERS parameters = - KE_THREAD_CREATE_NAMED_REALTIME(12, "selftest.sync.timeout"); - status = KeCreateKernelThread((uintptr_t)KiSynchronizationTimeoutWorker, - (uintptr_t)&state, - ¶meters, - &worker); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: worker create failed status=%u\n", status); - - status = KeWaitForEvent(state.ReadyEvent, 0); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: ready wait failed status=%u\n", status); - - (void)KiSchedulerAwaitMutexWait(worker, state.Mutex); - - THREAD* self = KeGetCurrentThread(); - KE_THREAD_SCHEDULER_SNAPSHOT snapshot; - KiSchedulerSnapshotThread(self, &snapshot); - if (snapshot.EffectiveSchedulingClass != KeSchedulingRealtime || - snapshot.EffectivePriority != 12) { - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: finite waiter donation missing class=%u priority=%u\n", - snapshot.EffectiveSchedulingClass, - snapshot.EffectivePriority); - } - - status = KeWaitForEvent(state.FinishedEvent, 0); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: finished wait failed status=%u\n", status); - if (state.MutexStatus != NTLSTATUS_TIMEOUT) - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: mutex wait returned status=%u\n", - state.MutexStatus); - - KiSchedulerSnapshotThread(self, &snapshot); - if (snapshot.EffectiveSchedulingClass != snapshot.BaseSchedulingClass || - snapshot.EffectivePriority != snapshot.BasePriority) { - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: timed-out donation remained base=%u/%u effective=%u/%u\n", - snapshot.BaseSchedulingClass, - snapshot.BasePriority, - snapshot.EffectiveSchedulingClass, - snapshot.EffectivePriority); - } - - KeReleaseMutex(state.Mutex); - status = KeAcquireMutex(state.Mutex, 0); - if (status != NTLSTATUS_SUCCESS) - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: zero-timeout mutex acquire failed status=%u\n", status); - KeReleaseMutex(state.Mutex); - - SEMAPHORE* semaphore = (SEMAPHORE*)0; - status = KeSemaphoreCreate(0, 1, &semaphore); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: semaphore create failed status=%u\n", status); - - status = KeWaitSemaphore(semaphore, 0); - if (status != NTLSTATUS_TIMEOUT) - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: zero-timeout semaphore wait returned status=%u\n", status); - - status = KeWaitSemaphore(semaphore, SCHED_TICK_MS * 3ULL); - if (status != NTLSTATUS_TIMEOUT) - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: finite semaphore wait returned status=%u\n", status); - - KeSignalSemaphore(semaphore); - status = KeWaitSemaphore(semaphore, 0); - if (status != NTLSTATUS_SUCCESS) - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: signaled semaphore probe failed status=%u\n", status); - status = KeWaitSemaphore(semaphore, 0); - if (status != NTLSTATUS_TIMEOUT) - KePanic(KE_PANIC_CODE_SELFTEST, - "SYNC/TIMEOUT selftest: semaphore count was consumed twice status=%u\n", status); - - ObDereferenceObject(&worker->Header); - ObDereferenceObject(&semaphore->Header); - ObDereferenceObject(&state.Mutex->Header); - ObDereferenceObject(&state.ReadyEvent->Header); - ObDereferenceObject(&state.FinishedEvent->Header); - - DbgPrint("SYNC/TIMEOUT selftest: PASS\n"); -} - -// Owns the link mutex and then blocks behind the self-test thread. -static void KiSchedulerChainWorker(void* argument) { - KI_SCHEDULER_SELFTEST_STATE* state = (KI_SCHEDULER_SELFTEST_STATE*)argument; - - KSTATUS status = KeAcquireMutex(state->LinkMutex, KE_WAIT_INFINITE); - if (status != NTLSTATUS_SUCCESS) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: chain link acquire failed status=%u\n", status); - - KeSignalEvent(state->ChainReadyEvent); - state->ChainAcquireStatus = KeAcquireMutex(state->GateMutex, KE_WAIT_INFINITE); - if (state->ChainAcquireStatus != NTLSTATUS_SUCCESS) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: chain gate acquire failed status=%u\n", - state->ChainAcquireStatus); - - KeReleaseMutex(state->GateMutex); - KeReleaseMutex(state->LinkMutex); - KeSignalEvent(state->ChainFinishedEvent); - KeExitThread(KeGetCurrentThread(), 0); -} - -// Blocks a real-time thread behind the normal chain worker. -static void KiSchedulerRealtimeWorker(void* argument) { - KI_SCHEDULER_SELFTEST_STATE* state = (KI_SCHEDULER_SELFTEST_STATE*)argument; - KeSignalEvent(state->RealtimeReadyEvent); - - state->RealtimeAcquireStatus = KeAcquireMutex(state->LinkMutex, KE_WAIT_INFINITE); - if (state->RealtimeAcquireStatus != NTLSTATUS_SUCCESS) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: real-time acquire failed status=%u\n", - state->RealtimeAcquireStatus); - - KeReleaseMutex(state->LinkMutex); - KeSignalEvent(state->RealtimeFinishedEvent); - KeExitThread(KeGetCurrentThread(), 0); -} - -// Verifies real-time dispatch and transitive mutex priority inheritance. -static void KiRunRealtimeSchedulerSelfTest(void) { - DbgPrint("SCHED selftest: begin\n"); - - THREAD* self = KeGetCurrentThread(); - if (!self || self->BaseSchedulingClass != KeSchedulingNormal) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: caller is not a normal thread\n"); - - KE_SCHED_STATS before; - KE_SCHED_STATS after; - KeGetSchedulerStats(&before); - - KI_SCHEDULER_SELFTEST_STATE state; - memset(&state, 0, sizeof(state)); - state.ChainAcquireStatus = NTLSTATUS_PENDING; - state.RealtimeAcquireStatus = NTLSTATUS_PENDING; - - KSTATUS status = KeMutexCreate(&state.GateMutex); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: gate mutex create failed status=%u\n", status); - - status = KeMutexCreate(&state.LinkMutex); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: link mutex create failed status=%u\n", status); - - status = KeEventCreate(EventAutoReset, 0, &state.ChainReadyEvent); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: chain ready event create failed status=%u\n", status); - - status = KeEventCreate(EventAutoReset, 0, &state.RealtimeReadyEvent); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: real-time ready event create failed status=%u\n", status); - - status = KeEventCreate(EventAutoReset, 0, &state.ChainFinishedEvent); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: chain finished event create failed status=%u\n", status); - - status = KeEventCreate(EventAutoReset, 0, &state.RealtimeFinishedEvent); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: real-time finished event create failed status=%u\n", status); - - status = KeAcquireMutex(state.GateMutex, KE_WAIT_INFINITE); - if (status != NTLSTATUS_SUCCESS) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: owner acquire failed status=%u\n", status); - - THREAD* chainWorker = (THREAD*)0; - const KE_THREAD_CREATE_PARAMETERS chainParameters = - KE_THREAD_CREATE_NAMED_NORMAL(PriorityNormal, "selftest.sched.chain"); - status = KeCreateKernelThread((uintptr_t)KiSchedulerChainWorker, - (uintptr_t)&state, - &chainParameters, - &chainWorker); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: chain worker create failed status=%u\n", status); - - status = KeWaitForEvent(state.ChainReadyEvent, 0); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: chain ready wait failed status=%u\n", status); - - (void)KiSchedulerAwaitMutexWait(chainWorker, state.GateMutex); - - status = KeAcquireMutex(state.LinkMutex, KE_WAIT_INFINITE); - if (status != NTLSTATUS_DEADLOCK) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: mutex cycle was not rejected status=%u\n", status); - - THREAD* realtimeWorker = (THREAD*)0; - const KE_THREAD_CREATE_PARAMETERS realtimeParameters = - KE_THREAD_CREATE_NAMED_REALTIME(16, "selftest.sched.rt"); - status = KeCreateKernelThread((uintptr_t)KiSchedulerRealtimeWorker, - (uintptr_t)&state, - &realtimeParameters, - &realtimeWorker); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: real-time worker create failed status=%u\n", status); - - status = KeWaitForEvent(state.RealtimeReadyEvent, 0); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: real-time ready wait failed status=%u\n", status); - - KE_THREAD_SCHEDULER_SNAPSHOT realtimeSnapshot = - KiSchedulerAwaitMutexWait(realtimeWorker, state.LinkMutex); - (void)realtimeSnapshot; - - KE_THREAD_SCHEDULER_SNAPSHOT chainSnapshot; - KE_THREAD_SCHEDULER_SNAPSHOT selfSnapshot; - KiSchedulerSnapshotThread(chainWorker, &chainSnapshot); - KiSchedulerSnapshotThread(self, &selfSnapshot); - - if (chainSnapshot.EffectiveSchedulingClass != KeSchedulingRealtime || - chainSnapshot.EffectivePriority != 16) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: intermediate donation missing class=%u priority=%u\n", - chainSnapshot.EffectiveSchedulingClass, - chainSnapshot.EffectivePriority); - - if (selfSnapshot.EffectiveSchedulingClass != KeSchedulingRealtime || - selfSnapshot.EffectivePriority != 16) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: transitive donation missing class=%u priority=%u\n", - selfSnapshot.EffectiveSchedulingClass, - selfSnapshot.EffectivePriority); - - KeReleaseMutex(state.GateMutex); - - KiSchedulerSnapshotThread(self, &selfSnapshot); - if (selfSnapshot.EffectiveSchedulingClass != selfSnapshot.BaseSchedulingClass || - selfSnapshot.EffectivePriority != selfSnapshot.BasePriority) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: inherited priority was not restored base=%u/%u effective=%u/%u\n", - selfSnapshot.BaseSchedulingClass, - selfSnapshot.BasePriority, - selfSnapshot.EffectiveSchedulingClass, - selfSnapshot.EffectivePriority); - - status = KeWaitForEvent(state.ChainFinishedEvent, 0); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: chain finished wait failed status=%u\n", status); - - status = KeWaitForEvent(state.RealtimeFinishedEvent, 0); - if (!KSUCCESS(status)) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: real-time finished wait failed status=%u\n", status); - - if (state.ChainAcquireStatus != NTLSTATUS_SUCCESS || - state.RealtimeAcquireStatus != NTLSTATUS_SUCCESS) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: handoff statuses chain=%u realtime=%u\n", - state.ChainAcquireStatus, - state.RealtimeAcquireStatus); - - KeGetSchedulerStats(&after); - if (after.RealtimeDispatches <= before.RealtimeDispatches || - after.PriorityInheritanceBoosts < before.PriorityInheritanceBoosts + 2 || - after.PriorityInheritanceDrops < before.PriorityInheritanceDrops + 2 || - after.PriorityInheritancePropagations < before.PriorityInheritancePropagations + 2 || - after.PriorityInheritanceMaxDepth < 2) - KePanic(KE_PANIC_CODE_SELFTEST, - "SCHED selftest: scheduler counters did not prove transitive inheritance\n"); - - ObDereferenceObject(&chainWorker->Header); - ObDereferenceObject(&realtimeWorker->Header); - ObDereferenceObject(&state.GateMutex->Header); - ObDereferenceObject(&state.LinkMutex->Header); - ObDereferenceObject(&state.ChainReadyEvent->Header); - ObDereferenceObject(&state.RealtimeReadyEvent->Header); - ObDereferenceObject(&state.ChainFinishedEvent->Header); - ObDereferenceObject(&state.RealtimeFinishedEvent->Header); - - DbgPrint("SCHED selftest: PASS\n"); -} - -// Runs trap Frame Self Test. -static void KiRunTrapFrameSelfTest(void) { - DbgPrint("TRAP selftest: begin\n"); - - if (KeGetCurrentTrapFrame()) - KePanic(KE_PANIC_CODE_SELFTEST, "TRAP selftest: kernel selftest unexpectedly has active trap frame\n"); - - DbgPrint("TRAP selftest: frame size=%llu query size=%llu\n", - (uint64_t)sizeof(KTRAP_FRAME), - (uint64_t)sizeof(SYS_TRAP_FRAME_INFO_K)); - - if (sizeof(KTRAP_FRAME) != 240) - KePanic(KE_PANIC_CODE_SELFTEST, "TRAP selftest: KTRAP_FRAME size mismatch %llu\n", - (uint64_t)sizeof(KTRAP_FRAME)); - - DbgPrint("TRAP selftest: PASS\n"); -} - -// Runs core Hardening Self Tests. -void KiRunCoreHardeningSelfTests(void) { - DbgPrint("CORE selftest: begin\n"); - - if (gCpuCount == 0 || gCpuCount > KE_MAX_CPUS) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: invalid CPU count %u\n", gCpuCount); - +void KiCheckBootState(void) { KCPU* cpu = KeGetCurrentCpu(); - if (cpu != &gCpus[0]) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: current CPU mismatch cpu=0x%016llX cpu0=0x%016llX\n", - (uint64_t)cpu, (uint64_t)&gCpus[0]); - if (cpu->Self != cpu) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: GS/KCPU self pointer mismatch self=0x%016llX cpu=0x%016llX\n", - (uint64_t)cpu->Self, (uint64_t)cpu); - if (KeGetCurrentCpuIndex() != 0) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: current CPU index is not boot processor/0\n"); - if (cpu->State != KeCpuOnline || !cpu->IsBootProcessor) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: boot-processor lifecycle invalid state=%ld boot=%u\n", - cpu->State, cpu->IsBootProcessor); - if (KeGetCurrentIrql() != PASSIVE_LEVEL) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: expected PASSIVE_LEVEL, got %u\n", KeGetCurrentIrql()); - if (KeGetCurrentTss() != &cpu->Tss) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: current TSS is not CPU-local\n"); - if (!cpu->Tss.RSP0 || cpu->Tss.IoMapBase != sizeof(TSS64)) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: invalid boot-processor TSS rsp0=0x%016llX iomap=%u\n", - cpu->Tss.RSP0, cpu->Tss.IoMapBase); - if (!cpu->Gdtr.Base || cpu->Gdtr.Base != (uint64_t)cpu->Gdt) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: GDT is not CPU-local base=0x%016llX gdt=0x%016llX\n", - cpu->Gdtr.Base, (uint64_t)cpu->Gdt); - - for (uint32_t i = 1; i < gCpuCount; i++) { - KCPU* ap = &gCpus[i]; - if (ap->State == KeCpuAbsent) - continue; - if (ap->State != KeCpuOnline || ap->IsBootProcessor) - KePanic(KE_PANIC_CODE_SELFTEST, - "CORE selftest: AP%u lifecycle invalid state=%ld boot=%u\n", - i, ap->State, ap->IsBootProcessor); - if (!ap->KernelStack || !ap->KernelStackTop) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: AP%u missing kernel stack\n", i); - if (!ap->Tss.RSP0 || ap->Tss.IoMapBase != sizeof(TSS64)) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: AP%u invalid TSS rsp0=0x%016llX iomap=%u\n", - i, ap->Tss.RSP0, ap->Tss.IoMapBase); - if (!ap->Gdtr.Base || ap->Gdtr.Base != (uint64_t)ap->Gdt) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: AP%u GDT not CPU-local\n", i); - if (!ap->IdleThread || !ap->CurrentThread || ap->CurrentThread != ap->IdleThread) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: AP%u idle/current thread missing\n", i); - if (!ap->IdleThread->IsIdleThread || ap->IdleThread->Queued || ap->IdleThread->RunningCpu != i) - KePanic(KE_PANIC_CODE_SELFTEST, - "CORE selftest: AP%u idle invariant failed queued=%u idle=%u runningCpu=%u\n", - i, ap->IdleThread->Queued, ap->IdleThread->IsIdleThread, ap->IdleThread->RunningCpu); - } - - THREAD* cur = KeGetCurrentThread(); - if (!cur || cur != cpu->CurrentThread) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: current thread mismatch\n"); - if (!cpu->IdleThread || cur != cpu->IdleThread) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: scheduler did not start on CPU0 idle thread\n"); - if (!cur->IsIdleThread || cur->Queued || cur->RunningCpu != cpu->CpuIndex) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: idle invariant failed queued=%u idle=%u runningCpu=%u\n", - cur->Queued, cur->IsIdleThread, cur->RunningCpu); - - DbgPrint("CORE selftest: PASS\n"); + THREAD* thread = KeGetCurrentThread(); + if (!cpu || gCpuCount == 0 || gCpuCount > KE_MAX_CPUS || + cpu != &gCpus[0] || cpu->Self != cpu || + cpu->State != KeCpuOnline || !cpu->IsBootProcessor || + KeGetCurrentIrql() != PASSIVE_LEVEL || + !thread || thread != cpu->CurrentThread || + thread != cpu->IdleThread || !thread->IsIdleThread) + KePanic(KE_PANIC_CODE_SELFTEST, "boot state: invalid bootstrap CPU or idle thread\n"); } -// Runs early Kernel Self Tests. -void KiRunEarlyKernelSelfTests(void) { - KSTATUS s = KeRunApplicationProcessorSchedulerSelfTests(); - if (!KSUCCESS(s)) { - KePanic(KE_PANIC_CODE_SELFTEST, - "SMP scheduler selftest failed status=0x%x\n", - s); - } -} - -// Runs kernel Self Tests. -void KiRunKernelSelfTests(const HAL_API* hal) { - (void)hal; - DbgPrint("Kernel selftests: begin\n"); - KiRunSynchronizationTimeoutSelfTest(); - KiRunRealtimeSchedulerSelfTest(); +void KiRunKernelSelfTests(void) { + DbgPrint("Kernel diagnostics: begin\n"); KiRunApcWaitRaceSelfTest(); - KiRunPfnDatabaseSelfTest(); - KiRunDmaSelfTest(); - KiRunMemoryMappingSelfTest(); - KiRunVadSparseSelfTest(); KiRunVadCopyOnWriteSelfTest(); - KiRunVadSwapSelfTest(); - KiRunStringViewSelfTest(); - KiRunVolumeRelativePathSelfTest(); - KiRunAsyncFileIoSelfTest(); - KiRunTrapFrameSelfTest(); - DbgPrint("Kernel selftests: PASS\n"); + DbgPrint("Kernel diagnostics: PASS\n"); } diff --git a/code/kernel/ke_smp.c b/code/kernel/ke_smp.c index 7e64c32..813d52e 100644 --- a/code/kernel/ke_smp.c +++ b/code/kernel/ke_smp.c @@ -863,218 +863,3 @@ KSTATUS KeAdmitApplicationProcessorsToScheduler(void) { DbgPrint("SMP: scheduler admitted %u APs\n", admitted); return NTLSTATUS_SUCCESS; } - -#define SMP_AP_WORKER_ROUNDS 64u -#define SMP_AP_WORKER_TIMEOUT_US 2000000ull -#define SMP_AP_TEST_MAGIC 0x524B575041504D53ULL // 'SMPAPWKR' - -typedef struct SMP_AP_WORK_TEST SMP_AP_WORK_TEST; - -typedef struct SMP_AP_WORKER_ARG { - uint64_t Magic; - uint32_t CpuIndex; - uint32_t WorkerIndex; - struct SMP_AP_WORK_TEST* Test; - EVENT* StepEvent; - volatile int32_t Ready; - volatile int32_t WaitArmed; - volatile int32_t Progress; - volatile int32_t Done; - volatile int32_t Failure; - uint32_t WrongCpu; - uint32_t WrongRunningCpu; -} SMP_AP_WORKER_ARG; - -struct SMP_AP_WORK_TEST { - uint64_t Magic; - uint32_t WorkerCount; - volatile int32_t ReadyCount; - volatile int32_t DoneCount; - volatile int32_t FailureCount; - volatile int32_t CpuHits[KE_MAX_CPUS]; - SMP_AP_WORKER_ARG Workers[KE_MAX_CPUS]; -}; - -// Implements the smp Ap Controlled Worker operation. -static void KiSmpApControlledWorker(void* context) { - SMP_AP_WORKER_ARG* arg = (SMP_AP_WORKER_ARG*)context; - if (!arg || arg->Magic != SMP_AP_TEST_MAGIC || !arg->StepEvent) { - KePanic(KE_PANIC_CODE_SELFTEST, "SMP AP worker received bad context 0x%016llX\n", (uint64_t)arg); - } - - NtlAtomicExchange32(&arg->Ready, 1); - NtlAtomicIncrement32(&arg->Test->ReadyCount); - - for (uint32_t round = 0; round < SMP_AP_WORKER_ROUNDS; round++) { - NtlAtomicIncrement32(&arg->WaitArmed); - KSTATUS waitStatus = KeWaitForEvent(arg->StepEvent, 0); - if (!KSUCCESS(waitStatus)) { - NtlAtomicExchange32(&arg->Failure, 1); - break; - } - - KCPU* cpu = KeGetCurrentCpu(); - THREAD* self = KeGetCurrentThread(); - if (!cpu || cpu->CpuIndex != arg->CpuIndex) { - arg->WrongCpu = cpu ? cpu->CpuIndex : KE_INVALID_CPU; - NtlAtomicExchange32(&arg->Failure, 1); - break; - } - if (!self || self->RunningCpu != cpu->CpuIndex) { - arg->WrongRunningCpu = self ? self->RunningCpu : KE_INVALID_CPU; - NtlAtomicExchange32(&arg->Failure, 1); - break; - } - if (self && (self->AffinityMask & (1ULL << cpu->CpuIndex)) == 0) { - arg->WrongCpu = cpu->CpuIndex; - NtlAtomicExchange32(&arg->Failure, 1); - break; - } - - if (cpu->CpuIndex < KE_MAX_CPUS) - NtlAtomicIncrement32(&arg->Test->CpuHits[cpu->CpuIndex]); - - NtlAtomicIncrement32(&arg->Progress); - if ((round & 3u) == 0) - KeYield(); - } - - if (NtlAtomicLoad32(&arg->Failure) != 0) { - NtlAtomicIncrement32(&arg->Test->FailureCount); - } - - NtlAtomicExchange32(&arg->Done, 1); - NtlAtomicIncrement32(&arg->Test->DoneCount); -} - -// Waits for for Worker Counter. -static bool KiWaitForWorkerCounter(volatile int32_t* counter, int32_t expected, const char* what) { - uint64_t deadline = KiSmpDeadlineAfter(SMP_AP_WORKER_TIMEOUT_US); - for (;;) { - if (NtlAtomicLoad32(counter) >= expected) - return true; - if (KiSmpQueryTicks() >= deadline) - break; - KeYield(); - } - DbgPrint("SMP scheduler selftest timeout waiting for %s expected=%d got=%ld\n", - what ? what : "counter", expected, NtlAtomicLoad32(counter)); - return false; -} - -// Runs application Processor Scheduler Self Tests. -KSTATUS KeRunApplicationProcessorSchedulerSelfTests(void) { - if (gCpuCount <= 1) { - DbgPrint("SMP scheduler selftest: SKIP, no APs\n"); - return NTLSTATUS_SUCCESS; - } - - DbgPrint("SMP scheduler selftest: begin AP-affine workers\n"); - - SMP_AP_WORK_TEST* test = (SMP_AP_WORK_TEST*)MmAllocatePool(sizeof(SMP_AP_WORK_TEST)); - if (!test) return NTLSTATUS_NO_MEMORY; - memset(test, 0, sizeof(*test)); - test->Magic = SMP_AP_TEST_MAGIC; - - uint32_t workerCount = 0; - for (uint32_t cpuIndex = 1; cpuIndex < gCpuCount; cpuIndex++) { - KCPU* cpu = &gCpus[cpuIndex]; - if (cpu->State != KeCpuOnline) - continue; - if (cpuIndex >= 64) - continue; - - SMP_AP_WORKER_ARG* arg = &test->Workers[workerCount]; - arg->Magic = SMP_AP_TEST_MAGIC; - arg->CpuIndex = cpuIndex; - arg->WorkerIndex = workerCount; - arg->Test = test; - - KSTATUS st = KeEventCreate(EventAutoReset, 0, &arg->StepEvent); - if (!KSUCCESS(st)) { - KePanic(KE_PANIC_CODE_SELFTEST, - "SMP scheduler selftest: AP%u step event create failed status=0x%x\n", - cpuIndex, st); - } - - KE_THREAD_CREATE_PARAMETERS workerParameters = - KE_THREAD_CREATE_NAMED_NORMAL(PriorityNormal, "selftest.smp"); - workerParameters.AffinityMask = 1ULL << cpuIndex; - st = KeCreateKernelThread((uintptr_t)KiSmpApControlledWorker, - (uintptr_t)arg, - &workerParameters, - (THREAD**)0); - if (!KSUCCESS(st)) { - KePanic(KE_PANIC_CODE_SELFTEST, - "SMP scheduler selftest: AP%u worker create failed status=0x%x\n", - cpuIndex, st); - } - - workerCount++; - } - test->WorkerCount = workerCount; - - if (workerCount == 0) { - MmFreePool(test); - DbgPrint("SMP scheduler selftest: SKIP, no online AP workers\n"); - return NTLSTATUS_SUCCESS; - } - - if (!KiWaitForWorkerCounter(&test->ReadyCount, (int32_t)workerCount, "AP worker ready")) { - KePanic(KE_PANIC_CODE_SELFTEST, "SMP scheduler selftest: workers failed to become ready ready=%ld workers=%u\n", - test->ReadyCount, workerCount); - } - - for (uint32_t round = 0; round < SMP_AP_WORKER_ROUNDS; round++) { - for (uint32_t w = 0; w < workerCount; w++) { - SMP_AP_WORKER_ARG* arg = &test->Workers[w]; - if (!KiWaitForWorkerCounter(&arg->WaitArmed, (int32_t)(round + 1), "AP worker wait-armed")) { - KePanic(KE_PANIC_CODE_SELFTEST, - "SMP scheduler selftest: AP%u did not arm wait for round %u armed=%ld progress=%ld\n", - arg->CpuIndex, round, arg->WaitArmed, arg->Progress); - } - KeSignalEvent(arg->StepEvent); - } - - for (uint32_t w = 0; w < workerCount; w++) { - SMP_AP_WORKER_ARG* arg = &test->Workers[w]; - if (!KiWaitForWorkerCounter(&arg->Progress, (int32_t)(round + 1), "AP worker progress")) { - KePanic(KE_PANIC_CODE_SELFTEST, - "SMP scheduler selftest: AP%u no progress round=%u progress=%ld failure=%ld wrongCpu=%u wrongRunningCpu=%u\n", - arg->CpuIndex, round, arg->Progress, arg->Failure, - arg->WrongCpu, arg->WrongRunningCpu); - } - if (NtlAtomicLoad32(&arg->Failure) != 0) { - KePanic(KE_PANIC_CODE_SELFTEST, - "SMP scheduler selftest: AP%u worker failed round=%u wrongCpu=%u wrongRunningCpu=%u progress=%ld\n", - arg->CpuIndex, round, arg->WrongCpu, arg->WrongRunningCpu, arg->Progress); - } - } - } - - if (!KiWaitForWorkerCounter(&test->DoneCount, (int32_t)workerCount, "AP worker done")) { - KePanic(KE_PANIC_CODE_SELFTEST, "SMP scheduler selftest: workers did not finish done=%d workers=%u\n", - test->DoneCount, workerCount); - } - - if (NtlAtomicLoad32(&test->FailureCount) != 0) { - KePanic(KE_PANIC_CODE_SELFTEST, "SMP scheduler selftest: failure count=%d\n", - test->FailureCount); - } - - for (uint32_t w = 0; w < workerCount; w++) { - SMP_AP_WORKER_ARG* arg = &test->Workers[w]; - int32_t hits = (arg->CpuIndex < KE_MAX_CPUS) ? test->CpuHits[arg->CpuIndex] : 0; - if (hits <= 0) { - KePanic(KE_PANIC_CODE_SELFTEST, - "SMP scheduler selftest: AP%u recorded no CPU hits\n", arg->CpuIndex); - } - if (arg->StepEvent) - ObDereferenceObject(&arg->StepEvent->Header); - } - - DbgPrint("SMP scheduler selftest: PASS workers=%u rounds=%u\n", - workerCount, SMP_AP_WORKER_ROUNDS); - MmFreePool(test); - return NTLSTATUS_SUCCESS; -} diff --git a/code/kernel/ke_syscall.c b/code/kernel/ke_syscall.c index b9d2fad..ab7ffde 100644 --- a/code/kernel/ke_syscall.c +++ b/code/kernel/ke_syscall.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -317,6 +318,40 @@ static size_t KiAlignUpSize(size_t value, size_t alignment) { // Individual syscall handlers. +static uint64_t SyscallOpenPowerEvent(SYS_HANDLE* handleOut) { + if (!handleOut) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS st = MmProbeUserWrite(handleOut, sizeof(*handleOut)); + if (!KSUCCESS(st)) return st; + PROCESS* caller = KeGetCurrentProcess(); + EVENT* event = KePowerRequestEvent(caller); + if (!event) return NTLSTATUS_ACCESS_DENIED; + SYS_HANDLE handle = SYS_HANDLE_INVALID; + st = KiAllocHandleForCurrentWithRights(&event->Header, + SYS_RIGHT_WAIT | SYS_RIGHT_QUERY, &handle); + if (!KSUCCESS(st)) return st; + st = KiCopyHandleToUser(handleOut, handle); + if (!KSUCCESS(st)) (void)KeCloseHandle(caller, handle); + return st; +} + +static uint64_t SyscallRequestSystemPower(uint32_t action) { + KSTATUS st = KiRequirePrivilege(SYS_PRIV_SYSTEM_POWER); + return KSUCCESS(st) ? KePowerRequest(action) : st; +} + +static uint64_t SyscallQuerySystemPower(uint32_t* actionOut) { + if (!actionOut) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS st = MmProbeUserWrite(actionOut, sizeof(*actionOut)); + if (!KSUCCESS(st)) return st; + uint32_t action = 0; + st = KePowerGetRequest(KeGetCurrentProcess(), &action); + return KSUCCESS(st) ? KiCopyU32ToUser(actionOut, action) : st; +} + +static uint64_t SyscallCompleteSystemPower(uint32_t action) { + return KePowerComplete(KeGetCurrentProcess(), action); +} + // Implements SYS_EXIT_PROCESS. static uint64_t SyscallExitProcess(int32_t exitCode) { THREAD* t = KeGetCurrentThread(); @@ -2873,12 +2908,12 @@ static uint64_t SyscallStartProcess(const SYS_START_PROCESS_REQUEST* request) { } for (uint32_t i = 0; i < req.StartupCapabilityCount && KSUCCESS(status); ++i) { - if (!capabilities[i].Tag || capabilities[i].Handle == SYS_HANDLE_INVALID) { + if (!SysStartupCapabilityNameValid(capabilities[i].Name) || capabilities[i].Handle == SYS_HANDLE_INVALID) { status = NTLSTATUS_INVALID_PARAMETER; break; } for (uint32_t j = 0; j < i; ++j) { - if (capabilities[j].Tag == capabilities[i].Tag) { + if (strcmp(capabilities[j].Name, capabilities[i].Name) == 0) { status = NTLSTATUS_OBJECT_NAME_COLLISION; break; } @@ -3414,48 +3449,48 @@ static uint64_t SyscallOpenPortPeerProcess(SYS_HANDLE endpointHandle, return status; } -// Implements SYS_REGISTER_SYSTEM_SERVICE. -static uint64_t SyscallRegisterSystemService(SYS_SERVICE_ID serviceId, SYS_HANDLE listenerHandle) { - if (serviceId == SYS_SERVICE_INVALID || serviceId > SYS_SERVICE_MAX) - return NTLSTATUS_INVALID_PARAMETER; - KSTATUS status = KiRequirePrivilege(SYS_PRIV_SYSTEM_SERVICE_REGISTER); +// Implements SYS_REGISTER_SYSTEM_SERVICE using a typed, attenuable registration authority. +static uint64_t SyscallRegisterSystemService(SYS_HANDLE registrationHandle, SYS_HANDLE listenerHandle) { + PROCESS* process = KeGetCurrentProcess(); + if (!process) return NTLSTATUS_FAILURE; + + OBJECT* authority = (OBJECT*)0; + KSTATUS status = KeReferenceHandle(process, registrationHandle, ObjectTypeServiceRegistration, + SYS_SERVICE_REGISTRATION_RIGHT_REGISTER, &authority); if (!KSUCCESS(status)) return status; - PROCESS* process = KeGetCurrentProcess(); - SYS_SERVICE_CLAIMS claim = KeSystemServiceRequiredClaim(serviceId); - if (!process || !process->SecurityContext || claim == 0 || - !SeContextHasServiceClaims(process->SecurityContext, claim)) - return NTLSTATUS_ACCESS_DENIED; + SYS_SERVICE_ID serviceId = SYS_SERVICE_INVALID; + status = KeSystemServiceRegistrationIdentity(authority, &serviceId); + ObDereferenceObject(authority); + if (!KSUCCESS(status)) return status; KSTATUS handleStatus = NTLSTATUS_SUCCESS; - PORT* listener = (PORT*)KiHandleToObject(listenerHandle, - ObjectTypePort, - SYS_PORT_RIGHT_ACCEPT, - &handleStatus); + PORT* listener = (PORT*)KiHandleToObject(listenerHandle, ObjectTypePort, + SYS_PORT_RIGHT_ACCEPT, &handleStatus); if (!listener) return handleStatus; status = KeSystemServiceRegister(serviceId, listener); ObDereferenceObject((OBJECT*)listener); return status; } -// Implements SYS_UNREGISTER_SYSTEM_SERVICE. -static uint64_t SyscallUnregisterSystemService(SYS_SERVICE_ID serviceId, SYS_HANDLE listenerHandle) { - if (serviceId == SYS_SERVICE_INVALID || serviceId > SYS_SERVICE_MAX) - return NTLSTATUS_INVALID_PARAMETER; - KSTATUS status = KiRequirePrivilege(SYS_PRIV_SYSTEM_SERVICE_REGISTER); +// Implements SYS_UNREGISTER_SYSTEM_SERVICE using the same typed authority. +static uint64_t SyscallUnregisterSystemService(SYS_HANDLE registrationHandle, SYS_HANDLE listenerHandle) { + PROCESS* process = KeGetCurrentProcess(); + if (!process) return NTLSTATUS_FAILURE; + + OBJECT* authority = (OBJECT*)0; + KSTATUS status = KeReferenceHandle(process, registrationHandle, ObjectTypeServiceRegistration, + SYS_SERVICE_REGISTRATION_RIGHT_UNREGISTER, &authority); if (!KSUCCESS(status)) return status; - PROCESS* process = KeGetCurrentProcess(); - SYS_SERVICE_CLAIMS claim = KeSystemServiceRequiredClaim(serviceId); - if (!process || !process->SecurityContext || claim == 0 || - !SeContextHasServiceClaims(process->SecurityContext, claim)) - return NTLSTATUS_ACCESS_DENIED; + SYS_SERVICE_ID serviceId = SYS_SERVICE_INVALID; + status = KeSystemServiceRegistrationIdentity(authority, &serviceId); + ObDereferenceObject(authority); + if (!KSUCCESS(status)) return status; KSTATUS handleStatus = NTLSTATUS_SUCCESS; - PORT* listener = (PORT*)KiHandleToObject(listenerHandle, - ObjectTypePort, - SYS_PORT_RIGHT_ACCEPT, - &handleStatus); + PORT* listener = (PORT*)KiHandleToObject(listenerHandle, ObjectTypePort, + SYS_PORT_RIGHT_ACCEPT, &handleStatus); if (!listener) return handleStatus; status = KeSystemServiceUnregister(serviceId, listener); ObDereferenceObject((OBJECT*)listener); @@ -3700,8 +3735,6 @@ static uint64_t SyscallDeriveSecurityContext(const SYS_DERIVE_SECURITY_CONTEXT_R status = SeDeriveSecurityContext(source, req.EffectivePrivileges, req.DelegablePrivileges, - req.ServiceClaims, - req.DelegableServiceClaims, &derived); ObDereferenceObject(&source->Header); if (!KSUCCESS(status)) return status; @@ -6197,6 +6230,10 @@ typedef struct SYSCALL_ENTRY { static const SYSCALL_ENTRY gSyscallTable[SYS_MAX] = { // Process, threads, waits, APC, and exceptions. + [SYS_OPEN_POWER_EVENT] = SYSCALL_ENTRY1(SyscallOpenPowerEvent), + [SYS_REQUEST_SYSTEM_POWER] = SYSCALL_ENTRY1(SyscallRequestSystemPower), + [SYS_QUERY_SYSTEM_POWER] = SYSCALL_ENTRY1(SyscallQuerySystemPower), + [SYS_COMPLETE_SYSTEM_POWER] = SYSCALL_ENTRY1(SyscallCompleteSystemPower), [SYS_EXIT_PROCESS] = SYSCALL_ENTRY1(SyscallExitProcess), [SYS_EXIT_THREAD] = SYSCALL_ENTRY1(SyscallExitThread), [SYS_CREATE_PROCESS_OBJECT] = SYSCALL_ENTRY1(SyscallCreateProcessObject), diff --git a/code/kernel/ke_system_service.c b/code/kernel/ke_system_service.c index 65ade33..8c1e2a1 100644 --- a/code/kernel/ke_system_service.c +++ b/code/kernel/ke_system_service.c @@ -4,7 +4,7 @@ #include #include #include -#include +#include static SPINLOCK gSystemServiceLock = SPINLOCK_INIT; static PORT* gSystemServices[SYS_SERVICE_MAX + 1u]; @@ -14,14 +14,35 @@ static bool KiSystemServiceIdValid(SYS_SERVICE_ID serviceId) { return serviceId > SYS_SERVICE_INVALID && serviceId <= SYS_SERVICE_MAX; } -// Returns the security claim that authorizes publication of one well-known service. -SYS_SERVICE_CLAIMS KeSystemServiceRequiredClaim(SYS_SERVICE_ID serviceId) { - switch (serviceId) { - case SYS_SERVICE_RESOURCE: - return SYS_SERVICE_CLAIM_RESOURCE; - default: - return 0; - } +// Authority is an object capability, never a process privilege or service role. +typedef struct KE_SERVICE_REGISTRATION_AUTHORITY { + OBJECT Header; + SYS_SERVICE_ID ServiceId; +} KE_SERVICE_REGISTRATION_AUTHORITY; + +// Creates a capability to register one numeric service identity. +KSTATUS KeSystemServiceCreateRegistrationAuthority(SYS_SERVICE_ID serviceId, OBJECT** authorityOut) { + if (!authorityOut) return NTLSTATUS_INVALID_PARAMETER; + *authorityOut = (OBJECT*)0; + if (!KiSystemServiceIdValid(serviceId)) return NTLSTATUS_INVALID_PARAMETER; + + KE_SERVICE_REGISTRATION_AUTHORITY* authority = + (KE_SERVICE_REGISTRATION_AUTHORITY*)ObAllocateObject(ObjectTypeServiceRegistration, + sizeof(*authority)); + if (!authority) return NTLSTATUS_NO_MEMORY; + authority->ServiceId = serviceId; + *authorityOut = &authority->Header; + return NTLSTATUS_SUCCESS; +} + +// Resolves an authority's registry identity without consulting process identity. +KSTATUS KeSystemServiceRegistrationIdentity(const OBJECT* object, SYS_SERVICE_ID* serviceIdOut) { + if (!object || !serviceIdOut || object->Type != ObjectTypeServiceRegistration) + return NTLSTATUS_INVALID_PARAMETER; + const KE_SERVICE_REGISTRATION_AUTHORITY* authority = (const KE_SERVICE_REGISTRATION_AUTHORITY*)object; + if (!KiSystemServiceIdValid(authority->ServiceId)) return NTLSTATUS_INVALID_PARAMETER; + *serviceIdOut = authority->ServiceId; + return NTLSTATUS_SUCCESS; } // Registers one weak listener reference without creating a string or pathname namespace. diff --git a/code/kernel/kmain.c b/code/kernel/kmain.c index d5441d7..99d011a 100644 --- a/code/kernel/kmain.c +++ b/code/kernel/kmain.c @@ -2,10 +2,12 @@ #include #include +#include #include #include #pragma intrinsic(_disable) #include +#include #include #include #include @@ -13,6 +15,8 @@ #include #include #include +#include +#include #include #include #include @@ -23,6 +27,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -38,6 +45,7 @@ #include #include #include +#include #include typedef struct { @@ -123,8 +131,6 @@ static void KiInitializeSwapFromSettings(void) { } } -typedef KSTATUS (*DRIVER_ENTRY_FN)(const NTL_DDK* ddk, NTL_DRIVER_INIT* init); - // HAL pointer cached at boot for subsystems that still need platform services. static const HAL_API* gKmainHal; @@ -168,231 +174,179 @@ static void KiReclaimBootloaderMemory(const BOOT_INFO* bootInfo) { KiReclaimBootBlob(bootInfo, list->FilesPhysical, list->PageCount); if (bootInfo->BootIni.PhysicalBase && bootInfo->BootIni.PageCount) KiReclaimBootBlob(bootInfo, bootInfo->BootIni.PhysicalBase, bootInfo->BootIni.PageCount); + if (bootInfo->DriversIni.PhysicalBase && bootInfo->DriversIni.PageCount) + KiReclaimBootBlob(bootInfo, bootInfo->DriversIni.PhysicalBase, bootInfo->DriversIni.PageCount); } -// Returns whether one drivers.ini line character is horizontal whitespace. -static bool KiDriverConfigSpace(char c) { - return c == ' ' || c == '\t' || c == '\r'; -} - -// Trims horizontal whitespace from one immutable drivers.ini line span. -static void KiDriverConfigTrim(const char** begin, const char** end) { - while (*begin < *end && KiDriverConfigSpace(**begin)) - ++*begin; - while (*end > *begin && KiDriverConfigSpace((*end)[-1])) - --*end; -} - -// Returns whether one immutable drivers.ini span exactly matches an ASCII string. -static bool KiDriverConfigSpanEquals(const char* begin, const char* end, const char* text) { - while (begin < end && *text) { - if (*begin++ != *text++) - return false; - } - return begin == end && *text == '\0'; -} - -// Loads one runtime driver filename from the system-root drivers directory. -static KSTATUS KiLoadRuntimeDriverByName(const char* name, const NTL_DDK* ddk) { - char relative[512]; - size_t length = strlen(name); - if (length + 9u > sizeof(relative)) - return NTLSTATUS_BUFFER_TOO_SMALL; - +// Loads a runtime image from the selected system root, keeping PE identity separate +// from the configured module id used for lifecycle and dependency accounting. +static KSTATUS KiLoadRuntimeDriver(const DM_DRIVER_CONFIG* driver) { + char relative[DM_IMAGE_MAX + 8u]; + size_t length = strlen(driver->Image); memcpy(relative, "drivers/", 8); - memcpy(relative + 8, name, length + 1); + memcpy(relative + 8, driver->Image, length + 1); FILE_OBJECT* file = NULL; KSTATUS status = KeOpenSystemRootFile(relative, &file); - if (!KSUCCESS(status)) - return status; + if (!KSUCCESS(status)) return status; - uint64_t driverSize = IoGetFileSize(file); - if (driverSize == 0 || driverSize > SIZE_MAX) { + uint64_t size = IoGetFileSize(file); + if (!size || size > SIZE_MAX) { IoCloseFile(file); return NTLSTATUS_INVALID_PARAMETER; } - - void* driverMemory = MmAllocatePool((size_t)driverSize); - if (!driverMemory) { + void* memory = MmAllocatePool((size_t)size); + if (!memory) { IoCloseFile(file); return NTLSTATUS_NO_MEMORY; } - size_t got = 0; - status = IoReadFile(file, driverMemory, (size_t)driverSize, &got); + status = IoReadFile(file, memory, (size_t)size, &got); IoCloseFile(file); - if (!KSUCCESS(status) || got != (size_t)driverSize) { - MmFreePool(driverMemory); + if (!KSUCCESS(status) || got != (size_t)size) { + MmFreePool(memory); return KSUCCESS(status) ? NTLSTATUS_FAILURE : status; } - - uintptr_t mappedBase = 0; - status = LdrLoadKernelImage(driverMemory, (size_t)driverSize, &mappedBase); - MmFreePool(driverMemory); - if (!KSUCCESS(status)) - return status; - - status = LdrResolveImports(mappedBase); - if (!KSUCCESS(status)) - return status; - - status = LdrRegisterModule(name, mappedBase); - if (!KSUCCESS(status)) - return status; - - DRIVER_ENTRY_FN entry = (DRIVER_ENTRY_FN)LdrGetEntryPoint(mappedBase); - if (!entry) - return NTLSTATUS_FAILURE; - - NTL_DRIVER_INIT init = {0}; - return entry(ddk, &init); + uintptr_t base = 0; + status = LdrLoadKernelImage(memory, (size_t)size, &base); + MmFreePool(memory); + if (!KSUCCESS(status)) return status; + status = LdrResolveImports(base); + if (!KSUCCESS(status)) return status; + status = LdrRegisterModule(driver->Image, base); + if (!KSUCCESS(status)) return status; + status = DmStartLoadedDriver(driver->Id, base); + if (KSUCCESS(status)) + status = LdrSetModuleExportsAvailable(driver->Image, true); + return status; } -// Scans only the [runtime] section of drivers.ini and loads each filename immediately. -static KSTATUS KiLoadRuntimeDriversFromConfig(const char* text, size_t length, const NTL_DDK* ddk) { - int inRuntime = 0; - size_t offset = 0; - - while (offset < length) { - size_t lineStart = offset; - while (offset < length && text[offset] != '\n') - ++offset; - size_t lineEnd = offset; - if (offset < length) - ++offset; - - const char* begin = text + lineStart; - const char* end = text + lineEnd; - KiDriverConfigTrim(&begin, &end); - if (begin == end || *begin == ';' || *begin == '#') - continue; - - if (*begin == '[') { - if (end[-1] != ']') - return NTLSTATUS_INVALID_PARAMETER; - const char* sectionBegin = begin + 1; - const char* sectionEnd = end - 1; - KiDriverConfigTrim(§ionBegin, §ionEnd); - inRuntime = KiDriverConfigSpanEquals(sectionBegin, sectionEnd, "runtime"); - continue; +// Starts a preloaded boot driver by its configuration identity, not array position. +static KSTATUS KiLoadBootDriver(const BOOT_INFO* bootInfo, + const DM_DRIVER_CONFIG* driver) { + const BOOT_FILE* files = (const BOOT_FILE*)(bootInfo->PhysicalMapBase + + bootInfo->BootDrivers.FilesPhysical); + const BOOT_FILE* found = NULL; + for (uint32_t i = 0; i < bootInfo->BootDrivers.Count; ++i) { + if (!strcmp(files[i].DriverId, driver->Id)) { + found = &files[i]; + break; } - - if (!inRuntime) - continue; - - size_t nameLength = (size_t)(end - begin); - if (nameLength == 0 || nameLength >= BOOT_FILE_NAME_MAX) - return NTLSTATUS_INVALID_PARAMETER; - for (const char* p = begin; p < end; ++p) { - if (*p == '=' || *p == '/' || *p == '\\') - return NTLSTATUS_INVALID_PARAMETER; - } - - char name[BOOT_FILE_NAME_MAX]; - memcpy(name, begin, nameLength); - name[nameLength] = '\0'; - DbgPrint("Found runtime driver: %s\n", name); - - KSTATUS status = KiLoadRuntimeDriverByName(name, ddk); - if (!KSUCCESS(status)) - DbgPrint("KiInitializeDrivers: runtime driver %s failed, status=0x%x\n", name, status); } - - return NTLSTATUS_SUCCESS; + if (!found || strcmp(found->Image, driver->Image)) return NTLSTATUS_INVALID_PARAMETER; + uintptr_t base = 0; + KSTATUS status = LdrLoadKernelImage( + (const void*)(bootInfo->PhysicalMapBase + found->PhysicalBase), found->Length, &base); + if (!KSUCCESS(status)) return status; + status = LdrResolveImports(base); + if (!KSUCCESS(status)) return status; + status = LdrRegisterModule(driver->Image, base); + if (!KSUCCESS(status)) return status; + status = DmStartLoadedDriver(driver->Id, base); + if (KSUCCESS(status)) + status = LdrSetModuleExportsAvailable(driver->Image, true); + return status; } -// Initializes drivers. +// Validates that the firmware preloaded exactly the configured boot modules. +static bool KiCheckBootDriverHandoff(const BOOT_INFO* bootInfo, const DM_CONFIG* config) { + const BOOT_FILE* files = (const BOOT_FILE*)(bootInfo->PhysicalMapBase + + bootInfo->BootDrivers.FilesPhysical); + uint32_t expected = 0; + for (uint32_t i = 0; i < config->DriverCount; ++i) { + const DM_DRIVER_CONFIG* driver = &config->Drivers[i]; + if (driver->Phase != DmStartBoot) continue; + ++expected; + uint32_t occurrences = 0; + for (uint32_t j = 0; j < bootInfo->BootDrivers.Count; ++j) { + if (!strcmp(files[j].DriverId, driver->Id) && + !strcmp(files[j].Image, driver->Image)) ++occurrences; + } + if (occurrences != 1) return false; + } + return expected == bootInfo->BootDrivers.Count; +} + +// Starts the lexicographically first eligible driver, independent of INI order. +// Failed providers block requires= dependents, while after= waits for settlement. +static void KiRunDriverPhase(DM_CONFIG* config, const BOOT_INFO* bootInfo, + DM_START_PHASE phase) { + config->ActivePhase = phase; + for (;;) { + int selected = -1; + for (uint32_t i = 0; i < config->DriverCount; ++i) { + const DM_DRIVER_CONFIG* d = &config->Drivers[i]; + if (d->Phase != phase || !DmDriverCanStart(config, i)) continue; + if (selected < 0 || strcmp(d->Id, config->Drivers[selected].Id) < 0) + selected = (int)i; + } + if (selected >= 0) { + DM_DRIVER_CONFIG* d = &config->Drivers[selected]; + DbgPrint("Driver manager: starting %s (%s)\n", d->Id, d->Image); + KSTATUS status = phase == DmStartBoot + ? KiLoadBootDriver(bootInfo, d) : KiLoadRuntimeDriver(d); + d->State = KSUCCESS(status) ? DmRunStarted : DmRunFailed; + if (!KSUCCESS(status)) { + DbgPrint("Driver manager: failed %s, status=0x%x\n", d->Id, status); + if (d->Critical) + KePanic(KE_PANIC_CODE_INIT, + "Critical driver %s failed (0x%x)\n", d->Id, status); + } + continue; + } + bool changed = false; + uint32_t waiting = 0; + for (uint32_t i = 0; i < config->DriverCount; ++i) { + DM_DRIVER_CONFIG* d = &config->Drivers[i]; + if (d->Phase != phase || d->State != DmRunWaiting) continue; + ++waiting; + if (DmDriverIsBlocked(config, i)) { + d->State = DmRunBlocked; + DbgPrint("Driver manager: blocked %s by unsatisfied dependency\n", d->Id); + if (d->Critical) + KePanic(KE_PANIC_CODE_INIT, "Critical driver %s blocked\n", d->Id); + changed = true; + } + } + if (changed) continue; + if (waiting) + KePanic(KE_PANIC_CODE_INIT, + "Driver manager: no eligible drivers in phase %u (waiting=%u)\n", + (unsigned)phase, waiting); + return; + } +} + +static DM_CONFIG* gDriverConfig; + static void KiInitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) { (void)hal; - const NTL_DDK* ddk = IoGetDdk(); - DbgPrint("KiInitializeDrivers: Initializing drivers...\n"); - - // Boot drivers are page-backed raw files supplied by the bootloader. - const BOOT_FILE* bootFiles = bootInfo->BootDrivers.FilesPhysical - ? (const BOOT_FILE*)(bootInfo->PhysicalMapBase + bootInfo->BootDrivers.FilesPhysical) - : NULL; + DM_CONFIG* config = (DM_CONFIG*)MmAllocatePool(sizeof(*config)); + if (!config) + KePanic(KE_PANIC_CODE_INIT, "Driver manager: configuration allocation failed\n"); + const char* text = (const char*)(bootInfo->PhysicalMapBase + + bootInfo->DriversIni.PhysicalBase); + char error[96] = {0}; + if (!DmConfigParse(config, text, bootInfo->DriversIni.Length, error, sizeof(error))) + KePanic(KE_PANIC_CODE_INIT, "Driver manager: invalid drivers.ini: %s\n", error); + if (!KiCheckBootDriverHandoff(bootInfo, config)) + KePanic(KE_PANIC_CODE_INIT, "Driver manager: boot preloads do not match drivers.ini\n"); - for (uint32_t i = 0; bootFiles && i < bootInfo->BootDrivers.Count; ++i) { - const BOOT_FILE* file = &bootFiles[i]; - if (!file->PhysicalBase || !file->Length) { - DbgPrint("KiInitializeDrivers: invalid boot driver descriptor at index %u\n", i); - continue; - } + KSTATUS pnpStatus = IoPnpInitialize(); + if (!KSUCCESS(pnpStatus)) + KePanic(KE_PANIC_CODE_INIT, "PnP initialization failed (0x%x)\n", pnpStatus); - DbgPrint("KiInitializeDrivers: Loading boot driver %s\n", file->Name); - void* rawPeBuffer = (void*)(bootInfo->PhysicalMapBase + file->PhysicalBase); + KiRunDriverPhase(config, bootInfo, DmStartBoot); + pnpStatus = IoPnpWaitForIdle(30000u); + if (!KSUCCESS(pnpStatus)) + KePanic(KE_PANIC_CODE_INIT, "PnP boot startup did not settle (0x%x)\n", pnpStatus); - uintptr_t mappedBase = 0; - KSTATUS st = LdrLoadKernelImage(rawPeBuffer, file->Length, &mappedBase); - if (!KSUCCESS(st)) { - DbgPrint("ERROR: Failed to map driver %s (0x%X)\n", file->Name, st); - continue; - } - - if (!KSUCCESS(LdrResolveImports(mappedBase))) { - DbgPrint("ERROR: Failed to resolve imports for %s\n", file->Name); - continue; - } - - KSTATUS registerStatus = LdrRegisterModule(file->Name, mappedBase); - if (!KSUCCESS(registerStatus)) { - DbgPrint("ERROR: Failed to register driver %s (0x%X)\n", file->Name, registerStatus); - continue; - } - - DRIVER_ENTRY_FN entry = (DRIVER_ENTRY_FN)LdrGetEntryPoint(mappedBase); - if (entry) { - NTL_DRIVER_INIT init = {0}; - KSTATUS ds = entry(ddk, &init); - if (!KSUCCESS(ds)) - DbgPrint("WARNING: Driver %s returned failure status 0x%X\n", file->Name, ds); - } - } - - // runtime drivers. - KSTATUS s; - FILE_OBJECT* f; - - s = KeInitializeSystemRootVolume(); - if (s != NTLSTATUS_SUCCESS) { - KePanic(KE_PANIC_CODE_INIT, "KiInitializeDrivers: Failed to find system root volume (status %d)\n", s); - } - - - s = KeOpenSystemRootFile("drivers/drivers.ini", &f); - if (s != NTLSTATUS_SUCCESS) { - KePanic(KE_PANIC_CODE_INIT, "KiInitializeDrivers: Failed to open drivers.ini on system root (status %d)\n", s); - } - - uint64_t fileSize = IoGetFileSize(f); - if (fileSize == 0 || fileSize >= SIZE_MAX) { - IoCloseFile(f); - KePanic(KE_PANIC_CODE_INIT, "KiInitializeDrivers: drivers.ini has an invalid size.\n"); - } - - size_t size = (size_t)fileSize; - char* mem = (char*)MmAllocatePool(size + 1u); - if (!mem) { - IoCloseFile(f); - KePanic(KE_PANIC_CODE_INIT, "KiInitializeDrivers: Failed to allocate drivers.ini buffer.\n"); - } - - size_t got = 0; - KSTATUS readStatus = IoReadFile(f, mem, size, &got); - IoCloseFile(f); - if (!KSUCCESS(readStatus) || got != size) { - MmFreePool(mem); - KePanic(KE_PANIC_CODE_INIT, "KiInitializeDrivers: Failed to read drivers.ini.\n"); - } - mem[got] = '\0'; - - KSTATUS runtimeStatus = KiLoadRuntimeDriversFromConfig(mem, got, ddk); - MmFreePool(mem); - if (!KSUCCESS(runtimeStatus)) { - KePanic(KE_PANIC_CODE_INIT, - "KiInitializeDrivers: drivers.ini [runtime] is malformed, status=0x%x\n", - runtimeStatus); - } + // Root volume readiness depends on real devices, not the driver module state. + KSTATUS status = KeInitializeSystemRootVolume(); + if (!KSUCCESS(status)) + KePanic(KE_PANIC_CODE_INIT, "Driver manager: no system root (0x%x)\n", status); + KiRunDriverPhase(config, bootInfo, DmStartRuntime); + // Retain the authoritative graph and module states for the shutdown slice. + gDriverConfig = config; } // Phase Helper: Load and launch a process from the filesystem. @@ -442,6 +396,7 @@ static PROCESS* KiLaunchUserProcess(const char* filePath, const char* args) { SYS_HANDLE volumeManagerHandle = SYS_HANDLE_INVALID; SYS_HANDLE inputManagerHandle = SYS_HANDLE_INVALID; SYS_HANDLE displayManagerHandle = SYS_HANDLE_INVALID; + SYS_HANDLE registrationAuthorityHandle = SYS_HANDLE_INVALID; s = KeReferenceSystemRootVolume(&systemVolume); if (KSUCCESS(s)) { s = KeCreateHandle(proc, &systemVolume->Header, @@ -493,20 +448,33 @@ static PROCESS* KiLaunchUserProcess(const char* filePath, const char* args) { ObDereferenceObject(&displayManager->Header); } } + if (KSUCCESS(s)) { + OBJECT* authority = (OBJECT*)0; + s = KeSystemServiceCreateRegistrationAuthority(SYS_SERVICE_RESOURCE, &authority); + if (KSUCCESS(s)) { + s = KeCreateHandle(proc, authority, + SYS_RIGHT_DUPLICATE | SYS_RIGHT_DERIVE | SYS_RIGHT_TRANSFER | + SYS_RIGHT_QUERY | SYS_SERVICE_REGISTRATION_RIGHT_REGISTER | + SYS_SERVICE_REGISTRATION_RIGHT_UNREGISTER, + ®istrationAuthorityHandle); + ObDereferenceObject(authority); + } + } if (!KSUCCESS(s)) { DbgPrint("KiLaunchUserProcess: Failed to create bootstrap startup capabilities. Status: %d\n", s); IoCloseFile(f); ObDereferenceObject(&proc->Header); return 0; } - SYS_STARTUP_CAPABILITY startupCapabilities[5] = { - { SYS_STARTUP_CAP_SYSTEM_VOLUME, systemVolumeHandle }, - { SYS_STARTUP_CAP_SYSTEM_VOLUME_NAMESPACE, namespaceVolumeHandle }, - { SYS_STARTUP_CAP_VOLUME_MANAGER, volumeManagerHandle }, - { SYS_STARTUP_CAP_INPUT_MANAGER, inputManagerHandle }, - { SYS_STARTUP_CAP_DISPLAY_MANAGER, displayManagerHandle }, + SYS_STARTUP_CAPABILITY startupCapabilities[6] = { + { NTL_BOOT_CAP_SYSTEM_VOLUME, systemVolumeHandle }, + { NTL_BOOT_CAP_NAMESPACE_ROOT, namespaceVolumeHandle }, + { NTL_BOOT_CAP_VOLUME_MANAGER, volumeManagerHandle }, + { NTL_BOOT_CAP_INPUT_MANAGER, inputManagerHandle }, + { NTL_BOOT_CAP_DISPLAY_POLICY, displayManagerHandle }, + { NTL_BOOT_CAP_RESOURCE_REGISTRATION, registrationAuthorityHandle }, }; - s = KeSetProcessStartupCapabilities(proc, startupCapabilities, 5); + s = KeSetProcessStartupCapabilities(proc, startupCapabilities, 6); if (!KSUCCESS(s)) { DbgPrint("KiLaunchUserProcess: Failed to install startup capabilities. Status: %d\n", s); IoCloseFile(f); @@ -538,16 +506,20 @@ static PROCESS* KiLaunchUserProcess(const char* filePath, const char* args) { return 0; } + proc->Flags |= KE_PROCESS_FLAG_TERMINATION_PROTECTED; proc->ImageBase = imageBase; proc->ImageSize = LdrGetMappedImageSize(proc, imageBase); uintptr_t entryPoint = LdrGetEntryPoint(imageBase); + KePowerSetInitProcess(proc); + // Create the main thread last. KeCreateThread queues it before returning. const KE_THREAD_CREATE_PARAMETERS threadParameters = KE_THREAD_CREATE_NAMED_NORMAL(PriorityNormal, proc->Name); s = KeCreateThread(proc, (uintptr_t)entryPoint, 0, &threadParameters, &thread); if (!KSUCCESS(s)) { + KePowerSetInitProcess((PROCESS*)0); DbgPrint("KiLaunchUserProcess: Failed to create thread. Status: %d\n", s); proc->ImageBase = 0; proc->ImageSize = 0; @@ -562,16 +534,33 @@ static PROCESS* KiLaunchUserProcess(const char* filePath, const char* args) { return proc; } +static void KiFlushRegisteredDisk(uint8_t disk, DEVICE_OBJECT* device, void* context) { + (void)device; + const char* phase = (const char*)context; + KSTATUS st = IoFlushDisk(disk); + if (!KSUCCESS(st) && st != NTLSTATUS_NOT_FOUND) + DbgPrint("shutdown: %s disk %u flush status 0x%x\n", phase, (unsigned)disk, st); +} + +static void KiFlushRegisteredDisks(const char* phase) { + KSTATUS st = IoEnumerateDiskDevices(KiFlushRegisteredDisk, (void*)phase); + if (!KSUCCESS(st)) + DbgPrint("shutdown: %s disk enumeration failed 0x%x\n", phase, st); +} + // Implements the system Init Thread operation. void SystemInitThread(void* arg) { SYSTEM_INIT_ARGS* args = (SYSTEM_INIT_ARGS*)arg; DbgPrint("SystemInitThread: Thread started. Igniting the system...\n"); - KiRunEarlyKernelSelfTests(); - KiInitializeDrivers(args->BootInfo, args->Hal); + KSTATUS powerStatus = KePowerInitialize(); + if (!KSUCCESS(powerStatus)) + KePanic(KE_PANIC_CODE_INIT, "Power coordinator failed (0x%x)\n", powerStatus); + + KePowerInitializeButton(args->Hal); KeTimeReloadSettings(); KiInitializeSwapFromSettings(); @@ -579,7 +568,7 @@ void SystemInitThread(void* arg) { KiReclaimBootloaderMemory(args->BootInfo); if (args->BootConfig.Debug & KE_DEBUG_SELFTEST) - KiRunKernelSelfTests(args->Hal); + KiRunKernelSelfTests(); // Launch the bootstrap service manager. Its allocated PID is not a system contract. PROCESS* initProcess = KiLaunchUserProcess("system/init.exe", args->BootConfig.CommandLine); @@ -588,12 +577,23 @@ void SystemInitThread(void* arg) { KePanic(KE_PANIC_CODE_INIT, "SystemInitThread: Failed to start system/init.exe.\n"); } - KeWaitForEvent(initProcess->ExitEvent, 0); + EVENT* exits[2] = { KePowerCompletionEvent(), initProcess->ExitEvent }; + uint32_t signaled = 0; + KSTATUS waitStatus = KeWaitForMultipleEvents(exits, 2u, false, + SYS_WAIT_TIMEOUT_INFINITE, &signaled); + if (!KSUCCESS(waitStatus) || signaled != 0u) + KePanic(KE_PANIC_CODE_INIT, "SystemInitThread: init exited without power commit\n"); + (void)KeWaitForEvent(initProcess->ExitEvent, false); int exitCode = initProcess->ExitCode; if (exitCode != 0) { KePanic(KE_PANIC_CODE_INIT, "SystemInitThread: init.exe exited with code %d.\n", exitCode); } - DbgPrint("SystemInitThread: init.exe exited with code %d.\n", exitCode); + DbgPrint("SystemInitThread: power commit accepted; init exited with code %d.\n", exitCode); + uint32_t action = KePowerCommittedAction(); + if (!action) KePanic(KE_PANIC_CODE_INIT, "SystemInitThread: missing power action\n"); + KSTATUS flush = MmFileCacheFlushAll(); + if (!KSUCCESS(flush)) DbgPrint("shutdown: cache flush status 0x%x\n", flush); + KiFlushRegisteredDisks("initial"); ObDereferenceObject(&initProcess->Header); if (args->BootConfig.Debug & (KE_DEBUG_CONSOLE | KE_DEBUG_FILE_LOG)) { @@ -601,6 +601,20 @@ void SystemInitThread(void* arg) { if (!KSUCCESS(logStatus)) DbgPrint("debuglog: shutdown flush failed st=0x%x\n", logStatus); } + flush = MmFileCacheFlushAll(); + if (!KSUCCESS(flush)) DbgPrint("shutdown: final cache flush status 0x%x\n", flush); + KiFlushRegisteredDisks("final"); + KSTATUS prepare = DmPrepareShutdown(); + if (!KSUCCESS(prepare)) DbgPrint("shutdown: prepare status 0x%x\n", prepare); + KSTATUS pnpShutdown = IoPnpShutdown(10000u); + if (!KSUCCESS(pnpShutdown)) + DbgPrint("shutdown: device stop status 0x%x\n", pnpShutdown); + DmCommitShutdown(); + if (action == SYS_POWER_ACTION_RESTART) { + if (!gKmainHal->Power.Reset) + KePanic(KE_PANIC_CODE_INIT, "shutdown: reset HAL callback unavailable\n"); + gKmainHal->Power.Reset(); + } gKmainHal->Power.Shutdown(); MmFreePool(args); } @@ -620,9 +634,11 @@ static bool KiValidateBootFile(const BOOT_FILE* file) { return false; uint64_t capacity = (uint64_t)file->PageCount * PAGE_SIZE; - if ((uint64_t)file->Length + 1u > capacity || file->Name[0] == '\0') + if ((uint64_t)file->Length + 1u > capacity) return false; - return memchr(file->Name, '\0', sizeof(file->Name)) != NULL; + return file->DriverId[0] != '\0' && file->Image[0] != '\0' && + memchr(file->DriverId, '\0', sizeof(file->DriverId)) && + memchr(file->Image, '\0', sizeof(file->Image)); } // Returns whether one fixed core-image descriptor fits inside its page-backed physical allocation. @@ -657,10 +673,20 @@ static bool KiValidateBootInfo(const BOOT_INFO* bootInfo) { return false; } + uint64_t driverIniCapacity = (uint64_t)bootInfo->DriversIni.PageCount * PAGE_SIZE; + if (!KiValidateBootPageRange(bootInfo->DriversIni.PhysicalBase, + bootInfo->DriversIni.PageCount) || + bootInfo->DriversIni.Length == 0 || + bootInfo->DriversIni.Length > DM_CONFIG_MAX_BYTES || + (uint64_t)bootInfo->DriversIni.Length + 1u > driverIniCapacity || + bootInfo->DriversIni.PhysicalBase > UINTPTR_MAX - bootInfo->PhysicalMapBase) + return false; + uint64_t driverListCapacity = (uint64_t)bootInfo->BootDrivers.PageCount * PAGE_SIZE; uint64_t driverListBytes = (uint64_t)bootInfo->BootDrivers.Count * sizeof(BOOT_FILE); if (!KiValidateBootPageRange(bootInfo->BootDrivers.FilesPhysical, bootInfo->BootDrivers.PageCount) || - bootInfo->BootDrivers.Count == 0 || driverListBytes > driverListCapacity || + bootInfo->BootDrivers.Count == 0 || bootInfo->BootDrivers.Count > DM_MAX_DRIVERS || + driverListBytes > driverListCapacity || bootInfo->BootDrivers.FilesPhysical > UINTPTR_MAX - bootInfo->PhysicalMapBase) { return false; } @@ -825,7 +851,8 @@ extern void KernelMain(BOOT_INFO* bootInfo) { KePanic(KE_PANIC_CODE_INIT, "SMP: failed to admit APs to scheduler, status=0x%x\n", smpSchedStatus); } - KiRunCoreHardeningSelfTests(); + if (bootConfig.Debug & KE_DEBUG_SELFTEST) + KiCheckBootState(); // Pack the arguments for the init thread. SYSTEM_INIT_ARGS* initArgs = MmAllocatePool(sizeof(SYSTEM_INIT_ARGS)); diff --git a/code/kernel/ldr_image.c b/code/kernel/ldr_image.c index e4da3eb..85b8592 100644 --- a/code/kernel/ldr_image.c +++ b/code/kernel/ldr_image.c @@ -170,6 +170,7 @@ typedef struct MODULE_ENTRY { char Name[LDR_KERNEL_MODULE_NAME_MAX]; uintptr_t Base; size_t Size; + bool ExportsAvailable; struct MODULE_ENTRY* Next; } MODULE_ENTRY; @@ -182,7 +183,8 @@ static KSTATUS KiEnsureUserImageWrite(PROCESS* process, bool user, uintptr_t add } // Allocates and inserts one global kernel-module registry entry. -static KSTATUS KiRegisterGlobalModule(const char* name, uintptr_t base, size_t size) { +static KSTATUS KiRegisterGlobalModule(const char* name, uintptr_t base, size_t size, + bool exportsAvailable) { if (!name || !*name || !base || !size) return NTLSTATUS_INVALID_PARAMETER; @@ -201,6 +203,7 @@ static KSTATUS KiRegisterGlobalModule(const char* name, uintptr_t base, size_t s entry->Name[nameLength] = '\0'; entry->Base = base; entry->Size = size; + entry->ExportsAvailable = exportsAvailable; entry->Next = gModules; gModules = entry; return NTLSTATUS_SUCCESS; @@ -213,19 +216,32 @@ KSTATUS LdrRegisterModulesFromBootInfo(const BOOT_INFO* bootInfo) { KSTATUS status = KiRegisterGlobalModule("kernel.exe", bootInfo->KernelImage.VirtualBase, - bootInfo->KernelImage.ImageSize); + bootInfo->KernelImage.ImageSize, true); if (!KSUCCESS(status)) return status; return KiRegisterGlobalModule("hal.dll", bootInfo->HalImage.VirtualBase, - bootInfo->HalImage.ImageSize); + bootInfo->HalImage.ImageSize, true); } // Registers one loaded kernel module without imposing a fixed registry capacity. KSTATUS LdrRegisterModule(const char* name, uintptr_t base) { size_t size = base ? LdrGetImageSize(base) : 0; - return KiRegisterGlobalModule(name, base, size); + return KiRegisterGlobalModule(name, base, size, false); +} + +// Makes an initialized module's exports visible to future import resolution. +// Failed images remain mapped for diagnostics but must not provide exports. +KSTATUS LdrSetModuleExportsAvailable(const char* name, bool available) { + if (!name) return NTLSTATUS_INVALID_PARAMETER; + for (MODULE_ENTRY* entry = gModules; entry; entry = entry->Next) { + if (_stricmp(entry->Name, name) == 0) { + entry->ExportsAvailable = available; + return NTLSTATUS_SUCCESS; + } + } + return NTLSTATUS_NOT_FOUND; } // Finds the loaded kernel module that owns an address. @@ -251,7 +267,7 @@ const char* LdrFindKernelModuleByAddress(uintptr_t address, uintptr_t* baseOut, static uintptr_t KiFindGlobalModuleBase(const char* name) { if (!name) return 0; for (MODULE_ENTRY* module = gModules; module; module = module->Next) { - if (_stricmp(module->Name, name) == 0) + if (module->ExportsAvailable && _stricmp(module->Name, name) == 0) return module->Base; } return 0; @@ -286,6 +302,11 @@ static void* KiGetExportAtBase(uintptr_t base, const char* functionName) { return (void*)0; } +// Resolves a named export without depending on the image's registration name. +void* LdrGetImageExport(uintptr_t imageBase, const char* functionName) { + return functionName ? KiGetExportAtBase(imageBase, functionName) : (void*)0; +} + // Export lookup. void* LdrGetExport(const char* moduleName, const char* functionName) { uintptr_t base = KiFindGlobalModuleBase(moduleName); diff --git a/code/kernel/mm_file_cache.c b/code/kernel/mm_file_cache.c index 0c3a0e6..95749a8 100644 --- a/code/kernel/mm_file_cache.c +++ b/code/kernel/mm_file_cache.c @@ -813,6 +813,25 @@ KSTATUS MmFileCacheWrite(FILE_OBJECT* file, uint64_t offset, const void* buf, return NTLSTATUS_SUCCESS; } +KSTATUS MmFileCacheFlushAll(void) { + if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; + for (uint32_t i = 0; i < 65536u; ++i) { + int didWork = 0; + KSTATUS st = KiFileCacheFlushOneDirtyPage((FILE_OBJECT*)0, &didWork); + if (st == NTLSTATUS_NOT_FOUND && !didWork) { + uintptr_t flags = KeAcquireSpinlock(&gFileCacheLock); + bool clean = gFileCacheDirtyPages == 0; + KeReleaseSpinlock(&gFileCacheLock, flags); + if (clean) return NTLSTATUS_SUCCESS; + KeYield(); + continue; + } + if (!KSUCCESS(st)) return st; + if (!didWork) return NTLSTATUS_SUCCESS; + } + return NTLSTATUS_LIMIT_REACHED; +} + KSTATUS MmFileCacheFlushFile(FILE_OBJECT* file) { if (!file) return NTLSTATUS_INVALID_PARAMETER; diff --git a/code/kernel/mm_phys.c b/code/kernel/mm_phys.c index 49ab8a0..c103ee3 100644 --- a/code/kernel/mm_phys.c +++ b/code/kernel/mm_phys.c @@ -794,12 +794,14 @@ void MmPfnQueryStats(MM_PFN_STATS* out) { MM_PFN_STATS stats; memset(&stats, 0, sizeof(stats)); - uintptr_t flags = KeAcquireSpinlock(&gPfnLock); - stats.TotalFrames = (uint64_t)gTotalFrames; - stats.FreeFrames = (uint64_t)gFreeFrames; - stats.UsedFrames = stats.TotalFrames - stats.FreeFrames; - - for (size_t pfn = 0; pfn < gMaxPfn; pfn++) { + // Detailed statistics are diagnostic snapshots. Scan bounded PFN batches + // so an ordinary query cannot monopolize the global allocator lock on + // machines with millions of physical frames. Counters are sampled at end. + const size_t pfnCount = gMaxPfn; + for (size_t base = 0; base < pfnCount;) { + size_t end = pfnCount - base < 1024u ? pfnCount : base + 1024u; + uintptr_t flags = KeAcquireSpinlock(&gPfnLock); + for (size_t pfn = base; pfn < end; ++pfn) { const MM_PFN* entry = &gPfnDatabase[pfn]; if (entry->State == MmPfnStateInvalid) continue; @@ -847,8 +849,15 @@ void MmPfnQueryStats(MM_PFN_STATS* out) { case MmPfnUseMmio: stats.MmioFrames++; break; default: break; } + } + KeReleaseSpinlock(&gPfnLock, flags); + base = end; } + uintptr_t flags = KeAcquireSpinlock(&gPfnLock); + stats.TotalFrames = (uint64_t)gTotalFrames; + stats.FreeFrames = (uint64_t)gFreeFrames; + stats.UsedFrames = stats.TotalFrames - stats.FreeFrames; stats.AvailableFrames = stats.FreeFrames + stats.ReclaimableFrames; stats.AllocationCount = gPfnAllocations; diff --git a/code/kernel/ob_object.c b/code/kernel/ob_object.c index 468509b..189934d 100644 --- a/code/kernel/ob_object.c +++ b/code/kernel/ob_object.c @@ -39,6 +39,7 @@ static uint32_t KiObjectPoolTag(OBJECT_TYPE type) { case ObjectTypeInputManager: return MM_POOL_TAG('i', 'm', 'g', 'r'); case ObjectTypeDisplayManager: return MM_POOL_TAG('d', 'm', 'g', 'r'); case ObjectTypePipeEndpoint: return MM_POOL_TAG('p', 'i', 'p', 'e'); + case ObjectTypeServiceRegistration: return MM_POOL_TAG('s', 'r', 'e', 'g'); default: return MM_POOL_TAG('o', 'b', 'j', ' '); } } diff --git a/code/kernel/se_security.c b/code/kernel/se_security.c index 7250fb7..269d5d3 100644 --- a/code/kernel/se_security.c +++ b/code/kernel/se_security.c @@ -17,20 +17,15 @@ static void KiSecurityContextDestroy(OBJECT* object) { // Allocates and initializes one immutable security context object. static KSTATUS KiCreateSecurityContext(SYS_PRIVILEGES effectivePrivileges, SYS_PRIVILEGES delegablePrivileges, - SYS_SERVICE_CLAIMS serviceClaims, - SYS_SERVICE_CLAIMS delegableServiceClaims, SECURITY_CONTEXT** contextOut) { if (!contextOut) return NTLSTATUS_INVALID_PARAMETER; *contextOut = (SECURITY_CONTEXT*)0; if ((effectivePrivileges & ~SYS_PRIV_ALL) != 0 || - (delegablePrivileges & ~SYS_PRIV_ALL) != 0 || - (serviceClaims & ~SYS_SERVICE_CLAIM_ALL) != 0 || - (delegableServiceClaims & ~SYS_SERVICE_CLAIM_ALL) != 0) + (delegablePrivileges & ~SYS_PRIV_ALL) != 0) return NTLSTATUS_INVALID_PARAMETER; - if ((delegablePrivileges & ~effectivePrivileges) != 0 || - (delegableServiceClaims & ~serviceClaims) != 0) + if ((delegablePrivileges & ~effectivePrivileges) != 0) return NTLSTATUS_ACCESS_DENIED; SECURITY_CONTEXT* context = (SECURITY_CONTEXT*)ObAllocateObject(ObjectTypeSecurityContext, @@ -47,8 +42,6 @@ static KSTATUS KiCreateSecurityContext(SYS_PRIVILEGES effectivePrivileges, context->EffectivePrivileges = effectivePrivileges; context->DelegablePrivileges = delegablePrivileges; - context->ServiceClaims = serviceClaims; - context->DelegableServiceClaims = delegableServiceClaims; context->Flags = SYS_SECURITY_CONTEXT_FLAG_NONE; *contextOut = context; return NTLSTATUS_SUCCESS; @@ -62,8 +55,6 @@ KSTATUS SeInitializeSecurity(void) { SECURITY_CONTEXT* context = (SECURITY_CONTEXT*)0; KSTATUS status = KiCreateSecurityContext(SYS_PRIV_ALL, SYS_PRIV_ALL, - SYS_SERVICE_CLAIM_ALL, - SYS_SERVICE_CLAIM_ALL, &context); if (!KSUCCESS(status)) return status; @@ -81,22 +72,16 @@ SECURITY_CONTEXT* SeGetRootSecurityContext(void) { KSTATUS SeDeriveSecurityContext(SECURITY_CONTEXT* source, SYS_PRIVILEGES effectivePrivileges, SYS_PRIVILEGES delegablePrivileges, - SYS_SERVICE_CLAIMS serviceClaims, - SYS_SERVICE_CLAIMS delegableServiceClaims, SECURITY_CONTEXT** contextOut) { if (!source || !contextOut) return NTLSTATUS_INVALID_PARAMETER; if ((effectivePrivileges & ~source->DelegablePrivileges) != 0 || - (delegablePrivileges & ~source->DelegablePrivileges) != 0 || - (serviceClaims & ~source->DelegableServiceClaims) != 0 || - (delegableServiceClaims & ~source->DelegableServiceClaims) != 0) + (delegablePrivileges & ~source->DelegablePrivileges) != 0) return NTLSTATUS_ACCESS_DENIED; return KiCreateSecurityContext(effectivePrivileges, delegablePrivileges, - serviceClaims, - delegableServiceClaims, contextOut); } @@ -107,13 +92,6 @@ bool SeProcessHasPrivilege(const struct PROCESS* process, SYS_PRIVILEGES privile return (process->SecurityContext->EffectivePrivileges & privileges) == privileges; } -// Tests one security context's effective service-claim mask. -bool SeContextHasServiceClaims(const SECURITY_CONTEXT* context, SYS_SERVICE_CLAIMS claims) { - if (!context) - return false; - return (context->ServiceClaims & claims) == claims; -} - // Exports immutable context metadata to one public structure. void SeQuerySecurityContext(const SECURITY_CONTEXT* context, SYS_SECURITY_CONTEXT_INFO* infoOut) { if (!context || !infoOut) @@ -124,7 +102,5 @@ void SeQuerySecurityContext(const SECURITY_CONTEXT* context, SYS_SECURITY_CONTEX infoOut->SecurityId = context->SecurityId; infoOut->EffectivePrivileges = context->EffectivePrivileges; infoOut->DelegablePrivileges = context->DelegablePrivileges; - infoOut->ServiceClaims = context->ServiceClaims; - infoOut->DelegableServiceClaims = context->DelegableServiceClaims; infoOut->Flags = context->Flags; } diff --git a/code/public/ntlite/boot_info.h b/code/public/ntlite/boot_info.h index 5f077fa..6e96e86 100644 --- a/code/public/ntlite/boot_info.h +++ b/code/public/ntlite/boot_info.h @@ -8,7 +8,7 @@ // Encodes the ASCII tag 'BOOTINFO'. #define BOOT_INFO_MAGIC 0x424F4F54494E464FULL -#define BOOT_INFO_VERSION 3u +#define BOOT_INFO_VERSION 4u #define BOOT_FILE_NAME_MAX 64u @@ -42,13 +42,14 @@ typedef struct BOOT_BLOB { // Describes a boot driver raw file passed to the kernel without mapping it as an image. typedef struct BOOT_FILE { - char Name[BOOT_FILE_NAME_MAX]; + char DriverId[BOOT_FILE_NAME_MAX]; + char Image[BOOT_FILE_NAME_MAX]; uintptr_t PhysicalBase; uint32_t Length; uint32_t PageCount; } BOOT_FILE; -// Describes the page-backed BOOT_FILE array produced from drivers.ini [boot]. +// Describes the page-backed, identified boot-driver array. typedef struct BOOT_FILE_LIST { uintptr_t FilesPhysical; uint32_t Count; @@ -84,6 +85,7 @@ typedef struct BOOT_INFO { BOOT_IMAGE KernelImage; BOOT_IMAGE HalImage; BOOT_BLOB BootIni; + BOOT_BLOB DriversIni; BOOT_FILE_LIST BootDrivers; // ACPI RSDP physical address. @@ -103,11 +105,11 @@ typedef struct BOOT_INFO { _Static_assert(sizeof(uintptr_t) == 8u, "BOOT_INFO requires x64 pointers"); _Static_assert(sizeof(BOOT_TIME) == 20u, "BOOT_TIME ABI size changed"); _Static_assert(sizeof(BOOT_BLOB) == 16u, "BOOT_BLOB ABI size changed"); -_Static_assert(sizeof(BOOT_FILE) == 80u, "BOOT_FILE ABI size changed"); +_Static_assert(sizeof(BOOT_FILE) == 144u, "BOOT_FILE ABI size changed"); _Static_assert(sizeof(BOOT_FILE_LIST) == 16u, "BOOT_FILE_LIST ABI size changed"); _Static_assert(sizeof(BOOT_IMAGE) == 32u, "BOOT_IMAGE ABI size changed"); _Static_assert(offsetof(BOOT_INFO, MemoryMap) == 16u, "BOOT_INFO MemoryMap offset changed"); _Static_assert(offsetof(BOOT_INFO, KernelImage) == 6200u, "BOOT_INFO KernelImage offset changed"); -_Static_assert(offsetof(BOOT_INFO, BootDrivers) == 6280u, "BOOT_INFO BootDrivers offset changed"); -_Static_assert(offsetof(BOOT_INFO, BootTime) == 6320u, "BOOT_INFO BootTime offset changed"); -_Static_assert(sizeof(BOOT_INFO) == 6344u, "BOOT_INFO ABI size changed"); +_Static_assert(offsetof(BOOT_INFO, BootDrivers) == 6296u, "BOOT_INFO BootDrivers offset changed"); +_Static_assert(offsetof(BOOT_INFO, BootTime) == 6336u, "BOOT_INFO BootTime offset changed"); +_Static_assert(sizeof(BOOT_INFO) == 6360u, "BOOT_INFO ABI size changed"); diff --git a/code/public/ntlite/bootstrap_capabilities.h b/code/public/ntlite/bootstrap_capabilities.h new file mode 100644 index 0000000..0bc347b --- /dev/null +++ b/code/public/ntlite/bootstrap_capabilities.h @@ -0,0 +1,10 @@ +// Canonical names for kernel-issued bootstrap capabilities and their attenuated service views. +#pragma once + +#define NTL_BOOT_CAP_SYSTEM_VOLUME "boot.system-volume" +#define NTL_BOOT_CAP_NAMESPACE_ROOT "boot.namespace-root" +#define NTL_BOOT_CAP_VOLUME_MANAGER "boot.volume-manager" +#define NTL_BOOT_CAP_INPUT_MANAGER "boot.input-manager" +#define NTL_BOOT_CAP_DISPLAY_POLICY "boot.display-policy" +#define NTL_BOOT_CAP_DISPLAY_PRESENT "boot.display-present" +#define NTL_BOOT_CAP_RESOURCE_REGISTRATION "boot.resource-registration" diff --git a/code/public/ntlite/console_service.h b/code/public/ntlite/console_service.h index 1f57e85..8858b32 100644 --- a/code/public/ntlite/console_service.h +++ b/code/public/ntlite/console_service.h @@ -24,6 +24,7 @@ #define NTL_CONSOLE_OP_READ_INPUT 8u #define NTL_CONSOLE_OP_PEEK_INPUT 9u #define NTL_CONSOLE_OP_FLUSH_INPUT 10u +#define NTL_CONSOLE_OP_WRITE_AT 11u typedef struct NTL_CONSOLE_INFO { uint32_t Size; @@ -54,6 +55,12 @@ typedef struct NTL_CONSOLE_REQUEST { int32_t X; int32_t Y; } Cursor; + struct { + int32_t X; + int32_t Y; + uint32_t Length; + char Data[NTL_CONSOLE_WRITE_MAX - 8u]; + } WriteAt; struct { char Character; // One legacy ASCII display cell, never one UTF-8 code point. uint8_t Reserved[3]; @@ -113,6 +120,9 @@ NTL_CONSOLE_API KSTATUS NtlConsoleConnect(SYS_HANDLE* sessionOut); NTL_CONSOLE_API KSTATUS NtlConsoleClose(SYS_HANDLE session); NTL_CONSOLE_API KSTATUS NtlConsoleWrite(SYS_HANDLE session, const void* data, size_t length, size_t* writtenOut); NTL_CONSOLE_API KSTATUS NtlConsoleQueryInfo(SYS_HANDLE session, NTL_CONSOLE_INFO* infoOut); +// Writes one ASCII text span at fixed console coordinates without moving the logical cursor. +NTL_CONSOLE_API KSTATUS NtlConsoleWriteAt(SYS_HANDLE session, int32_t x, int32_t y, + const char* text, size_t length, size_t* writtenOut); NTL_CONSOLE_API KSTATUS NtlConsoleSetCursor(SYS_HANDLE session, int32_t x, int32_t y); // Legacy cell operations accept ASCII glyph codes only. Unicode text uses // NtlConsoleWrite's byte stream until the RSSRV text renderer is upgraded. diff --git a/code/public/ntlite/ddi/display.h b/code/public/ntlite/ddi/display.h index ce25ab7..bcf433d 100644 --- a/code/public/ntlite/ddi/display.h +++ b/code/public/ntlite/ddi/display.h @@ -72,5 +72,6 @@ struct DISPLAY_DEVICE { }; DISPLAY_API KSTATUS KeDisplayRegisterDevice(DISPLAY_DEVICE* device); +DISPLAY_API KSTATUS KeDisplayUnregisterDevice(DISPLAY_DEVICE* device); DISPLAY_API KSTATUS KeDisplaySetPrimary(DISPLAY_DEVICE* device); DISPLAY_API void KeDisplayNotifyFrame(DISPLAY_DEVICE* device); diff --git a/code/public/ntlite/ddi/io_driver.h b/code/public/ntlite/ddi/io_driver.h index 1a711f1..3e22c10 100644 --- a/code/public/ntlite/ddi/io_driver.h +++ b/code/public/ntlite/ddi/io_driver.h @@ -128,7 +128,6 @@ struct DEVICE_OBJECT { __declspec(dllexport) void IoInitializeDriverObject(DRIVER_OBJECT* driver, SYS_STRING_VIEW name); -__declspec(dllexport) KSTATUS IoRegisterDriverObject(DRIVER_OBJECT* driver); __declspec(dllexport) KSTATUS IoCreateDevice(DRIVER_OBJECT* driver, size_t extensionSize, diff --git a/code/public/ntlite/ddk/audio.h b/code/public/ntlite/ddk/audio.h index e4b6c67..f1adb7e 100644 --- a/code/public/ntlite/ddk/audio.h +++ b/code/public/ntlite/ddk/audio.h @@ -77,30 +77,11 @@ typedef struct IO_AUDIO_ENDPOINT_REGISTRATION { uint32_t HardwareGainStepMilliDb; } IO_AUDIO_ENDPOINT_REGISTRATION; -// Driver-visible services for registering and advancing kernel audio endpoints. -typedef struct NTL_DDK_AUDIO { - // Registers one endpoint and returns a provider-owned reference valid until unregister returns. - KSTATUS (*RegisterEndpoint)(const IO_AUDIO_ENDPOINT_REGISTRATION* registration, - IO_AUDIO_ENDPOINT** outEndpoint); - // Stops the endpoint, removes it from enumeration, and consumes the provider-owned reference. - void (*UnregisterEndpoint)(IO_AUDIO_ENDPOINT* endpoint, IO_AUDIO_REMOVAL_MODE mode); - // Reports one completed hardware period from ISR/DPC completion processing. - // presentedFrames, hardwarePositionFrames, and interruptCount must be monotonic for the - // endpoint lifetime. timestampTicks uses the DDK monotonic microsecond source. - void (*NotifyPeriod)(IO_AUDIO_ENDPOINT* endpoint, - uint32_t completedPeriodIndex, - uint64_t presentedFrames, - uint64_t hardwarePositionFrames, - uint64_t interruptCount, - uint64_t timestampTicks); - // Updates jack-derived endpoint availability without changing endpoint identity. - void (*SetEndpointAvailable)(IO_AUDIO_ENDPOINT* endpoint, bool available); - - // Fails an endpoint and wakes every stream owner. - void (*ReportEndpointFailure)(IO_AUDIO_ENDPOINT* endpoint, - KSTATUS status, - uint32_t flags); -} NTL_DDK_AUDIO; +__declspec(dllexport) KSTATUS IoAudioRegisterEndpoint(const IO_AUDIO_ENDPOINT_REGISTRATION* registration, IO_AUDIO_ENDPOINT** outEndpoint); +__declspec(dllexport) void IoAudioUnregisterEndpoint(IO_AUDIO_ENDPOINT* endpoint, IO_AUDIO_REMOVAL_MODE mode); +__declspec(dllexport) void IoAudioNotifyPeriod(IO_AUDIO_ENDPOINT* endpoint, uint32_t completedPeriodIndex, uint64_t presentedFrames, uint64_t hardwarePositionFrames, uint64_t interruptCount, uint64_t timestampTicks); +__declspec(dllexport) void IoAudioSetEndpointAvailable(IO_AUDIO_ENDPOINT* endpoint, bool available); +__declspec(dllexport) void IoAudioReportEndpointFailure(IO_AUDIO_ENDPOINT* endpoint, KSTATUS status, uint32_t flags); diff --git a/code/public/ntlite/ddk/ddk.h b/code/public/ntlite/ddk/ddk.h index b24ee96..4ea8b3b 100644 --- a/code/public/ntlite/ddk/ddk.h +++ b/code/public/ntlite/ddk/ddk.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -26,23 +27,3 @@ #include #include #include - -#define NTL_DDK_VERSION 1u - -// Root DDK table passed to kernel drivers by the driver loader. -typedef struct NTL_DDK { - uint32_t Version; - const NTL_DDK_DEBUG* Debug; - const NTL_DDK_AUDIO* Audio; - const NTL_DDK_DMA* Dma; - const NTL_DDK_INTERRUPT* Interrupt; - const NTL_DDK_MEMORY* Memory; - const NTL_DDK_PLATFORM* Platform; - const NTL_DDK_SCHEDULER* Scheduler; - const NTL_DDK_TIMER* Timer; -} NTL_DDK; - -// Driver initialization record filled by DriverEntry in the DDK-table model. -typedef struct NTL_DRIVER_INIT { - const char* Name; -} NTL_DRIVER_INIT; diff --git a/code/public/ntlite/ddk/debug.h b/code/public/ntlite/ddk/debug.h index c779be2..3e80477 100644 --- a/code/public/ntlite/ddk/debug.h +++ b/code/public/ntlite/ddk/debug.h @@ -7,7 +7,7 @@ // Kernel boot debug flags. Keep these contiguous and document every bit in boot.ini. #define KE_DEBUG_CONSOLE 0x00000001u // Mirror debug output to the firmware/debug console. -#define KE_DEBUG_SELFTEST 0x00000002u // Run kernel and SMP boot self-tests. +#define KE_DEBUG_SELFTEST 0x00000002u // Run optional kernel diagnostics and bootstrap checks. #define KE_DEBUG_FILE_LOG 0x00000004u // Retain debug output for kernel.log persistence. #define KE_DEBUG_POOL_CANARY 0x00000008u // Validate pool block canaries. #define KE_DEBUG_POOL_POISON 0x00000010u // Poison released pool blocks. @@ -21,11 +21,7 @@ KE_DEBUG_POOL_QUARANTINE | KE_DEBUG_POOL_CALLERS | \ KE_DEBUG_DISPLAY_ASSERTS | KE_DEBUG_LIST_CHECKS) -// Driver-visible debug output service table passed through NTL_DDK. -typedef struct NTL_DDK_DEBUG { - void (*Print)(const char* fmt, ...); - uint32_t (*GetFlags)(void); -} NTL_DDK_DEBUG; + // Current fatal categories used by kernel-mode driver diagnostics. typedef enum KE_PANIC_CODE { @@ -44,3 +40,5 @@ __declspec(dllexport) void DbgPrint(const char* fmt, ...); // Stops the system with a formatted diagnostic message. __declspec(dllexport) _Noreturn void KePanic(int32_t code, const char* fmt, ...); + +__declspec(dllexport) uint32_t KeGetDebugFlags(void); diff --git a/code/public/ntlite/ddk/device.h b/code/public/ntlite/ddk/device.h new file mode 100644 index 0000000..10315cf --- /dev/null +++ b/code/public/ntlite/ddk/device.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include + +typedef struct NTL_DEVICE NTL_DEVICE; +typedef struct NTL_DEVICE_RESOURCE NTL_DEVICE_RESOURCE; + +typedef enum NTL_BUS_TYPE { + NtlBusPci = 1, + NtlBusUsb = 2, +} NTL_BUS_TYPE; + +typedef struct NTL_DEVICE_MATCH { + NTL_BUS_TYPE Bus; + const char* Id; +} NTL_DEVICE_MATCH; + +typedef KSTATUS (*NTL_DEVICE_START)(NTL_DEVICE* device); +typedef void (*NTL_DEVICE_STOP)(NTL_DEVICE* device); +typedef void (*NTL_DEVICE_CANCEL_START)(NTL_DEVICE* device); + +typedef struct NTL_DEVICE_DESC { + uint32_t Size; + uint32_t Flags; + const char* Name; + const NTL_DEVICE_MATCH* Matches; + uint32_t MatchCount; + uint32_t ContextSize; + NTL_DEVICE_START Start; + NTL_DEVICE_STOP Stop; + NTL_DEVICE_CANCEL_START CancelStart; +} NTL_DEVICE_DESC; + +#define NTL_DEVICE_DESCRIPTOR(nameValue, matchesValue, contextType, startFn, stopFn) \ + { sizeof(NTL_DEVICE_DESC), 0u, (nameValue), (matchesValue), \ + (uint32_t)(sizeof(matchesValue) / sizeof((matchesValue)[0])), \ + (uint32_t)sizeof(contextType), (startFn), (stopFn), 0 } + +typedef void (*NTL_DEVICE_RESOURCE_RELEASE)(void* context); + +#ifdef NTL_KERNEL_BUILD +#define NTL_DDK_DEVICE_API __declspec(dllexport) +#else +#define NTL_DDK_DEVICE_API __declspec(dllimport) +#endif + +#ifdef __cplusplus +extern "C" { +#endif +NTL_DDK_DEVICE_API void* NtlDeviceContext(NTL_DEVICE* device); +NTL_DDK_DEVICE_API const char* NtlDeviceId(NTL_DEVICE* device, uint32_t index); +NTL_DDK_DEVICE_API NTL_BUS_TYPE NtlDeviceBus(NTL_DEVICE* device); +NTL_DDK_DEVICE_API KSTATUS NtlDeviceCompleteStart(NTL_DEVICE* device, KSTATUS result); +NTL_DDK_DEVICE_API KSTATUS NtlDeviceOwnResource(NTL_DEVICE* device, + void* context, + NTL_DEVICE_RESOURCE_RELEASE release, + NTL_DEVICE_RESOURCE** out); +NTL_DDK_DEVICE_API KSTATUS NtlDeviceReleaseResource(NTL_DEVICE_RESOURCE* resource); +#ifdef __cplusplus +} +#endif diff --git a/code/public/ntlite/ddk/dma.h b/code/public/ntlite/ddk/dma.h index eb651ec..eb12e68 100644 --- a/code/public/ntlite/ddk/dma.h +++ b/code/public/ntlite/ddk/dma.h @@ -64,83 +64,21 @@ typedef struct IO_DMA_STATS { uint64_t FailedMappings; } IO_DMA_STATS; -// Driver-visible DMA services. Device addresses are intentionally distinct -// from physical addresses even while the first implementation maps them one to -// one on x86-64. -typedef struct NTL_DDK_DMA { - // Returns the one adapter and requirement set owned by a device object. - KSTATUS (*GetAdapter)(struct DEVICE_OBJECT* device, - const IO_DMA_REQUIREMENTS* requirements, - IO_DMA_ADAPTER** outAdapter); - // Releases one caller-owned adapter reference. - void (*PutAdapter)(IO_DMA_ADAPTER* adapter); - - // Allocates coherent memory with stable CPU and base device addresses. The - // caller must quiesce every device reference before releasing the buffer. - KSTATUS (*AllocateCommonBuffer)(IO_DMA_ADAPTER* adapter, - size_t size, - size_t alignment, - IO_DMA_BUFFER** outBuffer); - - // Releases a coherent common buffer. - void (*FreeCommonBuffer)(IO_DMA_BUFFER* buffer); - - // Returns the stable CPU address of a common buffer. - void* (*GetCommonBufferAddress)(const IO_DMA_BUFFER* buffer); - - // Returns the stable device address of a common buffer. - uint64_t (*GetCommonBufferDeviceAddress)(const IO_DMA_BUFFER* buffer); - - // Returns the caller-requested size of a common buffer. - size_t (*GetCommonBufferSize)(const IO_DMA_BUFFER* buffer); - - // Stabilizes a kernel range for one or more streaming mappings. Pinning - // preserves the pages, but does not transfer ownership to a device. - KSTATUS (*PinKernelMemory)(void* address, - size_t size, - IO_DMA_DIRECTION direction, - MM_PINNED_MEMORY** outMemory); - - // Stabilizes a process-owned user range for one or more mappings. The - // descriptor retains the process and range until the final unpin. - KSTATUS (*PinUserMemory)(struct PROCESS* process, - void* address, - size_t size, - IO_DMA_DIRECTION direction, - MM_PINNED_MEMORY** outMemory); - - // Releases one pinned-memory reference after all mappings leave. - void (*UnpinMemory)(MM_PINNED_MEMORY* memory); - - // Maps a pinned subrange and creates a constrained segment list. A new - // mapping begins CPU-owned and requires external operation serialization. - KSTATUS (*MapPinnedMemory)(IO_DMA_ADAPTER* adapter, - MM_PINNED_MEMORY* memory, - size_t offset, - size_t length, - IO_DMA_DIRECTION direction, - uint32_t flags, - IO_DMA_MAPPING** outMapping); - - // Releases a mapping and performs a final receive-side bounce copy only - // when ownership was transferred to the device and not returned to the CPU. - void (*UnmapMemory)(IO_DMA_MAPPING* mapping); - - // Transfers mapping ownership from the CPU to the device. Streaming DMA - // must not begin before this operation succeeds. - KSTATUS (*SynchronizeForDevice)(IO_DMA_MAPPING* mapping); - - // Transfers mapping ownership from the device to the CPU. CPU readers must - // not consume receive data before this operation succeeds. - KSTATUS (*SynchronizeForCpu)(IO_DMA_MAPPING* mapping); - - // Returns the immutable segment count owned by a mapping. - uint32_t (*GetSegmentCount)(const IO_DMA_MAPPING* mapping); - - // Returns the immutable segment array owned by a mapping. - const IO_DMA_SEGMENT* (*GetSegments)(const IO_DMA_MAPPING* mapping); - - // Copies current DMA counters into caller-owned storage. - void (*QueryStats)(IO_DMA_STATS* outStats); -} NTL_DDK_DMA; +__declspec(dllexport) KSTATUS IoDmaGetAdapter(struct DEVICE_OBJECT* device, const IO_DMA_REQUIREMENTS* requirements, IO_DMA_ADAPTER** outAdapter); +__declspec(dllexport) void IoDmaPutAdapter(IO_DMA_ADAPTER* adapter); +__declspec(dllexport) KSTATUS IoDmaAllocateCommonBuffer(IO_DMA_ADAPTER* adapter, size_t size, size_t alignment, IO_DMA_BUFFER** outBuffer); +__declspec(dllexport) void IoDmaFreeCommonBuffer(IO_DMA_BUFFER* buffer); +__declspec(dllexport) void* IoDmaGetCommonBufferAddress(const IO_DMA_BUFFER* buffer); +__declspec(dllexport) uint64_t IoDmaGetCommonBufferDeviceAddress(const IO_DMA_BUFFER* buffer); +__declspec(dllexport) size_t IoDmaGetCommonBufferSize(const IO_DMA_BUFFER* buffer); +__declspec(dllexport) KSTATUS IoDmaPinKernelMemory(void* address, size_t size, IO_DMA_DIRECTION direction, MM_PINNED_MEMORY** outMemory); +__declspec(dllexport) KSTATUS IoDmaPinUserMemory(struct PROCESS* process, void* address, size_t size, IO_DMA_DIRECTION direction, MM_PINNED_MEMORY** outMemory); +__declspec(dllexport) void IoDmaUnpinMemory(MM_PINNED_MEMORY* memory); +__declspec(dllexport) KSTATUS IoDmaMapPinnedMemory(IO_DMA_ADAPTER* adapter, MM_PINNED_MEMORY* memory, size_t offset, size_t length, IO_DMA_DIRECTION direction, uint32_t flags, IO_DMA_MAPPING** outMapping); +__declspec(dllexport) void IoDmaUnmapMemory(IO_DMA_MAPPING* mapping); +__declspec(dllexport) KSTATUS IoDmaSynchronizeForDevice(IO_DMA_MAPPING* mapping); +__declspec(dllexport) KSTATUS IoDmaSynchronizeForCpu(IO_DMA_MAPPING* mapping); +__declspec(dllexport) uint32_t IoDmaGetSegmentCount(const IO_DMA_MAPPING* mapping); +__declspec(dllexport) const IO_DMA_SEGMENT* IoDmaGetSegments(const IO_DMA_MAPPING* mapping); +__declspec(dllexport) void IoDmaQueryStats(IO_DMA_STATS* outStats); diff --git a/code/public/ntlite/ddk/driver.h b/code/public/ntlite/ddk/driver.h index 9621e9d..9c9dcd2 100644 --- a/code/public/ntlite/ddk/driver.h +++ b/code/public/ntlite/ddk/driver.h @@ -1,12 +1,2 @@ -// Public nt-lite DDK driver entry declarations. - #pragma once - -#include - -struct NTL_DDK; -struct NTL_DRIVER_INIT; - -// Kernel driver entrypoint used by the future DDK-table driver model. -typedef KSTATUS (*NTL_DRIVER_ENTRY_FN)(const struct NTL_DDK* ddk, - struct NTL_DRIVER_INIT* init); +#include diff --git a/code/public/ntlite/ddk/interrupt.h b/code/public/ntlite/ddk/interrupt.h index cc3c581..11941cf 100644 --- a/code/public/ntlite/ddk/interrupt.h +++ b/code/public/ntlite/ddk/interrupt.h @@ -11,14 +11,12 @@ typedef struct NTL_DDK_MSI_MESSAGE { uint32_t Reserved; } NTL_DDK_MSI_MESSAGE; -// Driver-visible interrupt services. -typedef struct NTL_DDK_INTERRUPT { - KSTATUS (*AllocateMsiVector)(uint8_t* outIrq); - KSTATUS (*ComposeMsiMessage)(uint8_t irq, NTL_DDK_MSI_MESSAGE* messageOut); -} NTL_DDK_INTERRUPT; + // Allocates one kernel IRQ slot for MSI delivery. __declspec(dllexport) KSTATUS KeIrqAllocateMsiVector(uint8_t* outIrq); // Registers a legacy interrupt handler for an IRQ slot. __declspec(dllexport) void KeIrqRegister(uint8_t irq, void (*handler)(void)); + +__declspec(dllexport) KSTATUS KeIrqComposeMsiMessage(uint8_t irq, NTL_DDK_MSI_MESSAGE* messageOut); diff --git a/code/public/ntlite/ddk/memory.h b/code/public/ntlite/ddk/memory.h index f1fe3d2..8d2183c 100644 --- a/code/public/ntlite/ddk/memory.h +++ b/code/public/ntlite/ddk/memory.h @@ -69,16 +69,6 @@ typedef enum NTL_DDK_MAP_MODE { ((uint32_t)(uint8_t)(a) | ((uint32_t)(uint8_t)(b) << 8) | \ ((uint32_t)(uint8_t)(c) << 16) | ((uint32_t)(uint8_t)(d) << 24)) -// Driver-visible memory services. These are intentionally narrower than the -// kernel memory-manager surface. -typedef struct NTL_DDK_MEMORY { - void* (*PhysToVirt)(uint64_t physicalAddress); - KSTATUS (*MapPage)(uintptr_t virtualAddress, - uint64_t physicalAddress, - uint64_t pageFlags, - NTL_DDK_MAP_MODE mode); -} NTL_DDK_MEMORY; - // Allocates from kernel pool for current kernel-mode drivers. __declspec(dllexport) void* MmAllocatePool(size_t size); @@ -90,3 +80,6 @@ __declspec(dllexport) void MmFreePool(void* ptr); // Reallocates kernel pool memory. __declspec(dllexport) void* MmReallocatePool(void* ptr, size_t newSize); + +__declspec(dllexport) void* MmDdkPhysToVirt(uint64_t physicalAddress); +__declspec(dllexport) KSTATUS MmDdkMapPage(uintptr_t virtualAddress, uint64_t physicalAddress, uint64_t flags, NTL_DDK_MAP_MODE mode); diff --git a/code/public/ntlite/ddk/module.h b/code/public/ntlite/ddk/module.h new file mode 100644 index 0000000..34fca20 --- /dev/null +++ b/code/public/ntlite/ddk/module.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include + +#define NTL_DDK_MODULE_EXPORT_NAME "NtlDriverModule" +#define NTL_DDK_MODULE_SIGNATURE 0x324D444Eu // "NDM2" in little-endian memory. +#define NTL_DDK_MODULE_ABI_VERSION 5u + +typedef struct NTL_MODULE NTL_MODULE; +typedef KSTATUS (*NTL_MODULE_INITIALIZE)(NTL_MODULE* module); +typedef KSTATUS (*NTL_MODULE_PREPARE_SHUTDOWN)(NTL_MODULE* module); +typedef void (*NTL_MODULE_COMMIT_SHUTDOWN)(NTL_MODULE* module); + +struct DRIVER_OBJECT; + +typedef struct NTL_MODULE_EXPORT { + uint32_t Signature; + uint32_t Version; + uint32_t Size; + uint32_t Flags; + NTL_MODULE_INITIALIZE Initialize; + const NTL_DEVICE_DESC* Devices; + uint32_t DeviceCount; + uint32_t Reserved; + struct DRIVER_OBJECT* const* IoDrivers; + uint32_t IoDriverCount; + uint32_t Reserved2; + NTL_MODULE_PREPARE_SHUTDOWN PrepareShutdown; + NTL_MODULE_COMMIT_SHUTDOWN CommitShutdown; +} NTL_MODULE_EXPORT; + +#ifdef __cplusplus +static_assert(sizeof(NTL_MODULE_EXPORT) == 72u, "DDK module export ABI changed"); +#define NTL_DDK_C_EXPORT extern "C" +#else +_Static_assert(sizeof(NTL_MODULE_EXPORT) == 72u, "DDK module export ABI changed"); +#define NTL_DDK_C_EXPORT +#endif + +#define NTL_DECLARE_MODULE(initialize_fn) \ + NTL_DDK_C_EXPORT __declspec(dllexport) const NTL_MODULE_EXPORT NtlDriverModule = { \ + NTL_DDK_MODULE_SIGNATURE, NTL_DDK_MODULE_ABI_VERSION, \ + (uint32_t)sizeof(NTL_MODULE_EXPORT), 0u, (initialize_fn), 0, 0u, 0u, 0, 0u, 0u, 0, 0 \ + } + +#define NTL_DECLARE_DEVICE_MODULE(deviceArray) \ + NTL_DDK_C_EXPORT __declspec(dllexport) const NTL_MODULE_EXPORT NtlDriverModule = { \ + NTL_DDK_MODULE_SIGNATURE, NTL_DDK_MODULE_ABI_VERSION, \ + (uint32_t)sizeof(NTL_MODULE_EXPORT), 0u, 0, (deviceArray), \ + (uint32_t)(sizeof(deviceArray) / sizeof((deviceArray)[0])), 0u, 0, 0u, 0u, 0, 0 \ + } + +#define NTL_DECLARE_IO_MODULE(initialize_fn, driverArray) \ + NTL_DDK_C_EXPORT __declspec(dllexport) const NTL_MODULE_EXPORT NtlDriverModule = { \ + NTL_DDK_MODULE_SIGNATURE, NTL_DDK_MODULE_ABI_VERSION, \ + (uint32_t)sizeof(NTL_MODULE_EXPORT), 0u, (initialize_fn), 0, 0u, 0u, \ + (driverArray), (uint32_t)(sizeof(driverArray) / sizeof((driverArray)[0])), 0u, 0, 0 \ + } + +#define NTL_DECLARE_MODULE_WITH_SHUTDOWN(initialize_fn, prepareFn, commitFn) \ + NTL_DDK_C_EXPORT __declspec(dllexport) const NTL_MODULE_EXPORT NtlDriverModule = { \ + NTL_DDK_MODULE_SIGNATURE, NTL_DDK_MODULE_ABI_VERSION, \ + (uint32_t)sizeof(NTL_MODULE_EXPORT), 0u, (initialize_fn), 0, 0u, 0u, \ + 0, 0u, 0u, (prepareFn), (commitFn) \ + } + +#define NTL_DECLARE_IO_MODULE_WITH_SHUTDOWN(initialize_fn, driverArray, prepareFn, commitFn) \ + NTL_DDK_C_EXPORT __declspec(dllexport) const NTL_MODULE_EXPORT NtlDriverModule = { \ + NTL_DDK_MODULE_SIGNATURE, NTL_DDK_MODULE_ABI_VERSION, \ + (uint32_t)sizeof(NTL_MODULE_EXPORT), 0u, (initialize_fn), 0, 0u, 0u, \ + (driverArray), (uint32_t)(sizeof(driverArray) / sizeof((driverArray)[0])), 0u, \ + (prepareFn), (commitFn) \ + } + +// Query the configured module identity; the returned string is borrowed. +#ifdef NTL_KERNEL_BUILD +#define NTL_DDK_MODULE_API __declspec(dllexport) +#else +#define NTL_DDK_MODULE_API __declspec(dllimport) +#endif + +#ifdef __cplusplus +extern "C" { +#endif +NTL_DDK_MODULE_API const char* NtlModuleGetName(const NTL_MODULE* module); +#ifdef __cplusplus +} +#endif diff --git a/code/public/ntlite/ddk/object.h b/code/public/ntlite/ddk/object.h index 311f3f1..0ece494 100644 --- a/code/public/ntlite/ddk/object.h +++ b/code/public/ntlite/ddk/object.h @@ -36,6 +36,7 @@ typedef enum OBJECT_TYPE { ObjectTypeInputManager = 27, ObjectTypeDisplayManager = 28, ObjectTypePipeEndpoint = 29, + ObjectTypeServiceRegistration = 30, } OBJECT_TYPE; typedef enum OBJECT_LIFETIME_STATE { diff --git a/code/public/ntlite/ddk/pci_caps.h b/code/public/ntlite/ddk/pci_caps.h new file mode 100644 index 0000000..658e861 --- /dev/null +++ b/code/public/ntlite/ddk/pci_caps.h @@ -0,0 +1,26 @@ +// Pure driver-library helpers for walking PCI capability lists. + +#pragma once + +#include +#include + +typedef struct NTL_PCI_CONFIG_READER { + void* Context; + uint32_t (*Read32)(void* context, uint16_t offset); +} NTL_PCI_CONFIG_READER; + +#ifdef __cplusplus +extern "C" { +#endif + +// Finds an ordinary PCI capability using an explicitly supplied config reader. +// Returns NOT_FOUND for a valid exhausted list and DEVICE_ERROR for malformed lists. +KSTATUS NtlPciFindCapability(const NTL_PCI_CONFIG_READER* reader, + uint8_t firstOffset, + uint8_t capabilityId, + uint8_t* outOffset); + +#ifdef __cplusplus +} +#endif diff --git a/code/public/ntlite/ddk/platform.h b/code/public/ntlite/ddk/platform.h index c0ac030..ad82007 100644 --- a/code/public/ntlite/ddk/platform.h +++ b/code/public/ntlite/ddk/platform.h @@ -4,18 +4,5 @@ #include -// Driver-visible platform services. PCI bus policy stays in pci.sys; these are -// only the raw platform hooks needed by the PCI bus driver itself. -typedef struct NTL_DDK_PLATFORM { - uint32_t (*PciRead32)(uint16_t segment, - uint8_t bus, - uint8_t device, - uint8_t function, - uint16_t offset); - void (*PciWrite32)(uint16_t segment, - uint8_t bus, - uint8_t device, - uint8_t function, - uint16_t offset, - uint32_t value); -} NTL_DDK_PLATFORM; +__declspec(dllexport) uint32_t KePciReadConfig32(uint16_t segment, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset); +__declspec(dllexport) void KePciWriteConfig32(uint16_t segment, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint32_t value); diff --git a/code/public/ntlite/ddk/scheduler.h b/code/public/ntlite/ddk/scheduler.h index 6fb278f..6efed1b 100644 --- a/code/public/ntlite/ddk/scheduler.h +++ b/code/public/ntlite/ddk/scheduler.h @@ -6,8 +6,7 @@ #include #include -// Driver-visible scheduler services. -typedef struct NTL_DDK_SCHEDULER { - KSTATUS (*DelayThread)(uint64_t milliseconds); - void (*Yield)(void); -} NTL_DDK_SCHEDULER; + + +__declspec(dllexport) KSTATUS KeDelayThread(uint64_t milliseconds); +__declspec(dllexport) void KeYield(void); diff --git a/code/public/ntlite/ddk/timer.h b/code/public/ntlite/ddk/timer.h index 06300d9..0c9884a 100644 --- a/code/public/ntlite/ddk/timer.h +++ b/code/public/ntlite/ddk/timer.h @@ -4,8 +4,7 @@ #include -// Driver-visible timing services. -typedef struct NTL_DDK_TIMER { - uint64_t (*QueryTicks)(void); - void (*Stall)(uint32_t microseconds); -} NTL_DDK_TIMER; + + +__declspec(dllexport) uint64_t KeQueryTicks(void); +__declspec(dllexport) void KeStallExecution(uint32_t microseconds); diff --git a/code/public/ntlite/handle.h b/code/public/ntlite/handle.h index e265af9..a8e3239 100644 --- a/code/public/ntlite/handle.h +++ b/code/public/ntlite/handle.h @@ -10,6 +10,7 @@ typedef uint32_t SYS_HANDLE; typedef uint32_t SYS_RIGHTS; #define SYS_HANDLE_INVALID ((SYS_HANDLE)0u) +#define SYS_STARTUP_CAPABILITY_NAME_MAX 63u // Capability-management rights shared by every handle type. Rights may only attenuate during delegation. #define SYS_RIGHT_DUPLICATE 0x00000001u @@ -110,6 +111,10 @@ typedef uint32_t SYS_RIGHTS; #define SYS_AUDIO_STREAM_RIGHT_WRITE 0x00000100u #define SYS_AUDIO_STREAM_RIGHT_CONTROL 0x00000200u +// System-service registry authority. Each authority object identifies one registry key. +#define SYS_SERVICE_REGISTRATION_RIGHT_REGISTER 0x00000100u +#define SYS_SERVICE_REGISTRATION_RIGHT_UNREGISTER 0x00000200u + // Security-context-specific rights are declared in . // Rights normally retained by a newly created root capability. diff --git a/code/public/ntlite/process_loader.h b/code/public/ntlite/process_loader.h index 51e78a2..de60789 100644 --- a/code/public/ntlite/process_loader.h +++ b/code/public/ntlite/process_loader.h @@ -22,7 +22,7 @@ typedef struct NTL_PROCESS_STRING_VECTOR { // Describes one source capability to duplicate into a constructing child. typedef struct NTL_PROCESS_STARTUP_CAPABILITY { - uint32_t Tag; + char Name[SYS_STARTUP_CAPABILITY_NAME_MAX + 1u]; SYS_HANDLE SourceHandle; SYS_RIGHTS DesiredRights; uint32_t DuplicateFlags; diff --git a/code/public/ntlite/resource.h b/code/public/ntlite/resource.h index c283633..72c0221 100644 --- a/code/public/ntlite/resource.h +++ b/code/public/ntlite/resource.h @@ -22,6 +22,7 @@ #define NTL_RESOURCE_OP_ENUMERATE 6u #define NTL_RESOURCE_OP_CREATE_DIRECTORY 7u #define NTL_RESOURCE_OP_CREATE_SYMBOLIC_LINK 8u +#define NTL_RESOURCE_OP_CREATE_PUBLISHER 9u #define NTL_RESOURCE_KIND_INVALID 0u #define NTL_RESOURCE_KIND_CAPABILITY 1u @@ -79,6 +80,9 @@ typedef struct NTL_RESOURCE_ENUM_ENTRY { // Connects one session to the globally registered resource service. NTL_RESOURCE_API KSTATUS NtlResourceConnect(SYS_HANDLE* sessionOut); NTL_RESOURCE_API KSTATUS NtlResourceOpenSystemRoot(SYS_RIGHTS desiredRights, SYS_HANDLE* handleOut); +// Creates an exact-path, bind/unbind-only publication listener through namespace-admin authority. +NTL_RESOURCE_API KSTATUS NtlResourceCreatePublisher(SYS_HANDLE adminSession, SYS_STRING_VIEW path, + SYS_HANDLE* listenerOut); // Connects one session through an explicitly delegated resource-listener capability. NTL_RESOURCE_API KSTATUS NtlResourceConnectListener(SYS_HANDLE listenerHandle, SYS_HANDLE* sessionOut); // Resolves one canonical global resource name to an attenuated capability. Symbolic links are followed. diff --git a/code/public/ntlite/security.h b/code/public/ntlite/security.h index 96645b6..b033274 100644 --- a/code/public/ntlite/security.h +++ b/code/public/ntlite/security.h @@ -7,27 +7,22 @@ #include typedef uint32_t SYS_PRIVILEGES; -typedef uint32_t SYS_SERVICE_CLAIMS; // Ambient privileges are reserved for kernel-mediated authority that is not represented by an object capability yet. #define SYS_PRIV_PROCESS_OPEN_ANY 0x00000001u #define SYS_PRIV_PROCESS_ENUMERATE 0x00000002u #define SYS_PRIV_THREAD_ENUMERATE_ANY 0x00000004u #define SYS_PRIV_KERNEL_DIAGNOSTICS 0x00000008u +#define SYS_PRIV_SYSTEM_POWER 0x00000010u #define SYS_PRIV_AUDIO_ACCESS 0x00000020u #define SYS_PRIV_AUDIO_MASTER_CONTROL 0x00000040u #define SYS_PRIV_CONSOLE_ACCESS 0x00000080u #define SYS_PRIV_DISPLAY_ACCESS 0x00000100u #define SYS_PRIV_DEBUG_OUTPUT 0x00000200u -#define SYS_PRIV_SYSTEM_SERVICE_REGISTER 0x00000400u #define SYS_PRIV_RAW_STORAGE 0x00000800u #define SYS_PRIV_FILESYSTEM_PROVIDER 0x00001000u #define SYS_PRIV_NETWORK_ACCESS 0x00002000u -#define SYS_PRIV_ALL 0x00003FEFu - -// Service claims constrain which well-known service identities a privileged context may register. -#define SYS_SERVICE_CLAIM_RESOURCE 0x00000001u -#define SYS_SERVICE_CLAIM_ALL SYS_SERVICE_CLAIM_RESOURCE +#define SYS_PRIV_ALL 0x00003BFFu // Security-context-specific capability rights. #define SYS_SECURITY_CONTEXT_RIGHT_DERIVE 0x00000100u @@ -41,8 +36,6 @@ typedef struct SYS_SECURITY_CONTEXT_INFO { uint32_t SecurityId; SYS_PRIVILEGES EffectivePrivileges; SYS_PRIVILEGES DelegablePrivileges; - SYS_SERVICE_CLAIMS ServiceClaims; - SYS_SERVICE_CLAIMS DelegableServiceClaims; uint32_t Flags; } SYS_SECURITY_CONTEXT_INFO; @@ -51,8 +44,6 @@ typedef struct SYS_DERIVE_SECURITY_CONTEXT_REQUEST { SYS_HANDLE SourceContext; SYS_PRIVILEGES EffectivePrivileges; SYS_PRIVILEGES DelegablePrivileges; - SYS_SERVICE_CLAIMS ServiceClaims; - SYS_SERVICE_CLAIMS DelegableServiceClaims; uint32_t Flags; SYS_HANDLE* ContextHandleOut; } SYS_DERIVE_SECURITY_CONTEXT_REQUEST; diff --git a/code/public/ntlite/service_control.h b/code/public/ntlite/service_control.h index e37e173..f64198e 100644 --- a/code/public/ntlite/service_control.h +++ b/code/public/ntlite/service_control.h @@ -1,18 +1,22 @@ -// Native service-manager control protocol. +// Native service-manager control protocol. Names determine exported handle identity. #pragma once +#include #include +#include +#include #include -#define NTL_SERVICE_CONTROL_VERSION 2u +#define NTL_SERVICE_CONTROL_VERSION 3u +#define NTL_SERVICE_CONTROL_READY 1u +#define NTL_SERVICE_CONTROL_STOP 2u +#define NTL_SERVICE_EXPORT_NAME_MAX 31u -#define NTL_SERVICE_CONTROL_READY 1u -#define NTL_SERVICE_CONTROL_STOP 2u - -// Service-control messages are notifications over a private Port connection. -// READY transfers ExportCount service-defined handles whose meaning is fixed by -// the service type known to the manager. Other operations transfer no handles. +// READY transfers one handle for each named entry in ExportNames. The entry at +// index i names the handle at transfer index i; its position has no semantic +// meaning. STOP transfers zero handles and carries only zeroed export names. +// Each message is wholly self-contained so the Port cannot retain user pointers. typedef struct NTL_SERVICE_CONTROL_MESSAGE { uint32_t Size; uint32_t Version; @@ -20,4 +24,73 @@ typedef struct NTL_SERVICE_CONTROL_MESSAGE { uint32_t ExportCount; KSTATUS Status; uint32_t Reserved[3]; + char ExportNames[SYS_PORT_MAX_TRANSFER_HANDLES][NTL_SERVICE_EXPORT_NAME_MAX + 1u]; } NTL_SERVICE_CONTROL_MESSAGE; + +// Accepts the deliberately restricted, case-sensitive capability-name grammar. +static inline bool NtlServiceExportNameValid(const char* name) { + if (!name || !name[0]) return false; + for (uint32_t i = 0; i <= NTL_SERVICE_EXPORT_NAME_MAX; ++i) { + char c = name[i]; + if (!c) return i != 0; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.')) + return false; + } + return false; +} + +// Labels an already counted READY transfer without assigning authority to its name. +static inline bool NtlServiceControlNameExport(NTL_SERVICE_CONTROL_MESSAGE* message, + uint32_t index, const char* name) { + if (!message || message->Operation != NTL_SERVICE_CONTROL_READY || + index >= message->ExportCount || index >= SYS_PORT_MAX_TRANSFER_HANDLES || + !NtlServiceExportNameValid(name)) + return false; + memset(message->ExportNames[index], 0, sizeof(message->ExportNames[index])); + size_t length = strlen(name); + memcpy(message->ExportNames[index], name, length); + return true; +} + +// Validates wire size, exact transfer count, export names, and zeroed unused entries. +static inline bool NtlServiceControlValid(const NTL_SERVICE_CONTROL_MESSAGE* message, + uint32_t operation, uint32_t handleCount) { + if (!message || message->Size != sizeof(*message) || + message->Version != NTL_SERVICE_CONTROL_VERSION || message->Operation != operation || + (operation != NTL_SERVICE_CONTROL_READY && operation != NTL_SERVICE_CONTROL_STOP) || + message->ExportCount != handleCount || handleCount > SYS_PORT_MAX_TRANSFER_HANDLES || + message->Reserved[0] || message->Reserved[1] || message->Reserved[2]) + return false; + if (operation == NTL_SERVICE_CONTROL_STOP && + (handleCount != 0u || message->Status != NTLSTATUS_SUCCESS)) + return false; + for (uint32_t i = 0; i < SYS_PORT_MAX_TRANSFER_HANDLES; ++i) { + const char* name = message->ExportNames[i]; + if (i >= handleCount) { + for (uint32_t j = 0; j <= NTL_SERVICE_EXPORT_NAME_MAX; ++j) + if (name[j]) return false; + continue; + } + if (!NtlServiceExportNameValid(name)) return false; + // The complete fixed-size wire representation has one canonical encoding. + uint32_t end = 0; + while (name[end]) ++end; + for (uint32_t j = end + 1u; j <= NTL_SERVICE_EXPORT_NAME_MAX; ++j) + if (name[j]) return false; + for (uint32_t earlier = 0; earlier < i; ++earlier) + if (strcmp(name, message->ExportNames[earlier]) == 0) return false; + } + return true; +} + +// Finds the transfer index for a validated named READY export. +static inline int32_t NtlServiceControlFindExport(const NTL_SERVICE_CONTROL_MESSAGE* message, + const char* name) { + if (!message || !NtlServiceExportNameValid(name) || + message->ExportCount > SYS_PORT_MAX_TRANSFER_HANDLES) + return -1; + for (uint32_t i = 0; i < message->ExportCount; ++i) + if (strcmp(message->ExportNames[i], name) == 0) return (int32_t)i; + return -1; +} diff --git a/code/public/ntlite/syscall.h b/code/public/ntlite/syscall.h index 8de8d94..abe8d02 100644 --- a/code/public/ntlite/syscall.h +++ b/code/public/ntlite/syscall.h @@ -29,26 +29,33 @@ // 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" -#define SYS_ENV_VERSION 4u +#define SYS_ENV_VERSION 5u #define SYS_PATH_MAX 1024u #ifndef SYS_USER_ENV_TYPES_DEFINED #define SYS_USER_ENV_TYPES_DEFINED 1 -#define SYS_STARTUP_CAP_SYSTEM_VOLUME 1u -#define SYS_STARTUP_CAP_SERVICE_CONTROL 2u -#define SYS_STARTUP_CAP_SYSTEM_VOLUME_NAMESPACE 3u -#define SYS_STARTUP_CAP_VOLUME_MANAGER 4u -#define SYS_STARTUP_CAP_INPUT_MANAGER 5u -#define SYS_STARTUP_CAP_DISPLAY_MANAGER 6u -#define SYS_STARTUP_CAP_SESSION_CONTROL 7u #define SYS_STARTUP_CAPABILITY_MAX 32u +// Named startup grants form an immutable process-local manifest. Names confer no rights. typedef struct SYS_STARTUP_CAPABILITY { - uint32_t Tag; + char Name[SYS_STARTUP_CAPABILITY_NAME_MAX + 1u]; SYS_HANDLE Handle; } SYS_STARTUP_CAPABILITY; +// Validates a canonical, zero-padded grant name received from the process loader. +static inline bool SysStartupCapabilityNameValid(const char name[SYS_STARTUP_CAPABILITY_NAME_MAX + 1u]) { + if (!name || !name[0]) return false; + bool ended = false; + for (uint32_t i = 0; i <= SYS_STARTUP_CAPABILITY_NAME_MAX; ++i) { + char c = name[i]; + if (!c) { ended = true; continue; } + if (ended || !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.')) return false; + } + return ended; +} + typedef struct SYS_PEB { uint32_t Magic; uint32_t Version; @@ -76,7 +83,7 @@ typedef struct SYS_TEB { } SYS_TEB; // User bootstrap is consumed by the kernel, loader and personalities. -_Static_assert(sizeof(SYS_STARTUP_CAPABILITY) == 8u, "SYS_STARTUP_CAPABILITY ABI size changed"); +_Static_assert(sizeof(SYS_STARTUP_CAPABILITY) == 68u, "SYS_STARTUP_CAPABILITY ABI size changed"); _Static_assert(offsetof(SYS_PEB, ImagePath) == 16u, "SYS_PEB ImagePath offset changed"); _Static_assert(offsetof(SYS_PEB, LoaderData) == 64u, "SYS_PEB LoaderData offset changed"); _Static_assert(sizeof(SYS_PEB) == 72u, "SYS_PEB ABI size changed"); @@ -91,21 +98,23 @@ static inline SYS_PEB* SysGetPeb(void) { return (SYS_PEB*)(uintptr_t)__readgsqword(0); } -// Returns one tagged startup capability from the immutable PEB descriptor table. -static inline SYS_HANDLE SysGetStartupCapability(uint32_t tag) { +// Resolves an immutable capability name without interpreting its authority. +static inline SYS_HANDLE SysGetStartupCapability(const char* name) { SYS_PEB* peb = SysGetPeb(); - if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION || !tag || - peb->StartupCapabilityCount > SYS_STARTUP_CAPABILITY_MAX || + if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION || + !name || !name[0] || peb->StartupCapabilityCount > SYS_STARTUP_CAPABILITY_MAX || (peb->StartupCapabilityCount && !peb->StartupCapabilities)) return SYS_HANDLE_INVALID; for (uint32_t i = 0; i < peb->StartupCapabilityCount; ++i) { - if (peb->StartupCapabilities[i].Tag == tag) + const char* entry = peb->StartupCapabilities[i].Name; + uint32_t n = 0; + while (n <= SYS_STARTUP_CAPABILITY_NAME_MAX && name[n] && entry[n] && name[n] == entry[n]) ++n; + if (n <= SYS_STARTUP_CAPABILITY_NAME_MAX && name[n] == '\0' && entry[n] == '\0') return peb->StartupCapabilities[i].Handle; } return SYS_HANDLE_INVALID; } - // Returns the current thread environment block. static inline SYS_TEB* SysGetTeb(void) { return (SYS_TEB*)(uintptr_t)__readgsqword(offsetof(SYS_TEB, Self)); @@ -681,6 +690,14 @@ typedef struct SYS_QUEUE_USER_APC_REQUEST { uint32_t Reserved; } SYS_QUEUE_USER_APC_REQUEST; +#define SYS_POWER_ACTION_SHUTDOWN 1u +#define SYS_POWER_ACTION_RESTART 2u + +KSTATUS SysOpenPowerEvent(SYS_HANDLE* eventHandleOut); +KSTATUS SysRequestSystemPower(uint32_t action); +KSTATUS SysQuerySystemPower(uint32_t* actionOut); +KSTATUS SysCompleteSystemPower(uint32_t action); + void SysExitProcess(int32_t exitCode); void SysExitThread(int32_t exitCode); KSTATUS SysCreateProcessObject(const SYS_CREATE_PROCESS_OBJECT_REQUEST* request); @@ -996,8 +1013,9 @@ KSTATUS SysReplyPort(const SYS_REPLY_PORT_REQUEST* request); KSTATUS SysDisconnectPort(SYS_HANDLE endpointHandle); KSTATUS SysOpenPortPeerProcess(SYS_HANDLE endpointHandle, SYS_RIGHTS desiredRights, SYS_HANDLE* processHandleOut); -KSTATUS SysRegisterSystemService(SYS_SERVICE_ID serviceId, SYS_HANDLE listenerHandle); -KSTATUS SysUnregisterSystemService(SYS_SERVICE_ID serviceId, SYS_HANDLE listenerHandle); +// The registration capability selects one numeric registry key and authorizes publication. +KSTATUS SysRegisterSystemService(SYS_HANDLE registrationHandle, SYS_HANDLE listenerHandle); +KSTATUS SysUnregisterSystemService(SYS_HANDLE registrationHandle, SYS_HANDLE listenerHandle); KSTATUS SysConnectSystemService(const SYS_CONNECT_SYSTEM_SERVICE_REQUEST* request); KSTATUS SysQueryVolumes(const SYS_QUERY_VOLUMES_REQUEST* request); diff --git a/code/services.ini b/code/services.ini index f7509cd..735b8cc 100644 --- a/code/services.ini +++ b/code/services.ini @@ -1,10 +1,21 @@ ; nt-lite user-mode system services. ; -; init.exe loads this mandatory file from the bootstrap system volume. Service -; identifiers are the case-sensitive suffix of [service.]. +; init.exe loads this mandatory file from the bootstrap system volume. +; [service.] Service identity; the suffix is case-sensitive. +; image= Executable path relative to the bootstrap system volume. +; start= auto starts at boot; disabled prevents automatic startup. +; critical= Whether service failure is fatal to system startup (true/false). +; restart= Restart policy (never or on-failure; current adapters restrict usage). +; ready_timeout_ms= Milliseconds allowed for the service READY handshake. +; stop_timeout_ms= Milliseconds allowed for graceful service shutdown. +; requires= Comma-separated service IDs that must start first; empty is allowed. +; grants= Comma-separated capability sources delegated at startup; empty is allowed. +; exports= Names of handles this service must transfer at READY; empty is allowed. +; Source names are boot. or .. +; Each service key is mandatory. Comments occupy separate lines. +; RSSRV defines publisher names and their allowed paths; init only forwards handles. [service.rssrv] -type=resource image=system/rssrv.exe start=auto critical=true @@ -12,12 +23,10 @@ restart=never ready_timeout_ms=5000 stop_timeout_ms=5000 requires= - -; Generic services export one public Port listener in READY. Init publishes it at -; /Services/ through RSSRV and supervises the process with restart=never. +grants=boot.namespace-root,boot.volume-manager,boot.input-manager,boot.display-policy,boot.resource-registration +exports=session-control,desktop-publisher,posix-publisher [service.posix] -type=generic image=system/posixsrv.exe start=auto critical=false @@ -25,9 +34,10 @@ restart=never ready_timeout_ms=5000 stop_timeout_ms=5000 requires=rssrv +grants=rssrv.posix-publisher +exports=public [service.dwm] -type=dwm image=system/dwm.exe start=auto critical=false @@ -35,3 +45,5 @@ restart=never ready_timeout_ms=5000 stop_timeout_ms=5000 requires=rssrv +grants=boot.display-present,rssrv.session-control,rssrv.desktop-publisher +exports=desktop diff --git a/code/settings.ini b/code/settings.ini index 73ca3a7..f29d7db 100644 --- a/code/settings.ini +++ b/code/settings.ini @@ -29,6 +29,10 @@ hardware_clock=utc ; Used when timezone is a custom/fixed policy; minutes east of UTC. standard_offset_minutes=60 +[power] +; ACPI fixed power button: shutdown, restart, or ignore. Changes apply after boot. +button=shutdown + [vm] ; 0 disables swap. Nonzero swap must be between 4 MB and 8192 MB. swap_size_mb=32 diff --git a/code/tests/ddk/module_export_smoke.c b/code/tests/ddk/module_export_smoke.c new file mode 100644 index 0000000..7da403c --- /dev/null +++ b/code/tests/ddk/module_export_smoke.c @@ -0,0 +1,10 @@ +// A minimal no-entry DLL image used to check the public module-export ABI. + +#include + +static KSTATUS TestInitialize(NTL_MODULE* module) { + (void)module; + return NTLSTATUS_SUCCESS; +} + +NTL_DECLARE_MODULE(TestInitialize); diff --git a/code/tests/ddk/pci_caps_test.c b/code/tests/ddk/pci_caps_test.c new file mode 100644 index 0000000..875c134 --- /dev/null +++ b/code/tests/ddk/pci_caps_test.c @@ -0,0 +1,37 @@ +// Host-runnable tests for the pure DDK PCI capability-list helper. + +#include +#include +#include + +static uint32_t ReadConfig(void* context, uint16_t offset) { + const uint32_t* config = (const uint32_t*)context; + return config[offset / 4u]; +} + +int main(void) { + uint32_t config[64] = {0}; + NTL_PCI_CONFIG_READER reader = { config, ReadConfig }; + uint8_t offset = 0xFFu; + + assert(NtlPciFindCapability(&reader, 0, 0x05u, &offset) == NTLSTATUS_NOT_FOUND); + assert(offset == 0); + + config[0x40u / 4u] = 0x00004801u; + config[0x48u / 4u] = 0x00000005u; + assert(KSUCCESS(NtlPciFindCapability(&reader, 0x40u, 0x05u, &offset))); + assert(offset == 0x48u); + assert(NtlPciFindCapability(&reader, 0x40u, 0x11u, &offset) == NTLSTATUS_NOT_FOUND); + assert(offset == 0); + + config[0x48u / 4u] = 0x00004002u; + assert(NtlPciFindCapability(&reader, 0x40u, 0x05u, &offset) == NTLSTATUS_DEVICE_ERROR); + assert(offset == 0); + + assert(NtlPciFindCapability(&reader, 0x20u, 0x05u, &offset) == NTLSTATUS_DEVICE_ERROR); + assert(NtlPciFindCapability(&reader, 0x41u, 0x05u, &offset) == NTLSTATUS_DEVICE_ERROR); + assert(NtlPciFindCapability((NTL_PCI_CONFIG_READER*)0, 0x40u, 0x05u, &offset) == NTLSTATUS_INVALID_PARAMETER); + assert(NtlPciFindCapability(&reader, 0x40u, 0x05u, (uint8_t*)0) == NTLSTATUS_INVALID_PARAMETER); + assert(NtlPciFindCapability(&reader, 0x40u, 0, &offset) == NTLSTATUS_INVALID_PARAMETER); + return 0; +} diff --git a/code/tests/driver_manager/README.md b/code/tests/driver_manager/README.md new file mode 100644 index 0000000..9372e41 --- /dev/null +++ b/code/tests/driver_manager/README.md @@ -0,0 +1,13 @@ +# Driver Manager v2 host checks + +Run the portable parser/graph regression test from the repository root: + +```sh +gcc -std=c17 -Wall -Wextra -Werror -Icode/kernel/include -Icode/public \ + code/kernel/dm_config.c code/tests/driver_manager/config_test.c \ + -o config_test +./config_test code/drivers/drivers.ini +``` + +The bootloader/kernel ABI must additionally be built with the MSVC toolchain; +these host tests do not establish a boot success or verify EFI firmware I/O. diff --git a/code/tests/driver_manager/config_test.c b/code/tests/driver_manager/config_test.c new file mode 100644 index 0000000..e1ecb64 --- /dev/null +++ b/code/tests/driver_manager/config_test.c @@ -0,0 +1,138 @@ +// Host-side regression tests for the canonical drivers.ini v2 graph semantics. + +#include +#include +#include +#include +#include + +static DM_CONFIG* gConfig; + +static bool Parse(const char* source) { + char error[128] = {0}; + bool ok = DmConfigParse(gConfig, source, strlen(source), error, sizeof(error)); + if (!ok) fprintf(stderr, "parse rejected (%s):\n%s\n", error, source); + return ok; +} + +static bool Reject(const char* source) { + char error[128] = {0}; + if (DmConfigParse(gConfig, source, strlen(source), error, sizeof(error))) { + fprintf(stderr, "unexpected acceptance:\n%s\n", source); + return false; + } + return error[0] != '\0'; +} + +static int Find(const char* id) { + for (uint32_t i = 0; i < gConfig->DriverCount; ++i) + if (!strcmp(gConfig->Drivers[i].Id, id)) return (int)i; + return -1; +} + +int main(int argc, char** argv) { + gConfig = calloc(1, sizeof(*gConfig)); + assert(gConfig); + assert(Parse("[driver.a]\nimage=a.sys\nstart=boot\n")); + assert(gConfig->Drivers[0].Class[0] == 0 && !gConfig->Drivers[0].Critical); + assert(Parse("[driver.a]\nimage=a.sys\nstart=boot\n" + "class=\nrequires=\nafter=\ncritical=\n")); + assert(gConfig->Drivers[0].Class[0] == 0 && !gConfig->Drivers[0].Critical); + assert(Reject("[driver.a]\nimage=a.sys\n")); + assert(Reject("[driver.a]\nimage=\nstart=boot\n")); + assert(Reject("[driver.a]\nimage=a.sys\nstart=boot\nfoobar=\n")); + assert(Reject("[driver.a]\nimage=a.sys\nstart=boot\nstart=runtime\n")); + assert(Reject("[driver.a]\nimage=a.sys\nstart=boot\n[driver.a]\nimage=b.sys\nstart=boot\n")); + assert(Reject("[driver.a]\nimage=a.sys\nstart=boot\n[class.other]\n")); + assert(Reject("[driver.a]\nimage=a.sys\nstart=boot\nrequires=missing\n")); + assert(Reject("[driver.a]\nimage=a.sys\nstart=boot\nafter=missing\n")); + assert(Reject("[driver.a]\nimage=a.sys\nstart=boot\nrequires=a\n")); + assert(Reject("[driver.a]\nimage=a.sys\nstart=boot\nrequires=b\n" + "[driver.b]\nimage=b.sys\nstart=boot\nrequires=a\n")); + assert(Reject("[class.a]\nrequires=b\n[class.b]\nrequires=a\n" + "[driver.c]\nimage=c.sys\nstart=boot\n")); + assert(Reject("[class.bus]\n[class.fs]\nafter=bus\n" + "[driver.a]\nimage=a.sys\nstart=boot\nclass=bus\nrequires=b\n" + "[driver.b]\nimage=b.sys\nstart=boot\nclass=fs\n")); + assert(Reject("[driver.a]\nimage=a.sys\nstart=boot\nrequires=b\n" + "[driver.b]\nimage=b.sys\nstart=runtime\n")); + assert(Reject("[class.bus]\n[driver.a]\nimage=a.sys\nstart=boot\nclass=bus\n" + "requires=\nrequires=\n")); + assert(Reject("[class.bus]\n[driver.a]\nimage=a.sys\nstart=boot\nclass=bus\n" + "requires=a\nafter=a\n")); + assert(Reject("[driver.a]\nimage=a.sys\nstart=boot\n" + "[driver.b]\nimage=a.sys\nstart=runtime\n")); + + // Provider alternatives are OR dependencies; requiring every provider here + // would manufacture a false cycle between client and the second provider. + assert(Parse("[class.providers]\n" + "[class.clients]\nrequires=providers\n" + "[driver.p1]\nimage=p1.sys\nstart=boot\nclass=providers\n" + "[driver.p2]\nimage=p2.sys\nstart=boot\nclass=providers\nrequires=client\n" + "[driver.client]\nimage=client.sys\nstart=boot\nclass=clients\n")); + gConfig->ActivePhase = DmStartBoot; + assert(DmDriverCanStart(gConfig, (uint32_t)Find("p1"))); + assert(!DmDriverCanStart(gConfig, (uint32_t)Find("client"))); + gConfig->Drivers[Find("p1")].State = DmRunStarted; + assert(DmDriverCanStart(gConfig, (uint32_t)Find("client"))); + gConfig->Drivers[Find("client")].State = DmRunStarted; + assert(DmDriverCanStart(gConfig, (uint32_t)Find("p2"))); + + // A soft barrier waits for all current-phase providers, even failures. + assert(Parse("[class.nic]\n[class.stack]\nafter=nic\n" + "[driver.a]\nimage=a.sys\nstart=runtime\nclass=nic\n" + "[driver.b]\nimage=b.sys\nstart=runtime\nclass=nic\n" + "[driver.stack]\nimage=stack.sys\nstart=runtime\nclass=stack\n")); + gConfig->ActivePhase = DmStartRuntime; + assert(!DmDriverCanStart(gConfig, (uint32_t)Find("stack"))); + gConfig->Drivers[Find("a")].State = DmRunFailed; + gConfig->Drivers[Find("b")].State = DmRunFailed; + assert(DmDriverCanStart(gConfig, (uint32_t)Find("stack"))); + + // An empty declared class is settled, not a parser error. A soft consumer + // still starts when no driver provides that class. + assert(Parse("[class.nic]\n[class.stack]\nafter=nic\n" + "[driver.stack]\nimage=stack.sys\nstart=runtime\nclass=stack\n")); + gConfig->ActivePhase = DmStartRuntime; + assert(DmDriverCanStart(gConfig, (uint32_t)Find("stack"))); + + // A boot class requirement can use a boot provider even when additional + // providers will join the class during the runtime phase. + assert(Parse("[class.bus]\n[class.client]\nrequires=bus\n" + "[driver.pci]\nimage=pci.sys\nstart=boot\nclass=bus\n" + "[driver.virtual]\nimage=virtual.sys\nstart=runtime\nclass=bus\n" + "[driver.client]\nimage=client.sys\nstart=boot\nclass=client\n")); + gConfig->ActivePhase = DmStartBoot; + gConfig->Drivers[Find("pci")].State = DmRunStarted; + assert(DmDriverCanStart(gConfig, (uint32_t)Find("client"))); + assert(DmClassSettled(gConfig, "bus")); + gConfig->ActivePhase = DmStartRuntime; + assert(!DmClassSettled(gConfig, "bus")); + + // A boot hard dependency cannot be satisfied solely by a runtime provider. + assert(Reject("[class.bus]\n[class.client]\nrequires=bus\n" + "[driver.virtual]\nimage=virtual.sys\nstart=runtime\nclass=bus\n" + "[driver.client]\nimage=client.sys\nstart=boot\nclass=client\n")); + + // A disabled provider never satisfies a hard requires edge. + assert(Parse("[driver.a]\nimage=a.sys\nstart=disabled\n" + "[driver.b]\nimage=b.sys\nstart=runtime\nrequires=a\n")); + assert(gConfig->Drivers[Find("a")].State == DmRunDisabled); + assert(DmDriverIsBlocked(gConfig, (uint32_t)Find("b"))); + + if (argc == 2) { + FILE* file = fopen(argv[1], "rb"); + assert(file); + char* input = calloc(1, DM_CONFIG_MAX_BYTES + 1); + assert(input); + size_t size = fread(input, 1, DM_CONFIG_MAX_BYTES, file); + fclose(file); + char error[128] = {0}; + assert(DmConfigParse(gConfig, input, size, error, sizeof(error))); + assert(gConfig->DriverCount == 14); + free(input); + } + puts("driver manager config tests passed"); + free(gConfig); + return 0; +} diff --git a/code/tests/driver_manager/pnp_mock/include/kernel/ke_debug.h b/code/tests/driver_manager/pnp_mock/include/kernel/ke_debug.h new file mode 100644 index 0000000..b80e346 --- /dev/null +++ b/code/tests/driver_manager/pnp_mock/include/kernel/ke_debug.h @@ -0,0 +1,2 @@ +#pragma once +void DbgPrint(const char* fmt, ...); diff --git a/code/tests/driver_manager/pnp_mock/include/kernel/ke_event.h b/code/tests/driver_manager/pnp_mock/include/kernel/ke_event.h new file mode 100644 index 0000000..c5e99b5 --- /dev/null +++ b/code/tests/driver_manager/pnp_mock/include/kernel/ke_event.h @@ -0,0 +1,10 @@ +#pragma once +#include +#include +#include +typedef struct EVENT EVENT; +typedef enum EVENT_TYPE { EventAutoReset, EventManualReset } EVENT_TYPE; +KSTATUS KeEventCreate(EVENT_TYPE type, bool initial, EVENT** out); +void KeSignalEvent(EVENT* event); +KSTATUS KeWaitForEvent(EVENT* event, bool alertable); +KSTATUS KeWaitForMultipleEvents(EVENT** events, uint32_t count, bool alertable, uint64_t timeoutMs, uint32_t* index); diff --git a/code/tests/driver_manager/pnp_mock/include/kernel/ke_spinlock.h b/code/tests/driver_manager/pnp_mock/include/kernel/ke_spinlock.h new file mode 100644 index 0000000..40ff7a9 --- /dev/null +++ b/code/tests/driver_manager/pnp_mock/include/kernel/ke_spinlock.h @@ -0,0 +1,7 @@ +#pragma once +#include +#include +typedef pthread_mutex_t SPINLOCK; +#define SPINLOCK_INIT PTHREAD_MUTEX_INITIALIZER +static inline uintptr_t KeAcquireSpinlock(SPINLOCK* l) { pthread_mutex_lock(l); return 0; } +static inline void KeReleaseSpinlock(SPINLOCK* l, uintptr_t ignored) { (void)ignored; pthread_mutex_unlock(l); } diff --git a/code/tests/driver_manager/pnp_mock/include/kernel/ke_thread.h b/code/tests/driver_manager/pnp_mock/include/kernel/ke_thread.h new file mode 100644 index 0000000..510417e --- /dev/null +++ b/code/tests/driver_manager/pnp_mock/include/kernel/ke_thread.h @@ -0,0 +1,9 @@ +#pragma once +#include +#include +typedef struct THREAD THREAD; +typedef enum THREAD_PRIORITY { PriorityNormal = 1 } THREAD_PRIORITY; +typedef struct KE_THREAD_CREATE_PARAMETERS { uint32_t Priority; const char* Name; } KE_THREAD_CREATE_PARAMETERS; +#define KE_THREAD_CREATE_NAMED_NORMAL(priority, name) { (priority), (name) } +KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg, + const KE_THREAD_CREATE_PARAMETERS* params, THREAD** out); diff --git a/code/tests/driver_manager/pnp_mock/include/kernel/ke_time.h b/code/tests/driver_manager/pnp_mock/include/kernel/ke_time.h new file mode 100644 index 0000000..069e524 --- /dev/null +++ b/code/tests/driver_manager/pnp_mock/include/kernel/ke_time.h @@ -0,0 +1,4 @@ +#pragma once +#include +uint64_t KeMonotonicDeadlineAfterMilliseconds(uint64_t ms); +uint64_t KeMonotonicMillisecondsUntil(uint64_t deadlineUs); diff --git a/code/tests/driver_manager/pnp_mock/include/kernel/ke_work.h b/code/tests/driver_manager/pnp_mock/include/kernel/ke_work.h new file mode 100644 index 0000000..d40fff9 --- /dev/null +++ b/code/tests/driver_manager/pnp_mock/include/kernel/ke_work.h @@ -0,0 +1,9 @@ +#pragma once +#include +typedef void (*KWORK_ROUTINE)(void* context); +typedef struct KWORK_ITEM { + KWORK_ROUTINE Routine; + void* Context; +} KWORK_ITEM; +void KeInitializeWorkItem(KWORK_ITEM* item, KWORK_ROUTINE routine, void* context); +bool KeQueueWorkItem(KWORK_ITEM* item); diff --git a/code/tests/driver_manager/pnp_mock/include/kernel/mm_virt.h b/code/tests/driver_manager/pnp_mock/include/kernel/mm_virt.h new file mode 100644 index 0000000..fd915cb --- /dev/null +++ b/code/tests/driver_manager/pnp_mock/include/kernel/mm_virt.h @@ -0,0 +1,4 @@ +#pragma once +#include +void* MmAllocatePool(size_t size); +void MmFreePool(void* ptr); diff --git a/code/tests/driver_manager/pnp_mock/include/ntlite/ddi/io_driver.h b/code/tests/driver_manager/pnp_mock/include/ntlite/ddi/io_driver.h new file mode 100644 index 0000000..cc0a0bc --- /dev/null +++ b/code/tests/driver_manager/pnp_mock/include/ntlite/ddi/io_driver.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include +#include + +typedef struct SYS_STRING_VIEW { + const char* Data; + size_t Length; +} SYS_STRING_VIEW; + +typedef enum IO_BUS_TYPE { IoBusUnknown, IoBusPci, IoBusUsb } IO_BUS_TYPE; +typedef enum DEVICE_OBJECT_TYPE { DeviceObjectPhysical = 1, DeviceObjectFunction = 2 } DEVICE_OBJECT_TYPE; +typedef struct DEVICE_OBJECT { + DEVICE_OBJECT_TYPE Type; + union { + struct { + IO_BUS_TYPE BusType; + uint8_t IdCount; + struct { SYS_STRING_VIEW Id; } Ids[8]; + } Pdo; + } u; + struct DEVICE_OBJECT* AttachedDevice; + struct DEVICE_OBJECT* Parent; + struct DEVICE_OBJECT* LowerDevice; + bool DeletePending; + int Refs; +} DEVICE_OBJECT; +void IoReferenceDeviceObject(DEVICE_OBJECT* device); +void IoDereferenceDeviceObject(DEVICE_OBJECT* device); + +#include +#include +#include + +typedef struct IO_DEVICE_ID { + IO_BUS_TYPE BusType; + SYS_STRING_VIEW Id; +} IO_DEVICE_ID; +typedef struct IO_DRIVER_ID_TABLE { + const IO_DEVICE_ID* Ids; + size_t Count; +} IO_DRIVER_ID_TABLE; +typedef struct DRIVER_OBJECT DRIVER_OBJECT; +struct DRIVER_OBJECT { + SYS_STRING_VIEW Name; + const IO_DRIVER_ID_TABLE* IdTable; + KSTATUS (*DriverAddDevice)(DRIVER_OBJECT*, DEVICE_OBJECT*); +}; +typedef enum IO_PNP_MINOR_FUNCTION { + IRP_MN_START_DEVICE, + IRP_MN_REMOVE_DEVICE, +} IO_PNP_MINOR_FUNCTION; +static inline SYS_STRING_VIEW SysSvFromParts(const char* ptr, size_t len) { + SYS_STRING_VIEW sv = {ptr, len}; + return sv; +} +static inline bool SysSvEqual(SYS_STRING_VIEW a, SYS_STRING_VIEW b) { + return a.Length == b.Length && (!a.Length || memcmp(a.Data, b.Data, a.Length) == 0); +} +KSTATUS IoSendPnpIrpSync(DEVICE_OBJECT* device, IO_PNP_MINOR_FUNCTION minor); diff --git a/code/tests/driver_manager/pnp_test.c b/code/tests/driver_manager/pnp_test.c new file mode 100644 index 0000000..eae9d23 --- /dev/null +++ b/code/tests/driver_manager/pnp_test.c @@ -0,0 +1,350 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct EVENT { + pthread_mutex_t Mutex; + pthread_cond_t Cond; + int Signaled; +}; + +static _Atomic int starts, stops, cancels, releases; +static _Atomic int ioAdds, ioPnPStarts, ioPnPRemoves; +static DEVICE_OBJECT* ioDevice; +static NTL_DEVICE* pendingDevice; +static NTL_DEVICE* ordinaryDevice; + +static uint64_t NowUs(void) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return (uint64_t)now.tv_sec * 1000000u + (uint64_t)now.tv_nsec / 1000u; +} +uint64_t KeMonotonicDeadlineAfterMilliseconds(uint64_t ms) { return NowUs() + ms * 1000u; } +uint64_t KeMonotonicMillisecondsUntil(uint64_t deadlineUs) { + uint64_t now = NowUs(); + return now >= deadlineUs ? 0 : (deadlineUs - now + 999u) / 1000u; +} +void DbgPrint(const char* fmt, ...) { (void)fmt; } +void* MmAllocatePool(size_t size) { return malloc(size); } +void MmFreePool(void* memory) { free(memory); } +void IoReferenceDeviceObject(DEVICE_OBJECT* device) { __atomic_add_fetch(&device->Refs, 1, __ATOMIC_SEQ_CST); } +void IoDereferenceDeviceObject(DEVICE_OBJECT* device) { __atomic_sub_fetch(&device->Refs, 1, __ATOMIC_SEQ_CST); } +KSTATUS IoSendPnpIrpSync(DEVICE_OBJECT* device, IO_PNP_MINOR_FUNCTION minor) { + if (minor == IRP_MN_START_DEVICE) ++ioPnPStarts; + if (minor == IRP_MN_REMOVE_DEVICE) ++ioPnPRemoves; + assert(device == ioDevice); + return NTLSTATUS_SUCCESS; +} +static KSTATUS IoAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* pdo) { + (void)driver; + ioDevice = pdo; + ++ioAdds; + pdo->AttachedDevice = pdo; + return NTLSTATUS_SUCCESS; +} +KSTATUS KeEventCreate(EVENT_TYPE type, bool initial, EVENT** out) { + (void)type; + EVENT* event = calloc(1, sizeof(*event)); + assert(event); + pthread_mutex_init(&event->Mutex, NULL); + pthread_cond_init(&event->Cond, NULL); + event->Signaled = initial; + *out = event; + return NTLSTATUS_SUCCESS; +} +void KeSignalEvent(EVENT* event) { + pthread_mutex_lock(&event->Mutex); + event->Signaled = 1; + pthread_cond_signal(&event->Cond); + pthread_mutex_unlock(&event->Mutex); +} +KSTATUS KeWaitForMultipleEvents(EVENT** events, uint32_t count, bool alertable, + uint64_t timeoutMs, uint32_t* index) { + (void)alertable; + assert(count == 1); + EVENT* event = events[0]; + pthread_mutex_lock(&event->Mutex); + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += (time_t)(timeoutMs / 1000); + ts.tv_nsec += (long)(timeoutMs % 1000) * 1000000L; + if (ts.tv_nsec >= 1000000000L) { ts.tv_nsec -= 1000000000L; ++ts.tv_sec; } + while (!event->Signaled) { + int result = pthread_cond_timedwait(&event->Cond, &event->Mutex, &ts); + if (result == ETIMEDOUT) { pthread_mutex_unlock(&event->Mutex); return NTLSTATUS_TIMEOUT; } + } + event->Signaled = 0; + pthread_mutex_unlock(&event->Mutex); + if (index) *index = 0; + return NTLSTATUS_SUCCESS; +} +KSTATUS KeWaitForEvent(EVENT* event, bool alertable) { + (void)alertable; + pthread_mutex_lock(&event->Mutex); + while (!event->Signaled) pthread_cond_wait(&event->Cond, &event->Mutex); + event->Signaled = 0; + pthread_mutex_unlock(&event->Mutex); + return NTLSTATUS_SUCCESS; +} +void KeInitializeWorkItem(KWORK_ITEM* item, KWORK_ROUTINE routine, void* context) { + item->Routine = routine; + item->Context = context; +} +bool KeQueueWorkItem(KWORK_ITEM* item) { + item->Routine(item->Context); + return true; +} +static void* RunThread(void* arg) { + uintptr_t* fnArg = arg; + void (*fn)(void*) = (void(*)(void*))fnArg[0]; + void* ctx = (void*)fnArg[1]; + free(fnArg); + fn(ctx); + return NULL; +} +KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg, const KE_THREAD_CREATE_PARAMETERS* params, THREAD** out) { + (void)params; (void)out; + uintptr_t* args = malloc(2 * sizeof(uintptr_t)); + assert(args); + args[0] = fn; args[1] = arg; + pthread_t id; + assert(!pthread_create(&id, NULL, RunThread, args)); + pthread_detach(id); + return NTLSTATUS_SUCCESS; +} +static pthread_mutex_t gateLock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t gateCond = PTHREAD_COND_INITIALIZER; +static _Atomic int slowEntered, fastDone; +static bool gateOpen; +static KSTATUS SlowStart(NTL_DEVICE* device) { + (void)device; + ++slowEntered; + pthread_mutex_lock(&gateLock); + while (!gateOpen) pthread_cond_wait(&gateCond, &gateLock); + pthread_mutex_unlock(&gateLock); + return NTLSTATUS_SUCCESS; +} +static KSTATUS FastStart(NTL_DEVICE* device) { + (void)device; + ++fastDone; + return NTLSTATUS_SUCCESS; +} +static void WaitForAtomic(_Atomic int* value) { + struct timespec delay = {0, 1000000L}; + for (int i = 0; i < 1000 && !*value; ++i) nanosleep(&delay, NULL); + assert(*value); +} +static void Released(void* ctx) { assert(ctx == (void*)0x1234); ++releases; } +static KSTATUS OrdinaryStart(NTL_DEVICE* device) { + ++starts; + assert(NtlDeviceBus(device) == NtlBusPci); + assert(!strcmp(NtlDeviceId(device, 0), "PCI\\VEN_1234")); + ordinaryDevice = device; + NTL_DEVICE_RESOURCE* r = NULL; + assert(KSUCCESS(NtlDeviceOwnResource(device, (void*)0x1234, Released, &r))); + assert(r); + return NTLSTATUS_SUCCESS; +} +static KSTATUS PendingStart(NTL_DEVICE* device) { + ++starts; + pendingDevice = device; + return NTLSTATUS_PENDING; +} +static KSTATUS FailingStart(NTL_DEVICE* device) { + ++starts; + NTL_DEVICE_RESOURCE* r = NULL; + assert(KSUCCESS(NtlDeviceOwnResource(device, (void*)0x1234, Released, &r))); + return NTLSTATUS_FAILURE; +} +static void Stopped(NTL_DEVICE* device) { assert(device); ++stops; } +static void Cancelled(NTL_DEVICE* device) { + ++cancels; + assert(KSUCCESS(NtlDeviceCompleteStart(device, NTLSTATUS_CANCELLED))); +} +static _Atomic int topologyLeafStopped, topologyHubStopped, topologyControllerStopped; + +static KSTATUS TopologyStart(NTL_DEVICE* device) { + (void)device; + return NTLSTATUS_SUCCESS; +} + +static void TopologyStop(NTL_DEVICE* device) { + const char* id = NtlDeviceId(device, 0); + if (!strcmp(id, "USB\\STORAGE")) { + ++topologyLeafStopped; + } else if (!strcmp(id, "USB\\ROOT_HUB")) { + assert(topologyLeafStopped == 1); + ++topologyHubStopped; + } else if (!strcmp(id, "PCI\\USB_XHCI")) { + assert(topologyHubStopped == 1); + ++topologyControllerStopped; + } else { + assert(0); + } +} + +static DEVICE_OBJECT MakePdo(const char* id) { + DEVICE_OBJECT pdo = {0}; + pdo.Type = DeviceObjectPhysical; + pdo.Refs = 1; + pdo.u.Pdo.BusType = IoBusPci; + pdo.u.Pdo.IdCount = 1; + pdo.u.Pdo.Ids[0].Id.Data = id; + pdo.u.Pdo.Ids[0].Id.Length = strlen(id); + return pdo; +} +int main(void) { + const NTL_DEVICE_MATCH matches[] = { { NtlBusPci, "PCI\\VEN_1234" } }; + const NTL_DEVICE_DESC descriptor = { + sizeof(NTL_DEVICE_DESC), 0, "pci.test", matches, 1, 16, OrdinaryStart, Stopped, NULL + }; + assert(KSUCCESS(IoPnpInitialize())); + assert(KSUCCESS(IoPnpPublishModule((void*)1, &descriptor, 1, NULL, 0))); + assert(IoPnpPublishModule((void*)1, &descriptor, 1, NULL, 0) == NTLSTATUS_ALREADY_EXISTS); + DEVICE_OBJECT pdo = MakePdo("PCI\\VEN_1234"); + assert(KSUCCESS(IoPnpPhysicalDeviceArrived(&pdo))); + IoPnpPhysicalDevicePublished(&pdo); + assert(KSUCCESS(IoPnpWaitForIdle(1000))); + assert(starts == 1 && ordinaryDevice); + IoPnpPhysicalDeviceRemoved(&pdo); + assert(KSUCCESS(IoPnpWaitForIdle(1000))); + assert(stops == 1 && releases == 1 && pdo.Refs == 1); + + const NTL_DEVICE_DESC pending = { + sizeof(NTL_DEVICE_DESC), 0, "pci.pending", matches, 1, 0, PendingStart, Stopped, Cancelled + }; + const NTL_DEVICE_MATCH pendingMatches[] = { { NtlBusPci, "PCI\\VEN_5678" } }; + NTL_DEVICE_DESC selected = pending; + selected.Matches = pendingMatches; + assert(KSUCCESS(IoPnpPublishModule((void*)2, &selected, 1, NULL, 0))); + DEVICE_OBJECT pdo2 = MakePdo("PCI\\VEN_5678"); + assert(KSUCCESS(IoPnpPhysicalDeviceArrived(&pdo2))); + IoPnpPhysicalDevicePublished(&pdo2); + assert(IoPnpWaitForIdle(50) == NTLSTATUS_TIMEOUT); + IoPnpPhysicalDeviceRemoved(&pdo2); + assert(KSUCCESS(IoPnpWaitForIdle(1000))); + assert(cancels == 1 && pdo2.Refs == 1); + + const NTL_DEVICE_MATCH failingMatches[] = { { NtlBusPci, "PCI\\VEN_9999" } }; + const NTL_DEVICE_DESC failing = { + sizeof(NTL_DEVICE_DESC), 0, "pci.failing", failingMatches, 1, 0, FailingStart, Stopped, NULL + }; + assert(KSUCCESS(IoPnpPublishModule((void*)3, &failing, 1, NULL, 0))); + DEVICE_OBJECT pdo3 = MakePdo("PCI\\VEN_9999"); + assert(KSUCCESS(IoPnpPhysicalDeviceArrived(&pdo3))); + IoPnpPhysicalDevicePublished(&pdo3); + assert(KSUCCESS(IoPnpWaitForIdle(1000))); + assert(releases == 2); + IoPnpPhysicalDeviceRemoved(&pdo3); + assert(KSUCCESS(IoPnpWaitForIdle(1000))); + assert(pdo3.Refs == 1); + const NTL_DEVICE_MATCH slowMatches[] = { { NtlBusPci, "PCI\\VEN_AAAA" } }; + const NTL_DEVICE_MATCH fastMatches[] = { { NtlBusPci, "PCI\\VEN_BBBB" } }; + const NTL_DEVICE_DESC concurrent[] = { + {sizeof(NTL_DEVICE_DESC), 0, "pci.slow", slowMatches, 1, 0, SlowStart, NULL, NULL}, + {sizeof(NTL_DEVICE_DESC), 0, "pci.fast", fastMatches, 1, 0, FastStart, NULL, NULL}, + }; + assert(KSUCCESS(IoPnpPublishModule((void*)4, concurrent, 2, NULL, 0))); + DEVICE_OBJECT slowPdo = MakePdo("PCI\\VEN_AAAA"); + DEVICE_OBJECT fastPdo = MakePdo("PCI\\VEN_BBBB"); + assert(KSUCCESS(IoPnpPhysicalDeviceArrived(&slowPdo))); + IoPnpPhysicalDevicePublished(&slowPdo); + WaitForAtomic(&slowEntered); + assert(KSUCCESS(IoPnpPhysicalDeviceArrived(&fastPdo))); + IoPnpPhysicalDevicePublished(&fastPdo); + WaitForAtomic(&fastDone); + pthread_mutex_lock(&gateLock); + gateOpen = true; + pthread_cond_broadcast(&gateCond); + pthread_mutex_unlock(&gateLock); + assert(KSUCCESS(IoPnpWaitForIdle(1000))); + IoPnpPhysicalDeviceRemoved(&slowPdo); + IoPnpPhysicalDeviceRemoved(&fastPdo); + assert(KSUCCESS(IoPnpWaitForIdle(1000))); + assert(slowPdo.Refs == 1 && fastPdo.Refs == 1); + const IO_DEVICE_ID ids[] = { { IoBusPci, {"PCI\\VEN_CAFE", 12} } }; + const IO_DRIVER_ID_TABLE idsTable = { ids, 1 }; + DRIVER_OBJECT ioDriver = { {"pci.io", 6}, &idsTable, IoAddDevice }; + DRIVER_OBJECT* ioDrivers[] = { &ioDriver }; + assert(KSUCCESS(IoPnpPublishModule((void*)5, NULL, 0, ioDrivers, 1))); + assert(IoPnpPublishModule((void*)5, NULL, 0, ioDrivers, 1) == NTLSTATUS_ALREADY_EXISTS); + const NTL_DEVICE_DESC conflicting = { + sizeof(NTL_DEVICE_DESC), 0, "pci.io", matches, 1, 0, OrdinaryStart, NULL, NULL + }; + assert(IoPnpPublishModule((void*)6, &conflicting, 1, NULL, 0) == NTLSTATUS_ALREADY_EXISTS); + DRIVER_OBJECT sameName = { {"pci.test", 8}, &idsTable, IoAddDevice }; + DRIVER_OBJECT* conflictingIo[] = { &sameName }; + assert(IoPnpPublishModule((void*)6, NULL, 0, conflictingIo, 1) == NTLSTATUS_ALREADY_EXISTS); + DEVICE_OBJECT ioPdo = MakePdo("PCI\\VEN_CAFE"); + assert(KSUCCESS(IoPnpPhysicalDeviceArrived(&ioPdo))); + IoPnpPhysicalDevicePublished(&ioPdo); + assert(KSUCCESS(IoPnpWaitForIdle(1000))); + assert(ioAdds == 1 && ioPnPStarts == 1); + IoPnpPhysicalDeviceRemoved(&ioPdo); + assert(KSUCCESS(IoPnpWaitForIdle(1000))); + assert(ioPnPRemoves == 1 && ioPdo.Refs == 1); + DEVICE_OBJECT predeleted = MakePdo("PCI\\VEN_CAFE"); + assert(KSUCCESS(IoPnpPhysicalDeviceArrived(&predeleted))); + IoPnpPhysicalDevicePublished(&predeleted); + assert(KSUCCESS(IoPnpWaitForIdle(1000))); + int previousRemoves = ioPnPRemoves; + predeleted.DeletePending = true; + IoPnpPhysicalDeviceRemoved(&predeleted); + assert(KSUCCESS(IoPnpWaitForIdle(1000))); + assert(ioPnPRemoves == previousRemoves); + assert(predeleted.Refs == 1); + + const NTL_DEVICE_MATCH topologyMatches[] = { + {NtlBusPci, "PCI\\USB_XHCI"}, + {NtlBusUsb, "USB\\ROOT_HUB"}, + {NtlBusUsb, "USB\\STORAGE"}, + }; + NTL_DEVICE_DESC topology[3] = { + {sizeof(NTL_DEVICE_DESC), 0, "shutdown.controller", &topologyMatches[0], 1, 0, TopologyStart, TopologyStop, NULL}, + {sizeof(NTL_DEVICE_DESC), 0, "shutdown.hub", &topologyMatches[1], 1, 0, TopologyStart, TopologyStop, NULL}, + {sizeof(NTL_DEVICE_DESC), 0, "shutdown.leaf", &topologyMatches[2], 1, 0, TopologyStart, TopologyStop, NULL}, + }; + assert(KSUCCESS(IoPnpPublishModule((void*)7, topology, 3, NULL, 0))); + + DEVICE_OBJECT controller = MakePdo("PCI\\USB_XHCI"); + DEVICE_OBJECT controllerFdo = {0}; + controllerFdo.Type = DeviceObjectFunction; + controllerFdo.LowerDevice = &controller; + DEVICE_OBJECT rootHub = MakePdo("USB\\ROOT_HUB"); + rootHub.u.Pdo.BusType = IoBusUsb; + rootHub.Parent = &controllerFdo; + DEVICE_OBJECT hubFdo = {0}; + hubFdo.Type = DeviceObjectFunction; + hubFdo.LowerDevice = &rootHub; + DEVICE_OBJECT leaf = MakePdo("USB\\STORAGE"); + leaf.u.Pdo.BusType = IoBusUsb; + leaf.Parent = &hubFdo; + + assert(KSUCCESS(IoPnpPhysicalDeviceArrived(&controller))); + IoPnpPhysicalDevicePublished(&controller); + assert(KSUCCESS(IoPnpPhysicalDeviceArrived(&rootHub))); + IoPnpPhysicalDevicePublished(&rootHub); + assert(KSUCCESS(IoPnpPhysicalDeviceArrived(&leaf))); + IoPnpPhysicalDevicePublished(&leaf); + assert(KSUCCESS(IoPnpWaitForIdle(1000))); + assert(KSUCCESS(IoPnpShutdown(1000))); + assert(topologyLeafStopped == 1); + assert(topologyHubStopped == 1); + assert(topologyControllerStopped == 1); + assert(controller.Refs == 1 && rootHub.Refs == 1 && leaf.Refs == 1); + puts("pnp tests passed"); + return 0; +} diff --git a/code/tests/host/service_control_contract.c b/code/tests/host/service_control_contract.c new file mode 100644 index 0000000..101c338 --- /dev/null +++ b/code/tests/host/service_control_contract.c @@ -0,0 +1,65 @@ +// Host-side regression test for the native service READY/STOP wire contract. +// Build: clang -std=c17 -Wall -Wextra -Werror -Icode/public tests/host/service_control_contract.c -o /tmp/service_control_contract + +#include +#include +#include + +static NTL_SERVICE_CONTROL_MESSAGE make_ready(uint32_t count) { + NTL_SERVICE_CONTROL_MESSAGE message; + memset(&message, 0, sizeof(message)); + message.Size = sizeof(message); + message.Version = NTL_SERVICE_CONTROL_VERSION; + message.Operation = NTL_SERVICE_CONTROL_READY; + message.ExportCount = count; + message.Status = NTLSTATUS_SUCCESS; + return message; +} + +int main(void) { + NTL_SERVICE_CONTROL_MESSAGE message = make_ready(2u); + assert(NtlServiceControlNameExport(&message, 0u, "session-control")); + assert(NtlServiceControlNameExport(&message, 1u, "namespace-admin")); + assert(NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_READY, 2u)); + assert(NtlServiceControlFindExport(&message, "namespace-admin") == 1); + assert(NtlServiceControlFindExport(&message, "session-control") == 0); + assert(NtlServiceControlFindExport(&message, "missing") == -1); + + assert(!NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_READY, 1u)); + assert(!NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_STOP, 2u)); + assert(!NtlServiceControlNameExport(&message, 2u, "outside")); + assert(!NtlServiceControlNameExport(&message, 0u, "invalid/name")); + assert(!NtlServiceControlNameExport(&message, 0u, "01234567890123456789012345678901")); + + assert(NtlServiceControlNameExport(&message, 1u, "session-control")); + assert(!NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_READY, 2u)); + assert(NtlServiceControlNameExport(&message, 1u, "namespace-admin")); + message.ExportNames[1][31] = 'x'; + assert(!NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_READY, 2u)); + message.ExportNames[1][31] = '\0'; + + message.ExportNames[2][0] = 'x'; + assert(!NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_READY, 2u)); + message.ExportNames[2][0] = '\0'; + message.Reserved[1] = 1u; + assert(!NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_READY, 2u)); + message.Reserved[1] = 0u; + message.Version = 2u; + assert(!NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_READY, 2u)); + + message = make_ready(0u); + assert(NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_READY, 0u)); + message = make_ready(0u); + message.Operation = NTL_SERVICE_CONTROL_STOP; + assert(NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_STOP, 0u)); + message.Status = NTLSTATUS_FAILURE; + assert(!NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_STOP, 0u)); + message.Status = NTLSTATUS_SUCCESS; + message.ExportNames[0][0] = 'x'; + assert(!NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_STOP, 0u)); + + message = make_ready(SYS_PORT_MAX_TRANSFER_HANDLES + 1u); + assert(!NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_READY, + SYS_PORT_MAX_TRANSFER_HANDLES + 1u)); + return 0; +} diff --git a/code/tests/memtop_host/test.c b/code/tests/memtop_host/test.c new file mode 100644 index 0000000..61af98d --- /dev/null +++ b/code/tests/memtop_host/test.c @@ -0,0 +1,62 @@ +// Runs one memtop sample with a fake console to detect accidental per-character RPCs. +#include +#include +#include +#include +#include +#include + +#define main memtop_entry +#include "../../user/apps/memtop/memtop.c" +#undef main + +static jmp_buf gDone; +static unsigned gQueries; +static unsigned gWrites; +static uint64_t gSleep; +static char gOutput[128]; + +KSTATUS NtlConsoleQueryInfo(SYS_HANDLE handle, NTL_CONSOLE_INFO* info) { + (void)handle; + ++gQueries; + memset(info, 0, sizeof(*info)); + info->Columns = 100u; + info->Rows = 30u; + return NTLSTATUS_SUCCESS; +} +KSTATUS SysGetMemoryStatus(SYS_MEMORY_STATUS* info) { + memset(info, 0, sizeof(*info)); + info->FreePhysicalBytes = 1024u * 1024u; + info->KernelPoolBytes = 512u * 1024u; + info->UserHeapBytes = 256u * 1024u; + return NTLSTATUS_SUCCESS; +} +KSTATUS NtlConsoleWriteAt(SYS_HANDLE handle, int32_t x, int32_t y, + const char* text, size_t length, size_t* written) { + (void)handle; + if (length >= sizeof(gOutput) || x != 100 - (int32_t)length || y != 0) abort(); + memcpy(gOutput, text, length); + gOutput[length] = '\0'; + if (written) *written = length; + ++gWrites; + return NTLSTATUS_SUCCESS; +} +KSTATUS SysSleep(uint64_t milliseconds) { + gSleep = milliseconds; + longjmp(gDone, 1); + return NTLSTATUS_SUCCESS; +} + +int main(void) { + if (!setjmp(gDone)) (void)memtop_entry(); + if (gQueries != 1u || gWrites != 1u || gSleep != 2000u || + strstr(gOutput, "Free: 1024K") == NULL || + strstr(gOutput, "Pool: 512K") == NULL || + strstr(gOutput, "Heap: 256K") == NULL) { + fprintf(stderr, "memtop regression: query=%u draw=%u sleep=%llu text=%s\n", + gQueries, gWrites, (unsigned long long)gSleep, gOutput); + return 1; + } + puts("memtop positioned batch: PASS"); + return 0; +} diff --git a/code/tests/service_grants_host/test.c b/code/tests/service_grants_host/test.c new file mode 100644 index 0000000..e4b3dd3 --- /dev/null +++ b/code/tests/service_grants_host/test.c @@ -0,0 +1,72 @@ +// Host regression checks for type-free startup and named capability delegation. +#include +#include +#include +#include "../../user/system/init/service_config.c" + +#define RSSRV "[service.rssrv]\nimage=system/rssrv.exe\nstart=auto\ncritical=true\n" \ + "restart=never\nready_timeout_ms=5000\nstop_timeout_ms=5000\nrequires=\n" \ + "grants=boot.namespace-root\nexports=session-control,desktop-publisher,posix-publisher\n" +#define DWM "[service.dwm]\nimage=system/dwm.exe\nstart=auto\ncritical=false\n" \ + "restart=never\nready_timeout_ms=5000\nstop_timeout_ms=5000\nrequires=rssrv\n" \ + "grants=rssrv.session-control,rssrv.desktop-publisher\nexports=desktop\n" + +static void expect(const char* label, const char* policy, bool shouldPass) { + size_t length = strlen(policy); + char* text = (char*)malloc(length + 1u); + if (!text) abort(); + memcpy(text, policy, length + 1u); + uint32_t count = 0; + uint32_t errorLine = 0; + INIT_SERVICE_CONFIG config; + KSTATUS result = parse_services_ini(text, &count, &errorLine); + if (KSUCCESS(result)) result = validate_services(count, &config, &errorLine); + if (KSUCCESS(result) != shouldPass) { + fprintf(stderr, "%s: result=0x%08x line=%u\n", label, (unsigned)result, errorLine); + exit(1); + } + if (shouldPass && (config.ServiceCount != 2u || + config.Services[1].GrantCount != 2u || + strcmp(config.Services[1].Grants[1], "rssrv.desktop-publisher") != 0)) { + fprintf(stderr, "%s: descriptor lost publication policy\n", label); + exit(1); + } + free(text); +} + +static void check_shipped(const char* path) { + FILE* input = fopen(path, "rb"); + if (!input) { perror(path); exit(1); } + char text[8192]; + size_t used = fread(text, 1u, sizeof(text) - 1u, input); + if (ferror(input) || !feof(input)) abort(); + fclose(input); + text[used] = '\0'; + uint32_t count = 0, errorLine = 0; + INIT_SERVICE_CONFIG config; + KSTATUS result = parse_services_ini(text, &count, &errorLine); + if (KSUCCESS(result)) result = validate_services(count, &config, &errorLine); + if (!KSUCCESS(result) || config.ServiceCount != 3u || + config.StartupCount != 3u || config.Services[0].ExportCount != 3u) { + fprintf(stderr, "shipping configuration: 0x%08x line %u\n", (unsigned)result, errorLine); + exit(1); + } +} + +int main(int argc, char** argv) { + expect("valid named grants", RSSRV DWM, true); + expect("type rejected", RSSRV DWM "type=dwm\n", false); + expect("publication syntax rejected", RSSRV DWM "publishes=desktop:/Sessions/0/Desktop\n", false); + expect("manager section rejected", "[manager]\npublication_authority=rssrv.namespace-admin\n" RSSRV DWM, false); + expect("unknown grant", RSSRV DWM "grants=boot.unknown\n", false); + expect("duplicate export", RSSRV DWM "exports=desktop\n", false); + expect("missing publisher", RSSRV "[service.dwm]\nimage=system/dwm.exe\nstart=auto\ncritical=false\n" + "restart=never\nready_timeout_ms=5000\nstop_timeout_ms=5000\nrequires=rssrv\n" + "grants=rssrv.unknown\nexports=desktop\n", false); + expect("missing grants key", RSSRV "[service.dwm]\nimage=system/dwm.exe\nstart=auto\ncritical=false\n" + "restart=never\nready_timeout_ms=5000\nstop_timeout_ms=5000\nrequires=rssrv\n" + "exports=desktop\n", false); + if (argc == 2) check_shipped(argv[1]); + puts("type-free service policy regression: PASS"); + return 0; +} diff --git a/code/tests/service_publication_host/test.c b/code/tests/service_publication_host/test.c new file mode 100644 index 0000000..d5061b7 --- /dev/null +++ b/code/tests/service_publication_host/test.c @@ -0,0 +1,30 @@ +// Host-only regression for RSSRV's exact-path publication capability restriction. +#include +#include +#include +#include "../../user/system/rssrv/publication_scope.h" + +static void test(const char* label, const char* scope, uint32_t op, + const char* path, size_t length, bool expect) { + if (RssrvScopeAllows(scope, op, path, (uint32_t)length) != expect) { + fprintf(stderr, "publisher scope mismatch: %s\n", label); + exit(1); + } +} + +int main(void) { + const char* scope = "/Sessions/0/Desktop"; + test("exact bind", scope, NTL_RESOURCE_OP_BIND, scope, strlen(scope), true); + test("exact unbind", scope, NTL_RESOURCE_OP_UNBIND, scope, strlen(scope), true); + test("parent", scope, NTL_RESOURCE_OP_BIND, "/Sessions/0", strlen("/Sessions/0"), false); + test("child", scope, NTL_RESOURCE_OP_BIND, "/Sessions/0/Desktop/A", strlen("/Sessions/0/Desktop/A"), false); + test("prefix", scope, NTL_RESOURCE_OP_BIND, "/Sessions/0/Deskto", strlen("/Sessions/0/Deskto"), false); + test("name change", scope, NTL_RESOURCE_OP_UNBIND, "/Services/posix", strlen("/Services/posix"), false); + test("no lookup", scope, NTL_RESOURCE_OP_LOOKUP, scope, strlen(scope), false); + test("no create", scope, NTL_RESOURCE_OP_CREATE_DIRECTORY, scope, strlen(scope), false); + test("no delegation", scope, NTL_RESOURCE_OP_CREATE_PUBLISHER, scope, strlen(scope), false); + test("canonical request length", scope, NTL_RESOURCE_OP_BIND, scope, strlen(scope) - 1u, false); + test("unscoped admin", NULL, NTL_RESOURCE_OP_CREATE_PUBLISHER, scope, strlen(scope), true); + puts("scoped publication regression: PASS"); + return 0; +} diff --git a/code/tests/service_restart_host/test.c b/code/tests/service_restart_host/test.c new file mode 100644 index 0000000..d3854d0 --- /dev/null +++ b/code/tests/service_restart_host/test.c @@ -0,0 +1,18 @@ +// Host test for the sliding restart window and exact three-attempt ceiling. +#include +#include +#include "../../user/system/init/restart_budget.h" +int main(void) { + INIT_RESTART_BUDGET b = {0}; + if (!InitConsumeRestartBudget(&b, 1000u, 10u) || + !InitConsumeRestartBudget(&b, 1010u, 10u) || + !InitConsumeRestartBudget(&b, 1020u, 10u) || + InitConsumeRestartBudget(&b, 1030u, 10u)) abort(); + // A sliding expiration depends on each attempt's own timestamp. + if (!InitConsumeRestartBudget(&b, 4000u, 10u) || b.Count != 3u || + InitConsumeRestartBudget(&b, 4001u, 10u) || + !InitConsumeRestartBudget(&b, 4010u, 10u)) abort(); + if (InitConsumeRestartBudget(&b, 4011u, 0u)) abort(); + puts("service sliding restart budget: PASS"); + return 0; +} diff --git a/code/user/apps/memtop/memtop.c b/code/user/apps/memtop/memtop.c index d4dbdd2..870d1da 100644 --- a/code/user/apps/memtop/memtop.c +++ b/code/user/apps/memtop/memtop.c @@ -29,6 +29,7 @@ int main(void) { NTL_CONSOLE_INFO console; SYS_MEMORY_STATUS mem; char metricsBuf[128]; + size_t previousLength = 0; while (true) { if (NtlConsoleQueryInfo(SYS_HANDLE_INVALID, &console) == NTLSTATUS_SUCCESS && @@ -52,22 +53,22 @@ int main(void) { strcat(metricsBuf, "K ]"); size_t textLen = strlen(metricsBuf); + // Erase any trailing characters left by a longer previous sample. + if (previousLength > textLen && previousLength < sizeof(metricsBuf)) { + memset(metricsBuf + textLen, ' ', previousLength - textLen); + textLen = previousLength; + } int targetX = columns - (int)textLen; - int targetY = 0; - if (targetX >= 0) { size_t written = 0; - - // Loop through the string and use the console's dedicated text filling API - // This utilizes the console's native font configuration instead of drawing raw screen bits - for (size_t i = 0; i < textLen; i++) { - (void)NtlConsoleFillCharacter(SYS_HANDLE_INVALID, metricsBuf[i], - targetX + (int)i, targetY, 1, &written); - } + if (NtlConsoleWriteAt(SYS_HANDLE_INVALID, targetX, 0, + metricsBuf, textLen, &written) == NTLSTATUS_SUCCESS && + written == textLen) + previousLength = textLen; } } - SysSleep(500); + SysSleep(2000); } return 0; } \ No newline at end of file diff --git a/code/user/apps/shell_native/shell_native.c b/code/user/apps/shell_native/shell_native.c index 4c85c84..70804ea 100644 --- a/code/user/apps/shell_native/shell_native.c +++ b/code/user/apps/shell_native/shell_native.c @@ -863,6 +863,8 @@ static void cmd_help() { " del - Delete a file\n" " write - Create a file with text\n" " clear - Clear the screen\n" + " shutdown - Request system shutdown\n" + " reboot - Request system restart\n" " exit - Close the shell\n"); } @@ -2203,6 +2205,13 @@ static KSTATUS waitForegroundProcess(uint32_t hProc) { // Run one command. static bool runCommand(const char* cmd) { if (!cmd || *cmd == '\0') return false; + if (strcmp(cmd, "shutdown") == 0 || strcmp(cmd, "reboot") == 0) { + uint32_t action = strcmp(cmd, "reboot") == 0 + ? SYS_POWER_ACTION_RESTART : SYS_POWER_ACTION_SHUTDOWN; + KSTATUS st = SysRequestSystemPower(action); + if (!KSUCCESS(st)) print("power: request failed\n"); + return false; + } if (strcmp(cmd, "exit") == 0) return true; if (strcmp(cmd, "help") == 0) { cmd_help(); return false; } if (strcmp(cmd, "clear") == 0) { (void)NtlConsoleClear(SYS_HANDLE_INVALID, 0x012456); return false; } diff --git a/code/user/apps/shell_posix/shell_posix.c b/code/user/apps/shell_posix/shell_posix.c index efbb10e..792659f 100644 --- a/code/user/apps/shell_posix/shell_posix.c +++ b/code/user/apps/shell_posix/shell_posix.c @@ -11,6 +11,7 @@ #include #include #include +#include #define MAX_CMD 256 @@ -80,6 +81,8 @@ static void cmd_help() { " del - Delete a file\n" " write - Create a file with text\n" " clear - Clear the screen\n" + " shutdown - Request system shutdown\n" + " reboot - Request system restart\n" " exit - Close the shell\n"); } @@ -196,6 +199,13 @@ static void cmd_type(const char* filename) { // Runs command. static bool runCommand(const char* cmd) { if (!cmd || *cmd == '\0') return false; + if (strcmp(cmd, "shutdown") == 0 || strcmp(cmd, "reboot") == 0) { + uint32_t action = strcmp(cmd, "reboot") == 0 + ? SYS_POWER_ACTION_RESTART : SYS_POWER_ACTION_SHUTDOWN; + KSTATUS st = SysRequestSystemPower(action); + if (!KSUCCESS(st)) print("power: request failed\n"); + return false; + } if (strcmp(cmd, "exit") == 0) return true; if (strcmp(cmd, "help") == 0) { cmd_help(); return false; } if (strcmp(cmd, "clear") == 0) { clearscreen(); return false; } diff --git a/code/user/apps/shell_win32/shell_win32.c b/code/user/apps/shell_win32/shell_win32.c index bef8876..b056653 100644 --- a/code/user/apps/shell_win32/shell_win32.c +++ b/code/user/apps/shell_win32/shell_win32.c @@ -1,6 +1,7 @@ // User application entry points for shell_win32. #include +#include #include #include #include @@ -76,6 +77,8 @@ static void cmd_help() { " del - Delete a file\n" " write - Create a file with text\n" " clear - Clear the screen\n" + " shutdown - Request system shutdown\n" + " reboot - Request system restart\n" " exit - Close the shell\n"); } @@ -211,6 +214,13 @@ static void cmd_type(const char* filename) { // Runs command. static bool runCommand(const char* cmd) { if (!cmd || *cmd == '\0') return false; + if (strcmp(cmd, "shutdown") == 0 || strcmp(cmd, "reboot") == 0) { + uint32_t action = strcmp(cmd, "reboot") == 0 + ? SYS_POWER_ACTION_RESTART : SYS_POWER_ACTION_SHUTDOWN; + KSTATUS st = SysRequestSystemPower(action); + if (!KSUCCESS(st)) print("power: request failed\n"); + return false; + } if (strcmp(cmd, "exit") == 0) return true; if (strcmp(cmd, "help") == 0) { cmd_help(); return false; } if (strcmp(cmd, "clear") == 0) { ClearConsoleScreenBuffer(hOut); return false; } diff --git a/code/user/personalities/posix/posix_proc.c b/code/user/personalities/posix/posix_proc.c index ca47028..bf1f222 100644 --- a/code/user/personalities/posix/posix_proc.c +++ b/code/user/personalities/posix/posix_proc.c @@ -247,8 +247,7 @@ static KSTATUS PosixProcessDeriveSecurityContext(SYS_HANDLE* contextOut) { info.Size = sizeof(info); KSTATUS status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &info); if (!KSUCCESS(status)) return status; - if ((info.EffectivePrivileges & ~info.DelegablePrivileges) != 0 || - (info.ServiceClaims & ~info.DelegableServiceClaims) != 0) + if ((info.EffectivePrivileges & ~info.DelegablePrivileges) != 0) return NTLSTATUS_ACCESS_DENIED; SYS_DERIVE_SECURITY_CONTEXT_REQUEST derive; @@ -257,8 +256,6 @@ static KSTATUS PosixProcessDeriveSecurityContext(SYS_HANDLE* contextOut) { derive.SourceContext = SYS_SECURITY_CONTEXT_CURRENT; derive.EffectivePrivileges = info.EffectivePrivileges; derive.DelegablePrivileges = info.DelegablePrivileges; - derive.ServiceClaims = info.ServiceClaims; - derive.DelegableServiceClaims = info.DelegableServiceClaims; derive.Flags = SYS_SECURITY_CONTEXT_FLAG_NONE; derive.ContextHandleOut = contextOut; return SysDeriveSecurityContext(&derive); diff --git a/code/user/syscall/console_service.c b/code/user/syscall/console_service.c index 538035f..60e731b 100644 --- a/code/user/syscall/console_service.c +++ b/code/user/syscall/console_service.c @@ -152,6 +152,27 @@ static KSTATUS NtlConsoleSimpleCall(SYS_HANDLE session, NTL_CONSOLE_REQUEST* req return status; } +// Sends one positioned text span in a single console request. +KSTATUS NtlConsoleWriteAt(SYS_HANDLE session, int32_t x, int32_t y, + const char* text, size_t length, size_t* writtenOut) { + if (writtenOut) *writtenOut = 0; + if (!text || length > NTL_CONSOLE_WRITE_MAX - 8u || x < 0 || y < 0) + return NTLSTATUS_INVALID_PARAMETER; + NTL_CONSOLE_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_CONSOLE_PROTOCOL_VERSION; + request.Operation = NTL_CONSOLE_OP_WRITE_AT; + request.Data.WriteAt.X = x; + request.Data.WriteAt.Y = y; + request.Data.WriteAt.Length = (uint32_t)length; + memcpy(request.Data.WriteAt.Data, text, length); + NTL_CONSOLE_REPLY reply; + KSTATUS status = NtlConsoleCall(session, &request, &reply); + if (writtenOut) *writtenOut = reply.Written; + return status; +} + // Updates the logical console cursor. KSTATUS NtlConsoleSetCursor(SYS_HANDLE session, int32_t x, int32_t y) { NTL_CONSOLE_REQUEST request; diff --git a/code/user/syscall/process_loader.c b/code/user/syscall/process_loader.c index da97464..062bc29 100644 --- a/code/user/syscall/process_loader.c +++ b/code/user/syscall/process_loader.c @@ -219,12 +219,12 @@ KSTATUS NtlPrepareProcess(const NTL_PREPARE_PROCESS_REQUEST* request, memset(state->ChildCapabilities, 0, sizeof(state->ChildCapabilities)); for (uint32_t i = 0; i < request->StartupCapabilityCount; ++i) { const NTL_PROCESS_STARTUP_CAPABILITY* source = &request->StartupCapabilities[i]; - if (!source->Tag || source->SourceHandle == SYS_HANDLE_INVALID) { + if (!SysStartupCapabilityNameValid(source->Name) || source->SourceHandle == SYS_HANDLE_INVALID) { status = NTLSTATUS_INVALID_PARAMETER; goto fail; } for (uint32_t j = 0; j < i; ++j) { - if (state->ChildCapabilities[j].Tag == source->Tag) { + if (strcmp(state->ChildCapabilities[j].Name, source->Name) == 0) { status = NTLSTATUS_INVALID_PARAMETER; goto fail; } @@ -240,7 +240,7 @@ KSTATUS NtlPrepareProcess(const NTL_PREPARE_PROCESS_REQUEST* request, duplicate.TargetHandleOut = &childHandle; status = SysDuplicateHandle(&duplicate); if (!KSUCCESS(status)) goto fail; - state->ChildCapabilities[i].Tag = source->Tag; + memcpy(state->ChildCapabilities[i].Name, source->Name, sizeof(source->Name)); state->ChildCapabilities[i].Handle = childHandle; } state->StartupCapabilityCount = request->StartupCapabilityCount; diff --git a/code/user/syscall/resource.c b/code/user/syscall/resource.c index 6c8e3ca..9eb78ed 100644 --- a/code/user/syscall/resource.c +++ b/code/user/syscall/resource.c @@ -160,6 +160,33 @@ KSTATUS NtlResourceConnectListener(SYS_HANDLE listenerHandle, SYS_HANDLE* sessio return SysConnectPort(&request); } +// Delegates the authority to bind or unbind exactly one resource path. +KSTATUS NtlResourceCreatePublisher(SYS_HANDLE adminSession, SYS_STRING_VIEW path, + SYS_HANDLE* listenerOut) { + if (!listenerOut) return NTLSTATUS_INVALID_PARAMETER; + *listenerOut = SYS_HANDLE_INVALID; + uint8_t payload[sizeof(NTL_RESOURCE_REQUEST) + NTL_RESOURCE_PATH_MAX]; + uint32_t length = 0; + KSTATUS status = NtlBuildResourceRequest(payload, sizeof(payload), + NTL_RESOURCE_OP_CREATE_PUBLISHER, path, + SysSvFromParts((const char*)0, 0), 0, 0, + NTL_RESOURCE_KIND_INVALID, &length); + if (!KSUCCESS(status)) return status; + NTL_RESOURCE_REPLY reply; + SYS_HANDLE handle = SYS_HANDLE_INVALID; + status = NtlCallResource(adminSession, payload, length, (const SYS_PORT_HANDLE_TRANSFER*)0, + 0, &reply, &handle); + if (!KSUCCESS(status)) return status; + if (reply.Operation != NTL_RESOURCE_OP_CREATE_PUBLISHER || + !KSUCCESS(reply.Status) || handle == SYS_HANDLE_INVALID) { + if (handle != SYS_HANDLE_INVALID) (void)SysCloseHandle(handle); + return reply.Operation == NTL_RESOURCE_OP_CREATE_PUBLISHER && !KSUCCESS(reply.Status) + ? reply.Status : NTLSTATUS_FAILURE; + } + *listenerOut = handle; + return NTLSTATUS_SUCCESS; +} + // Looks up one resource and requests an attenuated alias of its bound capability. KSTATUS NtlResourceLookup(SYS_HANDLE session, SYS_STRING_VIEW path, diff --git a/code/user/syscall/syscall_nr.inc b/code/user/syscall/syscall_nr.inc index 5098b8c..a7a1ab6 100644 --- a/code/user/syscall/syscall_nr.inc +++ b/code/user/syscall/syscall_nr.inc @@ -186,4 +186,9 @@ SYS_SET_SOCKET_OPTION EQU 159 SYS_GET_SOCKET_OPTION EQU 160 SYS_POLL EQU 161 -SYS_MAX EQU 162 +SYS_OPEN_POWER_EVENT EQU 162 +SYS_REQUEST_SYSTEM_POWER EQU 163 +SYS_QUERY_SYSTEM_POWER EQU 164 +SYS_COMPLETE_SYSTEM_POWER EQU 165 + +SYS_MAX EQU 166 diff --git a/code/user/syscall/syscall_stubs.asm b/code/user/syscall/syscall_stubs.asm index 21e2c28..a570f8e 100644 --- a/code/user/syscall/syscall_stubs.asm +++ b/code/user/syscall/syscall_stubs.asm @@ -38,6 +38,10 @@ ENDM ; --------------------------------------------------------------------------- ; Process & thread lifecycle ; --------------------------------------------------------------------------- +SYSCALL_STUB OpenPowerEvent, SYS_OPEN_POWER_EVENT +SYSCALL_STUB RequestSystemPower, SYS_REQUEST_SYSTEM_POWER +SYSCALL_STUB QuerySystemPower, SYS_QUERY_SYSTEM_POWER +SYSCALL_STUB CompleteSystemPower, SYS_COMPLETE_SYSTEM_POWER SYSCALL_STUB ExitProcess, SYS_EXIT_PROCESS SYSCALL_STUB ExitThread, SYS_EXIT_THREAD SYSCALL_STUB CreateProcessObject, SYS_CREATE_PROCESS_OBJECT diff --git a/code/user/system/dwm/dwm.c b/code/user/system/dwm/dwm.c index 203df36..91b2121 100644 --- a/code/user/system/dwm/dwm.c +++ b/code/user/system/dwm/dwm.c @@ -1,10 +1,12 @@ // Native Session 0 desktop/window manager. DWM owns window policy, composition, and visible cursor presentation. #include +#include #include #include #include #include +#include #include #include #include @@ -1156,10 +1158,11 @@ static KSTATUS DwmSendReady(SYS_HANDLE endpoint, SYS_HANDLE desktopListener) { message.Operation = NTL_SERVICE_CONTROL_READY; message.ExportCount = 1u; message.Status = NTLSTATUS_SUCCESS; + if (!NtlServiceControlNameExport(&message, 0u, "desktop")) return NTLSTATUS_FAILURE; SYS_PORT_HANDLE_TRANSFER transfer; transfer.Handle = desktopListener; - transfer.DesiredRights = SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT; + transfer.DesiredRights = SYS_RIGHT_DUPLICATE | SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT; transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE; SYS_SEND_PORT_REQUEST send; @@ -1196,8 +1199,7 @@ static void DwmControlLoop(SYS_HANDLE endpoint) { if (status == NTLSTATUS_PORT_DISCONNECTED || !KSUCCESS(status)) return; if (info.Type != SYS_PORT_MESSAGE_NOTIFICATION || info.HandleCount != 0 || info.PayloadLength != sizeof(message) || message.Size != sizeof(message) || - message.Version != NTL_SERVICE_CONTROL_VERSION || message.ExportCount != 0 || - message.Reserved[0] != 0 || message.Reserved[1] != 0 || message.Reserved[2] != 0) + !NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_STOP, 0u)) continue; if (message.Operation == NTL_SERVICE_CONTROL_STOP) return; } @@ -1255,15 +1257,15 @@ void _start(void) { memset(&security, 0, sizeof(security)); security.Size = sizeof(security); status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &security); - if (!KSUCCESS(status) || security.EffectivePrivileges != 0 || security.DelegablePrivileges != 0 || - security.ServiceClaims != 0 || security.DelegableServiceClaims != 0) + if (!KSUCCESS(status) || security.EffectivePrivileges != 0 || security.DelegablePrivileges != 0) DwmExit(2); - SYS_HANDLE controlListener = SysGetStartupCapability(SYS_STARTUP_CAP_SERVICE_CONTROL); - SYS_HANDLE displayManager = SysGetStartupCapability(SYS_STARTUP_CAP_DISPLAY_MANAGER); - SYS_HANDLE sessionListener = SysGetStartupCapability(SYS_STARTUP_CAP_SESSION_CONTROL); + SYS_HANDLE controlListener = SysGetStartupCapability("service.control"); + SYS_HANDLE displayManager = SysGetStartupCapability(NTL_BOOT_CAP_DISPLAY_PRESENT); + SYS_HANDLE sessionListener = SysGetStartupCapability("rssrv.session-control"); + SYS_HANDLE publicationListener = SysGetStartupCapability("rssrv.desktop-publisher"); if (controlListener == SYS_HANDLE_INVALID || displayManager == SYS_HANDLE_INVALID || - sessionListener == SYS_HANDLE_INVALID) + sessionListener == SYS_HANDLE_INVALID || publicationListener == SYS_HANDLE_INVALID) DwmExit(3); memset(&gDwm, 0, sizeof(gDwm)); @@ -1322,10 +1324,21 @@ void _start(void) { DwmUnlock(); } + SYS_HANDLE publication = SYS_HANDLE_INVALID; + status = NtlResourceConnectListener(publicationListener, &publication); + (void)SysCloseHandle(publicationListener); + if (!KSUCCESS(status)) DwmExit(8); + status = NtlResourceBind(publication, SYS_SV_LITERAL(NTL_DESKTOP_SERVICE_PATH), + NTL_RESOURCE_KIND_CAPABILITY, gDwm.DesktopListener, + SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT, + SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT); + if (!KSUCCESS(status)) DwmExit(8); status = DwmSendReady(controlEndpoint, gDwm.DesktopListener); if (!KSUCCESS(status)) DwmExit(8); DwmControlLoop(controlEndpoint); + (void)NtlResourceUnbind(publication, SYS_SV_LITERAL(NTL_DESKTOP_SERVICE_PATH)); + (void)NtlResourceClose(publication); gDwm.Running = 0u; (void)SysSignalEvent(gDwm.DirtyEvent); (void)SysDisconnectPort(gDwm.SessionEndpoint); diff --git a/code/user/system/init/init.c b/code/user/system/init/init.c index 3a6cb02..3db1a9a 100644 --- a/code/user/system/init/init.c +++ b/code/user/system/init/init.c @@ -1,6 +1,7 @@ // Init service manager. The kernel bootstraps only init; init starts and supervises user-mode system services. #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include "service_config.h" +#include "restart_budget.h" typedef enum INIT_SERVICE_STATE { InitServiceStopped = 0, @@ -31,13 +33,141 @@ typedef struct INIT_SERVICE { INIT_SERVICE_STATE State; } INIT_SERVICE; -typedef struct INIT_RESOURCE_SERVICE { - INIT_SERVICE Service; - SYS_HANDLE AdminListener; - SYS_HANDLE SessionControlListener; -} INIT_RESOURCE_SERVICE; - static INIT_SERVICE_CONFIG gServiceConfig; +static INIT_SERVICE gServices[INIT_SERVICE_MAX_COUNT]; + +// Capability sources are referenced by name; the catalog owns READY handles +// and borrows kernel-provided bootstrap handles for the lifetime of init. +typedef struct INIT_GRANT_SOURCE { + char Name[SYS_STARTUP_CAPABILITY_NAME_MAX + 1u]; + SYS_HANDLE Handle; + SYS_RIGHTS RightsCeiling; + const INIT_SERVICE_DESCRIPTOR* Provider; + bool OwnsHandle; +} INIT_GRANT_SOURCE; + +#define INIT_GRANT_SOURCE_MAX (8u + INIT_SERVICE_MAX_COUNT * SYS_PORT_MAX_TRANSFER_HANDLES) +static INIT_GRANT_SOURCE gGrantSources[INIT_GRANT_SOURCE_MAX]; +static uint32_t gGrantSourceCount; + +// Records one source and its strict rights ceiling without granting any authority. +static KSTATUS add_grant_source(const char* name, SYS_HANDLE handle, SYS_RIGHTS rights, + const INIT_SERVICE_DESCRIPTOR* provider, bool ownsHandle) { + if (!name || !name[0] || handle == SYS_HANDLE_INVALID || !rights || + gGrantSourceCount >= INIT_GRANT_SOURCE_MAX) return NTLSTATUS_INVALID_PARAMETER; + size_t length = strlen(name); + if (length > SYS_STARTUP_CAPABILITY_NAME_MAX) return NTLSTATUS_NAME_TOO_LONG; + for (uint32_t i = 0; i < gGrantSourceCount; ++i) + if (strcmp(gGrantSources[i].Name, name) == 0) return NTLSTATUS_OBJECT_NAME_COLLISION; + INIT_GRANT_SOURCE* source = &gGrantSources[gGrantSourceCount++]; + memset(source, 0, sizeof(*source)); + memcpy(source->Name, name, length + 1u); + source->Handle = handle; + source->RightsCeiling = rights; + source->Provider = provider; + source->OwnsHandle = ownsHandle; + return NTLSTATUS_SUCCESS; +} + +// Drops all sources exported by a service generation before its handles expire. +static void forget_service_grants(const INIT_SERVICE_DESCRIPTOR* provider) { + if (!provider) return; + for (uint32_t i = 0; i < gGrantSourceCount;) { + INIT_GRANT_SOURCE* source = &gGrantSources[i]; + if (source->Provider != provider) { ++i; continue; } + if (source->OwnsHandle && source->Handle != SYS_HANDLE_INVALID) + (void)SysCloseHandle(source->Handle); + gGrantSources[i] = gGrantSources[--gGrantSourceCount]; + memset(&gGrantSources[gGrantSourceCount], 0, sizeof(gGrantSources[0])); + } +} + +// Keeps the named READY exports for subsequent dependent services. Ownership of +// an export handle passes to the catalog only when retainHandles is true. +static KSTATUS register_ready_exports(const INIT_SERVICE_DESCRIPTOR* provider, + const NTL_SERVICE_CONTROL_MESSAGE* message, + const SYS_HANDLE* handles, uint32_t count, + bool retainHandles) { + if (!provider || !message || count != message->ExportCount) return NTLSTATUS_INVALID_PARAMETER; + if (gGrantSourceCount + count > INIT_GRANT_SOURCE_MAX) return NTLSTATUS_LIMIT_REACHED; + char names[SYS_PORT_MAX_TRANSFER_HANDLES][SYS_STARTUP_CAPABILITY_NAME_MAX + 1u]; + for (uint32_t i = 0; i < count; ++i) { + size_t left = strlen(provider->Id), right = strlen(message->ExportNames[i]); + if (left + right + 1u > SYS_STARTUP_CAPABILITY_NAME_MAX || + handles[i] == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_PARAMETER; + memcpy(names[i], provider->Id, left); + names[i][left] = '.'; + memcpy(names[i] + left + 1u, message->ExportNames[i], right + 1u); + for (uint32_t j = 0; j < gGrantSourceCount; ++j) + if (strcmp(names[i], gGrantSources[j].Name) == 0) return NTLSTATUS_OBJECT_NAME_COLLISION; + } + for (uint32_t i = 0; i < count; ++i) { + KSTATUS status = add_grant_source(names[i], handles[i], + SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT, + provider, retainHandles); + if (!KSUCCESS(status)) { + // All names and available capacity were validated before mutation. + forget_service_grants(provider); + return status; + } + } + return NTLSTATUS_SUCCESS; +} + +// Populates the trusted bootstrap catalog from kernel-issued, init-owned handles. +static KSTATUS initialize_boot_grants(SYS_HANDLE namespaceVolume, SYS_HANDLE volumeManager, + SYS_HANDLE inputManager, SYS_HANDLE displayManager, + SYS_HANDLE registrationAuthority) { + gGrantSourceCount = 0; + KSTATUS status; +#define ADD_BOOT(name, handle, rights) \ + do { status = add_grant_source(name, handle, rights, (const INIT_SERVICE_DESCRIPTOR*)0, false); \ + if (!KSUCCESS(status)) return status; } while (0) + ADD_BOOT(NTL_BOOT_CAP_NAMESPACE_ROOT, namespaceVolume, + SYS_RIGHT_QUERY | SYS_RIGHT_TRANSFER | SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY); + ADD_BOOT(NTL_BOOT_CAP_VOLUME_MANAGER, volumeManager, + SYS_RIGHT_QUERY | SYS_VOLUME_MANAGER_RIGHT_ENUMERATE | + SYS_VOLUME_MANAGER_RIGHT_OPEN | SYS_VOLUME_MANAGER_RIGHT_WAIT); + ADD_BOOT(NTL_BOOT_CAP_INPUT_MANAGER, inputManager, SYS_RIGHT_QUERY | SYS_INPUT_MANAGER_RIGHT_READ); + ADD_BOOT(NTL_BOOT_CAP_DISPLAY_POLICY, displayManager, + SYS_RIGHT_DERIVE | SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | + SYS_DISPLAY_MANAGER_RIGHT_ENUMERATE | SYS_DISPLAY_MANAGER_RIGHT_MODESET | + SYS_DISPLAY_MANAGER_RIGHT_CONSOLE); + ADD_BOOT(NTL_BOOT_CAP_DISPLAY_PRESENT, displayManager, + SYS_RIGHT_QUERY | SYS_DISPLAY_MANAGER_RIGHT_ENUMERATE | + SYS_DISPLAY_MANAGER_RIGHT_PRESENT | SYS_DISPLAY_MANAGER_RIGHT_CURSOR); + ADD_BOOT(NTL_BOOT_CAP_RESOURCE_REGISTRATION, registrationAuthority, + SYS_RIGHT_QUERY | SYS_SERVICE_REGISTRATION_RIGHT_REGISTER | + SYS_SERVICE_REGISTRATION_RIGHT_UNREGISTER); +#undef ADD_BOOT + return NTLSTATUS_SUCCESS; +} + +// Resolves configured names into attenuated child capabilities before creation. +static KSTATUS resolve_service_grants(const INIT_SERVICE_DESCRIPTOR* descriptor, + NTL_PROCESS_STARTUP_CAPABILITY* resolved, + uint32_t* countOut) { + if (!descriptor || !resolved || !countOut || descriptor->GrantCount >= SYS_STARTUP_CAPABILITY_MAX) + return NTLSTATUS_INVALID_PARAMETER; + *countOut = 0; + for (uint32_t i = 0; i < descriptor->GrantCount; ++i) { + const INIT_GRANT_SOURCE* found = (const INIT_GRANT_SOURCE*)0; + for (uint32_t j = 0; j < gGrantSourceCount; ++j) + if (strcmp(descriptor->Grants[i], gGrantSources[j].Name) == 0) { + found = &gGrantSources[j]; break; + } + if (!found) return NTLSTATUS_NOT_FOUND; + NTL_PROCESS_STARTUP_CAPABILITY* destination = &resolved[i]; + memset(destination, 0, sizeof(*destination)); + memcpy(destination->Name, found->Name, strlen(found->Name) + 1u); + destination->SourceHandle = found->Handle; + destination->DesiredRights = found->RightsCeiling; + destination->DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; + ++*countOut; + } + return NTLSTATUS_SUCCESS; +} + // Writes one string through the privileged kernel debug channel. static void debug_print(const char* text) { @@ -107,23 +237,6 @@ static void initialize_service(INIT_SERVICE* service, const INIT_SERVICE_DESCRIP service->State = InitServiceStopped; } -// Derives the least-privileged security context required by RSSRV. -static KSTATUS derive_resource_service_context(SYS_HANDLE* contextOut) { - if (!contextOut) return NTLSTATUS_INVALID_PARAMETER; - *contextOut = SYS_HANDLE_INVALID; - SYS_DERIVE_SECURITY_CONTEXT_REQUEST request; - memset(&request, 0, sizeof(request)); - request.Size = sizeof(request); - request.SourceContext = SYS_SECURITY_CONTEXT_CURRENT; - request.EffectivePrivileges = SYS_PRIV_SYSTEM_SERVICE_REGISTER; - request.DelegablePrivileges = 0; - request.ServiceClaims = SYS_SERVICE_CLAIM_RESOURCE; - request.DelegableServiceClaims = 0; - request.Flags = SYS_SECURITY_CONTEXT_FLAG_NONE; - request.ContextHandleOut = contextOut; - return SysDeriveSecurityContext(&request); -} - // Derives the unprivileged security context used by replaceable user-mode services. static KSTATUS derive_unprivileged_service_context(SYS_HANDLE* contextOut) { if (!contextOut) return NTLSTATUS_INVALID_PARAMETER; @@ -134,8 +247,6 @@ static KSTATUS derive_unprivileged_service_context(SYS_HANDLE* contextOut) { request.SourceContext = SYS_SECURITY_CONTEXT_CURRENT; request.EffectivePrivileges = 0; request.DelegablePrivileges = 0; - request.ServiceClaims = 0; - request.DelegableServiceClaims = 0; request.Flags = SYS_SECURITY_CONTEXT_FLAG_NONE; request.ContextHandleOut = contextOut; return SysDeriveSecurityContext(&request); @@ -166,6 +277,7 @@ static KSTATUS connect_control_listener(SYS_HANDLE listener, SYS_HANDLE* endpoin // Closes the generic parent-side handles retained for one managed service. static void close_service_handles(INIT_SERVICE* service) { if (!service) return; + forget_service_grants(service->Descriptor); if (service->ControlEndpoint != SYS_HANDLE_INVALID) { (void)SysDisconnectPort(service->ControlEndpoint); (void)SysCloseHandle(service->ControlEndpoint); @@ -179,12 +291,9 @@ static void close_service_handles(INIT_SERVICE* service) { // Starts one managed service process and establishes its private control connection. static KSTATUS start_service_process(SYS_HANDLE systemVolume, SYS_HANDLE securityContext, - const NTL_PROCESS_STARTUP_CAPABILITY* startupCapabilities, - uint32_t startupCapabilityCount, INIT_SERVICE* service) { if (!service || !service->Descriptor || service->Descriptor->ImagePath[0] == '\0' || - startupCapabilityCount > SYS_STARTUP_CAPABILITY_MAX - 1u || - (startupCapabilityCount && !startupCapabilities)) + service->Descriptor->GrantCount > SYS_STARTUP_CAPABILITY_MAX - 1u) return NTLSTATUS_INVALID_PARAMETER; if (service->Process != SYS_HANDLE_INVALID || service->ControlEndpoint != SYS_HANDLE_INVALID || (service->State != InitServiceStopped && service->State != InitServiceFailed)) @@ -201,13 +310,20 @@ static KSTATUS start_service_process(SYS_HANDLE systemVolume, NTL_PROCESS_STARTUP_CAPABILITY startup[SYS_STARTUP_CAPABILITY_MAX]; memset(startup, 0, sizeof(startup)); - for (uint32_t i = 0; i < startupCapabilityCount; ++i) - startup[i] = startupCapabilities[i]; - startup[startupCapabilityCount].Tag = SYS_STARTUP_CAP_SERVICE_CONTROL; + uint32_t startupCapabilityCount = 0; + status = resolve_service_grants(service->Descriptor, startup, &startupCapabilityCount); + if (!KSUCCESS(status)) { + (void)SysCloseHandle(controlListener); + service->State = InitServiceFailed; + return status; + } + memcpy(startup[startupCapabilityCount].Name, "service.control", sizeof("service.control")); startup[startupCapabilityCount].SourceHandle = controlListener; startup[startupCapabilityCount].DesiredRights = SYS_RIGHT_QUERY | SYS_PORT_RIGHT_ACCEPT; startup[startupCapabilityCount].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; + uint32_t capabilityCount = startupCapabilityCount + 1u; + SYS_HANDLE process = SYS_HANDLE_INVALID; SYS_STRING_VIEW imagePath = SysSvFromCstr(service->Descriptor->ImagePath); NTL_CREATE_PROCESS_AT_REQUEST create; @@ -223,7 +339,7 @@ static KSTATUS start_service_process(SYS_HANDLE systemVolume, create.ResolveModuleName = NtlResolveModuleNameIdentity; create.ResolveContext = (void*)0; create.StartupCapabilities = startup; - create.StartupCapabilityCount = startupCapabilityCount + 1u; + create.StartupCapabilityCount = capabilityCount; create.ProcessHandleOut = &process; status = NtlCreateProcessAt(&create); if (!KSUCCESS(status)) { @@ -251,8 +367,9 @@ static KSTATUS start_service_process(SYS_HANDLE systemVolume, static KSTATUS wait_service_ready(INIT_SERVICE* service, SYS_HANDLE* exportsOut, uint32_t exportCapacity, - uint32_t* exportCountOut) { - if (!service || !service->Descriptor || !exportCountOut || + uint32_t* exportCountOut, + NTL_SERVICE_CONTROL_MESSAGE* messageOut) { + if (!service || !service->Descriptor || !exportCountOut || !messageOut || exportCapacity > SYS_PORT_MAX_TRANSFER_HANDLES || (exportCapacity && !exportsOut)) return NTLSTATUS_INVALID_PARAMETER; @@ -281,12 +398,8 @@ static KSTATUS wait_service_ready(INIT_SERVICE* service, bool valid = info.Type == SYS_PORT_MESSAGE_NOTIFICATION && info.PayloadLength == sizeof(message) && - message.Size == sizeof(message) && - message.Version == NTL_SERVICE_CONTROL_VERSION && - message.Operation == NTL_SERVICE_CONTROL_READY && - message.ExportCount == info.HandleCount && - message.ExportCount <= exportCapacity && - message.Reserved[0] == 0 && message.Reserved[1] == 0 && message.Reserved[2] == 0; + info.HandleCount <= exportCapacity && + NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_READY, info.HandleCount); if (!valid || !KSUCCESS(message.Status)) { for (uint32_t i = 0; i < info.HandleCount && i < exportCapacity; ++i) { if (exportsOut[i] != SYS_HANDLE_INVALID) { @@ -298,383 +411,52 @@ static KSTATUS wait_service_ready(INIT_SERVICE* service, } *exportCountOut = info.HandleCount; + *messageOut = message; return NTLSTATUS_SUCCESS; } -// Waits for RSSRV readiness and receives init-only namespace and DWM-session bootstrap exports. -static KSTATUS wait_resource_service_ready(INIT_SERVICE* service, - SYS_HANDLE* adminListenerOut, - SYS_HANDLE* sessionControlListenerOut) { - if (!adminListenerOut || !sessionControlListenerOut) return NTLSTATUS_INVALID_PARAMETER; - *adminListenerOut = SYS_HANDLE_INVALID; - *sessionControlListenerOut = SYS_HANDLE_INVALID; - - SYS_HANDLE exports[2] = { SYS_HANDLE_INVALID, SYS_HANDLE_INVALID }; - uint32_t exportCount = 0; - KSTATUS status = wait_service_ready(service, exports, 2u, &exportCount); - if (!KSUCCESS(status)) return status; - if (exportCount != 2u || exports[0] == SYS_HANDLE_INVALID || exports[1] == SYS_HANDLE_INVALID) { - for (uint32_t i = 0; i < 2u; ++i) - if (exports[i] != SYS_HANDLE_INVALID) (void)SysCloseHandle(exports[i]); - return NTLSTATUS_FAILURE; - } - - *adminListenerOut = exports[0]; - *sessionControlListenerOut = exports[1]; - return NTLSTATUS_SUCCESS; -} - -// Verifies namespace structure, administration, symbolic links, enumeration, and public capability lookup. -static KSTATUS verify_resource_service(SYS_HANDLE adminListener, SYS_HANDLE namespaceVolume) { - SYS_HANDLE admin = SYS_HANDLE_INVALID; - SYS_HANDLE publicSession = SYS_HANDLE_INVALID; - SYS_HANDLE volume = SYS_HANDLE_INVALID; - SYS_HANDLE displayDevice = SYS_HANDLE_INVALID; - SYS_HANDLE consoleAlias = SYS_HANDLE_INVALID; - uint32_t initDirectoryCreated = 0; - uint32_t aliasCreated = 0; - uint32_t probeBound = 0; - NTL_RESOURCE_ENUM_CURSOR cursor; - uint32_t sawAlias = 0; - uint32_t sawProbe = 0; - memset(&cursor, 0, sizeof(cursor)); - KSTATUS status = NtlResourceConnectListener(adminListener, &admin); - if (!KSUCCESS(status)) return status; - - uint32_t kind = NTL_RESOURCE_KIND_INVALID; - status = NtlResourceQuery(admin, SYS_SV_LITERAL("/"), &kind); - if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_NAMESPACE_DIRECTORY) { - status = KSUCCESS(status) ? NTLSTATUS_FAILURE : status; - goto cleanup; - } - status = NtlResourceQuery(admin, SYS_SV_LITERAL("/SystemRoot"), &kind); - if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_SYMBOLIC_LINK) { - status = KSUCCESS(status) ? NTLSTATUS_FAILURE : status; - goto cleanup; - } - status = NtlResourceQuery(admin, SYS_SV_LITERAL("/Device/Display0"), &kind); - if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_DEVICE) { - status = KSUCCESS(status) ? NTLSTATUS_FAILURE : status; - goto cleanup; - } - status = NtlResourceLookup(admin, SYS_SV_LITERAL("/Device/Display0"), - SYS_RIGHT_QUERY | SYS_DISPLAY_MANAGER_RIGHT_ENUMERATE, &displayDevice); - if (!KSUCCESS(status)) goto cleanup; - SYS_DISPLAY_INFO displayInfo; - memset(&displayInfo, 0, sizeof(displayInfo)); - status = SysQueryDisplayManagerInfo(displayDevice, &displayInfo); - (void)SysCloseHandle(displayDevice); - displayDevice = SYS_HANDLE_INVALID; - if (!KSUCCESS(status) || displayInfo.Mode.Width == 0 || displayInfo.Mode.Height == 0) { - status = KSUCCESS(status) ? NTLSTATUS_FAILURE : status; - goto cleanup; - } - - status = NtlResourceQuery(admin, SYS_SV_LITERAL("/Sessions/0"), &kind); - if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_NAMESPACE_DIRECTORY) { - status = KSUCCESS(status) ? NTLSTATUS_FAILURE : status; - goto cleanup; - } - status = NtlResourceQuery(admin, SYS_SV_LITERAL("/Sessions/0/Desktop"), &kind); - if (status != NTLSTATUS_NOT_FOUND) { - status = KSUCCESS(status) ? NTLSTATUS_FAILURE : status; - goto cleanup; - } - status = NtlResourceQuery(admin, SYS_SV_LITERAL("/Sessions/0/Console"), &kind); - if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_CAPABILITY) { - status = KSUCCESS(status) ? NTLSTATUS_FAILURE : status; - goto cleanup; - } - status = NtlResourceQuery(admin, SYS_SV_LITERAL("/Console"), &kind); - if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_SYMBOLIC_LINK) { - status = KSUCCESS(status) ? NTLSTATUS_FAILURE : status; - goto cleanup; - } - status = NtlResourceLookup(admin, SYS_SV_LITERAL("/Console"), - SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT, &consoleAlias); - if (!KSUCCESS(status)) goto cleanup; - (void)SysCloseHandle(consoleAlias); - consoleAlias = SYS_HANDLE_INVALID; - status = NtlResourceQuery(admin, SYS_SV_LITERAL(NTL_INPUT_SERVICE_PATH), &kind); - if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_CAPABILITY) { - status = KSUCCESS(status) ? NTLSTATUS_FAILURE : status; - goto cleanup; - } - SYS_HANDLE inputSession = SYS_HANDLE_INVALID; - status = NtlInputConnect(&inputSession); - if (!KSUCCESS(status)) goto cleanup; - NTL_INPUT_STATE inputState; - status = NtlInputQueryState(inputSession, &inputState); - (void)NtlInputClose(inputSession); - if (!KSUCCESS(status) || inputState.Size != sizeof(inputState) || - inputState.Flags != NTL_INPUT_STATE_FLAG_NONE) { - status = KSUCCESS(status) ? NTLSTATUS_FAILURE : status; - goto cleanup; - } - - status = NtlResourceQuery(admin, SYS_SV_LITERAL(NTL_CONSOLE_SERVICE_PATH), &kind); - if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_CAPABILITY) { - status = KSUCCESS(status) ? NTLSTATUS_FAILURE : status; - goto cleanup; - } - SYS_HANDLE consoleSession = SYS_HANDLE_INVALID; - status = NtlConsoleConnect(&consoleSession); - if (!KSUCCESS(status)) goto cleanup; - NTL_CONSOLE_INFO consoleInfo; - memset(&consoleInfo, 0, sizeof(consoleInfo)); - status = NtlConsoleQueryInfo(consoleSession, &consoleInfo); - (void)NtlConsoleClose(consoleSession); - if (!KSUCCESS(status) || consoleInfo.Size != sizeof(consoleInfo) || - consoleInfo.Flags != NTL_CONSOLE_FLAG_NONE || consoleInfo.Columns == 0 || - consoleInfo.Rows == 0 || consoleInfo.PixelWidth == 0 || consoleInfo.PixelHeight == 0) { - status = KSUCCESS(status) ? NTLSTATUS_FAILURE : status; - goto cleanup; - } - - // Exercise explicit parent creation rather than allowing bind to manufacture namespace directories. - status = NtlResourceCreateDirectory(admin, SYS_SV_LITERAL("/Init")); - if (!KSUCCESS(status)) goto cleanup; - initDirectoryCreated = 1; - - // Exercise a symbolic-link chain: /Init/SystemRoot -> /SystemRoot -> /Device/HarddiskVolumeN. - status = NtlResourceCreateSymbolicLink(admin, SYS_SV_LITERAL("/Init/SystemRoot"), - SYS_SV_LITERAL("/SystemRoot")); - if (!KSUCCESS(status)) goto cleanup; - aliasCreated = 1; - - status = NtlResourceBind(admin, SYS_SV_LITERAL("/Init/BootstrapProbe"), - NTL_RESOURCE_KIND_VOLUME, namespaceVolume, - SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, - SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN); - if (!KSUCCESS(status)) goto cleanup; - probeBound = 1; - - status = NtlResourceConnect(&publicSession); - if (!KSUCCESS(status)) goto cleanup; - - status = NtlResourceLookup(publicSession, SYS_SV_LITERAL("/SystemRoot"), - SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &volume); - if (!KSUCCESS(status)) goto cleanup; - (void)SysCloseHandle(volume); - volume = SYS_HANDLE_INVALID; - - status = NtlResourceLookup(publicSession, SYS_SV_LITERAL("/Init/SystemRoot"), - SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &volume); - if (!KSUCCESS(status)) goto cleanup; - (void)SysCloseHandle(volume); - volume = SYS_HANDLE_INVALID; - - kind = NTL_RESOURCE_KIND_INVALID; - status = NtlResourceQuery(publicSession, SYS_SV_LITERAL("/Init/BootstrapProbe"), &kind); - if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_VOLUME) { - status = KSUCCESS(status) ? NTLSTATUS_FAILURE : status; - goto cleanup; - } - - for (;;) { - NTL_RESOURCE_ENUM_ENTRY entry; - status = NtlResourceEnumerate(publicSession, SYS_SV_LITERAL("/Init"), &cursor, &entry); - if (status == NTLSTATUS_NO_MORE_FILES) { - status = NTLSTATUS_SUCCESS; - break; - } - if (!KSUCCESS(status)) goto cleanup; - if (strcmp(entry.Name, "SystemRoot") == 0 && entry.Kind == NTL_RESOURCE_KIND_SYMBOLIC_LINK) - sawAlias = 1; - if (strcmp(entry.Name, "BootstrapProbe") == 0 && entry.Kind == NTL_RESOURCE_KIND_VOLUME) - sawProbe = 1; - } - if (!sawAlias || !sawProbe) { - status = NTLSTATUS_FAILURE; - goto cleanup; - } - - status = NtlResourceUnbind(admin, SYS_SV_LITERAL("/Init/BootstrapProbe")); - if (!KSUCCESS(status)) goto cleanup; - probeBound = 0; - kind = NTL_RESOURCE_KIND_INVALID; - if (NtlResourceQuery(publicSession, SYS_SV_LITERAL("/Init/BootstrapProbe"), &kind) != NTLSTATUS_NOT_FOUND) { - status = NTLSTATUS_FAILURE; - goto cleanup; - } - -cleanup: - if (consoleAlias != SYS_HANDLE_INVALID) (void)SysCloseHandle(consoleAlias); - if (displayDevice != SYS_HANDLE_INVALID) (void)SysCloseHandle(displayDevice); - if (volume != SYS_HANDLE_INVALID) (void)SysCloseHandle(volume); - if (publicSession != SYS_HANDLE_INVALID) (void)NtlResourceClose(publicSession); - if (probeBound) (void)NtlResourceUnbind(admin, SYS_SV_LITERAL("/Init/BootstrapProbe")); - if (aliasCreated) { - KSTATUS cleanupStatus = NtlResourceUnbind(admin, SYS_SV_LITERAL("/Init/SystemRoot")); - if (KSUCCESS(status) && !KSUCCESS(cleanupStatus)) status = cleanupStatus; - } - if (initDirectoryCreated) { - KSTATUS cleanupStatus = NtlResourceUnbind(admin, SYS_SV_LITERAL("/Init")); - if (KSUCCESS(status) && !KSUCCESS(cleanupStatus)) status = cleanupStatus; - } - (void)NtlResourceClose(admin); - return status; -} - -// Tears down retained RSSRV-specific and generic handles without sending a stop message. -static void close_resource_service(INIT_RESOURCE_SERVICE* resourceService) { - if (!resourceService) return; - if (resourceService->AdminListener != SYS_HANDLE_INVALID) - (void)SysCloseHandle(resourceService->AdminListener); - if (resourceService->SessionControlListener != SYS_HANDLE_INVALID) - (void)SysCloseHandle(resourceService->SessionControlListener); - resourceService->AdminListener = SYS_HANDLE_INVALID; - resourceService->SessionControlListener = SYS_HANDLE_INVALID; - close_service_handles(&resourceService->Service); -} - -// Initializes the RSSRV-specific extension of one managed-service runtime. -static void initialize_resource_service(INIT_RESOURCE_SERVICE* resourceService, - const INIT_SERVICE_DESCRIPTOR* descriptor) { - memset(resourceService, 0, sizeof(*resourceService)); - initialize_service(&resourceService->Service, descriptor); - resourceService->AdminListener = SYS_HANDLE_INVALID; - resourceService->SessionControlListener = SYS_HANDLE_INVALID; -} - -// Starts one restricted RSSRV instance through the generic managed-service process path. -static KSTATUS start_resource_service(SYS_HANDLE systemVolume, - SYS_HANDLE namespaceVolume, - SYS_HANDLE volumeManager, - SYS_HANDLE inputManager, - SYS_HANDLE displayManager, - SYS_HANDLE resourceContext, - INIT_RESOURCE_SERVICE* resourceService) { - if (!resourceService || !resourceService->Service.Descriptor || - resourceService->Service.Descriptor->Type != InitServiceTypeResource || - resourceService->AdminListener != SYS_HANDLE_INVALID) - return NTLSTATUS_INVALID_PARAMETER; - - NTL_PROCESS_STARTUP_CAPABILITY startup[4]; - memset(startup, 0, sizeof(startup)); - startup[0].Tag = SYS_STARTUP_CAP_SYSTEM_VOLUME_NAMESPACE; - startup[0].SourceHandle = namespaceVolume; - startup[0].DesiredRights = SYS_RIGHT_QUERY | SYS_RIGHT_TRANSFER | - SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY; - startup[0].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; - startup[1].Tag = SYS_STARTUP_CAP_VOLUME_MANAGER; - startup[1].SourceHandle = volumeManager; - startup[1].DesiredRights = SYS_RIGHT_QUERY | - SYS_VOLUME_MANAGER_RIGHT_ENUMERATE | SYS_VOLUME_MANAGER_RIGHT_OPEN | - SYS_VOLUME_MANAGER_RIGHT_WAIT; - startup[1].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; - startup[2].Tag = SYS_STARTUP_CAP_INPUT_MANAGER; - startup[2].SourceHandle = inputManager; - startup[2].DesiredRights = SYS_RIGHT_QUERY | SYS_INPUT_MANAGER_RIGHT_READ; - startup[2].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; - startup[3].Tag = SYS_STARTUP_CAP_DISPLAY_MANAGER; - startup[3].SourceHandle = displayManager; - startup[3].DesiredRights = SYS_RIGHT_DERIVE | SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_DISPLAY_MANAGER_RIGHT_ENUMERATE | - SYS_DISPLAY_MANAGER_RIGHT_MODESET | SYS_DISPLAY_MANAGER_RIGHT_CONSOLE; - startup[3].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; - - KSTATUS status = start_service_process(systemVolume, resourceContext, startup, 4, - &resourceService->Service); - if (!KSUCCESS(status)) return status; - - SYS_HANDLE adminListener = SYS_HANDLE_INVALID; - SYS_HANDLE sessionControlListener = SYS_HANDLE_INVALID; - status = wait_resource_service_ready(&resourceService->Service, &adminListener, &sessionControlListener); - if (KSUCCESS(status)) - status = verify_resource_service(adminListener, namespaceVolume); +// Every service uses the same process, READY, export and failure path. +static KSTATUS start_managed_service(SYS_HANDLE systemVolume, + SYS_HANDLE securityContext, INIT_SERVICE* service) { + KSTATUS status = start_service_process(systemVolume, securityContext, service); if (!KSUCCESS(status)) { - if (adminListener != SYS_HANDLE_INVALID) (void)SysCloseHandle(adminListener); - if (sessionControlListener != SYS_HANDLE_INVALID) (void)SysCloseHandle(sessionControlListener); - debug_early_service_exit(&resourceService->Service); - (void)SysKillProcess(resourceService->Service.Process, 1); - (void)SysWait(resourceService->Service.Process, SYS_WAIT_FLAG_NONE); - close_service_handles(&resourceService->Service); - resourceService->Service.State = InitServiceFailed; - return status; - } - - resourceService->AdminListener = adminListener; - resourceService->SessionControlListener = sessionControlListener; - resourceService->Service.State = InitServiceReady; - return NTLSTATUS_SUCCESS; -} - -// Publishes DWM's desktop listener at the stable Session 0 desktop resource path. -static KSTATUS publish_dwm_desktop(SYS_HANDLE adminListener, SYS_HANDLE desktopListener) { - if (adminListener == SYS_HANDLE_INVALID || desktopListener == SYS_HANDLE_INVALID) - return NTLSTATUS_INVALID_PARAMETER; - SYS_HANDLE admin = SYS_HANDLE_INVALID; - KSTATUS status = NtlResourceConnectListener(adminListener, &admin); - if (!KSUCCESS(status)) return status; - status = NtlResourceBind(admin, SYS_SV_LITERAL(NTL_DESKTOP_SERVICE_PATH), - NTL_RESOURCE_KIND_CAPABILITY, desktopListener, - SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT, - SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT); - (void)NtlResourceClose(admin); - return status; -} - -// Removes DWM's public desktop endpoint while retaining Session 0 itself inside RSSRV. -static void unpublish_dwm_desktop(SYS_HANDLE adminListener) { - if (adminListener == SYS_HANDLE_INVALID) return; - SYS_HANDLE admin = SYS_HANDLE_INVALID; - if (!KSUCCESS(NtlResourceConnectListener(adminListener, &admin))) return; - (void)NtlResourceUnbind(admin, SYS_SV_LITERAL(NTL_DESKTOP_SERVICE_PATH)); - (void)NtlResourceClose(admin); -} - -// Starts one DWM instance with physical presentation, cursor policy, and attenuated Session 0 authority. -static KSTATUS start_dwm_service(SYS_HANDLE systemVolume, - SYS_HANDLE displayManager, - SYS_HANDLE securityContext, - const INIT_RESOURCE_SERVICE* resourceService, - INIT_SERVICE* service) { - if (!resourceService || !service || !service->Descriptor || service->Descriptor->Type != InitServiceTypeDwm || - displayManager == SYS_HANDLE_INVALID || securityContext == SYS_HANDLE_INVALID || - resourceService->AdminListener == SYS_HANDLE_INVALID || - resourceService->SessionControlListener == SYS_HANDLE_INVALID) - return NTLSTATUS_INVALID_PARAMETER; - - NTL_PROCESS_STARTUP_CAPABILITY startup[2]; - memset(startup, 0, sizeof(startup)); - startup[0].Tag = SYS_STARTUP_CAP_DISPLAY_MANAGER; - startup[0].SourceHandle = displayManager; - startup[0].DesiredRights = SYS_RIGHT_QUERY | SYS_DISPLAY_MANAGER_RIGHT_ENUMERATE | - SYS_DISPLAY_MANAGER_RIGHT_PRESENT | SYS_DISPLAY_MANAGER_RIGHT_CURSOR; - startup[0].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; - startup[1].Tag = SYS_STARTUP_CAP_SESSION_CONTROL; - startup[1].SourceHandle = resourceService->SessionControlListener; - startup[1].DesiredRights = SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT; - startup[1].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; - - KSTATUS status = start_service_process(systemVolume, securityContext, startup, 2u, service); - if (!KSUCCESS(status)) { - debug_print("[init] DWM spawn/delegation failed: "); + debug_print("[init] process or startup grant creation failed for "); + debug_print(service->Descriptor->Id); + debug_print(": "); debug_status(status); debug_print("\n"); return status; } - SYS_HANDLE exports[1] = { SYS_HANDLE_INVALID }; - uint32_t exportCount = 0; - status = wait_service_ready(service, exports, 1u, &exportCount); + SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES]; + uint32_t count = 0; + NTL_SERVICE_CONTROL_MESSAGE ready; + status = wait_service_ready(service, handles, SYS_PORT_MAX_TRANSFER_HANDLES, &count, &ready); if (!KSUCCESS(status)) { - debug_print("[init] DWM READY wait failed: "); + debug_print("[init] READY receive failed for "); + debug_print(service->Descriptor->Id); + debug_print(": "); debug_status(status); debug_print("\n"); - } else if (exportCount != 1u || exports[0] == SYS_HANDLE_INVALID) { - debug_print("[init] DWM READY contract invalid\n"); - status = NTLSTATUS_FAILURE; - } else { - status = publish_dwm_desktop(resourceService->AdminListener, exports[0]); - if (!KSUCCESS(status)) { - debug_print("[init] DWM desktop publication failed: "); - debug_status(status); - debug_print("\n"); + } + if (KSUCCESS(status)) { + const INIT_SERVICE_DESCRIPTOR* descriptor = service->Descriptor; + if (count != descriptor->ExportCount) status = NTLSTATUS_INVALID_PARAMETER; + for (uint32_t i = 0; KSUCCESS(status) && i < count; ++i) { + bool declared = false; + for (uint32_t j = 0; j < descriptor->ExportCount; ++j) + if (strcmp(ready.ExportNames[i], descriptor->Exports[j]) == 0) declared = true; + if (!declared || handles[i] == SYS_HANDLE_INVALID) + status = NTLSTATUS_INVALID_PARAMETER; + } + if (KSUCCESS(status)) { + status = register_ready_exports(descriptor, &ready, handles, count, true); + if (KSUCCESS(status)) + for (uint32_t i = 0; i < count; ++i) handles[i] = SYS_HANDLE_INVALID; } } - if (exports[0] != SYS_HANDLE_INVALID) (void)SysCloseHandle(exports[0]); - + for (uint32_t i = 0; i < count; ++i) + if (handles[i] != SYS_HANDLE_INVALID) (void)SysCloseHandle(handles[i]); if (!KSUCCESS(status)) { debug_early_service_exit(service); (void)SysKillProcess(service->Process, 1); @@ -683,163 +465,71 @@ static KSTATUS start_dwm_service(SYS_HANDLE systemVolume, service->State = InitServiceFailed; return status; } - service->State = InitServiceReady; return NTLSTATUS_SUCCESS; } -// Builds the canonical RSSRV publication path for one generic service identifier. -static KSTATUS build_generic_service_path(const INIT_SERVICE_DESCRIPTOR* descriptor, - char* pathOut, - size_t pathCapacity) { - static const char prefix[] = "/Services/"; - if (!descriptor || !pathOut || pathCapacity == 0) return NTLSTATUS_INVALID_PARAMETER; - size_t prefixLength = sizeof(prefix) - 1u; - size_t idLength = strlen(descriptor->Id); - if (idLength == 0 || prefixLength + idLength + 1u > pathCapacity) - return NTLSTATUS_NAME_TOO_LONG; - memcpy(pathOut, prefix, prefixLength); - memcpy(pathOut + prefixLength, descriptor->Id, idLength + 1u); - return NTLSTATUS_SUCCESS; -} - -// Publishes one generic service listener through RSSRV rather than the kernel service registry. -static KSTATUS publish_generic_service(SYS_HANDLE adminListener, - const INIT_SERVICE_DESCRIPTOR* descriptor, - SYS_HANDLE publicListener) { - if (adminListener == SYS_HANDLE_INVALID || !descriptor || publicListener == SYS_HANDLE_INVALID) - return NTLSTATUS_INVALID_PARAMETER; - char path[64]; - KSTATUS status = build_generic_service_path(descriptor, path, sizeof(path)); - if (!KSUCCESS(status)) return status; - - SYS_HANDLE admin = SYS_HANDLE_INVALID; - status = NtlResourceConnectListener(adminListener, &admin); - if (!KSUCCESS(status)) return status; - status = NtlResourceBind(admin, SysSvFromCstr(path), NTL_RESOURCE_KIND_CAPABILITY, - publicListener, - SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT, - SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT); - (void)NtlResourceClose(admin); - return status; -} - -// Removes one generic service publication before its process is stopped or forgotten. -static void unpublish_generic_service(SYS_HANDLE adminListener, - const INIT_SERVICE_DESCRIPTOR* descriptor) { - if (adminListener == SYS_HANDLE_INVALID || !descriptor) return; - char path[64]; - if (!KSUCCESS(build_generic_service_path(descriptor, path, sizeof(path)))) return; - SYS_HANDLE admin = SYS_HANDLE_INVALID; - if (!KSUCCESS(NtlResourceConnectListener(adminListener, &admin))) return; - (void)NtlResourceUnbind(admin, SysSvFromCstr(path)); - (void)NtlResourceClose(admin); -} - -// Starts one generic service, validates its READY listener export, and publishes it through RSSRV. -static KSTATUS start_generic_service(SYS_HANDLE systemVolume, - SYS_HANDLE securityContext, - const INIT_RESOURCE_SERVICE* resourceService, - INIT_SERVICE* service) { - if (!resourceService || !service || !service->Descriptor || - service->Descriptor->Type != InitServiceTypeGeneric || - resourceService->AdminListener == SYS_HANDLE_INVALID) - return NTLSTATUS_INVALID_PARAMETER; - - KSTATUS status = start_service_process(systemVolume, securityContext, (const NTL_PROCESS_STARTUP_CAPABILITY*)0, - 0u, service); - if (!KSUCCESS(status)) return status; - - SYS_HANDLE exports[1] = { SYS_HANDLE_INVALID }; - uint32_t exportCount = 0; - status = wait_service_ready(service, exports, 1u, &exportCount); - if (KSUCCESS(status) && (exportCount != 1u || exports[0] == SYS_HANDLE_INVALID)) - status = NTLSTATUS_FAILURE; - if (KSUCCESS(status)) - status = publish_generic_service(resourceService->AdminListener, service->Descriptor, exports[0]); - if (exports[0] != SYS_HANDLE_INVALID) (void)SysCloseHandle(exports[0]); - - if (!KSUCCESS(status)) { - debug_early_service_exit(service); - (void)SysKillProcess(service->Process, 1); - (void)SysWait(service->Process, SYS_WAIT_FLAG_NONE); - close_service_handles(service); - service->State = InitServiceFailed; - return status; - } - - service->State = InitServiceReady; - return NTLSTATUS_SUCCESS; -} - -// Requests graceful shutdown of one managed service, falling back to process termination. +// Requests graceful STOP and bounds the fallback termination of any service. static void stop_service(INIT_SERVICE* service) { if (!service || service->Process == SYS_HANDLE_INVALID) return; - service->State = InitServiceStopping; - - NTL_SERVICE_CONTROL_MESSAGE message; - memset(&message, 0, sizeof(message)); + NTL_SERVICE_CONTROL_MESSAGE message = {0}; message.Size = sizeof(message); message.Version = NTL_SERVICE_CONTROL_VERSION; message.Operation = NTL_SERVICE_CONTROL_STOP; - message.ExportCount = 0; - - SYS_SEND_PORT_REQUEST send; - memset(&send, 0, sizeof(send)); + SYS_SEND_PORT_REQUEST send = {0}; send.Size = sizeof(send); send.EndpointHandle = service->ControlEndpoint; - send.Flags = SYS_PORT_FLAG_NONE; send.Payload = &message; send.PayloadLength = sizeof(message); send.TimeoutMilliseconds = service->Descriptor->StopTimeoutMilliseconds; (void)SysSendPort(&send); - KSTATUS status = SysWaitSingle(service->Process, SYS_WAIT_FLAG_NONE, service->Descriptor->StopTimeoutMilliseconds); if (!KSUCCESS(status)) { (void)SysKillProcess(service->Process, 1); - (void)SysWait(service->Process, SYS_WAIT_FLAG_NONE); + status = SysWaitSingle(service->Process, SYS_WAIT_FLAG_NONE, 2000u); + if (!KSUCCESS(status)) debug_print("[init] service termination timed out\n"); } close_service_handles(service); service->State = InitServiceStopped; } -// Unpublishes and stops DWM so new desktop connections cannot race service teardown. -static void stop_dwm_service(INIT_RESOURCE_SERVICE* resourceService, INIT_SERVICE* service) { - if (!resourceService || !service || service->State != InitServiceReady) return; - unpublish_dwm_desktop(resourceService->AdminListener); - stop_service(service); +// Shuts down the declared graph in reverse dependency order. +static void stop_all_services(void) { + for (uint32_t order = gServiceConfig.StartupCount; order > 0; --order) + stop_service(&gServices[gServiceConfig.StartupOrder[order - 1u]]); } -// Unpublishes and stops one generic service before RSSRV namespace teardown. -static void stop_generic_service(const INIT_RESOURCE_SERVICE* resourceService, INIT_SERVICE* service) { - if (!resourceService || !service || service->State != InitServiceReady) return; - unpublish_generic_service(resourceService->AdminListener, service->Descriptor); - stop_service(service); +// Tests a transitive hard dependency in the validated acyclic service graph. +static bool service_depends_on(uint16_t serviceIndex, uint16_t providerIndex) { + const INIT_SERVICE_DESCRIPTOR* desc = &gServiceConfig.Services[serviceIndex]; + for (uint32_t i = 0; i < desc->DependencyCount; ++i) { + uint16_t dep = desc->Dependencies[i]; + if (dep == providerIndex || service_depends_on(dep, providerIndex)) return true; + } + return false; } -// Stops ready generic services in reverse dependency order. -static void stop_generic_services(const INIT_RESOURCE_SERVICE* resourceService, - INIT_SERVICE* services) { - if (!resourceService || !services) return; +// Before restarting a provider, quiesce every transitive consumer in reverse order. +static void stop_service_dependents(uint16_t providerIndex) { for (uint32_t order = gServiceConfig.StartupCount; order > 0; --order) { uint16_t index = gServiceConfig.StartupOrder[order - 1u]; - if (gServiceConfig.Services[index].Type == InitServiceTypeGeneric) - stop_generic_service(resourceService, &services[index]); + if (index != providerIndex && service_depends_on(index, providerIndex)) + stop_service(&gServices[index]); } } -// Requests graceful RSSRV shutdown and releases its private administrative export. -static void stop_resource_service(INIT_RESOURCE_SERVICE* resourceService) { - if (!resourceService) return; - stop_service(&resourceService->Service); - if (resourceService->AdminListener != SYS_HANDLE_INVALID) - (void)SysCloseHandle(resourceService->AdminListener); - if (resourceService->SessionControlListener != SYS_HANDLE_INVALID) - (void)SysCloseHandle(resourceService->SessionControlListener); - resourceService->AdminListener = SYS_HANDLE_INVALID; - resourceService->SessionControlListener = SYS_HANDLE_INVALID; +// Starts eligible descendants again using the dependency-first startup order. +static KSTATUS restart_service_dependents(uint16_t providerIndex, SYS_HANDLE volume, + SYS_HANDLE context) { + for (uint32_t order = 0; order < gServiceConfig.StartupCount; ++order) { + uint16_t index = gServiceConfig.StartupOrder[order]; + if (!service_depends_on(index, providerIndex)) continue; + KSTATUS status = start_managed_service(volume, context, &gServices[index]); + if (!KSUCCESS(status)) return status; + } + return NTLSTATUS_SUCCESS; } // Starts the configured native session process from the system-volume capability. @@ -875,15 +565,42 @@ void _start(void) { if (!KSUCCESS(status)) exit_now((int)status); init_set_current_thread_name("init.main"); - SYS_HANDLE systemVolume = SysGetStartupCapability(SYS_STARTUP_CAP_SYSTEM_VOLUME); - SYS_HANDLE namespaceVolume = SysGetStartupCapability(SYS_STARTUP_CAP_SYSTEM_VOLUME_NAMESPACE); - SYS_HANDLE volumeManager = SysGetStartupCapability(SYS_STARTUP_CAP_VOLUME_MANAGER); - SYS_HANDLE inputManager = SysGetStartupCapability(SYS_STARTUP_CAP_INPUT_MANAGER); - SYS_HANDLE displayManager = SysGetStartupCapability(SYS_STARTUP_CAP_DISPLAY_MANAGER); - if (systemVolume == SYS_HANDLE_INVALID || namespaceVolume == SYS_HANDLE_INVALID || - volumeManager == SYS_HANDLE_INVALID || inputManager == SYS_HANDLE_INVALID || - displayManager == SYS_HANDLE_INVALID) { - debug_print("[init] FATAL: bootstrap resource-plane capability missing\n"); + SYS_HANDLE systemVolume = SysGetStartupCapability(NTL_BOOT_CAP_SYSTEM_VOLUME); + SYS_HANDLE namespaceVolume = SysGetStartupCapability(NTL_BOOT_CAP_NAMESPACE_ROOT); + SYS_HANDLE volumeManager = SysGetStartupCapability(NTL_BOOT_CAP_VOLUME_MANAGER); + SYS_HANDLE inputManager = SysGetStartupCapability(NTL_BOOT_CAP_INPUT_MANAGER); + SYS_HANDLE displayManager = SysGetStartupCapability(NTL_BOOT_CAP_DISPLAY_POLICY); + SYS_HANDLE registrationAuthority = SysGetStartupCapability(NTL_BOOT_CAP_RESOURCE_REGISTRATION); + SYS_HANDLE powerEvent = SYS_HANDLE_INVALID; + status = SysOpenPowerEvent(&powerEvent); + if (!KSUCCESS(status)) { + debug_print("[init] FATAL: power coordinator unavailable\n"); + exit_now(1); + } + // Report the missing name so bootstrap ABI mismatches can be diagnosed directly. + const struct { + const char* Name; + SYS_HANDLE Handle; + } requiredBootstrap[] = { + { NTL_BOOT_CAP_SYSTEM_VOLUME, systemVolume }, + { NTL_BOOT_CAP_NAMESPACE_ROOT, namespaceVolume }, + { NTL_BOOT_CAP_VOLUME_MANAGER, volumeManager }, + { NTL_BOOT_CAP_INPUT_MANAGER, inputManager }, + { NTL_BOOT_CAP_DISPLAY_POLICY, displayManager }, + { NTL_BOOT_CAP_RESOURCE_REGISTRATION, registrationAuthority }, + }; + for (uint32_t i = 0; i < sizeof(requiredBootstrap) / sizeof(requiredBootstrap[0]); ++i) { + if (requiredBootstrap[i].Handle != SYS_HANDLE_INVALID) continue; + debug_print("[init] FATAL: missing bootstrap capability: "); + debug_print(requiredBootstrap[i].Name); + debug_print("\n"); + exit_now(1); + } + + status = initialize_boot_grants(namespaceVolume, volumeManager, inputManager, + displayManager, registrationAuthority); + if (!KSUCCESS(status)) { + debug_print("[init] FATAL: bootstrap grant catalog initialization failed\n"); exit_now(1); } @@ -901,22 +618,12 @@ void _start(void) { exit_now(1); } - SYS_HANDLE resourceContext = SYS_HANDLE_INVALID; - status = derive_resource_service_context(&resourceContext); - if (!KSUCCESS(status)) { - debug_print("[init] FATAL: cannot derive RSSRV security context: "); - debug_status(status); - debug_print("\n"); - exit_now(1); - } - SYS_HANDLE unprivilegedServiceContext = SYS_HANDLE_INVALID; status = derive_unprivileged_service_context(&unprivilegedServiceContext); if (!KSUCCESS(status)) { debug_print("[init] FATAL: cannot derive unprivileged service context: "); debug_status(status); debug_print("\n"); - (void)SysCloseHandle(resourceContext); exit_now(1); } @@ -924,101 +631,51 @@ void _start(void) { if (!childPath.Data || childPath.Length == 0) { debug_print("[init] FATAL: no configured session process\n"); if (unprivilegedServiceContext != SYS_HANDLE_INVALID) (void)SysCloseHandle(unprivilegedServiceContext); - (void)SysCloseHandle(resourceContext); exit_now(1); } - INIT_RESOURCE_SERVICE resourceService; - initialize_resource_service(&resourceService, - &gServiceConfig.Services[gServiceConfig.ResourceServiceIndex]); - - INIT_SERVICE dwmService; - memset(&dwmService, 0, sizeof(dwmService)); - if (gServiceConfig.DwmServiceIndex != UINT16_MAX) - initialize_service(&dwmService, &gServiceConfig.Services[gServiceConfig.DwmServiceIndex]); - else { - dwmService.Process = SYS_HANDLE_INVALID; - dwmService.ControlEndpoint = SYS_HANDLE_INVALID; - dwmService.State = InitServiceStopped; - } - - INIT_SERVICE genericServices[INIT_SERVICE_MAX_COUNT]; - memset(genericServices, 0, sizeof(genericServices)); - uint32_t automaticGenericCount = 0; - for (uint32_t i = 0; i < gServiceConfig.ServiceCount; ++i) { - if (gServiceConfig.Services[i].Type != InitServiceTypeGeneric) continue; - initialize_service(&genericServices[i], &gServiceConfig.Services[i]); - if (gServiceConfig.Services[i].StartPolicy == InitServiceStartAuto) ++automaticGenericCount; - } - if (automaticGenericCount + 3u > SYS_WAIT_MANY_MAX_HANDLES) { - debug_print("[init] FATAL: too many automatic generic services for supervision wait set\n"); - (void)SysCloseHandle(unprivilegedServiceContext); - (void)SysCloseHandle(resourceContext); + uint32_t autoCount = gServiceConfig.StartupCount; + if (autoCount + 2u > SYS_WAIT_MANY_MAX_HANDLES) { + debug_print("[init] FATAL: service graph exceeds supervision wait capacity\n"); exit_now(1); } + for (uint32_t i = 0; i < gServiceConfig.ServiceCount; ++i) + initialize_service(&gServices[i], &gServiceConfig.Services[i]); for (uint32_t order = 0; order < gServiceConfig.StartupCount; ++order) { uint16_t index = gServiceConfig.StartupOrder[order]; - const INIT_SERVICE_DESCRIPTOR* descriptor = &gServiceConfig.Services[index]; - if (descriptor->Type == InitServiceTypeResource) { - status = start_resource_service(systemVolume, namespaceVolume, volumeManager, inputManager, - displayManager, resourceContext, &resourceService); - if (!KSUCCESS(status)) { - debug_print("[init] FATAL: RSSRV startup failed: "); - debug_status(status); - debug_print("\n"); - if (unprivilegedServiceContext != SYS_HANDLE_INVALID) (void)SysCloseHandle(unprivilegedServiceContext); - (void)SysCloseHandle(resourceContext); + INIT_SERVICE* service = &gServices[index]; + status = start_managed_service(systemVolume, unprivilegedServiceContext, service); + debug_print("[init] service "); + debug_print(service->Descriptor->Id); + if (KSUCCESS(status)) debug_print(" READY\n"); + else { + debug_print(" startup failed: "); + debug_status(status); + debug_print("\n"); + if (service->Descriptor->Critical) { + stop_all_services(); exit_now(1); } - debug_print("[init] RSSRV ready\n"); - } else if (descriptor->Type == InitServiceTypeDwm) { - status = start_dwm_service(systemVolume, displayManager, unprivilegedServiceContext, &resourceService, &dwmService); - if (!KSUCCESS(status)) { - debug_print("[init] noncritical DWM startup failed: "); - debug_status(status); - debug_print("\n"); - } else { - debug_print("[init] DWM ready\n"); - } - } else if (descriptor->Type == InitServiceTypeGeneric) { - status = start_generic_service(systemVolume, unprivilegedServiceContext, &resourceService, - &genericServices[index]); - if (!KSUCCESS(status)) { - debug_print("[init] noncritical generic service startup failed: "); - debug_print(descriptor->Id); - debug_print(" status="); - debug_status(status); - debug_print("\n"); - } else { - debug_print("[init] generic service ready: "); - debug_print(descriptor->Id); - debug_print("\n"); - } } } - if (resourceService.Service.State != InitServiceReady) { - debug_print("[init] FATAL: configured resource service was not started\n"); - if (dwmService.State == InitServiceReady) stop_dwm_service(&resourceService, &dwmService); - stop_generic_services(&resourceService, genericServices); - if (unprivilegedServiceContext != SYS_HANDLE_INVALID) (void)SysCloseHandle(unprivilegedServiceContext); - (void)SysCloseHandle(resourceContext); + INIT_RESTART_BUDGET restartBudgets[INIT_SERVICE_MAX_COUNT] = {0}; + uint64_t tickFrequency = 0; + status = SysGetTickFrequency(&tickFrequency); + if (!KSUCCESS(status) || tickFrequency == 0u) { + debug_print("[init] FATAL: monotonic clock unavailable for supervision\n"); + stop_all_services(); exit_now(1); } - for (;;) { SYS_HANDLE child = SYS_HANDLE_INVALID; status = start_configured_child(systemVolume, childPath, &child); if (!KSUCCESS(status)) { - debug_print("[init] FATAL: failed to start configured child: "); + debug_print("[init] FATAL: session start failed: "); debug_status(status); debug_print("\n"); - if (dwmService.State == InitServiceReady) stop_dwm_service(&resourceService, &dwmService); - stop_generic_services(&resourceService, genericServices); - stop_resource_service(&resourceService); - if (unprivilegedServiceContext != SYS_HANDLE_INVALID) (void)SysCloseHandle(unprivilegedServiceContext); - (void)SysCloseHandle(resourceContext); + stop_all_services(); exit_now(1); } debug_print("[init] session process started\n"); @@ -1028,121 +685,97 @@ void _start(void) { SYS_HANDLE waits[SYS_WAIT_MANY_MAX_HANDLES]; uint32_t kinds[SYS_WAIT_MANY_MAX_HANDLES]; uint32_t waitCount = 0; - waits[waitCount] = resourceService.Service.Process; - kinds[waitCount++] = 0u; - if (dwmService.State == InitServiceReady && dwmService.Process != SYS_HANDLE_INVALID) { - waits[waitCount] = dwmService.Process; - kinds[waitCount++] = 1u; - } + waits[waitCount] = powerEvent; + kinds[waitCount++] = UINT32_MAX; waits[waitCount] = child; - kinds[waitCount++] = 2u; + kinds[waitCount++] = UINT32_MAX - 1u; for (uint32_t i = 0; i < gServiceConfig.ServiceCount; ++i) { - if (gServiceConfig.Services[i].Type != InitServiceTypeGeneric || - genericServices[i].State != InitServiceReady || - genericServices[i].Process == SYS_HANDLE_INVALID) - continue; - waits[waitCount] = genericServices[i].Process; - kinds[waitCount++] = 3u + i; + if (gServices[i].State != InitServiceReady || + gServices[i].Process == SYS_HANDLE_INVALID) continue; + waits[waitCount] = gServices[i].Process; + kinds[waitCount++] = i; } - uint32_t signaled = 0; - SYS_WAIT_MANY_REQUEST wait; - memset(&wait, 0, sizeof(wait)); + SYS_WAIT_MANY_REQUEST wait = {0}; wait.Size = sizeof(wait); - wait.Flags = SYS_WAIT_FLAG_NONE; wait.Handles = waits; wait.Count = waitCount; wait.SignaledIndexOut = &signaled; wait.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE; status = SysWaitMany(&wait); if (!KSUCCESS(status)) { + debug_print("[init] FATAL: service supervision wait failed\n"); (void)SysKillProcess(child, 1); (void)SysWait(child, SYS_WAIT_FLAG_NONE); (void)SysCloseHandle(child); - if (dwmService.State == InitServiceReady) stop_dwm_service(&resourceService, &dwmService); - stop_generic_services(&resourceService, genericServices); - stop_resource_service(&resourceService); - if (unprivilegedServiceContext != SYS_HANDLE_INVALID) (void)SysCloseHandle(unprivilegedServiceContext); - (void)SysCloseHandle(resourceContext); - debug_print("[init] FATAL: service wait failed\n"); + stop_all_services(); exit_now(1); } - - if (kinds[signaled] == 0u) { - int32_t serviceExit = -1; - (void)SysGetProcessExitCode(resourceService.Service.Process, &serviceExit); - resourceService.Service.State = InitServiceFailed; - debug_print("[init] FATAL: critical RSSRV exited unexpectedly: "); - debug_i32(serviceExit); - debug_print("\n"); - (void)SysKillProcess(child, 1); - (void)SysWait(child, SYS_WAIT_FLAG_NONE); - (void)SysCloseHandle(child); - if (dwmService.State == InitServiceReady) stop_dwm_service(&resourceService, &dwmService); - for (uint32_t i = 0; i < gServiceConfig.ServiceCount; ++i) { - if (gServiceConfig.Services[i].Type == InitServiceTypeGeneric && - genericServices[i].Process != SYS_HANDLE_INVALID) { - (void)SysKillProcess(genericServices[i].Process, 1); - (void)SysWait(genericServices[i].Process, SYS_WAIT_FLAG_NONE); - close_service_handles(&genericServices[i]); - genericServices[i].State = InitServiceFailed; - } + if (kinds[signaled] == UINT32_MAX) { + uint32_t action = 0; + status = SysQuerySystemPower(&action); + if (!KSUCCESS(status) || !action) { + debug_print("[init] FATAL: invalid power request\n"); + exit_now(1); + } + (void)SysKillProcess(child, 0); + (void)SysWaitSingle(child, SYS_WAIT_FLAG_NONE, 3000u); + (void)SysCloseHandle(child); + stop_all_services(); + (void)SysCloseHandle(unprivilegedServiceContext); + (void)SysCloseHandle(powerEvent); + status = SysCompleteSystemPower(action); + if (!KSUCCESS(status)) { + debug_print("[init] FATAL: power commit rejected\n"); + exit_now(1); } - close_resource_service(&resourceService); - if (unprivilegedServiceContext != SYS_HANDLE_INVALID) (void)SysCloseHandle(unprivilegedServiceContext); - (void)SysCloseHandle(resourceContext); - exit_now(1); - } - - if (kinds[signaled] == 1u) { - int32_t serviceExit = -1; - (void)SysGetProcessExitCode(dwmService.Process, &serviceExit); - debug_print("[init] noncritical DWM exited unexpectedly: "); - debug_i32(serviceExit); - debug_print("\n"); - unpublish_dwm_desktop(resourceService.AdminListener); - close_service_handles(&dwmService); - dwmService.State = InitServiceFailed; - continue; - } - - if (kinds[signaled] >= 3u) { - uint32_t index = kinds[signaled] - 3u; - INIT_SERVICE* generic = &genericServices[index]; - int32_t serviceExit = -1; - (void)SysGetProcessExitCode(generic->Process, &serviceExit); - debug_print("[init] noncritical generic service exited unexpectedly: "); - debug_print(generic->Descriptor->Id); - debug_print(" code="); - debug_i32(serviceExit); - debug_print("\n"); - unpublish_generic_service(resourceService.AdminListener, generic->Descriptor); - close_service_handles(generic); - generic->State = InitServiceFailed; - continue; - } - - int32_t exitCode = -1; - status = SysGetProcessExitCode(child, &exitCode); - (void)SysCloseHandle(child); - child = SYS_HANDLE_INVALID; - if (!KSUCCESS(status)) exitCode = 1; - if (exitCode == 0) { - if (dwmService.State == InitServiceReady) stop_dwm_service(&resourceService, &dwmService); - stop_generic_services(&resourceService, genericServices); - stop_resource_service(&resourceService); - debug_print("[init] done\n"); - if (unprivilegedServiceContext != SYS_HANDLE_INVALID) (void)SysCloseHandle(unprivilegedServiceContext); - (void)SysCloseHandle(resourceContext); exit_now(0); } - debug_print("[init] session process crashed; restarting\n"); - restartChild = true; - break; + if (kinds[signaled] == UINT32_MAX - 1u) { + int32_t exitCode = 1; + (void)SysGetProcessExitCode(child, &exitCode); + (void)SysCloseHandle(child); + if (exitCode == 0) debug_print("[init] session ended; restarting\n"); + else debug_print("[init] session crashed; restarting\n"); + restartChild = true; + break; + } + uint16_t failedIndex = (uint16_t)kinds[signaled]; + INIT_SERVICE* failed = &gServices[failedIndex]; + int32_t exitCode = -1; + (void)SysGetProcessExitCode(failed->Process, &exitCode); + debug_print("[init] service exited: "); + debug_print(failed->Descriptor->Id); + debug_print(" code="); + debug_i32(exitCode); + debug_print("\n"); + stop_service_dependents(failedIndex); + close_service_handles(failed); + failed->State = InitServiceFailed; + if (failed->Descriptor->RestartPolicy == InitServiceRestartOnFailure && + InitConsumeRestartBudget(&restartBudgets[failedIndex], + SysGetTick(), tickFrequency)) { + status = start_managed_service(systemVolume, unprivilegedServiceContext, failed); + if (KSUCCESS(status)) + status = restart_service_dependents(failedIndex, systemVolume, unprivilegedServiceContext); + if (KSUCCESS(status)) continue; + debug_print("[init] service restart failed\n"); + } + // Any affected critical service is a fatal dependency loss. + bool fatal = failed->Descriptor->Critical; + for (uint32_t i = 0; i < gServiceConfig.ServiceCount; ++i) + if (gServices[i].Descriptor->Critical && service_depends_on((uint16_t)i, failedIndex)) + fatal = true; + if (fatal) { + (void)SysKillProcess(child, 1); + (void)SysWait(child, SYS_WAIT_FLAG_NONE); + (void)SysCloseHandle(child); + stop_all_services(); + debug_print("[init] FATAL: critical service dependency lost\n"); + exit_now(1); + } } - if (!restartChild) break; } - exit_now(1); } diff --git a/code/user/system/init/restart_budget.h b/code/user/system/init/restart_budget.h new file mode 100644 index 0000000..41d8127 --- /dev/null +++ b/code/user/system/init/restart_budget.h @@ -0,0 +1,27 @@ +// Service restart budget: three automatic attempts in any sliding five-minute interval. + +#pragma once +#include +#include + +typedef struct INIT_RESTART_BUDGET { + uint64_t Attempts[3]; + uint32_t Count; +} INIT_RESTART_BUDGET; + +static inline bool InitConsumeRestartBudget(INIT_RESTART_BUDGET* budget, + uint64_t now, uint64_t frequency) { + if (!budget || !frequency || budget->Count > 3u || + frequency > UINT64_MAX / 300u) return false; + uint64_t window = frequency * 300u; + uint32_t keep = 0; + for (uint32_t i = 0; i < budget->Count; ++i) { + uint64_t started = budget->Attempts[i]; + if (now >= started && now - started < window) + budget->Attempts[keep++] = started; + } + budget->Count = keep; + if (keep == 3u) return false; + budget->Attempts[budget->Count++] = now; + return true; +} diff --git a/code/user/system/init/service_config.c b/code/user/system/init/service_config.c index 9fa9276..1c03258 100644 --- a/code/user/system/init/service_config.c +++ b/code/user/system/init/service_config.c @@ -1,12 +1,12 @@ // Init service-configuration parser. Malformed or ambiguous service policy is rejected at boot. #include "service_config.h" +#include #include #include #include -#define INIT_SERVICE_KEY_TYPE 0x00000001u #define INIT_SERVICE_KEY_IMAGE 0x00000002u #define INIT_SERVICE_KEY_START 0x00000004u #define INIT_SERVICE_KEY_CRITICAL 0x00000008u @@ -14,7 +14,9 @@ #define INIT_SERVICE_KEY_READY_TIMEOUT 0x00000020u #define INIT_SERVICE_KEY_STOP_TIMEOUT 0x00000040u #define INIT_SERVICE_KEY_REQUIRES 0x00000080u -#define INIT_SERVICE_REQUIRED_KEYS 0x000000FFu +#define INIT_SERVICE_KEY_GRANTS 0x00000100u +#define INIT_SERVICE_KEY_EXPORTS 0x00000200u +#define INIT_SERVICE_REQUIRED_KEYS 0x000003FEu typedef struct INIT_PARSED_SERVICE { INIT_SERVICE_DESCRIPTOR Descriptor; @@ -155,17 +157,65 @@ static bool parse_requires(char* value, INIT_PARSED_SERVICE* service) { return true; } +// Parses explicit capability-source names without associating them with service roles. +static bool parse_grants(char* value, INIT_PARSED_SERVICE* service) { + service->Descriptor.GrantCount = 0; + if (!*value) return true; + char* cursor = value; + for (;;) { + char* comma = strchr(cursor, ','); + if (comma) *comma = '\0'; + char* name = trim_left(cursor); + trim_right(name); + size_t length = strlen(name); + if (length == 0 || length > INIT_SERVICE_GRANT_NAME_MAX || + service->Descriptor.GrantCount >= INIT_SERVICE_GRANT_MAX) return false; + for (size_t i = 0; i < length; ++i) + if (!is_service_id_char(name[i])) return false; + if (!strchr(name, '.') || name[0] == '.' || name[length - 1u] == '.') return false; + if (strcmp(name, "service.control") == 0 || strncmp(name, "publish.", 8u) == 0) + return false; + for (uint32_t i = 0; i < service->Descriptor.GrantCount; ++i) + if (strcmp(service->Descriptor.Grants[i], name) == 0) return false; + if (!copy_bounded(service->Descriptor.Grants[service->Descriptor.GrantCount], + sizeof(service->Descriptor.Grants[0]), name)) return false; + ++service->Descriptor.GrantCount; + if (!comma) break; + cursor = comma + 1; + if (!*cursor) return false; + } + return true; +} + +// Reads the declared READY export names, which are an exact startup contract. +static bool parse_exports(char* value, INIT_PARSED_SERVICE* service) { + service->Descriptor.ExportCount = 0; + if (!*value) return true; + char* cursor = value; + for (;;) { + char* comma = strchr(cursor, ','); + if (comma) *comma = '\0'; + char* name = trim_left(cursor); + trim_right(name); + if (!NtlServiceExportNameValid(name) || + service->Descriptor.ExportCount >= INIT_SERVICE_EXPORT_MAX) return false; + for (uint32_t i = 0; i < service->Descriptor.ExportCount; ++i) + if (strcmp(name, service->Descriptor.Exports[i]) == 0) return false; + if (!copy_bounded(service->Descriptor.Exports[service->Descriptor.ExportCount], + sizeof(service->Descriptor.Exports[0]), name)) return false; + ++service->Descriptor.ExportCount; + if (!comma) break; + cursor = comma + 1; + if (!*cursor) return false; + } + return true; +} + // Applies one known key to the current service and rejects duplicates or invalid values. static bool parse_service_key(INIT_PARSED_SERVICE* service, char* key, char* value) { uint32_t bit = 0; - if (strcmp(key, "type") == 0) { - bit = INIT_SERVICE_KEY_TYPE; - if (strcmp(value, "resource") == 0) service->Descriptor.Type = InitServiceTypeResource; - else if (strcmp(value, "dwm") == 0) service->Descriptor.Type = InitServiceTypeDwm; - else if (strcmp(value, "generic") == 0) service->Descriptor.Type = InitServiceTypeGeneric; - else return false; - } else if (strcmp(key, "image") == 0) { + if (strcmp(key, "image") == 0) { bit = INIT_SERVICE_KEY_IMAGE; if (!validate_image_path(value) || !copy_bounded(service->Descriptor.ImagePath, sizeof(service->Descriptor.ImagePath), value)) @@ -192,6 +242,12 @@ static bool parse_service_key(INIT_PARSED_SERVICE* service, char* key, char* val } else if (strcmp(key, "requires") == 0) { bit = INIT_SERVICE_KEY_REQUIRES; if (!parse_requires(value, service)) return false; + } else if (strcmp(key, "grants") == 0) { + bit = INIT_SERVICE_KEY_GRANTS; + if (!parse_grants(value, service)) return false; + } else if (strcmp(key, "exports") == 0) { + bit = INIT_SERVICE_KEY_EXPORTS; + if (!parse_exports(value, service)) return false; } else { return false; } @@ -270,7 +326,8 @@ static KSTATUS parse_services_ini(char* buffer, uint32_t* serviceCountOut, uint3 line = saved ? end + 1u : end; } - if (serviceCount == 0) return NTLSTATUS_INVALID_PARAMETER; + if (serviceCount == 0) + return NTLSTATUS_INVALID_PARAMETER; *serviceCountOut = serviceCount; return NTLSTATUS_SUCCESS; } @@ -279,14 +336,59 @@ static KSTATUS parse_services_ini(char* buffer, uint32_t* serviceCountOut, uint3 static KSTATUS validate_services(uint32_t serviceCount, INIT_SERVICE_CONFIG* configOut, uint32_t* errorLineOut) { - uint32_t resourceCount = 0; - uint16_t resourceIndex = 0; - uint32_t dwmCount = 0; - uint16_t dwmIndex = UINT16_MAX; - memset(configOut, 0, sizeof(*configOut)); - configOut->ResourceServiceIndex = UINT16_MAX; - configOut->DwmServiceIndex = UINT16_MAX; + + static const char* const bootGrants[] = { + NTL_BOOT_CAP_NAMESPACE_ROOT, NTL_BOOT_CAP_VOLUME_MANAGER, NTL_BOOT_CAP_INPUT_MANAGER, + NTL_BOOT_CAP_DISPLAY_POLICY, NTL_BOOT_CAP_DISPLAY_PRESENT, NTL_BOOT_CAP_RESOURCE_REGISTRATION + }; + for (uint32_t i = 0; i < serviceCount; ++i) { + INIT_SERVICE_DESCRIPTOR* descriptor = &gParsedServices[i].Descriptor; + for (uint32_t g = 0; g < descriptor->GrantCount; ++g) { + const char* name = descriptor->Grants[g]; + bool known = false; + for (uint32_t b = 0; b < sizeof(bootGrants) / sizeof(bootGrants[0]); ++b) + if (strcmp(name, bootGrants[b]) == 0) known = true; + if (known) continue; + if (strncmp(name, "boot.", 5u) == 0) { + if (errorLineOut) *errorLineOut = gParsedServices[i].SectionLine; + return NTLSTATUS_INVALID_PARAMETER; + } + // Service export sources require an explicit dependency; the longest + // matching service ID owns a qualified export name. + int32_t provider = -1; + size_t prefixLength = 0; + for (uint32_t j = 0; j < serviceCount; ++j) { + const char* id = gParsedServices[j].Descriptor.Id; + size_t length = strlen(id); + if (length > prefixLength && strncmp(name, id, length) == 0 && + name[length] == '.' && name[length + 1u] != '\0') { + provider = (int32_t)j; + prefixLength = length; + } + } + bool declaredDependency = false; + if (provider >= 0 && (uint32_t)provider != i && + gParsedServices[provider].Descriptor.StartPolicy == InitServiceStartAuto) { + for (uint32_t d = 0; d < gParsedServices[i].DependencyCount; ++d) + if (strcmp(gParsedServices[i].DependencyIds[d], + gParsedServices[provider].Descriptor.Id) == 0) + declaredDependency = true; + } + if (declaredDependency) { + const char* exportName = name + prefixLength + 1u; + bool declaredExport = false; + const INIT_SERVICE_DESCRIPTOR* source = &gParsedServices[provider].Descriptor; + for (uint32_t e = 0; e < source->ExportCount; ++e) + if (strcmp(source->Exports[e], exportName) == 0) declaredExport = true; + if (!declaredExport) declaredDependency = false; + } + if (!declaredDependency) { + if (errorLineOut) *errorLineOut = gParsedServices[i].SectionLine; + return NTLSTATUS_INVALID_PARAMETER; + } + } + } for (uint32_t i = 0; i < serviceCount; ++i) { INIT_PARSED_SERVICE* parsed = &gParsedServices[i]; @@ -297,6 +399,13 @@ static KSTATUS validate_services(uint32_t serviceCount, INIT_SERVICE_DESCRIPTOR* descriptor = &configOut->Services[i]; *descriptor = parsed->Descriptor; + for (uint32_t e = 0; e < descriptor->ExportCount; ++e) { + if (strlen(descriptor->Id) + 1u + strlen(descriptor->Exports[e]) > + INIT_SERVICE_GRANT_NAME_MAX) { + if (errorLineOut) *errorLineOut = parsed->SectionLine; + return NTLSTATUS_NAME_TOO_LONG; + } + } descriptor->DependencyCount = parsed->DependencyCount; for (uint32_t d = 0; d < parsed->DependencyCount; ++d) { int32_t dependency = find_parsed_service(gParsedServices, serviceCount, parsed->DependencyIds[d]); @@ -306,48 +415,6 @@ static KSTATUS validate_services(uint32_t serviceCount, } descriptor->Dependencies[d] = (uint16_t)dependency; } - - if (descriptor->Type == InitServiceTypeResource) { - ++resourceCount; - resourceIndex = (uint16_t)i; - } else if (descriptor->Type == InitServiceTypeDwm) { - ++dwmCount; - dwmIndex = (uint16_t)i; - } - } - - if (resourceCount != 1u || dwmCount > 1u) return NTLSTATUS_INVALID_PARAMETER; - - INIT_SERVICE_DESCRIPTOR* resource = &configOut->Services[resourceIndex]; - if (resource->StartPolicy != InitServiceStartAuto || !resource->Critical || - resource->RestartPolicy != InitServiceRestartNever || resource->DependencyCount != 0u) { - if (errorLineOut) *errorLineOut = gParsedServices[resourceIndex].SectionLine; - return NTLSTATUS_INVALID_PARAMETER; - } - - if (dwmCount == 1u) { - INIT_SERVICE_DESCRIPTOR* dwm = &configOut->Services[dwmIndex]; - bool requiresResource = false; - for (uint32_t d = 0; d < dwm->DependencyCount; ++d) - if (dwm->Dependencies[d] == resourceIndex) requiresResource = true; - if (dwm->StartPolicy == InitServiceStartAuto && - (dwm->Critical || dwm->RestartPolicy != InitServiceRestartNever || !requiresResource)) { - if (errorLineOut) *errorLineOut = gParsedServices[dwmIndex].SectionLine; - return NTLSTATUS_INVALID_PARAMETER; - } - } - - // The generic adapter publishes READY listeners through RSSRV, so automatic generic services require it. - for (uint32_t i = 0; i < serviceCount; ++i) { - INIT_SERVICE_DESCRIPTOR* generic = &configOut->Services[i]; - if (generic->Type != InitServiceTypeGeneric || generic->StartPolicy != InitServiceStartAuto) continue; - bool requiresResource = false; - for (uint32_t d = 0; d < generic->DependencyCount; ++d) - if (generic->Dependencies[d] == resourceIndex) requiresResource = true; - if (generic->Critical || generic->RestartPolicy != InitServiceRestartNever || !requiresResource) { - if (errorLineOut) *errorLineOut = gParsedServices[i].SectionLine; - return NTLSTATUS_INVALID_PARAMETER; - } } for (uint32_t i = 0; i < serviceCount; ++i) { @@ -406,8 +473,8 @@ static KSTATUS validate_services(uint32_t serviceCount, } configOut->ServiceCount = serviceCount; - configOut->ResourceServiceIndex = resourceIndex; - configOut->DwmServiceIndex = dwmIndex; + if (configOut->StartupCount + 2u > SYS_WAIT_MANY_MAX_HANDLES) + return NTLSTATUS_LIMIT_REACHED; return NTLSTATUS_SUCCESS; } diff --git a/code/user/system/init/service_config.h b/code/user/system/init/service_config.h index 5fd86dc..374397f 100644 --- a/code/user/system/init/service_config.h +++ b/code/user/system/init/service_config.h @@ -5,18 +5,16 @@ #include #include #include +#include #define INIT_SERVICES_PATH "system/services.ini" #define INIT_SERVICES_MAX_FILE_BYTES (64u * 1024u) #define INIT_SERVICE_MAX_COUNT 32u #define INIT_SERVICE_ID_MAX 31u #define INIT_SERVICE_DEPENDENCY_MAX 8u - -typedef enum INIT_SERVICE_TYPE { - InitServiceTypeResource = 0, - InitServiceTypeDwm, - InitServiceTypeGeneric, -} INIT_SERVICE_TYPE; +#define INIT_SERVICE_GRANT_MAX 8u +#define INIT_SERVICE_GRANT_NAME_MAX SYS_STARTUP_CAPABILITY_NAME_MAX +#define INIT_SERVICE_EXPORT_MAX 8u typedef enum INIT_SERVICE_START_POLICY { InitServiceStartAuto = 0, @@ -31,7 +29,6 @@ typedef enum INIT_SERVICE_RESTART_POLICY { typedef struct INIT_SERVICE_DESCRIPTOR { char Id[INIT_SERVICE_ID_MAX + 1u]; char ImagePath[SYS_PATH_MAX]; - INIT_SERVICE_TYPE Type; INIT_SERVICE_START_POLICY StartPolicy; bool Critical; INIT_SERVICE_RESTART_POLICY RestartPolicy; @@ -39,6 +36,10 @@ typedef struct INIT_SERVICE_DESCRIPTOR { uint32_t StopTimeoutMilliseconds; uint32_t DependencyCount; uint16_t Dependencies[INIT_SERVICE_DEPENDENCY_MAX]; + uint32_t GrantCount; + char Grants[INIT_SERVICE_GRANT_MAX][INIT_SERVICE_GRANT_NAME_MAX + 1u]; + uint32_t ExportCount; + char Exports[INIT_SERVICE_EXPORT_MAX][NTL_SERVICE_EXPORT_NAME_MAX + 1u]; } INIT_SERVICE_DESCRIPTOR; typedef struct INIT_SERVICE_CONFIG { @@ -46,8 +47,6 @@ typedef struct INIT_SERVICE_CONFIG { uint16_t StartupOrder[INIT_SERVICE_MAX_COUNT]; uint32_t ServiceCount; uint32_t StartupCount; - uint16_t ResourceServiceIndex; - uint16_t DwmServiceIndex; } INIT_SERVICE_CONFIG; // Loads, strictly parses, and validates services.ini beneath the bootstrap system volume. diff --git a/code/user/system/posixsrv/posixsrv.c b/code/user/system/posixsrv/posixsrv.c index 9a581f0..3303057 100644 --- a/code/user/system/posixsrv/posixsrv.c +++ b/code/user/system/posixsrv/posixsrv.c @@ -1682,8 +1682,7 @@ static KSTATUS PosixsrvValidateSecurity(void) { security.Size = sizeof(security); KSTATUS status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &security); if (!KSUCCESS(status)) return status; - if (security.EffectivePrivileges != 0 || security.DelegablePrivileges != 0 || - security.ServiceClaims != 0 || security.DelegableServiceClaims != 0) + if (security.EffectivePrivileges != 0 || security.DelegablePrivileges != 0) return NTLSTATUS_ACCESS_DENIED; return NTLSTATUS_SUCCESS; } @@ -5081,11 +5080,12 @@ static KSTATUS PosixsrvSendReady(SYS_HANDLE controlEndpoint, SYS_HANDLE publicLi message.Operation = NTL_SERVICE_CONTROL_READY; message.ExportCount = 1u; message.Status = NTLSTATUS_SUCCESS; + if (!NtlServiceControlNameExport(&message, 0u, "public")) return NTLSTATUS_FAILURE; SYS_PORT_HANDLE_TRANSFER transfer; memset(&transfer, 0, sizeof(transfer)); transfer.Handle = publicListener; - transfer.DesiredRights = SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT; + transfer.DesiredRights = SYS_RIGHT_DUPLICATE | SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT; transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE; SYS_SEND_PORT_REQUEST send; @@ -5122,8 +5122,7 @@ static void PosixsrvControlLoop(SYS_HANDLE endpoint) { if (status == NTLSTATUS_PORT_DISCONNECTED || !KSUCCESS(status)) return; if (info.Type != SYS_PORT_MESSAGE_NOTIFICATION || info.HandleCount != 0 || info.PayloadLength != sizeof(message) || message.Size != sizeof(message) || - message.Version != NTL_SERVICE_CONTROL_VERSION || message.ExportCount != 0 || - message.Reserved[0] != 0 || message.Reserved[1] != 0 || message.Reserved[2] != 0) + !NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_STOP, 0u)) continue; if (message.Operation == NTL_SERVICE_CONTROL_STOP) return; } @@ -5141,8 +5140,9 @@ void _start(void) { status = SysCreateMutex(&gProcessRegistryMutex); if (!KSUCCESS(status)) PosixsrvExit(3); - SYS_HANDLE controlListener = SysGetStartupCapability(SYS_STARTUP_CAP_SERVICE_CONTROL); - if (controlListener == SYS_HANDLE_INVALID) PosixsrvExit(4); + SYS_HANDLE controlListener = SysGetStartupCapability("service.control"); + SYS_HANDLE publicationListener = SysGetStartupCapability("rssrv.posix-publisher"); + if (controlListener == SYS_HANDLE_INVALID || publicationListener == SYS_HANDLE_INVALID) PosixsrvExit(4); SYS_HANDLE publicListener = SYS_HANDLE_INVALID; status = PosixsrvCreateListener(&publicListener); @@ -5163,10 +5163,21 @@ void _start(void) { status = SysCreateThread(&create); if (!KSUCCESS(status)) PosixsrvExit(7); + SYS_HANDLE publication = SYS_HANDLE_INVALID; + status = NtlResourceConnectListener(publicationListener, &publication); + (void)SysCloseHandle(publicationListener); + if (!KSUCCESS(status)) PosixsrvExit(8); + status = NtlResourceBind(publication, SYS_SV_LITERAL(NTL_POSIX_SERVICE_PATH), + NTL_RESOURCE_KIND_CAPABILITY, publicListener, + SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT, + SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT); + if (!KSUCCESS(status)) PosixsrvExit(8); status = PosixsrvSendReady(controlEndpoint, publicListener); if (!KSUCCESS(status)) PosixsrvExit(8); PosixsrvControlLoop(controlEndpoint); + (void)NtlResourceUnbind(publication, SYS_SV_LITERAL(NTL_POSIX_SERVICE_PATH)); + (void)NtlResourceClose(publication); (void)SysDisconnectPort(controlEndpoint); (void)SysCloseHandle(controlEndpoint); (void)SysCloseHandle(publicListener); diff --git a/code/user/system/rssrv/console.c b/code/user/system/rssrv/console.c index 1a1d765..f584202 100644 --- a/code/user/system/rssrv/console.c +++ b/code/user/system/rssrv/console.c @@ -112,6 +112,37 @@ static KSTATUS RssrvConsoleWrite(RSSRV_CONSOLE_STATE* console, return status; } +// Writes one positioned status line, retaining the console cursor under the +// same RSSRV session lock so ordinary writers keep their cursor position. +static KSTATUS RssrvConsoleWriteAt(RSSRV_CONSOLE_STATE* console, + int32_t x, int32_t y, const char* data, + uint32_t length, uint32_t* writtenOut) { + if (writtenOut) *writtenOut = 0; + if (!console || !data || x < 0 || y < 0 || length > NTL_CONSOLE_WRITE_MAX - 8u) + return NTLSTATUS_INVALID_PARAMETER; + for (uint32_t i = 0; i < length; ++i) + if ((uint8_t)data[i] > 0x7fu || (uint8_t)data[i] < 0x20u) + return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = RssrvConsoleLock(console); + if (!KSUCCESS(status)) return status; + SYS_CONSOLE_INFO original; + memset(&original, 0, sizeof(original)); + status = RssrvDisplayQueryConsoleInfo(console->Display, &original); + if (KSUCCESS(status)) + status = RssrvDisplaySetConsoleCursor(console->Display, x, y); + if (KSUCCESS(status)) { + status = RssrvDisplayWriteConsole(console->Display, data, length); + if (KSUCCESS(status) && length) RssrvConsoleChangedLocked(console); + KSTATUS restore = RssrvDisplaySetConsoleCursor(console->Display, + (int32_t)original.CursorX, + (int32_t)original.CursorY); + if (KSUCCESS(status)) status = restore; + } + RssrvConsoleUnlock(console); + if (KSUCCESS(status) && writtenOut) *writtenOut = length; + return status; +} + // Queries dimensions from the backing display and snapshots RSSRV's console generation. static KSTATUS RssrvConsoleQueryInfo(RSSRV_CONSOLE_STATE* console, NTL_CONSOLE_INFO* infoOut) { if (!console || !infoOut) return NTLSTATUS_INVALID_PARAMETER; @@ -297,6 +328,18 @@ static void RssrvConsoleSessionThread(uintptr_t argument) { (void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation, status, written, 0, 0); break; + case NTL_CONSOLE_OP_WRITE_AT: + if (request.Data.WriteAt.Length > NTL_CONSOLE_WRITE_MAX - 8u) { + status = NTLSTATUS_INVALID_PARAMETER; + } else { + status = RssrvConsoleWriteAt(console, request.Data.WriteAt.X, + request.Data.WriteAt.Y, + request.Data.WriteAt.Data, + request.Data.WriteAt.Length, &written); + } + (void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation, + status, written, 0, 0); + break; case NTL_CONSOLE_OP_QUERY_INFO: status = RssrvConsoleQueryInfo(console, &consoleInfo); (void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation, diff --git a/code/user/system/rssrv/publication_scope.h b/code/user/system/rssrv/publication_scope.h new file mode 100644 index 0000000..4f03b24 --- /dev/null +++ b/code/user/system/rssrv/publication_scope.h @@ -0,0 +1,17 @@ +// Exact-path namespace publication policy. A scoped capability can bind or unbind one name. + +#pragma once + +#include +#include +#include +#include +#include + +static inline bool RssrvScopeAllows(const char* scope, uint32_t operation, + const char* path, uint32_t pathLength) { + if (!scope) return true; // Unscoped administrator; separately issued by RSSRV. + return path && + (operation == NTL_RESOURCE_OP_BIND || operation == NTL_RESOURCE_OP_UNBIND) && + strlen(scope) == (size_t)pathLength && memcmp(scope, path, pathLength) == 0; +} diff --git a/code/user/system/rssrv/resource_service.c b/code/user/system/rssrv/resource_service.c index 3b91501..18f9897 100644 --- a/code/user/system/rssrv/resource_service.c +++ b/code/user/system/rssrv/resource_service.c @@ -2,6 +2,7 @@ #include #include "rssrv_internal.h" +#include "publication_scope.h" #include #include @@ -9,6 +10,7 @@ typedef struct RSSRV_SESSION { RSSRV_RESOURCE_SERVICE* Service; SYS_HANDLE Endpoint; bool Administrative; + const char* ScopePath; } RSSRV_SESSION; // Replies with one fixed resource-protocol reply and no handle transfer. @@ -248,10 +250,16 @@ static KSTATUS RssrvHandleBind(RSSRV_RESOURCE_SERVICE* service, return KSUCCESS(replyStatus) ? bindStatus : replyStatus; } +// Allocates one durable, exact-path publication listener owned by RSSRV. +static KSTATUS RssrvCreatePublisher(RSSRV_RESOURCE_SERVICE* service, + const char* path, uint32_t length, + SYS_HANDLE* listenerOut); + // Dispatches one received resource request and returns whether a received handle was consumed. static uint32_t RssrvDispatchRequest(RSSRV_RESOURCE_SERVICE* service, SYS_HANDLE endpoint, uint32_t administrative, + const char* scopePath, const uint8_t* payload, uint32_t payloadLength, const SYS_PORT_MESSAGE_INFO* info, @@ -281,6 +289,53 @@ static uint32_t RssrvDispatchRequest(RSSRV_RESOURCE_SERVICE* service, return 0; } + // A scoped listener can only publish or withdraw its exact delegated path. + if (!RssrvScopeAllows(scopePath, operation, path, request.PathLength)) { + (void)RssrvReplyStatus(endpoint, info->MessageId, operation, + NTLSTATUS_ACCESS_DENIED, NTL_RESOURCE_KIND_INVALID); + return 0; + } + + if (operation == NTL_RESOURCE_OP_CREATE_PUBLISHER) { + if (!administrative || scopePath || request.Flags != NTL_RESOURCE_FLAG_NONE || + request.AuxLength || info->HandleCount || request.Rights || request.ExportRights || + request.Kind != NTL_RESOURCE_KIND_INVALID || + RssrvVolumesOwnPath(path, request.PathLength) || request.PathLength > RSSRV_PUBLICATION_PATH_MAX) { + (void)RssrvReplyStatus(endpoint, info->MessageId, operation, + NTLSTATUS_ACCESS_DENIED, NTL_RESOURCE_KIND_INVALID); + return 0; + } + SYS_HANDLE publisher = SYS_HANDLE_INVALID; + status = RssrvCreatePublisher(service, path, request.PathLength, &publisher); + if (!KSUCCESS(status)) { + (void)RssrvReplyStatus(endpoint, info->MessageId, operation, + status, NTL_RESOURCE_KIND_INVALID); + return 0; + } + NTL_RESOURCE_REPLY reply = {0}; + reply.Size = sizeof(reply); + reply.Version = NTL_RESOURCE_PROTOCOL_VERSION; + reply.Operation = operation; + reply.Status = NTLSTATUS_SUCCESS; + SYS_PORT_HANDLE_TRANSFER transfer = {0}; + transfer.Handle = publisher; + transfer.DesiredRights = SYS_RIGHT_QUERY | SYS_RIGHT_DUPLICATE | + SYS_RIGHT_TRANSFER | SYS_PORT_RIGHT_CONNECT; + SYS_REPLY_PORT_REQUEST out = {0}; + out.Size = sizeof(out); + out.EndpointHandle = endpoint; + out.MessageId = info->MessageId; + out.Payload = &reply; + out.PayloadLength = sizeof(reply); + out.Handles = &transfer; + out.HandleCount = 1u; + status = SysReplyPort(&out); + if (!KSUCCESS(status)) + (void)RssrvReplyStatus(endpoint, info->MessageId, operation, + status, NTL_RESOURCE_KIND_INVALID); + return 0; + } + switch (operation) { case NTL_RESOURCE_OP_LOOKUP: if (request.Flags != NTL_RESOURCE_FLAG_NONE || request.AuxLength != 0 || info->HandleCount != 0 || @@ -408,6 +463,7 @@ static void RssrvSessionThread(uintptr_t argument) { RSSRV_RESOURCE_SERVICE* service = session->Service; SYS_HANDLE endpoint = session->Endpoint; uint32_t administrative = session->Administrative; + const char* scopePath = session->ScopePath; RssrvSetCurrentThreadName(administrative ? "rssrv.session.admin" : "rssrv.session.public"); free(session); @@ -440,6 +496,7 @@ static void RssrvSessionThread(uintptr_t argument) { consumed = RssrvDispatchRequest(service, endpoint, administrative, + scopePath, payload, info.PayloadLength, &info, @@ -449,6 +506,10 @@ static void RssrvSessionThread(uintptr_t argument) { } } + // A scoped publisher owns the binding for the lifetime of its connected + // session. Release it on abrupt disconnect as well as graceful unbind. + if (scopePath) + (void)RssrvNamespaceUnbind(service->Namespace, scopePath, (uint32_t)strlen(scopePath)); (void)SysCloseHandle(endpoint); SysExitThread(0); } @@ -483,6 +544,7 @@ static void RssrvListenerThread(uintptr_t argument) { session->Service = worker->Service; session->Endpoint = endpoint; session->Administrative = worker->Administrative; + session->ScopePath = worker->ScopePath[0] ? worker->ScopePath : NULL; SYS_CREATE_THREAD_REQUEST create; memset(&create, 0, sizeof(create)); @@ -532,6 +594,53 @@ static KSTATUS RssrvStartListenerWorker(RSSRV_RESOURCE_SERVICE* service, return SysCreateThread(&create); } +// Reuses a per-path authority across service generations and never broadens its scope. +static KSTATUS RssrvCreatePublisher(RSSRV_RESOURCE_SERVICE* service, + const char* path, uint32_t length, + SYS_HANDLE* listenerOut) { + if (!service || !path || !listenerOut || !length || + length > RSSRV_PUBLICATION_PATH_MAX) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = SysWait(service->ScopeMutex, SYS_WAIT_FLAG_NONE); + if (!KSUCCESS(status)) return status; + for (uint32_t i = 0; i < service->ScopeCount; ++i) { + RSSRV_LISTENER_WORKER* worker = &service->ScopeWorkers[i]; + if (strlen(worker->ScopePath) == length && + memcmp(worker->ScopePath, path, length) == 0) { + *listenerOut = worker->Listener; + (void)SysReleaseMutex(service->ScopeMutex); + return NTLSTATUS_SUCCESS; + } + } + if (service->ScopeCount >= RSSRV_PUBLICATION_SCOPE_MAX) { + (void)SysReleaseMutex(service->ScopeMutex); + return NTLSTATUS_LIMIT_REACHED; + } + RSSRV_LISTENER_WORKER* worker = &service->ScopeWorkers[service->ScopeCount]; + memset(worker, 0, sizeof(*worker)); + memcpy(worker->ScopePath, path, length); + worker->ScopePath[length] = '\0'; + status = RssrvCreateListener(&worker->Listener); + if (KSUCCESS(status)) + status = RssrvStartListenerWorker(service, worker, worker->Listener, 1u); + if (!KSUCCESS(status)) { + if (worker->Listener != SYS_HANDLE_INVALID) (void)SysCloseHandle(worker->Listener); + memset(worker, 0, sizeof(*worker)); + (void)SysReleaseMutex(service->ScopeMutex); + return status; + } + ++service->ScopeCount; + *listenerOut = worker->Listener; + (void)SysReleaseMutex(service->ScopeMutex); + return NTLSTATUS_SUCCESS; +} + +// Issues a publisher restricted to one RSSRV-owned namespace path. +KSTATUS RssrvResourceCreatePublisher(RSSRV_RESOURCE_SERVICE* service, + const char* path, SYS_HANDLE* listenerOut) { + if (!path) return NTLSTATUS_INVALID_PARAMETER; + return RssrvCreatePublisher(service, path, (uint32_t)strlen(path), listenerOut); +} + // Initializes the public/admin resource protocol component around existing namespace state. KSTATUS RssrvResourceInitialize(RSSRV_RESOURCE_SERVICE* service, RSSRV_NAMESPACE* namespaceState) { @@ -539,6 +648,8 @@ KSTATUS RssrvResourceInitialize(RSSRV_RESOURCE_SERVICE* service, return NTLSTATUS_INVALID_PARAMETER; memset(service, 0, sizeof(*service)); service->Namespace = namespaceState; + KSTATUS lockStatus = SysCreateMutex(&service->ScopeMutex); + if (!KSUCCESS(lockStatus)) return lockStatus; service->PublicListener = SYS_HANDLE_INVALID; service->AdminListener = SYS_HANDLE_INVALID; service->PublicWorker.Listener = SYS_HANDLE_INVALID; diff --git a/code/user/system/rssrv/rssrv.c b/code/user/system/rssrv/rssrv.c index 5323f4f..fa33332 100644 --- a/code/user/system/rssrv/rssrv.c +++ b/code/user/system/rssrv/rssrv.c @@ -2,10 +2,13 @@ #include "rssrv_internal.h" #include +#include +#include #include #include #include #include +#include #include static RSSRV_STATE gRssrv; @@ -37,12 +40,14 @@ static KSTATUS RssrvAcceptControl(SYS_HANDLE controlListener, SYS_HANDLE* endpoi return SysAcceptPort(&accept); } -// Sends service readiness and delegates init-only namespace and DWM-session bootstrap capabilities. +// Exports only the capabilities consumed by dependent services. Init treats +// the names as opaque; RSSRV owns the underlying namespace policies. static KSTATUS RssrvSendReady(SYS_HANDLE controlEndpoint, - const RSSRV_RESOURCE_SERVICE* resource, - const RSSRV_SESSION_STATE* session) { - if (!resource || !session || resource->AdminListener == SYS_HANDLE_INVALID || - session->DwmListener == SYS_HANDLE_INVALID) + const RSSRV_SESSION_STATE* session, + SYS_HANDLE desktopPublisher, + SYS_HANDLE posixPublisher) { + if (!session || session->DwmListener == SYS_HANDLE_INVALID || + desktopPublisher == SYS_HANDLE_INVALID || posixPublisher == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_PARAMETER; NTL_SERVICE_CONTROL_MESSAGE message; @@ -50,18 +55,23 @@ static KSTATUS RssrvSendReady(SYS_HANDLE controlEndpoint, message.Size = sizeof(message); message.Version = NTL_SERVICE_CONTROL_VERSION; message.Operation = NTL_SERVICE_CONTROL_READY; - message.ExportCount = 2u; + message.ExportCount = 3u; message.Status = NTLSTATUS_SUCCESS; + if (!NtlServiceControlNameExport(&message, 0u, "session-control") || + !NtlServiceControlNameExport(&message, 1u, "desktop-publisher") || + !NtlServiceControlNameExport(&message, 2u, "posix-publisher")) + return NTLSTATUS_FAILURE; - SYS_PORT_HANDLE_TRANSFER transfers[2]; + SYS_PORT_HANDLE_TRANSFER transfers[3]; memset(transfers, 0, sizeof(transfers)); - transfers[0].Handle = resource->AdminListener; - transfers[0].DesiredRights = SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT; - transfers[0].Flags = SYS_PORT_TRANSFER_FLAG_NONE; - transfers[1].Handle = session->DwmListener; - // Init must transfer this private listener once more into DWM. DWM itself receives only connect/query rights. - transfers[1].DesiredRights = SYS_RIGHT_DUPLICATE | SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT; - transfers[1].Flags = SYS_PORT_TRANSFER_FLAG_NONE; + transfers[0].Handle = session->DwmListener; + transfers[1].Handle = desktopPublisher; + transfers[2].Handle = posixPublisher; + for (uint32_t i = 0; i < 3u; ++i) { + transfers[i].DesiredRights = SYS_RIGHT_DUPLICATE | SYS_RIGHT_TRANSFER | + SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT; + transfers[i].Flags = SYS_PORT_TRANSFER_FLAG_NONE; + } SYS_SEND_PORT_REQUEST send; memset(&send, 0, sizeof(send)); @@ -71,7 +81,7 @@ static KSTATUS RssrvSendReady(SYS_HANDLE controlEndpoint, send.Payload = &message; send.PayloadLength = sizeof(message); send.Handles = transfers; - send.HandleCount = 2u; + send.HandleCount = 3u; send.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE; return SysSendPort(&send); } @@ -99,8 +109,7 @@ static void RssrvControlLoop(SYS_HANDLE controlEndpoint) { return; if (info.Type != SYS_PORT_MESSAGE_NOTIFICATION || info.HandleCount != 0 || info.PayloadLength != sizeof(message) || message.Size != sizeof(message) || - message.Version != NTL_SERVICE_CONTROL_VERSION || message.ExportCount != 0 || - message.Reserved[0] != 0 || message.Reserved[1] != 0 || message.Reserved[2] != 0) + !NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_STOP, 0u)) continue; if (message.Operation == NTL_SERVICE_CONTROL_STOP) return; @@ -154,19 +163,19 @@ void _start(void) { memset(&security, 0, sizeof(security)); security.Size = sizeof(security); status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &security); - if (!KSUCCESS(status) || security.EffectivePrivileges != SYS_PRIV_SYSTEM_SERVICE_REGISTER || - security.DelegablePrivileges != 0 || security.ServiceClaims != SYS_SERVICE_CLAIM_RESOURCE || - security.DelegableServiceClaims != 0) + if (!KSUCCESS(status) || security.EffectivePrivileges != 0 || + security.DelegablePrivileges != 0) RssrvExit(2); - SYS_HANDLE systemVolume = SysGetStartupCapability(SYS_STARTUP_CAP_SYSTEM_VOLUME_NAMESPACE); - SYS_HANDLE controlListener = SysGetStartupCapability(SYS_STARTUP_CAP_SERVICE_CONTROL); - SYS_HANDLE volumeManager = SysGetStartupCapability(SYS_STARTUP_CAP_VOLUME_MANAGER); - SYS_HANDLE inputManager = SysGetStartupCapability(SYS_STARTUP_CAP_INPUT_MANAGER); - SYS_HANDLE displayManager = SysGetStartupCapability(SYS_STARTUP_CAP_DISPLAY_MANAGER); + SYS_HANDLE systemVolume = SysGetStartupCapability(NTL_BOOT_CAP_NAMESPACE_ROOT); + SYS_HANDLE controlListener = SysGetStartupCapability("service.control"); + SYS_HANDLE volumeManager = SysGetStartupCapability(NTL_BOOT_CAP_VOLUME_MANAGER); + SYS_HANDLE inputManager = SysGetStartupCapability(NTL_BOOT_CAP_INPUT_MANAGER); + SYS_HANDLE displayManager = SysGetStartupCapability(NTL_BOOT_CAP_DISPLAY_POLICY); + SYS_HANDLE registrationAuthority = SysGetStartupCapability(NTL_BOOT_CAP_RESOURCE_REGISTRATION); if (systemVolume == SYS_HANDLE_INVALID || controlListener == SYS_HANDLE_INVALID || volumeManager == SYS_HANDLE_INVALID || inputManager == SYS_HANDLE_INVALID || - displayManager == SYS_HANDLE_INVALID) + displayManager == SYS_HANDLE_INVALID || registrationAuthority == SYS_HANDLE_INVALID) RssrvExit(3); uint32_t systemVolumeId = 0; @@ -232,13 +241,22 @@ void _start(void) { status = RssrvVolumesStartWorker(&gRssrv.Volumes); if (!KSUCCESS(status)) RssrvExit(11); - status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, gRssrv.Resource.PublicListener); + status = SysRegisterSystemService(registrationAuthority, gRssrv.Resource.PublicListener); if (!KSUCCESS(status)) RssrvExit(12); - status = RssrvSendReady(controlEndpoint, &gRssrv.Resource, &gRssrv.Session); + SYS_HANDLE desktopPublisher = SYS_HANDLE_INVALID; + SYS_HANDLE posixPublisher = SYS_HANDLE_INVALID; + status = RssrvResourceCreatePublisher(&gRssrv.Resource, NTL_DESKTOP_SERVICE_PATH, + &desktopPublisher); + if (!KSUCCESS(status)) RssrvExit(13); + status = RssrvResourceCreatePublisher(&gRssrv.Resource, NTL_POSIX_SERVICE_PATH, + &posixPublisher); + if (!KSUCCESS(status)) RssrvExit(13); + status = RssrvSendReady(controlEndpoint, &gRssrv.Session, + desktopPublisher, posixPublisher); if (!KSUCCESS(status)) RssrvExit(13); RssrvControlLoop(controlEndpoint); - (void)SysUnregisterSystemService(SYS_SERVICE_RESOURCE, gRssrv.Resource.PublicListener); + (void)SysUnregisterSystemService(registrationAuthority, gRssrv.Resource.PublicListener); (void)SysDisconnectPort(controlEndpoint); (void)SysCloseHandle(controlEndpoint); RssrvExit(0); diff --git a/code/user/system/rssrv/rssrv_internal.h b/code/user/system/rssrv/rssrv_internal.h index 350e3ae..3c15d31 100644 --- a/code/user/system/rssrv/rssrv_internal.h +++ b/code/user/system/rssrv/rssrv_internal.h @@ -10,6 +10,8 @@ #include #define RSSRV_LISTENER_BACKLOG 16u +#define RSSRV_PUBLICATION_SCOPE_MAX 32u +#define RSSRV_PUBLICATION_PATH_MAX 127u #define RSSRV_SYMBOLIC_LINK_MAX_DEPTH 16u #define RSSRV_VOLUME_PATH_CAPACITY 48u #define RSSRV_INPUT_KEY_QUEUE_SIZE 256u @@ -137,6 +139,7 @@ typedef struct RSSRV_LISTENER_WORKER { RSSRV_RESOURCE_SERVICE* Service; SYS_HANDLE Listener; bool Administrative; + char ScopePath[RSSRV_PUBLICATION_PATH_MAX + 1u]; } RSSRV_LISTENER_WORKER; struct RSSRV_RESOURCE_SERVICE { @@ -145,6 +148,9 @@ struct RSSRV_RESOURCE_SERVICE { SYS_HANDLE AdminListener; RSSRV_LISTENER_WORKER PublicWorker; RSSRV_LISTENER_WORKER AdminWorker; + SYS_HANDLE ScopeMutex; + uint32_t ScopeCount; + RSSRV_LISTENER_WORKER ScopeWorkers[RSSRV_PUBLICATION_SCOPE_MAX]; }; typedef struct RSSRV_STATE { @@ -305,3 +311,5 @@ KSTATUS RssrvResourceInitialize(RSSRV_RESOURCE_SERVICE* service, // Creates the resource listeners and starts their accept workers. KSTATUS RssrvResourceStart(RSSRV_RESOURCE_SERVICE* service); +KSTATUS RssrvResourceCreatePublisher(RSSRV_RESOURCE_SERVICE* service, + const char* path, SYS_HANDLE* listenerOut); 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..374bf4f --- /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 = { sizeof(SYS_WRITE_CONSOLE_REQUEST), 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 = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current test process. +static void exit_now(int code) { + while (true) 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), NTLSTATUS_INVALID_PARAMETER); + expect_status("is key down null", SysIsKeyDown(INPUT_KEY_A, (bool*)0), NTLSTATUS_INVALID_PARAMETER); + expect_status("read key null", SysReadKey((INPUT_KEY*)0), NTLSTATUS_INVALID_PARAMETER); + expect_status("read char null", SysReadKeyboardChar((char*)0), NTLSTATUS_INVALID_PARAMETER); + + bool down = true; + uint32_t status = SysIsKeyDown(INPUT_KEY_A, &down); + if (status != NTLSTATUS_SUCCESS) + fail_status("is key down", status); + + INPUT_KEY key = INPUT_KEY_A; + status = SysPollKeyboard(&key); + if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_WOULD_BLOCK) + fail_status("poll keyboard", status); + if (status == NTLSTATUS_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), NTLSTATUS_INVALID_PARAMETER); + status = SysPeekInputEvent(&inputReq); + if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_WOULD_BLOCK) + fail_status("peek input", status); + + INPUT_MOUSE_EVENT mouse; + expect_status("peek mouse null", SysPeekMouse((INPUT_MOUSE_EVENT*)0), NTLSTATUS_INVALID_PARAMETER); + expect_status("read mouse null", SysReadMouse((INPUT_MOUSE_EVENT*)0), NTLSTATUS_INVALID_PARAMETER); + + status = SysPeekMouse(&mouse); + if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_WOULD_BLOCK) + fail_status("peek mouse", status); + + uint32_t readStatus = SysReadMouse(&mouse); + if (status == NTLSTATUS_SUCCESS) { + if (readStatus != NTLSTATUS_SUCCESS) + fail_status("read mouse after peek", readStatus); + } else if (readStatus != NTLSTATUS_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), NTLSTATUS_INVALID_PARAMETER); + expect_status("get mouse pos null y", SysGetMousePos(&x, (int*)0), NTLSTATUS_INVALID_PARAMETER); + status = SysGetMousePos(&x, &y); + if (status != NTLSTATUS_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..ec46fb2 --- /dev/null +++ b/code/user/tests/input_model_smoke/input_model_smoke.c @@ -0,0 +1,135 @@ +// 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 = { sizeof(SYS_WRITE_CONSOLE_REQUEST), 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 = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current test process. +static void exit_now(int code) { + while (true) 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), NTLSTATUS_INVALID_PARAMETER); + expect_status("open input queue bad flags", SysOpenInputQueue(1, &inputQueue), NTLSTATUS_INVALID_PARAMETER); + + uint32_t status = SysOpenInputQueue(SYS_INPUT_QUEUE_FLAG_NONE, &inputQueue); + if (status != NTLSTATUS_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), NTLSTATUS_INVALID_PARAMETER); + + badReq = req; + badReq.RecordOut = (INPUT_KEY_RECORD*)0; + expect_status("peek input null out", SysPeekInputEvent(&badReq), NTLSTATUS_INVALID_PARAMETER); + + badReq = req; + badReq.QueueHandle = 0xdeadbeefu; + expect_status("read input bad handle", SysReadInputEvent(&badReq), NTLSTATUS_INVALID_HANDLE); + + SYS_GLOBAL_INPUT_STATE state; + state.Size = sizeof(state); + state.Flags = SYS_GLOBAL_INPUT_STATE_FLAG_NONE; + expect_status("query global input null", SysQueryGlobalInputState((SYS_GLOBAL_INPUT_STATE*)0), NTLSTATUS_INVALID_PARAMETER); + + SYS_GLOBAL_INPUT_STATE badState = state; + badState.Size = 0; + expect_status("query global input bad size", SysQueryGlobalInputState(&badState), NTLSTATUS_INVALID_PARAMETER); + + badState = state; + badState.Flags = 1; + expect_status("query global input bad flags", SysQueryGlobalInputState(&badState), NTLSTATUS_INVALID_PARAMETER); + + status = SysQueryGlobalInputState(&state); + if (status != NTLSTATUS_SUCCESS) + fail_status("query global input", status); + if (state.Size != sizeof(state)) + fail_status("query global input size", state.Size); + if (state.Flags != SYS_GLOBAL_INPUT_STATE_FLAG_NONE) + fail_status("query global input flags", state.Flags); + + status = SysPeekInputEvent(&req); + if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_WOULD_BLOCK) + fail_status("peek input immediate", status); + + uint32_t waitStatus = SysWaitSingle(inputQueue, SYS_WAIT_FLAG_NONE, 0); + if (status == NTLSTATUS_SUCCESS) { + if (waitStatus != NTLSTATUS_SUCCESS) + fail_status("wait input after readable peek", waitStatus); + } else if (waitStatus != NTLSTATUS_TIMEOUT) { + fail_status("wait empty input immediate", waitStatus); + } + + req.TimeoutMilliseconds = 1; + status = SysReadInputEvent(&req); + if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_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/named_queue_lifetime_smoke/named_queue_lifetime_smoke.c b/code/user/tests/named_queue_lifetime_smoke/named_queue_lifetime_smoke.c new file mode 100644 index 0000000..d03668e --- /dev/null +++ b/code/user/tests/named_queue_lifetime_smoke/named_queue_lifetime_smoke.c @@ -0,0 +1,197 @@ +// Native named queue lifetime smoke test. +// Verifies explicit name withdrawal and automatic withdrawal on owner handle close. + +#include +#include +#include + +#define TEST_MESSAGE_TYPE 0x71756575u +#define TEST_MESSAGE_VALUE 0x123456789abcdef0ull + +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 = { sizeof(SYS_WRITE_CONSOLE_REQUEST), 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 = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current test process. +static void exit_now(int code) { + while (true) SysExitProcess(code); +} + +// Fails the test with one plain message. +static void fail(const char* msg) { + print("named_queue_lifetime_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("named_queue_lifetime_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("named_queue_lifetime_smoke: "); + print(what); + print(" got="); + print_u64(got); + print(" expected="); + print_u64(expected); + print("\n"); + exit_now(1); +} + +// Publishes one queue under a short test name. +static uint32_t publish_queue(const char* name, uint32_t queue) { + SYS_PUBLISH_QUEUE_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.Name = SysSvFromCstr(name); + req.QueueHandle = queue; + return SysPublishQueue(&req); +} + +// Withdraws one named queue publication. +static uint32_t unpublish_queue(const char* name) { + SYS_UNPUBLISH_QUEUE_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.Name = SysSvFromCstr(name); + return SysUnpublishQueue(&req); +} + +// Opens one named queue publication. +static uint32_t open_named_queue(const char* name, uint32_t* out) { + SYS_OPEN_NAMED_QUEUE_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.Name = SysSvFromCstr(name); + req.HandleOut = out; + return SysOpenNamedQueue(&req); +} + +// Sends one fixed-size queue message. +static uint32_t send_message(uint32_t queue, const TEST_QUEUE_MESSAGE* message) { + SYS_QUEUE_SEND_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.QueueHandle = queue; + req.Flags = SYS_QUEUE_FLAG_NONE; + req.TimeoutMilliseconds = 0; + req.Message = message; + return SysSendQueue(&req); +} + +// Receives one fixed-size queue message. +static uint32_t recv_message(uint32_t queue, TEST_QUEUE_MESSAGE* message) { + SYS_QUEUE_RECV_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.QueueHandle = queue; + req.Flags = SYS_QUEUE_FLAG_NONE; + req.TimeoutMilliseconds = 0; + req.MessageOut = message; + return SysRecvQueue(&req); +} + +// Verifies explicit unpublish removes only the name, not existing handles. +static void check_explicit_unpublish(void) { + uint32_t queue = 0; + uint32_t opened = 0; + uint32_t status = SysCreateQueue(&queue); + if (status != NTLSTATUS_SUCCESS) fail_status("create queue", status); + + status = publish_queue("ntl.queue.explicit", queue); + if (status != NTLSTATUS_SUCCESS) fail_status("publish queue", status); + + status = open_named_queue("ntl.queue.explicit", &opened); + if (status != NTLSTATUS_SUCCESS) fail_status("open published queue", status); + if (!opened) fail("opened handle is zero"); + + status = unpublish_queue("ntl.queue.explicit"); + if (status != NTLSTATUS_SUCCESS) fail_status("unpublish queue", status); + + uint32_t missing = 0; + status = open_named_queue("ntl.queue.explicit", &missing); + expect_status("open withdrawn name", status, NTLSTATUS_NOT_FOUND); + + TEST_QUEUE_MESSAGE out; + TEST_QUEUE_MESSAGE in; + memset(&out, 0, sizeof(out)); + memset(&in, 0, sizeof(in)); + out.Type = TEST_MESSAGE_TYPE; + out.Param[0] = TEST_MESSAGE_VALUE; + status = send_message(queue, &out); + if (status != NTLSTATUS_SUCCESS) fail_status("send after unpublish", status); + status = recv_message(opened, &in); + if (status != NTLSTATUS_SUCCESS) fail_status("recv after unpublish", status); + if (in.Type != TEST_MESSAGE_TYPE || in.Param[0] != TEST_MESSAGE_VALUE) + fail("message mismatch after unpublish"); + + SysCloseHandle(opened); + SysCloseHandle(queue); +} + +// Verifies closing the publishing handle withdraws its names. +static void check_close_withdraws_name(void) { + uint32_t queue = 0; + uint32_t status = SysCreateQueue(&queue); + if (status != NTLSTATUS_SUCCESS) fail_status("create close queue", status); + + status = publish_queue("ntl.queue.close", queue); + if (status != NTLSTATUS_SUCCESS) fail_status("publish close queue", status); + + SysCloseHandle(queue); + + uint32_t opened = 0; + status = open_named_queue("ntl.queue.close", &opened); + expect_status("open close-withdrawn name", status, NTLSTATUS_NOT_FOUND); +} + +// Runs the named queue lifetime smoke test. +int main(void) { + print("named_queue_lifetime_smoke: start\n"); + check_explicit_unpublish(); + check_close_withdraws_name(); + print("named_queue_lifetime_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/port_ipc_smoke/port_ipc_smoke.c b/code/user/tests/port_ipc_smoke/port_ipc_smoke.c index 27e76c2..0756698 100644 --- a/code/user/tests/port_ipc_smoke/port_ipc_smoke.c +++ b/code/user/tests/port_ipc_smoke/port_ipc_smoke.c @@ -464,77 +464,21 @@ static int run_local_transport_test(void) { return 0; } -// Verifies that registration privilege alone cannot publish a claimed well-known service. -static int run_claimless_client(void) { - SYS_SECURITY_CONTEXT_INFO contextInfo; - memset(&contextInfo, 0, sizeof(contextInfo)); - contextInfo.Size = sizeof(contextInfo); - KSTATUS status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &contextInfo); - if (!KSUCCESS(status)) return 10; - if (contextInfo.EffectivePrivileges != SYS_PRIV_SYSTEM_SERVICE_REGISTER || - contextInfo.ServiceClaims != 0) - return 11; - - SYS_HANDLE listener = SYS_HANDLE_INVALID; - status = create_listener(1, &listener); - if (!KSUCCESS(status)) return 12; - status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, listener); - (void)SysCloseHandle(listener); - return status == NTLSTATUS_ACCESS_DENIED ? 0 : 13; -} - -// Verifies that the live resource service owns its well-known registry slot and remains connectable. +// Verifies that unrelated handles cannot authorize numeric service publication. static int run_service_test(void) { SYS_HANDLE listener = SYS_HANDLE_INVALID; KSTATUS status = create_listener(1, &listener); if (!KSUCCESS(status)) return fail("create service probe listener", status); - status = SysRegisterSystemService(SYS_SERVICE_MAX + 1u, listener); - if (status != NTLSTATUS_INVALID_PARAMETER) - return fail("invalid service id", status); + status = SysRegisterSystemService(SYS_HANDLE_INVALID, listener); + if (status != NTLSTATUS_INVALID_HANDLE) + return fail("missing registration capability", status); - status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, listener); - if (status != NTLSTATUS_ALREADY_EXISTS) - return fail("resource service must already be registered", status); + status = SysRegisterSystemService(listener, listener); + if (KSUCCESS(status)) return fail("listener cannot impersonate registry authority", status); - SYS_HANDLE claimlessContext = SYS_HANDLE_INVALID; - SYS_DERIVE_SECURITY_CONTEXT_REQUEST deriveContext; - memset(&deriveContext, 0, sizeof(deriveContext)); - deriveContext.Size = sizeof(deriveContext); - deriveContext.SourceContext = SYS_SECURITY_CONTEXT_CURRENT; - deriveContext.EffectivePrivileges = SYS_PRIV_SYSTEM_SERVICE_REGISTER; - deriveContext.ContextHandleOut = &claimlessContext; - status = SysDeriveSecurityContext(&deriveContext); - if (!KSUCCESS(status)) return fail("derive claimless service context", status); - - SYS_PEB* peb = SysGetPeb(); - if (!peb || !peb->ImagePath.Data || peb->ImagePath.Length == 0) - return fail("missing image path", NTLSTATUS_FAILURE); - - SYS_HANDLE claimlessChild = SYS_HANDLE_INVALID; - NTL_CREATE_SYSTEM_PROCESS_REQUEST claimlessCreate; - memset(&claimlessCreate, 0, sizeof(claimlessCreate)); - claimlessCreate.Size = sizeof(claimlessCreate); - claimlessCreate.Flags = NTL_PROCESS_LOAD_FLAG_NONE; - claimlessCreate.Path = peb->ImagePath; - claimlessCreate.CommandLine = SysSvFromCstr("claimless"); - claimlessCreate.SecurityContext = claimlessContext; - claimlessCreate.ResolveModuleName = NtlResolveSystemModuleName; - claimlessCreate.ProcessHandleOut = &claimlessChild; - status = NtlCreateSystemProcess(&claimlessCreate); - (void)SysCloseHandle(claimlessContext); - if (!KSUCCESS(status)) return fail("create claimless service client", status); - status = SysWait(claimlessChild, SYS_WAIT_FLAG_NONE); - if (!KSUCCESS(status)) return fail("wait claimless service client", status); - int32_t claimlessExit = -1; - status = SysGetProcessExitCode(claimlessChild, &claimlessExit); - (void)SysCloseHandle(claimlessChild); - if (!KSUCCESS(status)) return fail("query claimless service client", status); - if (claimlessExit != 0) return fail("service claim enforcement", (KSTATUS)claimlessExit); - - status = SysUnregisterSystemService(SYS_SERVICE_RESOURCE, listener); - if (status != NTLSTATUS_ACCESS_DENIED) - return fail("foreign listener cannot unregister service", status); + status = SysUnregisterSystemService(listener, listener); + if (KSUCCESS(status)) return fail("listener cannot revoke registry authority", status); SYS_HANDLE endpoint = SYS_HANDLE_INVALID; SYS_CONNECT_SYSTEM_SERVICE_REQUEST connect; @@ -550,14 +494,8 @@ static int run_service_test(void) { return 0; } -// Runs either the privileged parent or claimless child half. +// Runs the capability-scoped port and registry smoke tests. int main(void) { - SYS_PEB* peb = SysGetPeb(); - if (!peb) return 2; - if (peb->CommandLine.Length == 9 && - memcmp(peb->CommandLine.Data, "claimless", 9) == 0) - return run_claimless_client(); - print("port_ipc_smoke: start\n"); if (run_connection_lifetime_test() != 0) return 1; if (run_peer_process_test() != 0) return 1; diff --git a/code/user/tests/process_builder_smoke/process_builder_smoke.c b/code/user/tests/process_builder_smoke/process_builder_smoke.c index b623992..ed62603 100644 --- a/code/user/tests/process_builder_smoke/process_builder_smoke.c +++ b/code/user/tests/process_builder_smoke/process_builder_smoke.c @@ -9,7 +9,6 @@ #include #include -#define TEST_STARTUP_EVENT_TAG 0x50425445u #define TEST_STARTUP_WORD 0x31503550u // Exits without relying on a hosted runtime. @@ -58,7 +57,7 @@ static void verify_child_bootstrap(void) { // Runs the child half and signals the inherited parent event after validation. static void run_child(void) { - SYS_HANDLE event = SysGetStartupCapability(TEST_STARTUP_EVENT_TAG); + SYS_HANDLE event = SysGetStartupCapability("test.builder-event"); if (event == SYS_HANDLE_INVALID) exit_now(10); verify_child_bootstrap(); if (SysSignalEvent(event) != NTLSTATUS_SUCCESS) exit_now(11); @@ -88,7 +87,7 @@ static int run_parent(void) { NTL_PROCESS_STARTUP_CAPABILITY startupCapability; memset(&startupCapability, 0, sizeof(startupCapability)); - startupCapability.Tag = TEST_STARTUP_EVENT_TAG; + strcpy(startupCapability.Name, "test.builder-event"); startupCapability.SourceHandle = event; startupCapability.DesiredRights = SYS_EVENT_RIGHT_SIGNAL | SYS_RIGHT_QUERY; startupCapability.DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; diff --git a/code/user/tests/process_protection_smoke/process_protection_smoke.c b/code/user/tests/process_protection_smoke/process_protection_smoke.c index 4eb33d9..3d0bd40 100644 --- a/code/user/tests/process_protection_smoke/process_protection_smoke.c +++ b/code/user/tests/process_protection_smoke/process_protection_smoke.c @@ -2,10 +2,11 @@ // Verifies that kernel-owned processes remain queryable but cannot be terminated. #include +#include #include #include -#define MAX_PROCESSES 16u +#define MAX_PROCESSES 64u // Prints one NUL-terminated string to the console. static void print(const char* text) { @@ -81,10 +82,12 @@ void main(void) { int sawIdle = 0; int sawSystem = 0; + int sawInit = 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))) + int isInit = strcmp(process->Name, "system/init.exe") == 0; + if (!(process->Flags & (SYS_PROCESS_FLAG_IDLE | SYS_PROCESS_FLAG_SYSTEM)) && !isInit) continue; if (!(process->Flags & SYS_PROCESS_FLAG_TERMINATION_PROTECTED)) @@ -93,11 +96,19 @@ void main(void) { verify_protected_process(process); if (process->Flags & SYS_PROCESS_FLAG_IDLE) sawIdle = 1; if (process->Flags & SYS_PROCESS_FLAG_SYSTEM) sawSystem = 1; + if (isInit) sawInit = 1; } - if (!sawIdle || !sawSystem) + if (!sawIdle || !sawSystem || !sawInit) fail_status("missing kernel process", (uint32_t)((sawIdle ? 1u : 0u) | (sawSystem ? 2u : 0u))); + uint32_t action = 0; + SYS_HANDLE event = SYS_HANDLE_INVALID; + if (SysOpenPowerEvent(&event) != NTLSTATUS_ACCESS_DENIED || + SysQuerySystemPower(&action) != NTLSTATUS_ACCESS_DENIED || + SysCompleteSystemPower(SYS_POWER_ACTION_SHUTDOWN) != NTLSTATUS_ACCESS_DENIED) + fail_status("power coordinator ownership", NTLSTATUS_FAILURE); + print("process_protection_smoke: PASS\n"); exit_now(0); } diff --git a/code/user/tests/security_smoke/security_smoke.c b/code/user/tests/security_smoke/security_smoke.c index 2ddec3b..11c5c6e 100644 --- a/code/user/tests/security_smoke/security_smoke.c +++ b/code/user/tests/security_smoke/security_smoke.c @@ -86,8 +86,7 @@ static int run_restricted_child(uint32_t parentPid) { info.Size = sizeof(info); KSTATUS status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &info); if (!KSUCCESS(status)) return 10; - if (info.EffectivePrivileges || info.DelegablePrivileges || - info.ServiceClaims || info.DelegableServiceClaims) + if (info.EffectivePrivileges || info.DelegablePrivileges) return 11; SYS_HANDLE process = SYS_HANDLE_INVALID; @@ -145,9 +144,9 @@ static int run_restricted_child(uint32_t parentPid) { createPort.HandleOut = &listener; status = SysCreatePort(&createPort); if (!KSUCCESS(status)) return 25; - status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, listener); + status = SysRegisterSystemService(SYS_HANDLE_INVALID, listener); (void)SysCloseHandle(listener); - if (status != NTLSTATUS_ACCESS_DENIED) return 26; + if (status != NTLSTATUS_INVALID_HANDLE) return 26; uint32_t selfPid = SysGetCurrentPid(); status = SysOpenProcess(selfPid, SYS_RIGHT_QUERY, &process); @@ -188,9 +187,7 @@ static int run_parent(void) { KSTATUS status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &rootInfo); if (!KSUCCESS(status)) fail_status("query current context", status); if (rootInfo.EffectivePrivileges != SYS_PRIV_ALL || - rootInfo.DelegablePrivileges != SYS_PRIV_ALL || - rootInfo.ServiceClaims != SYS_SERVICE_CLAIM_ALL || - rootInfo.DelegableServiceClaims != SYS_SERVICE_CLAIM_ALL) + rootInfo.DelegablePrivileges != SYS_PRIV_ALL) fail_status("bootstrap context authority", NTLSTATUS_FAILURE); SYS_HANDLE selfProcess = SYS_HANDLE_INVALID; @@ -209,9 +206,7 @@ static int run_parent(void) { if (!KSUCCESS(status)) fail_status("query exact process security context", status); if (exactInfo.SecurityId != rootInfo.SecurityId || exactInfo.EffectivePrivileges != rootInfo.EffectivePrivileges || - exactInfo.DelegablePrivileges != rootInfo.DelegablePrivileges || - exactInfo.ServiceClaims != rootInfo.ServiceClaims || - exactInfo.DelegableServiceClaims != rootInfo.DelegableServiceClaims) + exactInfo.DelegablePrivileges != rootInfo.DelegablePrivileges) fail_status("exact process security context identity", NTLSTATUS_FAILURE); (void)SysCloseHandle(exactContext); (void)SysCloseHandle(selfProcess); @@ -232,8 +227,7 @@ static int run_parent(void) { status = SysQuerySecurityContext(restricted, &restrictedInfo); if (!KSUCCESS(status)) fail_status("query stripped context", status); if (restrictedInfo.SecurityId <= rootInfo.SecurityId || - restrictedInfo.EffectivePrivileges || restrictedInfo.DelegablePrivileges || - restrictedInfo.ServiceClaims || restrictedInfo.DelegableServiceClaims) + restrictedInfo.EffectivePrivileges || restrictedInfo.DelegablePrivileges) fail_status("stripped context contents", NTLSTATUS_FAILURE); SYS_HANDLE illegal = SYS_HANDLE_INVALID; @@ -247,15 +241,6 @@ static int run_parent(void) { if (status != NTLSTATUS_ACCESS_DENIED) fail_status("blocked privilege amplification", status); - memset(&lify, 0, sizeof(amplify)); - amplify.Size = sizeof(amplify); - amplify.SourceContext = restricted; - amplify.ServiceClaims = SYS_SERVICE_CLAIM_RESOURCE; - amplify.ContextHandleOut = &illegal; - status = SysDeriveSecurityContext(&lify); - if (status != NTLSTATUS_ACCESS_DENIED) - fail_status("blocked service-claim amplification", status); - SYS_HANDLE queryOnly = SYS_HANDLE_INVALID; SYS_DERIVE_HANDLE_REQUEST deriveHandle = { sizeof(deriveHandle), restricted, SYS_RIGHT_QUERY, &queryOnly diff --git a/code/user/tests/surface_cross_process_smoke/surface_cross_process_smoke.c b/code/user/tests/surface_cross_process_smoke/surface_cross_process_smoke.c index 76ac228..e84c537 100644 --- a/code/user/tests/surface_cross_process_smoke/surface_cross_process_smoke.c +++ b/code/user/tests/surface_cross_process_smoke/surface_cross_process_smoke.c @@ -7,8 +7,6 @@ #include #include -#define TEST_SURFACE_TAG 0x53555246u -#define TEST_READY_TAG 0x53555245u #define TEST_WIDTH 32u #define TEST_HEIGHT 16u #define TEST_PIXEL_X 7u @@ -49,8 +47,8 @@ static void write_pixel(void* base, uint32_t pitch, uint32_t value) { // Runs the child side using only the attenuated capabilities supplied at process construction. static void run_child(void) { - SYS_HANDLE surface = SysGetStartupCapability(TEST_SURFACE_TAG); - SYS_HANDLE ready = SysGetStartupCapability(TEST_READY_TAG); + SYS_HANDLE surface = SysGetStartupCapability("test.surface"); + SYS_HANDLE ready = SysGetStartupCapability("test.ready"); if (surface == SYS_HANDLE_INVALID || ready == SYS_HANDLE_INVALID) exit_now(10); void* invalidBase = (void*)0; @@ -112,11 +110,11 @@ int main(void) { NTL_PROCESS_STARTUP_CAPABILITY startup[2]; memset(startup, 0, sizeof(startup)); - startup[0].Tag = TEST_SURFACE_TAG; + strcpy(startup[0].Name, "test.surface"); startup[0].SourceHandle = surface; startup[0].DesiredRights = SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_SURFACE_RIGHT_MAP_READ; startup[0].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; - startup[1].Tag = TEST_READY_TAG; + strcpy(startup[1].Name, "test.ready"); startup[1].SourceHandle = ready; startup[1].DesiredRights = SYS_RIGHT_QUERY | SYS_EVENT_RIGHT_SIGNAL; startup[1].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; diff --git a/docs.md b/docs.md index e9da84f..b3b5901 100644 --- a/docs.md +++ b/docs.md @@ -4,12 +4,12 @@ nt-lite is a clean-design hobby kernel. It is not a stable compatibility platfor ## Principles -- Break old code when the new model is better. +- Break old code. - Breaking ABI is encouraged. -- Do not keep compatibility paths unless they have a clear purpose. +- Do not keep compatibility paths. - Plan the model before writing the patch. - Make one architectural move at a time. -- Delete stale paths once the real path works. +- Delete stale paths, treat like wildfire. - Prefer compile failures over wrappers that hide stale users. - Keep the tree internally consistent. - Keep one canonical API for each operation. @@ -21,18 +21,14 @@ nt-lite is a clean-design hobby kernel. It is not a stable compatibility platfor ## Workflow - Keep patches reviewable. -- Boot-test meaningful changes. - Add diagnostics before optimizing. - Use logs, counters, and assertions instead of guessing. - Remove stale comments and dead code as part of the same cleanup. - Treat a fallback as either a supported mode or delete it before it becomes permanent. -- Ask design questions before changing API shape, object lifetime, ownership, cancellation, persistence, or compatibility behavior. +- **Ask design questions before changing API shape, object lifetime, ownership, cancellation, persistence, or compatibility behavior.** - Do not add a side path to avoid updating callers. - When changing an interface, update every caller in the same patch. - When adding a build artifact, update build, packaging, copy, and runtime discovery paths in the same patch. -- Do not count apply checks or syntax-only checks as a real build. -- Be precise about what was actually validated. -- If behavior can be tested cheaply, include a focused runtime test or a visible diagnostic. - Prefer counters that prove both success and non-use. ## Breaking changes @@ -54,7 +50,6 @@ Breaking changes are encouraged. When making one: - When asked to provide the full tree, also provide the patch. - The patch is for review; the full tree is for reproducing the exact baseline. - Avoid optimistic claims. -- Do not introduce compatibility aliases unless they are intentionally supported API. ## Comment style @@ -63,13 +58,14 @@ Breaking changes are encouraged. When making one: - Use sentence case. - End every sentence with a period. - Every owned source file should start with a short file comment. -- Every function should have a comment, even if it is only a one-line summary. - Keep the tone practical and technical. - Humor is allowed rarely, but never instead of the real explanation. -- Debugging history belongs in comments only when it explains a lasting constraint. +- Debugging history belongs in comments is forbidden. +- Describing past state in comments is forbidden. +- Always describe what something is rather than isn't. +- Avoid the word 'not'. +- Keep comments short. - Document workarounds by saying what looks strange, why it is necessary, and what breaks if it is simplified. -- Avoid trailing comments except for flags, bitfields, register tables, and packed layouts. -- Keep real limitations as normal prose. ## Development lessons @@ -91,12 +87,11 @@ Breaking changes are encouraged. When making one: - Prefer finishing the feature the user needs over endlessly polishing the audit around it. - Do not hide uncertainty. Say what was checked and what still needs a real build or boot test. -## comments from developer +## comments from angry developer to ai - 0x%016llX over %p, it looks prettier and is standard. -- never add Ex, it makes me mad, never, rearchitect instead - i do put a focus on overengineering if it means it will be cleaner and futureproof -- **ASK QUESTIONS!** i can't stress this enough. never assume you know the right answer. if you do, you are probably wrong. +- **ASK QUESTIONS!** i can't stress this enough. never assume you know the right answer. if you do, you are probably wrong but you keep doing it. - **DISCUSS!** if it comes to designing something, anything, discuss it with me first. often times i can suggest something simpler, faster and elegant. - shift the syscall number table every time you add a syscall, do not append to the end - do not worry if a patch breaks something, it's better to break, document and fix rather than work around it. diff --git a/ntlite_remaining_roadmap.md b/ntlite_remaining_roadmap.md deleted file mode 100644 index 36332c1..0000000 --- a/ntlite_remaining_roadmap.md +++ /dev/null @@ -1,3154 +0,0 @@ -# nt-lite remaining roadmap - -**Status:** living design specification -**Scope:** unresolved architecture, implementation work, and validation still required. -**Intent:** completed work and repository-wide development guidance are intentionally omitted; the source tree and development notes are authoritative for those. -**Current implementation focus:** P7 is complete and runtime-validated. POSIX is intentionally paused before P8 while native ABI cleanup, KFS identity/lifetime work, and native security are corrected at their owning layers. After those prerequisites, resume the revised P8, then P9/P10. - -Current near-term sequence: - -```text -UABI.1 fixed-width/std-bool/KSTATUS cleanup and ABI assertions -UABI.2 explicit UTF-8 native text contract and validation -UABI.3 opaque prepared-process transaction API -UABI.4 remove stale named-queue publication and legacy global-input paths -KFS.1-7 native backing identity/lifetime/link/rename/times/directory reset -SEC.0-15 native principals/ACLs/authorization/accounts/audit -P8 revised POSIX filesystem projection + FIFOs -P9 tty/termios -P10 leaf APIs/profile closure -``` - ---- - -# Part I — driver lifecycle direction - -## 1. Driver lifecycle overhaul - -The next major kernel architecture task is a real driver manager with graph-based startup and shutdown. - -The driver manager must solve four related problems together: - -1. configuration and startup dependencies; -2. driver/module state tracking; -3. graceful system shutdown; -4. future demand loading, unload, and reload. - -`drivers.ini` becomes the declarative source of lifecycle policy. Runtime device topology remains owned by the I/O/PnP manager. - -A static configured dependency graph and a runtime device stack are separate structures: - -```text -configured module graph runtime device topology ------------------------ ----------------------- -usb-hcd -> usb PCI PDO -usb -> usb-storage | -network-adapter -> network-stack v -filesystem after storage xHCI FDO - | - v - USB PDO - | - v - storage FDO -``` - -The first graph answers when modules may start/stop. The second answers which concrete devices are above/below one another and therefore how actual hardware is quiesced and removed. - ---- - -# Part II — `drivers.ini` v2 and the driver manager - -## 2. Design goals - -The new driver configuration must have these properties: - -- File order has no lifecycle meaning. -- Driver classes are declared in configuration, not compiled into the kernel. -- Class names are opaque lifecycle identities to the kernel. -- A new provider driver can join an existing class without editing consumers. -- Alternatives are represented as multiple members of one class. -- Classes may depend on other classes. -- Individual drivers may additionally have exact driver dependencies for genuine implementation coupling. -- Class dependencies and exact driver dependencies participate in one effective lifecycle model. -- Startup dependencies automatically determine reverse shutdown ordering. -- The bootloader does not understand dependency policy. -- The kernel is the authoritative parser/validator/scheduler. -- The format remains deliberately small and strict, similar in spirit to `services.ini`. -- The design must admit later `demand`, deferred initialization, unload, reload, and cascade reload without replacing the model. - ---- - -## 3. Bootloader/kernel responsibility split - -### 3.1 Bootloader - -The UEFI loader remains intentionally dumb. - -It reads `drivers.ini` only far enough to locate driver descriptors and identify: - -```text -image= -start=boot -``` - -For every `start=boot` driver it: - -1. loads the raw `.sys` image into boot-owned pages; -2. records the configured driver ID and image name with the blob; -3. includes the raw `drivers.ini` bytes in `BOOT_INFO`; -4. passes the boot-driver blob list and configuration blob to the kernel. - -The bootloader does **not**: - -- build the class graph; -- resolve `requires`; -- resolve `after`; -- decide startup order; -- interpret `critical`; -- determine class availability; -- propagate dependency failures; -- initialize a driver image. - -The bootloader performs only the parsing needed to safely identify and preload boot images. The kernel re-parses the same configuration authoritatively. - -### 3.2 Boot ABI - -`BOOT_INFO` should gain a raw `DriversIni` blob. The boot-driver handoff should retain enough identity to match every preloaded blob to a configured driver without relying on list position. - -Conceptually: - -```c -typedef struct BOOT_DRIVER_FILE { - char DriverId[64]; - char Image[64]; - uintptr_t PhysicalBase; - uint32_t Length; - uint32_t PageCount; -} BOOT_DRIVER_FILE; -``` - -The exact ABI may be shaped differently, but driver identity must be explicit. - -The kernel copies/parses the configuration before reclaiming the bootloader pages. The same parsed configuration remains authoritative for the later runtime phase; it should not need to re-read a potentially different `drivers.ini` after mounting the system root. - -### 3.3 Kernel - -Before calling any configured `DriverEntry`, the kernel: - -1. parses the full file; -2. validates every class and driver descriptor; -3. validates boot blob/config correspondence; -4. constructs class and exact-driver graphs; -5. validates the combined effective graph; -6. computes initial eligibility; -7. initializes drivers according to graph state. - -Malformed lifecycle configuration is an initialization failure, not something silently repaired with fallback ordering. - ---- - -## 4. Configuration grammar - -All class sections appear before driver sections. This is primarily a human-readability rule and is also easy for the strict parser to enforce. - -Initial grammar: - -```ini -; Lifecycle classes first. - -[class.platform] -requires= -after= - -[class.bus] -requires= -after=platform - -[class.usb-hcd] -requires=bus -after= - -[class.usb] -requires=usb-hcd -after= - -; Driver descriptors follow. - -[driver.pci] -image=pci.sys -start=boot -class=bus -critical=true -requires= -after= -``` - -### 4.1 Class keys - -Every class has exactly: - -```text -requires= -after= -``` - -Both keys are required even when empty. - -### 4.2 Driver keys - -Every driver initially has exactly: - -```text -image= -start= -class= -critical= -requires= -after= -``` - -All keys are required. - -Initial `start=` values: - -```text -boot -runtime -disabled -``` - -Planned later values include: - -```text -demand -manual -``` - -A separate `deferred` policy may be introduced only if it has semantics distinct from `demand` and graph waiting. - -### 4.3 Identity rules - -Class IDs and driver IDs are separate namespaces. - -Recommended syntax: - -```text -[A-Za-z0-9._-]+ -``` - -with a bounded length suitable for fixed diagnostic buffers. - -IDs are case-sensitive. - -The `image=` field names one file under the system driver directory. Paths outside that directory are not part of the initial format. - -One configured driver ID maps to one module image. Duplicate enabled image mappings should be rejected unless a future concrete use case establishes multi-instance image configuration semantics. - ---- - -## 5. Class semantics - -Classes are **config-defined lifecycle groups**, not kernel enums and not hardcoded subsystem taxonomy. - -The kernel must never contain policy such as: - -```c -if (Class == DriverClassStorage) { ... } -``` - -It knows only opaque class IDs and graph edges. - -A driver initially belongs to exactly one lifecycle class. If a future architecture genuinely needs multi-class membership, change the format deliberately rather than smuggling it into comma syntax now. - -### 5.1 `requires=` — availability edge - -For a class: - -```ini -[class.usb] -requires=usb-hcd -after= -``` - -`requires=usb-hcd` means: - -> The `usb` class may begin only after `usb-hcd` becomes available. - -A class becomes **Available** as soon as at least one eligible member driver reaches `Started`. Availability describes a loaded module/provider contract, not the presence of a physical device. A successfully initialized NIC driver can make `network-adapter` available even when no NIC is currently enumerated; hotplug and device existence remain PnP/device-topology concerns. - -This is intentionally “one provider from the class,” not “every member must succeed.” - -Example: - -```text -usb-hcd class - xhci Started - ehci Failed - ohci Failed - -result: usb-hcd is Available -``` - -A dependent `usb` class may proceed. - -If a requirement lists multiple classes, all listed classes are required. Provider alternatives belong as multiple drivers inside one class; the format does not need a `requires_any` expression language. - -### 5.2 `after=` — settlement/barrier edge - -For a class: - -```ini -[class.filesystem] -requires= -after=storage-controller,usb-storage -``` - -`after=` means: - -> Wait until the referenced class has settled for the current automatic startup phase, regardless of whether any member succeeded. - -This is useful when the consumer only needs all likely providers to have had their initialization opportunity before it registers or probes. - -A filesystem class can therefore start on a machine with no storage controller at all; later hotplug remains possible. - -### 5.3 Class state - -The scheduler should represent at least: - -```text -Pending -Available -SettledAvailable -SettledUnavailable -Blocked -``` - -Useful interpretation: - -- **Pending:** eligible member work or dependencies remain unresolved. -- **Available:** at least one member reached `Started`; more members may still be pending. -- **SettledAvailable:** all automatic members for the current phase are terminal and at least one started. -- **SettledUnavailable:** all automatic members are terminal and none started. -- **Blocked:** the class cannot proceed because a required class settled unavailable or an unsatisfiable dependency was discovered. `Blocked` is terminal for barrier purposes. - -A class with no started member and no remaining eligible provider in the current startup epoch settles unavailable. A member that started in an earlier phase continues to make its class available in later phases. When a later phase introduces additional automatic members, availability may remain true while settlement reopens until those new members reach terminal states. - -A `requires` dependent may become eligible at **Available**; it need not wait for settlement. A required class that reaches `SettledUnavailable` or `Blocked` without an already-started provider cannot satisfy the edge. - -An `after` dependent waits for a terminal class state: `SettledAvailable`, `SettledUnavailable`, or `Blocked`. - -### 5.4 No cardinality policy - -The driver manager does not enforce “singleton class” or “only one network stack.” - -Multiple members may start in any class. If a subsystem eventually requires exclusivity, that policy belongs in configuration/start selection or in the subsystem that owns the resource, not in generic class machinery. - ---- - -## 6. Driver-level exact dependencies - -Drivers retain exact dependencies for real module-to-module coupling. - -Example: - -```ini -[driver.foo-filter] -image=foo_filter.sys -start=runtime -class=foo-filter -critical=false -requires=foo-core -after= -``` - -Driver-section references name **driver IDs**, while class-section references name **class IDs**. - -Initial graph domains remain simple: - -```text -[class.*].requires -> class IDs -[class.*].after -> class IDs -[driver.*].requires -> driver IDs -[driver.*].after -> driver IDs -``` - -Cross-domain references are unnecessary because membership composes the two graphs. - -### 6.1 Driver `requires=` - -An exact required driver must reach `Started` before the dependent driver may start. - -If it reaches a terminal failure/blocked state, the dependent becomes dependency-blocked. - -### 6.2 Driver `after=` - -An exact `after` target need only reach a terminal startup state. Its failure does not by itself prevent the dependent from starting. - -### 6.3 Combined cycle validation - -Validating the class DAG and driver DAG separately is insufficient. - -Class membership plus exact driver edges can create an effective cycle even when each individual graph is acyclic. The kernel must validate the combined scheduling constraints before initialization. - ---- - -## 7. Driver state model - -The driver manager should track configured module state independently from `DRIVER_OBJECT` device-driver objects. - -Suggested module states: - -```text -Disabled -Preloaded -Waiting -Loading -Starting -Started -Blocked -Failed -Stopping -Stopped -Unloading -Unloaded -``` - -Future demand loading may add an explicit idle/requested state if useful. - -A configured driver module owns: - -```text -identity -image name -start policy -lifecycle class -critical flag -exact requires/after edges -load/module base -startup status -registered DRIVER_OBJECTs -shutdown status -reload generation -``` - -One module may register more than one `DRIVER_OBJECT`; the module manager must not assume a one-to-one relationship even if driver decomposition gradually makes that common. - ---- - -## 8. Startup phases - -### 8.1 Boot phase - -All `start=boot` images are already resident as raw blobs from the bootloader. - -The kernel validates the complete graph, then starts eligible boot drivers according to class and exact-driver constraints. - -A boot driver may not have an automatic hard dependency whose only possible configured providers are runtime drivers. Such a configuration is unsatisfiable during boot and should be rejected. - -### 8.2 Runtime phase - -After the system-root volume is available, `start=runtime` modules become eligible. - -Runtime modules may depend on already-started boot modules/classes or other runtime modules/classes. Boot-started drivers continue contributing to class availability. If a class gains additional `start=runtime` members, its runtime settlement barrier is reopened until those members are attempted; an already-started member can still satisfy a `requires` edge while that happens. - -The kernel loads runtime images from the system driver directory according to graph eligibility rather than file order. - -### 8.3 Future demand phase - -`start=demand` should mean: - -- the descriptor participates in graph validation; -- the module is not started automatically merely because prerequisites are available; -- a PnP match, explicit request, or dependency demand can request it; -- its hard dependency closure is started first; -- failure is returned to the requester and reflected in module/class state. - -This allows future device discovery to trigger drivers naturally without replacing the graph model. - ---- - -## 9. Criticality and failure propagation - -### 9.1 Ordinary driver failure - -When a driver fails to load or `DriverEntry` returns failure: - -1. mark the module `Failed`; -2. update its class availability/settlement; -3. block exact hard dependents when appropriate; -4. block class hard dependents only if the required class ultimately cannot become available; -5. allow `after` dependents once the relevant target settles. - -### 9.2 `critical=true` - -For automatic system startup, a critical configured driver represents a requirement for successful system initialization. - -A critical driver that: - -- fails to load; -- fails import resolution; -- fails `DriverEntry`; -- becomes blocked by an unsatisfied hard dependency; - -causes startup to fail with explicit dependency diagnostics. - -Criticality should not be silently inherited by every provider in a multi-provider class. If one class member is critical, that is a property of that configured driver. - -For future manual reload/demand operations, `critical` does not imply that a user-requested reload failure must bugcheck an already-running system; those operations need separate transactional failure semantics. - ---- - -## 10. Validation rules - -The parser/graph validator rejects at least: - -- duplicate class IDs; -- duplicate driver IDs; -- duplicate keys; -- unknown keys; -- malformed section names; -- class sections after driver sections; -- missing required keys; -- invalid `start` values; -- invalid booleans; -- invalid IDs or image names; -- unknown class membership; -- unknown class dependencies; -- unknown driver dependencies; -- self-dependencies; -- duplicate entries in dependency lists; -- the same target present in both `requires` and `after` for one descriptor; -- class cycles; -- driver cycles; -- combined effective scheduling cycles; -- enabled boot dependencies impossible to satisfy during the boot phase; -- `start=boot` images missing from the bootloader handoff; -- unexpected or duplicate boot image handoff entries; -- duplicate enabled module-image identities unless deliberately supported later. - -No fallback ordered list is synthesized when the graph is invalid. - ---- - -## 11. Determinism and scheduling - -Graph edges are the only lifecycle ordering contract. - -When multiple drivers are simultaneously eligible with no ordering edge between them, the initial implementation may run them serially using a deterministic lexicographic driver-ID tie-breaker for reproducible logs. - -That tie-breaker is explicitly **not** an API or dependency contract. - -Once the driver manager is stable, independent graph nodes may be initialized concurrently if profiling shows value. `requires=Available` and `after=Settled` semantics are intentionally defined so concurrency does not require a format change. - ---- - -## 12. Worked class graph - -A useful initial class graph after USB decomposition could look like this: - -```ini -[class.platform] -requires= -after= - -[class.bus] -requires= -after=platform - -[class.storage-controller] -requires=bus -after= - -[class.usb-hcd] -requires=bus -after= - -[class.usb] -requires=usb-hcd -after= - -[class.usb-storage] -requires=usb -after= - -[class.usb-hid] -requires=usb -after= - -[class.filesystem] -requires= -after=storage-controller,usb-storage - -[class.audio] -requires=bus -after= - -[class.display] -requires=bus -after= - -[class.network-adapter] -requires=bus -after= - -[class.network-stack] -requires=network-adapter -after= -``` - -Graphically: - -```text - platform - | - | after - v - bus - _____________|____________________ - / | | \ - v v v v - storage-controller usb-hcd audio network-adapter - | | - | requires | requires - v v - usb network-stack - / \ - v v - usb-storage usb-hid - | - | barrier together with storage-controller - v - filesystem -``` - -The exact class names are configuration policy and may evolve. The kernel has no semantic knowledge of them. - ---- - -## 13. Worked driver configuration - -A target configuration after splitting the current USB image could be: - -```ini -[class.platform] -requires= -after= - -[class.bus] -requires= -after=platform - -[class.storage-controller] -requires=bus -after= - -[class.usb-hcd] -requires=bus -after= - -[class.usb] -requires=usb-hcd -after= - -[class.usb-storage] -requires=usb -after= - -[class.usb-hid] -requires=usb -after= - -[class.filesystem] -requires= -after=storage-controller,usb-storage - -[class.audio] -requires=bus -after= - -[class.display] -requires=bus -after= - -[class.network-adapter] -requires=bus -after= - -[class.network-stack] -requires=network-adapter -after= - -[driver.cereal] -image=cereal.sys -start=boot -class=platform -critical=false -requires= -after= - -[driver.ps2] -image=ps2.sys -start=boot -class=platform -critical=false -requires= -after= - -[driver.pci] -image=pci.sys -start=boot -class=bus -critical=true -requires= -after= - -[driver.hda] -image=hda.sys -start=boot -class=audio -critical=false -requires= -after= - -[driver.ahci] -image=ahci.sys -start=boot -class=storage-controller -critical=false -requires= -after= - -[driver.ata] -image=ata.sys -start=boot -class=storage-controller -critical=false -requires= -after= - -; Future NVMe requires no filesystem edits. -;[driver.nvme] -;image=nvme.sys -;start=boot -;class=storage-controller -;critical=false -;requires= -;after= - -[driver.xhci] -image=xhci.sys -start=boot -class=usb-hcd -critical=false -requires= -after= - -[driver.usb] -image=usb.sys -start=boot -class=usb -critical=false -requires= -after= - -[driver.usbhid] -image=usbhid.sys -start=boot -class=usb-hid -critical=false -requires= -after= - -[driver.usbstor] -image=usbstor.sys -start=boot -class=usb-storage -critical=false -requires= -after= - -[driver.fat] -image=fat.sys -start=boot -class=filesystem -critical=false -requires= -after= - -[driver.ntfs] -image=ntfs.sys -start=boot -class=filesystem -critical=false -requires= -after= - -[driver.ext4] -image=ext4.sys -start=boot -class=filesystem -critical=false -requires= -after= - -[driver.vmwaresvga] -image=vmwaresvga.sys -start=runtime -class=display -critical=false -requires= -after= - -[driver.rtl8139] -image=rtl8139.sys -start=runtime -class=network-adapter -critical=false -requires= -after= - -[driver.rtl816x] -image=rtl816x.sys -start=runtime -class=network-adapter -critical=false -requires= -after= - -[driver.lwip] -image=lwip.sys -start=runtime -class=network-stack -critical=false -requires= -after= -``` - -The important property is structural rather than the exact sample names: - -- adding NVMe means adding one `storage-controller` member; -- adding EHCI/OHCI means adding another `usb-hcd` member; -- adding a NIC means adding another `network-adapter` member; -- adding another network stack does not require a generic-driver-manager singleton rule; -- consumers depend on lifecycle classes rather than enumerating every implementation. - ---- - -## 14. USB driver decomposition - -Split the current USB implementation into four module ownership domains: - -```text -xhci.sys - xHCI PCI hardware/controller mechanics - -usb.sys - generic USB bus/core, enumeration, hubs, descriptors, configuration, - composite/interface enumeration - -usbhid.sys - USB HID function driver - -usbstor.sys - USB mass-storage function/transport driver -``` - -### 14.1 `xhci.sys` - -Owns xHCI-specific mechanism: - -- PCI BAR/resources; -- xHCI capability/operational/runtime registers; -- DCBAA; -- scratchpads; -- command/event/transfer rings; -- TRBs; -- interrupters; -- slots/endpoints; -- controller DMA; -- xHCI port state; -- controller reset/start/stop; -- hardware interrupt/DPC processing. - -Lifecycle class: `usb-hcd`. - -### 14.2 `usb.sys` - -Owns controller-independent USB behavior: - -- host-controller consumption/binding; -- USB addressing; -- standard control transfers; -- descriptors; -- configurations/interfaces; -- hub topology; -- enumeration; -- composite/interface PDO creation; -- USB device identity/matching. - -Lifecycle class: `usb` with `requires=usb-hcd` at the class level. - -The runtime HCD/USB contract should use I/O-manager device/interface mechanisms rather than direct imports from a specific HCD implementation. `usb.sys` must not know that the provider is xHCI rather than EHCI/OHCI/a future virtual HCD. - -### 14.3 `usbhid.sys` - -Owns: - -- HID descriptors; -- report parsing; -- boot keyboard/mouse protocol; -- conversion into the existing logical input mechanism. - -Lifecycle class: `usb-hid` with class-level `requires=usb`. - -### 14.4 `usbstor.sys` - -Owns: - -- USB mass-storage transport; -- BOT initially; -- UAS later if desired; -- SCSI command transport over USB; -- USB storage device lifetime and block-device publication. - -Lifecycle class: `usb-storage` with class-level `requires=usb`. - -### 14.5 Kept together initially - -Hub and composite enumeration remain inside generic `usb.sys`. They can become separate images later only if independent lifecycle/reload value justifies the extra module boundaries. - ---- - -# Part III — graceful driver and device shutdown - -## 15. Shutdown goals - -System shutdown must become a real lifecycle transition rather than jumping from user-mode shutdown directly to ACPI S5 while drivers and devices remain active. - -The shutdown contract must ensure: - -- filesystems have a chance to flush/freeze; -- pending cached writes are drained; -- network/audio/display workers stop coherently; -- storage flushes complete while controllers remain operational; -- DMA engines are disabled before poweroff; -- interrupt sources are masked/stopped; -- worker threads no longer reference device/module state being torn down; -- shutdown cannot hang forever on one broken driver; -- failure diagnostics remain available before the final hardware power transition. - ---- - -## 16. Separate system shutdown, PnP removal, and unload - -Three lifecycle operations remain distinct: - -### System shutdown - -The machine is powering off. Devices should quiesce and hardware should stop. Module images do not need to be unmapped before S5. - -### PnP removal - -A concrete runtime device is disappearing. Existing PnP query/remove/surprise/remove semantics remain device-topology operations. - -### Driver unload - -A module image is being unloaded or reloaded. All owned devices/resources must already be detached or quiesced sufficiently for code removal. - -`DriverUnload` remains an unload callback; it should not be overloaded to mean “the whole machine is shutting down.” - ---- - -## 17. Proposed shutdown state machine - -System shutdown should be explicit: - -```text -Running - | - v -UserShutdown - | init stops user-mode services and session processes - v -KernelQuiescing - | prevent creation of new ordinary work that would outlive shutdown - v -DriverPrepare - | reverse dependency graph; flush/freeze/stop producers - v -DeviceCommit - | stop DMA/IRQs and hardware activity using runtime topology - v -DriverCommit - | stop remaining module-global workers/resources - v -FinalFlushDiagnostics - | - v -HAL S5 -``` - -The exact division between device and module callbacks should be implemented so the same primitives can later support reload without conflating system shutdown with removal. - ---- - -## 18. Driver shutdown API shape - -Add an explicit shutdown contract separate from `DriverUnload`. - -A two-phase model is preferred: - -```text -Prepare -Commit -``` - -### Prepare - -May: - -- flush buffered state; -- reject new ordinary requests; -- drain work queues; -- stop higher-level protocol activity; -- freeze filesystem mutation; -- send final device flushes; -- report failure. - -Dependencies remain alive while a consumer prepares. - -### Commit - -May: - -- disable device DMA; -- disable controller interrupts; -- stop hardware engines; -- terminate device workers; -- transition hardware into a final safe state. - -Commit is bounded and best-effort. Once shutdown has committed, the system continues toward poweroff even if a driver reports failure. - -The API may be represented as a module callback, a `DRIVER_OBJECT` callback, a shutdown/power IRP, or a combination. The final implementation should support both module-global state and concrete device stacks without duplicating shutdown order in each driver. - -A likely shape is: - -```text -configured module graph - orders module prepare/commit callbacks - -runtime DEVICE_OBJECT topology - orders per-device shutdown/power IRPs -``` - ---- - -## 19. Shutdown ordering - -Startup dependency edges reverse naturally for shutdown. - -Example: - -```text -startup: - bus -> usb-hcd -> usb -> usb-storage -> filesystem - -shutdown: - filesystem -> usb-storage -> usb -> usb-hcd -> bus -``` - -Likewise: - -```text -startup: - bus -> network-adapter -> network-stack - -shutdown: - network-stack -> network-adapter -> bus -``` - -`after` edges also reverse for shutdown because they express lifecycle barriers. - -Within module order, runtime device topology determines the correct per-device traversal: upper consumers/children quiesce before lower providers/parents. - -No separate hand-authored shutdown order should exist in configuration. - ---- - -## 20. Shutdown timeouts and failure policy - -Every shutdown wait is bounded. - -For each driver/device phase record: - -```text -start timestamp -completion timestamp -status -outstanding IRP count -worker count if available -``` - -Prepare failures/timeouts are logged. The kernel may attempt remaining independent drivers and then continue shutdown according to policy; one noncritical driver cannot create an infinite shutdown hang. - -Commit failure is diagnostic. Hardware poweroff continues once all bounded commit attempts finish. - -Before HAL S5, print a compact shutdown summary if any component failed or timed out. - ---- - -# Part IV — future driver-manager features - -## 21. Demand loading - -Once the graph manager is stable, add `start=demand`. - -Possible triggers: - -- PnP device ID match; -- an explicit kernel management request; -- a dependency demand from another requested driver; -- later, controlled user-mode administration if a secure management API is introduced. - -Demand loading resolves the hard dependency closure first. The class graph remains applicable; no alternate demand-only dependency system is introduced. - ---- - -## 22. Driver unload - -Implement real module unload only after lifetime accounting is trustworthy. - -Requirements: - -- prevent new device binds to the target module; -- identify all `DRIVER_OBJECT`s registered by the module; -- remove/quiesce all owned runtime devices; -- drain outstanding IRPs; -- stop module worker threads/timers/DPCs; -- unregister interrupt handlers; -- release DMA mappings/adapters; -- call `DriverUnload`/module unload callback; -- unregister exported module identity/import references; -- unmap image pages only after no executable/data references remain. - -The loader must track import/module references strongly enough to reject unloading a module that still supplies imported symbols to another loaded kernel image. - ---- - -## 23. Driver reload - -Reload builds on unload plus the existing graph. - -Initial operation: - -```text -reload driver-id -``` - -is allowed only when no started hard dependent requires the target and device lifetime permits removal. - -Later: - -```text -reload --cascade driver-id -``` - -may compute the reverse hard-dependency closure, quiesce dependents first, reload the target, and restart the closure in forward graph order. - -Class availability may temporarily change during reload. Dependents must not observe a provider as available while its module is stopped. - -Reload should be transactional where possible: an ordinary reload failure should return an error rather than intentionally crash an otherwise running system. - ---- - -## 24. Driver-manager observability - -Add structured diagnostics sufficient to answer: - -- which classes exist; -- class dependencies; -- class current state; -- class members; -- which drivers are configured; -- start policy; -- current module state; -- load/start status; -- blocking dependency; -- module base/image identity; -- registered `DRIVER_OBJECT`s; -- device count; -- shutdown/reload status. - -A debug graph dump should show both class and driver edges and explain why a waiting/blocked driver is not eligible. - -Later expose a read-only native query interface for system tools rather than parsing debug logs. - ---- - -# Part V — desktop/session work - -## 25. Move the native console into the desktop - -The largest remaining architectural cleanup in the current graphical stack is to make the RSSRV native console a real DWM-managed content source. - -Target ownership: - -```text -RSSRV - owns console state, input queue, cells/text semantics - -console renderer/client - renders console state into a kernel surface - -DWM - owns the window, z-order, decorations, composition and physical presentation -``` - -`/Sessions/0/Console` remains the native console object. The graphical desktop displays it; DWM does not become the console authority. - -Once this path works, moving keyboard focus between console and graphical windows becomes one native desktop/session mechanism instead of a transitional competition between console and DWM targets. - ---- - -## 26. Delete stale direct input/console/display application paths - -After the console is fully on the intended RSSRV/DWM path, remove old ambient application-facing input and text-console APIs. - -Migration rule: - -1. prove the canonical RSSRV/DWM path; -2. migrate every normal caller and test; -3. retain only low-level manager/device mechanisms needed by RSSRV/kernel tests; -4. remove stale application ABI in one breaking patch; -5. shift syscall numbers as required. - -Physical display presentation remains capability-restricted to DisplayManager authority. Client surface commit never becomes physical scanout. - ---- - -## 27. DWM protocol and compositor growth - -DWM is authoritative for native top-level window geometry, interactive move/resize, z-order, activation/focus, capture, decorations, clipping/occlusion, and composition. Applications own client content and may request bounds/minimize/maximize/fullscreen, but a client request is not an unconditional placement command. Interactive move/resize is DWM-driven: DWM hit-tests server-side decoration, captures the pointer, updates authoritative bounds, and sends a configure event so the client redraws. - -Use server-side native decorations as the canonical model, with explicit borderless/none modes for applications that need them. The native frame protocol does not become application-defined client-side chrome. Keep theme metrics/fonts/colors/borders/shadows/button layout/animations in a shared userspace theme schema/library used by DWM and the native UI toolkit; do not introduce a theme service until cross-process state actually requires one. - -Keep shell policy separate from DWM. DWM owns windows/monitors/work areas/activation/focus/z/composition/decorations. The shell owns taskbar/dock/start/launcher/icons/notifications/file-manager/wallpaper policy as a privileged DWM client and may reserve work areas or enumerate/activate/minimize top-level windows through explicit capabilities. - -Treat focus, activation, pointer capture, z-order, and attention as distinct state. Background applications cannot force activation merely by asking; user gestures/shell policy grant it. Security-sensitive desktop operations such as global window enumeration, capture pixels, input injection, secure UI, always-on-top system UI, work-area reservation, global theme selection, and display topology require explicit authority. - -Build richer behavior on the current native desktop protocol rather than introducing a Win32-specific compositor contract. - -Remaining native window features should include: - -- window title/application identity metadata; -- close-request event and explicit client response/lifetime semantics; -- move/resize interaction; -- decorations; -- minimum/maximum size policy if needed; -- activation policy distinct from raw focus requests; -- better z-order groups if concrete use cases require them; -- multiple damage rectangles rather than whole-window dirtying; -- region/occlusion tracking; -- compositor clipping/region algebra; -- incremental composition scheduling; -- optional frame/present timing and future vblank integration; -- explicit monitor/display geometry once multiple displays exist. - -Keep CPU composition as the correctness baseline. GPU acceleration can replace selected raster/composition mechanisms later without moving window policy into the kernel. - ---- - -## 28. DWM restart and reconstruction - -DWM is a natural future `restart=on-failure` service only after restart semantics are defined. - -A restart design must decide what persists outside DWM: - -- client desktop connections; -- window identities; -- attached surface capabilities; -- bounds/visibility; -- z-order; -- focus/capture ownership; -- cursor mode; -- decorations/title metadata. - -Two acceptable broad models are: - -1. **reconstruct:** RSSRV or another durable owner retains enough session/window registration state for a replacement DWM to rebuild; -2. **loss policy:** DWM restart explicitly invalidates graphical windows/connections and clients reconnect/recreate. - -Choose one and make failure visible to clients. Do not enable automatic restart while the behavior is accidental. - -Once defined, DWM may use the fixed service restart budget: at most three automatic restart attempts in any sliding five-minute window, with deliberate stop/shutdown excluded from the budget. - ---- - -# Part VI — POSIX remaining work - -## 29. POSIX direction - -Target a useful POSIX.1-1990-shaped personality while preserving nt-lite extensions and the native process model. - -The process model is intentionally **not** Unix process cloning: - -- `fork()` is deliberately unsupported. Do not add copy-on-write address-space cloning, Unix parentage, or a kernel `fork` compatibility path. -- `vfork()` is the constrained pre-exec construction primitive. Its implementation may temporarily borrow the parent's native address space and caller stack, but it remains a POSIX personality mechanism. -- `execve()` is a `posixsrv` transaction. `posix.dll` only marshals the request and exports inherited POSIX state; it does not load the image, create the replacement process, commit PID identity, or kill the old process on its own. -- `posix_spawn()` is also server-owned process construction, not `fork()+exec` emulation. -- POSIX PID identity is owned by `posixsrv`, not by the kernel native PID allocator. - -The kernel must not gain POSIX-named process, signal, session, process-group, or exec mechanisms. If the implementation exposes a missing substrate, add the smallest useful **generic native mechanism** and keep POSIX policy in userspace. - -The public API/profile manifest remains the source of truth for whether each POSIX entry is implemented, partial, planned, or deliberately unsupported. `fork()` must be marked deliberately unsupported rather than left ambiguously planned. - -PE32+ subsystem value 7 is reserved as the POSIX personality marker for future loader selection, but it keeps the native PEB/TEB layout and native GS contract. Finishing POSIX does not depend on changing that layout. - ---- - -## 30. `posixsrv.exe` authority and protocol - -Introduce `posixsrv` as the shared POSIX policy authority. It is an init-managed noncritical service with an explicit RSSRV dependency. Start with `restart=never`; do not add restart until client reconnection and server-state reconstruction have a defined contract. - -`posixsrv` owns shared state that cannot be process-local without breaking POSIX semantics: - -```text -stable POSIX PID allocation -native-process generation bound to each POSIX PID -parent/child launch associations -child exit records and wait collection -uid/euid/gid/egid -supplementary groups -POSIX process groups/session IDs -current directory identity used by server-owned spawn/exec -cross-process and process-group signal routing/pending state -synthetic POSIX filesystem metadata -fake hard-link aliases -FIFO namespace metadata -passwd/group database -POSIX tty line discipline and foreground process-group state -``` - -It does not own native console hardware/session routing. POSIX tty remains an adapter above RSSRV console/session mechanisms. - -The protocol is versioned and capability-oriented. Port sender identity is kernel-stamped and is never trusted from a client payload. Large `argv`, `envp`, descriptor manifests, or metadata transactions are streamed/chunked or transferred through generic mappings; do not raise Port inline/handle limits merely for POSIX. - -A server process record should be conceptually close to: - -```text -POSIX_PROCESS_RECORD - Pid - ParentPid - ProcessGroupId - SessionId - - NativeProcessHandle - NativePid - NativeGeneration - State - Constructing - Running - VforkBorrowed - ExecPreparing - ExecCommitting - Exited - - Credentials - SupplementaryGroups - CurrentDirectory - - ProcessPendingSignals - - ChildList / wait records - ExitStatus -``` - -`NativeProcessHandle` and `NativePid` are replaceable implementation identities. `Pid` is the stable POSIX identity. - -Every POSIX process receives a server-issued bootstrap token/generation in userspace startup data. A replacement image proves that it initialized successfully with that token before `posixsrv` commits an exec transition. - ---- - -## 31. Stable POSIX process lifecycle - -### Stable PID and parentage - -`getpid()` returns the cached `posixsrv` PID from process/thread personality state. It must not expose `SysGetCurrentPid()` once server-owned identity is active. - -`getppid()` uses the server launch association. There is no kernel process-parent field, Unix reparenting, adoption, or init-reaper model. Parent death and orphaned child records need one explicit POSIX policy, but native process ancestry never becomes authoritative. - -A process can move through multiple native generations while retaining one POSIX PID: - -```text -POSIX pid 42 - generation 1 -> native pid 113 - execve() - generation 2 -> native pid 126 - execve() - generation 3 -> native pid 141 -``` - -A native generation retired by exec is **not** a POSIX process exit. It produces no waitable child completion and no `SIGCHLD` solely because the old native process died. - -### Server-owned `execve()` - -`execve()` in `posix.dll` is a thin client operation: - -```text -capture path/argv/envp -capture the caller's logical descriptor table and FD_CLOEXEC state -export required native capabilities/open-description state -send EXEC transaction to posixsrv - -failure before commit: - receive errno - return -1 - -success: - never return to application code -``` - -`posixsrv` performs the replacement transaction: - -1. resolve the executable according to the POSIX process current directory/path facade; -2. validate the image before changing the old process; -3. build a new native process with the userspace process builder; -4. install inherited non-`FD_CLOEXEC` descriptors and reconstruct process-local wrappers; -5. build `argv`, `envp`, loader/bootstrap data, signal state, credentials, pgid/sid, and cwd; -6. start the replacement behind a userspace POSIX bootstrap barrier; -7. wait for the new runtime to report READY with the server-issued exec token; -8. enter `ExecCommitting`, queue new process-directed signals instead of routing them to the retiring generation; -9. retire the old native generation; -10. bind the stable POSIX PID to the new generation, apply queued signal state, and release the new image. - -For an ordinary process, the old `execve()` caller remains blocked in its Port call and `posixsrv` terminates the retained old native process handle. There is no success reply. - -An exec transaction must either fail while the old image is still valid or reach the committed replacement state. Do not expose a user-visible interval in which the POSIX PID disappears merely because native generations are changing. - -Signal exec semantics are applied by the personality: caught handlers reset to default, ignored dispositions remain ignored where required, the calling thread's blocked mask becomes the initial replacement-thread mask, and process-pending signals survive according to the selected POSIX profile. - -### `vfork()` - -Implement `vfork()` without cloning a native process or address space. - -The intended mechanism is a temporary borrowed native thread: - -```text -native process A - parent calling thread -> POSIX pid 20 - borrowed child thread -> POSIX pid 21, state=VforkBorrowed -``` - -The parent calling thread is suspended until the borrowed child either `_exit()`s or successfully `execve()`s. Other native threads in the parent process need not be frozen unless testing proves a stricter contract is required. - -The child receives a distinct stable POSIX PID and thread-local logical process state. It must not overwrite the parent's process-global POSIX state. A child descriptor-table overlay references the same inherited open descriptions while keeping descriptor-number mutations logically separate from the parent. - -The architecture-specific `vfork` trampoline may borrow the parent's user stack while the parent is blocked. It must make the child continuation observe a return value of zero and the parent continuation eventually observe the child POSIX PID. This is userspace continuation machinery, not a kernel clone operation. - -Initial supported child behavior is deliberately narrow: `_exit()` and `execve()` are the safe terminal operations. Do not add kernel complexity to make arbitrary post-`vfork` mutation safe. - -For `vfork()+execve()`: - -1. the borrowed child sends the normal EXEC request under its child POSIX identity; -2. `posixsrv` builds a new independent native process for the child PID; -3. the replacement reports READY behind its bootstrap barrier; -4. `posixsrv` commits the child PID to the new native generation; -5. the borrowed child receives an internal retire result and calls `SysExitThread()` without returning to application code; -6. the parent waits on the borrowed native thread handle, so its user stack cannot be reused until the child is actually gone; -7. once the borrowed thread is dead, `posixsrv` releases the parent and replacement image. - -For `vfork()+_exit()`, the server records a genuine POSIX child exit and the borrowed thread self-terminates; the parent then resumes and may collect the child through `wait`/`waitpid`. - -### `wait` and `_exit` - -`wait()`/`waitpid()` observe `POSIX_PROCESS_RECORD` completion, not raw native process exit events. Native process handles remain useful mechanisms for observing generations, but only `posixsrv` decides whether a native exit means POSIX process death or exec retirement. - -Implement: - -```text -_exit -getpid -getppid -wait -waitpid including WNOHANG -``` - -Child exit records remain collectible after native object teardown until the POSIX parent collects them or the selected orphan policy discards them. - ---- - -## 32. Credentials, groups, sessions, and identity database - -Keep POSIX identity metadata separate from native security authority. Unix mode/uid/gid data is a personality facade unless an explicit bridge to native security is designed later. - -Implement shared server state and APIs for: - -```text -getuid/geteuid/getgid/getegid -setuid/setgid -getgroups/getlogin -getpgrp/setpgid/setsid -getpwnam/getpwuid -getgrnam/getgrgid -``` - -The initial passwd/group database may be synthesized/configured rather than requiring `/etc/passwd` or `/etc/group`. - -Credential policy is explicit: synthetic effective uid 0 is currently the credential-changing authority. `setuid()` by uid 0 replaces both real and effective uid; `setgid()` is authorized by effective uid 0 and replaces both real and effective gid. After dropping uid 0, the current API cannot regain it. - -This is transitional POSIX personality state, not the final native security model. Once SEC lands, POSIX uid/gid/group identities map onto stable native principal IDs and POSIX permission APIs become projections of native security descriptors. Do not preserve a second synthetic filesystem-security authority in `posixsrv`. - -Process-group/session state is authoritative in `posixsrv` because it is consumed by `kill`, tty foreground-group policy, spawn attributes, and future job-control behavior. - -Move the authoritative POSIX current directory into the process record, or otherwise give `posixsrv` a stable capability/path representation sufficient to resolve relative server-owned `spawn`/`exec` requests. `chdir`/`fchdir` must update that authority consistently. - ---- - -## 33. POSIX process construction, environment, and descriptor inheritance - -### Userspace native prepared-process transaction - -Replace the public fixed-size `NTL_PROCESS_BUILDER.Opaque[...]` storage with a genuinely opaque userspace preparation object. The implementation owns its allocation and may change size without freezing private loader state into the public ABI. - -Target shape: - -```text -NtlPrepareProcess(...) -> NTL_PROCESS_PREPARATION* -NtlPreparedProcessGetHandle(...) -> borrowed process capability -NtlStartPreparedProcess(...) -> consumes preparation -NtlAbortPreparedProcess(...) -> consumes preparation without start -``` - -A valid `NtlStartPreparedProcess()` call consumes the preparation on both success and ordinary start failure. This gives the transaction one terminal operation and removes cleanup ambiguity. - -The preparation should permit `posixsrv` to: - -```text -create inert native process -load main PE and dependency modules -inspect PE32+ subsystem/personality -install arbitrary inherited native handles/capabilities -build loader/personality bootstrap data -start the process -retain the process capability -``` - -`NtlCreateProcessAt` remains a convenience wrapper over this mechanism for ordinary native callers. If a service needs the initial native thread capability, expose it generically from the prepared-process transaction rather than adding personality-specific thread lookup. - -### `argv` and environment - -Add a userspace process bootstrap representation for `argv` and `envp`. `environ`/`getenv` are process-local runtime views of the environment block; `posix_spawn` and `execve` may replace it. - -Do not require a kernel-defined POSIX environment block. The kernel only validates that generic loader/bootstrap memory is user-readable as it already does for native loader data. - -Implement: - -```text -getenv -environ -posix_spawn envp -execve envp -``` - -### Transferable POSIX open descriptions - -Before process inheritance, make the existing open-description model transferable across native processes without changing its same-process `dup` semantics. - -Requirements: - -- descriptor flags such as `FD_CLOEXEC` remain descriptor-local; -- mutable status belongs to the shared open description; -- duplicate regular-file native handles continue to share the native file object's offset and append state; -- duplicate socket handles continue to share native socket status; -- pipe endpoint state and `O_NONBLOCK` must remain shared across inherited duplicates; -- the replacement/child gets process-local wrapper allocations around shared/native backing state; -- closing one process-local wrapper must not invalidate another process's inherited description. - -The current shared pipe fast path also needs a supported way to map the pipe backing into an inheriting/replacement process. Do not rely on the current shared-PML4 accident. First try to express this with a reusable native mapping object. If the kernel lacks the needed mechanism, add one generic shared-memory/section mapping capability and use it for pipe backing rather than adding spawn/exec-specific VM syscalls. - -Descriptor export uses a logical manifest of unique open descriptions plus FD-to-description references. Port handle transfer is batched within existing limits; do not raise the eight-handle message limit just to transfer a large descriptor table. - -### `posix_spawn()` - -`posix_spawn()` sends construction policy to `posixsrv`; the server allocates a new POSIX PID, builds the native process, installs inherited descriptors, and returns the stable POSIX PID only after the child bootstrap has succeeded. - -Support: - -- `envp`; -- file actions: close, dup2, open; -- process-group attribute; -- signal mask/default attributes once signal state exists; -- cwd inheritance; -- exact open-description sharing and `FD_CLOEXEC` behavior. - -The server applies file actions to the logical descriptor manifest before the new process is released. The child must not briefly execute with the wrong descriptor table. - ---- - -## 34. POSIX signals - -Implement signal policy in `posix.dll`/`posixsrv` over a personality-neutral asynchronous native mechanism. - -Ownership split: - -```text -POSIX process-local/shared runtime state - dispositions - -POSIX thread state - blocked mask - thread-pending set - -posixsrv - process-pending set - process-group routing - target-thread selection policy - alarm/process lifecycle association -``` - -Implement: - -```text -sigemptyset/sigfillset/sigaddset/sigdelset/sigismember -sigaction -sigprocmask -sigpending -sigsuspend -kill -alarm -pause -sleep -signal -raise -``` - -Per-thread registration must let `posixsrv` identify a target native thread capability without trusting user-supplied numeric IDs alone. - -The current kernel already has user APC machinery for asynchronous I/O, but userspace has no generic operation that lets a service queue an asynchronous notification to an arbitrary thread capability. If no existing mechanism can satisfy signal delivery, expose the smallest generic native thread-targeted user-APC/notification primitive with explicit handle rights. Do **not** add `SysKillPosix`, kernel signal masks, or kernel signal numbers. - -Signal handlers run through a POSIX userspace dispatcher. Blocked/process-pending state remains POSIX policy. A generic APC is only the mechanism used to interrupt the selected user thread and enter the dispatcher. - -Translate selected native user exceptions into POSIX signals at the personality boundary, including access/page faults, illegal instruction, and arithmetic faults. Kernel exception codes remain personality-neutral. - -Process-group `kill` routing uses server pgid/session state. Tty-generated `VINTR`/`VQUIT` later reuses the same signal routing path. - ---- - -## 35. POSIX filesystem facade - -P8 is deferred until KFS and SEC establish the native semantics it projects. Do not land the previous synthetic-metadata/fake-hard-link design as an intermediate architecture. - -The revised POSIX filesystem facade should implement: - -```text -rewinddir -creat -umask -link -mkdir -mkfifo -rmdir -rename -access -chmod/fchmod -chown/fchown -utime -pathconf/fpathconf -``` - -Native filesystem identity, open-after-unlink lifetime, hard links, rename replacement, timestamps, directory reset, and security are owned below `posixsrv`. POSIX translates those semantics rather than recreating them. - -### Permissions and ownership - -- uid/gid/group values map to native principal IDs through the POSIX/account layer; -- `chmod()` transforms the owner/group/everyone projection of the native ACL; -- `chown()` updates native owner/owning-group state subject to native privilege checks; -- `access()` evaluates the POSIX view of native authorization; -- `umask` remains POSIX creation-time restriction policy and is not persisted as a kernel concept. - -### Hard links and pathname operations - -`link()` uses the native filesystem hard-link operation. NTFS and ext4 providers implement real links; filesystems such as FAT may return `NTLSTATUS_NOT_SUPPORTED`. POSIX must not maintain fake alias tables or synthetic link counts. - -`rename()` uses native replace-existing semantics and maps cross-volume operations through the canonical native status mapper. `utime()` uses the generic native file-information setter. `rewinddir()` uses native directory reset rather than close-and-reopen-by-path. - -### FIFOs - -`mkfifo()` remains a POSIX namespace/rendezvous feature owned by `posixsrv`; data transport continues to use native pipe endpoints/shared backing. Blocking/nonblocking FIFO-open semantics must not use busy polling. - -Native/RSSRV symbolic links and POSIX filesystem symbolic-link semantics remain separate namespaces/features. - ---- - -## 36. POSIX tty/termios - -Layer POSIX terminal behavior over RSSRV's native console/input mechanisms: - -```text -kernel device input/display - -> RSSRV native console/session - -> posixsrv tty/line discipline - -> POSIX tty open description -``` - -Implement: - -```text -ctermid/ttyname/isatty -cfgetispeed/cfgetospeed -cfsetispeed/cfsetospeed -tcgetattr/tcsetattr -tcsendbreak -tcdrain -tcflush -tcflow -tcgetpgrp/tcsetpgrp -``` - -Initial useful flags/control characters: - -```text -ICANON ECHO ECHOE ISIG -VINTR VQUIT VERASE VKILL VEOF VMIN VTIME -OPOST ONLCR -``` - -Foreground POSIX process-group policy belongs in `posixsrv`; RSSRV retains actual native input/session authority. - -`ISIG`, `VINTR`, and `VQUIT` route through the same server signal mechanism implemented earlier. Do not create a second tty-specific signaling path. - ---- - -## 37. POSIX staged implementation plan - -The following is the active implementation sequence. Finish each stage with focused runtime coverage before beginning the next architectural move. - -### P0 — status boundary and profile audit - -Implementation state: final cleanup bundled with P2. The first P0/P1 patch was runtime-validated. The bundled P2 patch finishes the ABI cleanup by giving POSIX errno a deliberate nt-lite-owned numbering and replacing Windows-numbered native exception codes with compact nt-lite exception classes. Win32 now translates native exceptions explicitly at the personality boundary. Runtime validation of that bundled ABI break is still required before marking P0 complete. - -Goal: make later POSIX APIs fail consistently before adding more calls. - -Work: - -- audit the POSIX profile manifest against exported headers and current implementations; -- explicitly mark `fork()` unsupported and `vfork`/`execve` planned; -- add missing personality-neutral native status distinctions needed by remaining POSIX work, including directory/type errors, non-empty directory, cross-device operation, no child, exec format, long name, too many links, read-only filesystem, and file too large; -- make `PosixStatusToErrno` the one complete POSIX mapper; -- assign public POSIX errno values from one compact nt-lite-owned table rather than preserving accidental Linux/BSD/newlib numeric inheritance; third-party errno domains remain private and translate through `KSTATUS`; -- keep native exception IDs personality-neutral and nt-lite-owned; Win32 maps them to Windows exception values and the later POSIX signal layer maps them to signals; user-raised exceptions carry an opaque personality/application code separately from the native exception class; -- remove POSIX-local ad-hoc status mappings as each distinction becomes canonical. - -Exit criteria: - -- one native status maps to one intentional POSIX errno; -- public errno numbers have one documented nt-lite provenance and no dependency on third-party numeric coincidence; -- native exception IDs contain no Win32 values and every personality translation is explicit; -- no new stage is allowed to add a private errno switch in an API implementation; -- profile manifest exactly describes the starting surface. - -### P1 — `posixsrv` service and protocol skeleton - -Implementation state: complete and runtime-validated. The generic service READY contract exports one public Port listener, init publishes it at `/Services/posix` through RSSRV, and the versioned connection/transaction bootstrap protocol plus native smoke coverage pass. No POSIX process semantics were added to the kernel. - -Goal: establish the shared authority before moving policy into it. - -Work: - -- add the real generic-service startup/READY adapter; -- add init service configuration with explicit RSSRV dependency and `restart=never`; -- define a versioned `posixsrv` Port protocol; -- implement connection lifecycle, kernel-stamped client identity validation, request dispatch, diagnostics, and disconnect cleanup; -- define server-side transaction IDs so large/multi-message spawn/exec/FD transfers are bounded and cancelable; -- provide a native test client before moving POSIX APIs. - -Exit criteria: - -- service starts, reports READY, accepts multiple clients, rejects malformed protocol versions, and cleans up disconnected transactions; -- no POSIX process semantics have been duplicated in the kernel. - -### P2 — stable POSIX PID registry and bootstrap - -Implementation state: started in the bundled ABI-cleanup/P2 patch. `posixsrv` now allocates stable POSIX PIDs, retains an attenuated native process capability to observe genuine termination, recovers the same identity across Port reconnects, and the POSIX runtime registers itself during startup. `getpid()` now reads thread-selected logical POSIX identity. Generic opaque process-loader startup data and reserved POSIX bootstrap token/generation fields are present for later server-owned spawn/exec construction; nonzero bootstrap tokens are intentionally not activated in P2. - -Goal: sever POSIX process identity from native PID identity. - -Work: - -- add server POSIX PID allocator and `POSIX_PROCESS_RECORD` lifecycle; -- add bootstrap token/generation data to userspace process startup metadata; -- register the initial POSIX runtime with the server and cache stable PID in `POSIX_PROCESS_STATE`; -- make POSIX thread state identify its logical POSIX process rather than blindly assuming one process-global identity; -- change `getpid()` to return stable POSIX PID; -- retain/native-open process capabilities needed to observe termination; -- distinguish genuine exit from disconnect/protocol failure. - -Tests: - -- stable PID differs safely from native PID; -- multiple processes receive unique stable PIDs; -- disconnect does not masquerade as process exit, and native process death transitions the correct record; -- reconnecting the same live native generation recovers the same stable POSIX PID; -- thread attachment inherits the same logical POSIX identity; -- generic startup data reaches a child unchanged and reserved zero-token bootstrap framing is validated; -- no `SysGetCurrentPid()` value leaks through `getpid()`. - -### P3 — shared process attributes: cwd, credentials, pgid/sid, identity database - -Goal: establish state needed by spawn, signals, filesystem metadata, and tty before those subsystems use it. - -Current implementation, awaiting runtime validation: - -- `POSIX_PROCESS_RECORD` owns canonical native cwd, uid/euid/gid/egid, supplementary groups, login identity, pgid and sid; -- initial standalone registrations begin as synthetic root and as their own process-group/session leader; -- POSIX `chdir()` updates the native process-local resolver first, publishes the canonical result to `posixsrv`, and rolls back the native cwd if the server commit fails; -- POSIX `getcwd()` reads the server-owned cwd so tests cannot accidentally validate only the native mirror; -- synthetic uid 0 is the explicit credential-changing authority; dropping it through `setuid()` is irreversible with the current API; -- a minimal server-owned identity database provides `root`/`user` and `root`/`users` records; -- `getuid/geteuid/getgid/getegid`, `setuid/setgid`, `getgroups/getlogin`, `getpgrp/setsid`, `getpwnam/getpwuid/getgrnam/getgrgid` are implemented; -- `setpgid` is PARTIAL until P5 establishes authoritative parent/child launch associations; self/session validation is already server-owned; -- `fchdir` is not in the current POSIX.1-1990 public profile and is not introduced merely to satisfy an internal planning bullet. - -P3 runtime gate: - -- server registration reports the correct cwd/credentials/pgid/sid and synthesized login; -- `chdir` followed by server-backed `getcwd` proves cwd synchronization; -- process-group/session leader rejection is correct; -- uid 0 can drop gid/uid and cannot regain authority afterward; -- passwd/group lookup agrees with the synthesized database. - -Inheritance of cwd/credentials and successful parent-driven `setpgid` are P5 acceptance tests because P5 is the stage that makes parent/child launch association authoritative. - -### P4 — transferable descriptors and open descriptions - -Goal: make FD inheritance correct before implementing server-owned process creation, using the SASOS directly rather than introducing a conventional shared-memory mapping API. - -The VM audit established the ownership rule: - -- user virtual addresses are globally meaningful through the shared PML4; -- page faults resolve against the global VAD list, so another process may directly use a pointer into a live owner's VAD; -- native process teardown destroys that process's VADs and frames, so shared POSIX state must not be allocated in an ordinary client if it must outlive that client; -- therefore long-lived shared POSIX control/data allocations belong to `posixsrv`, while each native process retains only process-local capability handles/wrappers. - -#### P4.1 — server-owned shared open descriptions - -Status: **implemented and runtime validated.** - -- protocol v4 adds stable open-description IDs and pointers to `posixsrv`-owned shared control blocks; -- each native process keeps a local wrapper containing its local native handle plus the shared description pointer/ID; -- `FD_CLOEXEC` remains descriptor-local and never enters the shared control block; -- `dup`/`dup2`/`F_DUPFD` remain local and do not create extra server holders; -- `posixsrv` retains one attenuated master capability for file/socket/pipe descriptions and one holder per POSIX process/open-description pair; -- genuine native-process death releases every holder even if userspace did not close its descriptors; -- `F_GETFL` is a direct shared-memory read; -- `F_SETFL` is serialized by `posixsrv`, which changes native file/socket state through the master capability and only then publishes `StatusFlags`; -- `setsockopt(SO_NONBLOCK)` uses that same authority instead of maintaining a second status path; -- P4.1 left the pipe ring VAD client-owned as its one remaining lifetime exception; P4.2 below removes that exception; -- no kernel API or VM change is introduced. - -Validation for P4.1: - -- existing descriptor duplication/file-offset tests must continue to pass; -- existing `F_SETFL` sharing/append tests exercise server-serialized file status; -- socket nonblocking behavior must remain coherent between `F_SETFL`, `setsockopt`, and `F_GETFL`; -- service smoke directly dereferences a `posixsrv`-owned shared description, checks create/set/release accounting, rejects double release, and proves process-death cleanup of an intentionally leaked holder. - -#### P4.2 — server-owned pipe backing - -Status: **implemented; runtime validation pending.** - -- protocol v5 adds `CREATE_PIPE`, reply-side capability transfer, a second open-description result slot, and shared-description data-plane pointers; -- `SysCreatePipe()` is invoked only by `posixsrv`, so the resulting `SYS_PIPE_SHARED_STATE` VAD is owned by the long-lived personality server while clients use the same globally meaningful pointer; -- the process-owned `POSIX_PIPE_MAPPING` lifetime wrapper and client-side `SysFreeVm()` path are deleted; -- `posixsrv` retains the read/write master endpoint capabilities and transfers attenuated aliases to the requesting client; -- ordinary `read`, `write`, `poll`, blocking waits, notifications, and `PIPE_BUF` handling continue to use the shared-ring/native-endpoint fast path with no byte pumping through the server; -- each pipe endpoint shared description points at the same server-owned ring through `SharedObjectData`; -- local wrappers close their native aliases before releasing the server holder, so the server may free backing only after the client no longer owns that endpoint capability; -- the server closes each master endpoint only when its description loses the final holder, preserving kernel-object EOF/broken-pipe semantics; -- the server frees the pipe VAD only after both endpoint descriptions are destroyed; -- `posix_service_smoke` checks two-description accounting, direct shared-state visibility, master/alias close ordering, and final cleanup; existing `posix_pipe_smoke` remains the end-to-end data/readiness/lifetime test; -- no kernel API, VAD, or syscall change is introduced. - -Validation for P4.2 remains part of the combined P4 runtime gate below. - -#### P4.3 — cross-process descriptor export/import - -Status: **implemented; runtime validation pending.** - -- protocol v6 adds bounded descriptor-inheritance snapshots, one-shot import tokens, batched description metadata/capability transfer, and an explicit child-lifetime arm operation; -- a snapshot contains exact descriptor numbers, descriptor-local flags, and stable open-description IDs; shared status and pipe data-plane pointers are not copied or reconstructed; -- `FD_CLOEXEC` filtering is performed while the parent snapshots its descriptor table, and descriptor-local flags remain process-local in the receiver; -- `posixsrv` pins every unique referenced open description while the snapshot exists and creates at most one holder per receiving POSIX process/open-description pair; -- each unique inherited native-backed description receives one fresh process-local capability alias from the server-retained master handle, in batches bounded by `SYS_PORT_MAX_TRANSFER_HANDLES`; -- duplicate inherited descriptors in one process share one local open-description wrapper and therefore one native alias; -- the receiving process reconstructs exact descriptor numbers from the manifest before user entry continues; -- the current partial `posix_spawn()` uses this inheritance transport so P4 can be exercised before P5 moves process construction into `posixsrv`; -- after native child creation, the parent arms the snapshot by transferring a restricted child process capability to `posixsrv`; only that native child PID may claim an armed snapshot; -- an arm monitor contains only `{token, native pid, process handle}` rather than a raw snapshot pointer: child exit before import reclaims the snapshot, while successful import makes the monitor a harmless no-op; -- once armed, source-process exit no longer destroys an unclaimed snapshot; once claimed, the receiver process lifetime owns incomplete-import cleanup; -- `posix_fd_smoke` now exercises two real native processes and verifies shared file offset, cross-process shared `F_SETFL`, `FD_CLOEXEC` filtering/locality, inherited pipe status, cross-process pipe I/O, EOF, and broken-pipe transitions; -- no kernel API, VM, or syscall change is introduced. - -Combined P4 runtime gate: - -- full `all_tests` passes with P4.1 + P4.2 + P4.3; -- `posix_service_smoke` passes shared-description/pipe ownership and cleanup checks; -- `posix_pipe_smoke` passes blocking/nonblocking, EOF, broken pipe, poll, and `PIPE_BUF`; -- `posix_fd_smoke` passes the new cross-process inheritance scenario; -- no regression appears in unrelated VM/page-cache tests; -- after this gate P4 is complete and P5 becomes the active stage. - -### P5 — userspace builder, environment, server-owned `posix_spawn`, and wait - -Goal: make ordinary POSIX child creation entirely server-owned. - -Work: - -- refactor native userspace process construction into a builder API; -- let `posixsrv` resolve/load the executable and retain the native process handle; -- add userspace bootstrap `argv`/`envp` data and implement `environ`/`getenv`; -- allocate the POSIX child PID before starting the image; -- install inherited descriptors and apply close/dup2/open file actions before child release; -- support cwd and process-group spawn attributes; -- register child bootstrap READY before returning the child PID; -- replace the fixed process-local spawn table with server child associations; -- implement `_exit`, `getppid`, `wait`, `waitpid`, and `WNOHANG` over server exit records. - -Tests: - -- env replacement/inheritance; -- file actions including collisions and close-on-exec descriptors; -- parent/child relationship independent of native PID; -- child exits before parent waits; -- multiple children exit out of order; -- `WNOHANG` and `ECHILD` behavior; -- process construction failure leaves no leaked POSIX PID or native process. - -### P6 — signals and generic asynchronous delivery substrate - -Goal: complete signal semantics before exec/vfork need to preserve/reset them. - -Work: - -- implement signal-set operations, dispositions, per-thread masks, pending sets, `raise`, and local dispatch; -- register every POSIX native thread with `posixsrv` using retained/transferred thread capabilities; -- if required, expose a generic thread-targeted user APC/notification syscall and explicit capability right; reuse existing kernel APC machinery rather than adding POSIX semantics; -- implement cross-process/process-group `kill` routing; -- implement `sigsuspend`, `pause`, `alarm`, and `sleep` interaction with pending delivery; -- translate selected native exceptions at the POSIX boundary; -- complete `posix_spawn` signal-mask/default attributes. - -Tests: - -- per-thread blocked masks; -- pending signal becomes deliverable after unblock; -- process-directed delivery chooses an eligible thread; -- process-group routing; -- signal interrupts POSIX blocking operations correctly; -- caught/ignored/default dispositions; -- nested and repeated delivery remains bounded; -- exception-to-signal translation does not alter native exception ABI. - -### P7 — `execve()` replacement and `vfork()` - -Implementation state: **complete and runtime-validated by the full `all_tests` suite.** P7.1 `execve()` replacement, P7.2 borrowed logical-child substrate, and P7.3 public `vfork()` Option A server-monitor finalization are all complete. The generic `SysResumeThreadAndWait` primitive atomically resumes the borrowed thread and waits for its death without a userspace return window. - -Goal: complete the non-fork POSIX process model. - -Work: - -- add EXEC prepare/READY/commit transaction to `posixsrv`; -- implement descriptor export with `FD_CLOEXEC` filtering; -- create replacement native generation behind bootstrap barrier; -- preserve stable POSIX PID, parent, credentials, cwd, pgid/sid, process-pending signals, and inherited descriptors; -- reset/preserve signal state according to exec semantics; -- retire the old native generation without generating child exit; -- implement assembly `vfork` continuation/trampoline; -- create borrowed child thread + logical child POSIX process state and descriptor overlay; -- suspend parent on the borrowed thread handle; -- implement `vfork()+_exit` and `vfork()+execve` retirement handshakes; -- make `fork()` return `-1`/`ENOSYS` if it remains declared at all; do not emulate it. - -Tests: - -- successful exec retains POSIX PID while native PID changes; -- failed exec returns with the old image/process state intact; -- exec does not satisfy parent's `waitpid`; -- `FD_CLOEXEC` filtering; -- environment/argv replacement; -- signal disposition/mask semantics across exec; -- repeated exec generations; -- `vfork` child observes zero, parent observes child PID; -- parent cannot resume while child still uses its borrowed stack; -- `vfork()+_exit` creates a waitable child exit; -- `vfork()+execve` produces an independent running child without native process cloning. - -### P8 — revised POSIX filesystem projection and FIFO namespace - -Status: **paused until KFS and SEC complete the required native substrate.** - -Goal: finish the POSIX filesystem facade by projecting native identity, links, timestamps, directory iteration, and security rather than synthesizing competing filesystem semantics in `posixsrv`. - -Work: - -- map POSIX uid/gid/groups to native principals; -- implement `chmod/chown/access` as projections of native security descriptors; -- keep `umask` as POSIX creation-time restriction policy; -- implement `link()` with the native hard-link operation; -- use native rename-replace and cross-device status; -- use native file-information time mutation for `utime`; -- use native directory reset for `rewinddir`; -- finish remaining path/directory APIs and canonical errno mapping; -- implement FIFO namespace/rendezvous records in `posixsrv`; -- use native pipe endpoint/shared backing transfer for FIFO data transport. - -Tests: - -- native owner/group/mode projection is coherent across reopen and hard-link aliases; -- umask restricts creation without granting rights absent from inherited native policy; -- real hard-link data/link-count behavior; -- open-after-unlink and rename-replacement edge cases; -- timestamp mutation and directory reset; -- FIFO blocking/nonblocking open, data transfer, EOF, broken-pipe, and cleanup. - -### P9 — tty/termios - -Goal: finish interactive POSIX process behavior over RSSRV. - -Work: - -- add tty object/open-description adapter over RSSRV console/session capabilities; -- implement canonical/raw input queues and output postprocessing; -- implement speed/control APIs and drain/flush/flow operations; -- implement foreground pgid state in `posixsrv`; -- route `VINTR`/`VQUIT` through the signal subsystem; -- implement `ctermid`, `ttyname`, `isatty`, `tcgetpgrp`, and `tcsetpgrp`. - -Tests: - -- canonical line completion/editing/EOF; -- raw VMIN/VTIME behavior; -- echo and output newline translation; -- foreground/background process-group behavior selected by the profile; -- `VINTR`/`VQUIT` signal routing; -- disconnect/console loss cleanup. - -### P10 — leaf APIs, cleanup, and POSIX completion gate - -Goal: close the profile rather than leave a nearly-finished personality. - -Work: - -- implement remaining leaf APIs such as `uname`, `time/times`, `sysconf`, and any unfinished `pathconf/fpathconf` cases; -- audit every public POSIX header/export against the profile manifest; -- remove old process-local spawn associations and any compatibility paths replaced by `posixsrv`; -- remove stale local errno mappings and temporary process-construction helpers; -- document deliberate non-support such as `fork()`; -- run the full native + POSIX test suite after each cleanup patch. - -POSIX is complete for this roadmap when: - -- all shared process policy has one `posixsrv` owner; -- `spawn`, `wait`, `execve`, and `vfork` follow the stable-PID model; -- `fork()` is deliberately unsupported with no hidden clone substrate; -- inherited descriptors/open descriptions are correct across processes; -- signals are asynchronous and process-group aware without kernel POSIX policy; -- filesystem metadata/aliases/FIFOs are coherent; -- tty/termios uses RSSRV rather than bypassing native session authority; -- the profile manifest accurately describes every exposed POSIX API; -- the full test suite passes without relying on the shared-PML4 accident for cross-process correctness. - ---- - -# Part VI-A — native ABI, filesystem, and security prerequisites - -## UABI — native interface cleanup before KFS - -Perform the public/native ABI cleanup as separate architectural moves before changing filesystem/security contracts. - -### UABI.1 — fixed-width types, booleans, statuses, and atomics - -- use `` widths where persistent/shared/module interfaces require an exact width; -- use `` and `true`/`false` for genuine predicates; -- use `KSTATUS` for native status-returning APIs rather than raw integer aliases; -- keep POSIX/Win32/UEFI/third-party externally mandated types intact; -- keep compiler `_Interlocked*` conventions confined behind typed `` helpers; -- add ABI layout assertions around important shared structs; -- sweep implementation state and callbacks as well as public syscalls; do not mechanically rewrite ordinary local arithmetic indices. - -### UABI.2 — UTF-8 native text contract - -nt-lite native text is UTF-8. `SYS_STRING_VIEW` lengths are byte lengths. Paths, resource names, module names, thread descriptions, window titles, account names, service names, and other retained native text use UTF-8. Validate malformed UTF-8 at trust/ownership boundaries before namespace or persistent state accepts it. - -Do not impose Unicode normalization or Unicode case folding on native identity. Byte-exact valid UTF-8 remains the native namespace identity unless one subsystem explicitly documents an ASCII-only compatibility rule. Byte payloads remain byte payloads and do not become text merely because they use `char*`. - -Single-character APIs must not pretend one UTF-8 byte is one Unicode character. Where an interface genuinely represents one character, use a Unicode scalar value or a separate text event; keep sophisticated composition/IME work for the input/desktop stack. - -### UABI.3+ — prepared process and stale path deletion - -- replace fixed-size opaque process-builder storage with the prepared-process transaction from Section 33; -- remove the legacy kernel-global input convenience/snapshot syscalls once all callers use RSSRV's capability-scoped logical-input path; -- remove the kernel named-queue publication namespace while retaining anonymous queue objects if still useful; RSSRV remains the global resource namespace owner; -- keep `SysResolveHost` and its existing comment until networking is actually redesigned rather than cosmetically hiding the current lwIP-in-kernel shortcut. - -## KFS — coherent native filesystem substrate - -### KFS.1 — backing identity and coherent file cache - -Introduce an internal refcounted `IO_FILE_BACKING` (name illustrative) distinct from `FILE_OBJECT`. Independent opens retain separate open descriptions/offsets but converge on one backing identity for shared size, page-cache identity, mapped-file coherence, and file security. - -Provider identity is mounted volume + stable `FileId` + generation/reuse discriminator. NTFS MFT sequence and ext4 inode generation should be wired where available. FAT needs a provider-specific identity policy that remains correct across rename. - -### KFS.2 — unlink/open lifetime - -Native unlink removes one directory link. A backing with link count zero survives while native opens/mappings retain it and is reclaimed after the final backing reference disappears. Providers must support this contract rather than having the VFS fake it. - -### KFS.3 — native hard links - -Add one generic capability-relative native hard-link operation. Source and destination authority are independently checked, cross-volume returns `NTLSTATUS_CROSS_DEVICE`, NTFS maps to `ntfs_link`, ext4 maps to `ext4_flink`, and FAT may return `NTLSTATUS_NOT_SUPPORTED`. - -### KFS.4 — rename replacement - -Native rename supports atomic destination replacement when the provider can do so and reports cross-volume operations canonically. Do not preserve adapters that only support rename-to-nonexistent as the generic contract. - -### KFS.5 — generic file-information mutation - -Add one generic setter for mutable file information, including persistent timestamps. Persistent filesystem timestamps use a small UTC timestamp type rather than `SYS_SYSTEM_TIME`, which contains current-clock provenance/timezone fields. - -### KFS.6 — directory reset - -Add provider reset/restart-directory support and a native operation so `rewinddir()` does not close and reopen by pathname after rename. Do not invent seekdir/telldir semantics until required. - -### KFS.7 — validation - -Runtime tests cover alias/cache coherence, independent-open coherence, open-after-unlink, hard-link counts/data identity, rename replacement, timestamps, directory reset, mmap/page-cache aliases, and provider-specific unsupported cases. - -## SEC — native principals, ACLs, authorization, accounts, and audit - -### Security model - -Use one simple native nt-lite security descriptor rather than filesystem-dependent kernel semantics. A descriptor stores concrete owner and owning-group principal IDs plus an access ACL and directory creation policy (`FileAcl` and `DirectoryAcl`). ACL subjects support specific principal IDs plus `OWNER`, `OWNING_GROUP`, and `EVERYONE`. - -For all matching subjects: - -```text -allowed = union of matching allow masks -denied = union of matching deny masks -effective = allowed & ~denied -``` - -ACE order has no meaning; deny always wins. Owner status implicitly grants security-descriptor mutation authority but does not grant object data read/write/delete. - -Authorization is capability-first: ACL/security is checked when authority is acquired/opened. Once a capability is granted, possession is sufficient. Explicit capability transfer/derivation delegates the granted authority without rechecking the receiver ACL. ACL/security changes affect future acquisitions; existing capabilities remain valid unless explicitly revoked. - -### Principal and context model - -Human names remain userspace data. The kernel sees stable opaque `PRINCIPAL_ID`s and immutable/materialized `SECURITY_CONTEXT`s containing primary principal, groups/roles, privileges, restrictions, authentication/session identity, and a unique context/audit ID. Do not reuse current `SecurityId` as an account ID; rename that concept to `SecurityContextId`/equivalent when SEC1 lands. - -A userspace account/authentication service owns name↔principal mapping, password hashes/secrets, group database, account state, profile/home data, and login policy. Access checks never synchronously call that service; group membership is materialized into the security context. - -### Inheritance and filesystem translation - -Directory `AccessAcl` controls the directory itself. Creation policy contains independent file and directory ACL templates. Creation materializes the relevant template into the child; a new directory recursively copies the parent's creation templates unless an authorized creator supplies different ones. Inheritance occurs at creation only. - -POSIX mode translation maps owner/group/other bits to `OWNER`/`OWNING_GROUP`/`EVERYONE`; named POSIX ACL entries map to specific principals and the POSIX ACL mask is folded into effective translated rights. NTFS descriptors translate best-effort into the simpler native flavor; exact Windows round-trip fidelity is not a goal. ext4 ownership/mode/ACL data translates to the same native semantics. - -Security metadata belongs to KFS backing identity, not to a pathname or one `FILE_OBJECT`. - -### Privileges, restriction, and audit - -Do not encode Unix root as a native kernel special case. Use separable privileges such as security administration, take ownership, backup-read, restore-write, context creation, impersonation, raw storage, driver load, process debugging, service administration, and shutdown. POSIX root maps to an appropriate native context. - -Make restricted/sandbox contexts first-class and add kernel-stamped audit records for security-context creation, privilege use, failed access checks, security/ownership changes, capability delegation/revocation, cross-process opens, raw storage/driver/service administration, and similar authority transitions. A userspace service persists/filters audit data. - -### SASOS threat boundary - -The current shared-PML4 SASOS model intentionally does **not** claim inter-process userspace memory confidentiality/integrity. Accounts and ACLs protect kernel-mediated authority; hostile userspace may still inspect live userspace mappings under this model. Do not silently introduce page-table isolation as part of SEC unless that threat-model decision changes. - -### SEC staged sequence - -```text -SEC0 threat model + rights taxonomy -SEC1 principal IDs + SECURITY_CONTEXT v2 -SEC2 native security descriptor/ACL -SEC3 generic access check -SEC4 object-manager authorization -SEC5 filesystem provider security contract -SEC6 ext4 translation -SEC7 NTFS translation -SEC8 creation/inheritance -SEC9 ownership/security mutation -SEC10 privilege architecture -SEC11 restricted/sandbox contexts -SEC12 account/authentication service -SEC13 audit subsystem -SEC14 process/device/service/IPC security sweep -SEC15 personality mappings/migration -``` - ---- - -# Part VII — native status, loader identity, and personality runtime - -## 38. Native status cleanup - -Keep kernel/native status values personality-neutral and make mapping complete at personality boundaries. - -Add missing distinctions as needed, including equivalents for: - -```text -not a directory -is a directory -directory not empty -cross-device operation -no child -exec format error -name too long -too many links -read-only filesystem -file too large -``` - -Then maintain exactly one complete mapper per personality: - -```text -KSTATUS -> Win32 LastError -KSTATUS -> POSIX errno -``` - -The POSIX-required slice is Stage P0 and may be completed before the broader Win32 mapper cleanup. Remove ad-hoc per-call mappings after the canonical mappers cover the necessary status space. - ---- - -## 39. Loader module identity - -Before the planned CRT/personality DLL split, change loaded-module identity from requested basename to resolved file/object identity. - -A loaded image needs: - -```text -resolved file identity -canonical module object -requested-name aliases -reference/import counts -``` - -Two names referring to the same backing image must not create two independent runtime instances. - -This is required before making aliases such as `msvcrt.dll` refer to the same module identity as the nt-lite CRT. - -It also becomes important for kernel driver unload/reload because the kernel loader must know which loaded modules import from which provider modules. - ---- - -## 40. PE32+ subsystem personality selection and Win32 userspace GS shim - -Use the PE32+ Optional Header `Subsystem` value as the loader's initial personality selector. This is userspace image-loader policy; do not teach the kernel PE loader about Win32 or POSIX personalities. - -Initial mapping: - -```text -Subsystem 1 IMAGE_SUBSYSTEM_NATIVE - nt-lite native personality - current SYS_PEB / SYS_TEB layout - initial user GS base -> native TEB - GS:[0] -> native PEB pointer under the native TEB ABI - -Subsystem 2 IMAGE_SUBSYSTEM_WINDOWS_GUI - Win32 personality - Win64-compatible PEB/TEB userspace shim - GUI startup policy - -Subsystem 3 IMAGE_SUBSYSTEM_WINDOWS_CUI - Win32 personality - same Win64-compatible PEB/TEB shape as subsystem 2 - console-oriented Win32 startup policy - -Subsystem 7 IMAGE_SUBSYSTEM_POSIX_CUI - POSIX personality marker - keep native SYS_PEB / SYS_TEB layout - no GS layout change -``` - -Subsystem 7 exists so future loader/runtime policy can identify POSIX images without inventing a new executable format. Finishing POSIX does not require converting existing POSIX runtime state away from the native TEB layout. - -### Win32 process bootstrap - -For subsystems 2 and 3, the userspace process loader loads/chooses a Win32 bootstrap shim and starts that shim before the application's original PE entry point. - -The initial thread may begin with the normal native bootstrap environment so generic nt-lite process startup remains unchanged. The Win32 bootstrap then: - -1. captures any native loader/process data it still needs; -2. constructs the Win64-compatible PEB and initial TEB in userspace; -3. places the Win32 PEB pointer at TEB offset `0x60`; -4. initializes only fields actually required by the compatibility layer/runtime; -5. executes `WRGSBASE` to make the Win32 TEB the application-visible GS base; -6. transfers control to the original image entry point. - -Win32 `CreateThread` similarly uses a userspace thread bootstrap: the native thread begins on its kernel-created stack/native bootstrap TEB, constructs the Win32 TEB using the known native stack bounds/thread ID, switches GS with `WRGSBASE`, then calls the Win32 start routine. - -Raw native `SysCreateThread` remains a native extension. Win32 ABI guarantees apply to threads created through the Win32 shim. - -### Generic FS/GS architectural-state requirement - -FS and GS bases are ordinary per-thread architectural state. The kernel preserves them because the scheduler preserves machine state, not because either register has a personality-specific meaning. - -Keep the kernel-owned native TEB pointer separate from the live architectural bases: - -```text -THREAD.Teb / TebSize - kernel-owned native bootstrap allocation and lifetime metadata - -thread saved machine context - FsBase - GsBase -``` - -At native user-thread creation, initialize `GsBase = THREAD.Teb`. This preserves the existing convenient native ABI: a new thread starts with GS addressing its native TEB and does not need a replacement syscall to rediscover state that was previously ambient. Initialize FS according to the native ABI's existing rule. - -After the first user instruction, there is no invariant connecting `THREAD.Teb` to the current FS or GS base. Userspace may execute `WRFSBASE` and `WRGSBASE` arbitrarily. It may point either base at a personality TEB/TLS block, ordinary process memory, zero, or any other user-selected address. The kernel must not validate or infer personality from those values. - -On kernel entry, `swapgs` places the interrupted user GS base in `IA32_KERNEL_GS_BASE` while active GS addresses trusted per-CPU kernel state. If a context switch occurs in kernel mode, save the outgoing thread's user GS base from `IA32_KERNEL_GS_BASE` into its architectural context and load the incoming thread's saved `GsBase` into `IA32_KERNEL_GS_BASE`. Save/restore FSBASE as normal thread architectural state. The eventual `swapgs` on user return restores the incoming thread's opaque user-selected GS base. - -The kernel may retain `THREAD.Teb` for native bootstrap, APC/runtime bookkeeping, and cleanup, but it must never assert that `THREAD.Teb == GsBase` after thread startup. Do not add `GetTeb`, `SetPersonalityGs`, or similar syscalls merely to replace what userspace can do directly with FSGSBASE. - -Verify/enable CR4.FSGSBASE as generic architectural substrate if it is not already enabled. Kernel code must not use user FS as kernel-private state, and no Win32 or POSIX PEB/TEB structure enters the scheduler. - -### Native helper boundary - -After a Win32 thread switches GS, native inline helpers that assume `SYS_TEB` at GS can no longer be called blindly. The Win32 bootstrap must cache/native-wrap any process-loader state it needs before switching, and Win32-facing libraries must use their Win32 TEB/PEB view. - -Low-level syscall stubs remain GS-independent. Do not add hidden GS swaps around every syscall merely to keep native helper code working inside Win32 processes. - -### Validation - -Future tests must include: - -- subsystem 1 keeps native `GS:[0]` behavior; -- subsystems 2/3 execute ordinary compiled `gs:[60h]` PEB access; -- preemption/migration after `WRGSBASE` preserves the Win32 TEB; -- Win32-created secondary threads receive independent TEBs; -- subsystem 7 selects POSIX personality policy while retaining native PEB/TEB layout; -- unknown/unsupported subsystem values fail deliberately rather than silently selecting native. - ---- - -## 41. CRT and personality DLL split - -After module identity is correct, split monolithic compatibility/runtime modules along real ownership boundaries. - -### Native/POSIX runtime - -```text -crt.dll - malloc/free/realloc - memory/string CRT - stdio - environment - locale - C time bindings - process startup/termination - exit/atexit - setjmp family as appropriate - -posix.dll - POSIX FD APIs - signals - credentials/process groups - termios - POSIX filesystem facade - POSIX extensions -``` - -### Win32-facing split - -Move toward real module names/ownership: - -```text -kernel32.dll -user32.dll -gdi32.dll -ole32.dll -winmm.dll -``` - -The PE subsystem shim chooses personality startup; it does not replace module identity. Aliases belong in loader/module-name policy, not in PE import parsing. - ---- - -# Part VIII — minimal WinDbg KD support - -## 42. Basic serial KD target - -Add a deliberately small WinDbg-compatible KD target after POSIX work. The goal is source/symbol debugging, breakpoints, registers, memory inspection, and panic entry—not Windows kernel emulation. - -### Scope - -Initial supported debugger behavior should be limited to the useful core: - -```text -connect / reset / reconnect -host break-in -exception stop notification -get/set AMD64 register context -read/write virtual memory -software breakpoint insert/remove -continue -single-step -kernel debug print forwarding where practical -minimal kernel/module identity sufficient for symbols -``` - -Physical-memory, MSR, I/O-port manipulation, crash-dump writing, KDNET, and rich debugger extension commands are not initial requirements. - -Do not fabricate `EPROCESS`, `ETHREAD`, NT pool headers, Windows IRPs, or other Windows internals merely so Windows-specific `!process`/`!thread` extensions appear to work. A future nt-lite WinDbg extension may inspect native structures explicitly. - -### Serial transport - -Start with a polling 16550 transport, normally COM1 in QEMU: - -```text -KdSerialPutByte -KdSerialGetByte -KdSerialPollBreakIn -``` - -The debugger transport must not depend on the scheduler, normal serial input driver, interrupt-driven I/O, heap allocation, or ordinary locks. - -COM1 is already used by HAL serial logging and the serial input driver. KD mode therefore claims the selected UART exclusively at boot. When KD owns COM1: - -- HAL must not register the ordinary COM1 text-output endpoint on that UART; -- the normal serial input driver must not bind it; -- pre-attach diagnostics continue through other output paths; -- once attached, debugger-oriented output may use KD DEBUG_IO rather than intermixing raw text with KD packets. - -Use a boot/debug configuration switch to select KD serial ownership. Do not make debugger packet auto-detection race normal console traffic. - -### KD protocol core - -Implement the classic KD packet framing/acknowledgement/reset subset required by current WinDbg serial sessions. Keep protocol definitions isolated under a KD compatibility layer. - -The target translates between WinDbg AMD64 context structures and nt-lite trap/thread context structures. nt-lite internal structures remain canonical. - -Debugger memory accesses must be fault-contained: an invalid WinDbg address returns a failed manipulate request rather than recursively panicking the target. - -### Exception and panic integration - -`#BP`, `#DB`, explicit debugger break, and fatal exception/panic paths may enter KD when it is enabled and attached. - -The normal panic path remains available when KD is disabled or cannot establish a session. KD failure must not make panic diagnostics disappear. - -Host break-in may initially be polled from safe recurring kernel points/idle. Do not require the ordinary serial IRQ driver for Ctrl+Break. - -### SMP rendezvous - -Single-vCPU QEMU is acceptable for first protocol bring-up, but SMP stop-the-world is required before KD is considered complete. - -When one CPU becomes debugger owner: - -```text -owner CPU enters KD - -> send debugger-stop IPI to other online CPUs - -> each CPU saves minimal architectural context and enters a lockless hold loop - -> owner speaks KD -continue - -> publish release generation - -> held CPUs resume -``` - -The rendezvous must use debugger-owned atomics/IPIs only. It must not wait on scheduler mutexes or ordinary kernel objects because KD is expected to work when those systems are damaged. - -### Symbols and module identity - -Expose the smallest synthetic KD version/module view WinDbg needs to associate the running kernel and later drivers with their PE/PDB images. - -The compatibility view is debugger metadata only. It does not make nt-lite module/process/thread objects adopt Windows layouts. - -### KD implementation stages - -**K0 — UART ownership and packet loop** - -- boot-time KD serial option; -- exclusive COM1 claim; -- polling RX/TX with bounded hardware waits; -- packet framing, checksum, ACK/resend/reset; -- deterministic test harness against captured/constructed KD packets. - -**K1 — single-CPU debugger stop** - -- `int3`/debug exception entry; -- AMD64 context translation; -- read/write virtual memory; -- breakpoint add/remove; -- continue and trap-flag single-step; -- WinDbg connection on one-vCPU QEMU. - -**K2 — panic and host break-in** - -- panic/fatal-exception entry when attached; -- safe host break-in polling; -- invalid debugger memory access recovery; -- debugger detach/reconnect behavior. - -**K3 — SMP freeze/release** - -- debugger owner arbitration; -- stop IPI and per-CPU context capture; -- release generation; -- breakpoint/panic testing under SMP load. - -**K4 — symbols/module polish** - -- minimal KD version block; -- kernel module record and driver module notifications as needed; -- PDB packaging/path documentation; -- optional nt-lite-specific WinDbg extension later, outside the initial KD acceptance gate. - -KD is complete for this roadmap when WinDbg can attach over serial to SMP QEMU, break in, stop all CPUs, inspect/modify registers and virtual memory, set/clear a kernel software breakpoint, single-step, continue, and enter on panic without depending on the ordinary serial driver. - ---- - -# Part IX — USER/GDI and display architecture - -## 43. Native desktop stays below Win32 USER/GDI - -DWM is the native session desktop/window manager, not a Win32 server. - -Future USER/GDI libraries translate Win32 ABI semantics onto native DWM/RSSRV/surface facilities. - -Initial useful USER/GDI surface may include: - -```text -CreateWindow / DestroyWindow / ShowWindow / SetWindowPos -BeginPaint / EndPaint -GetMessage / PeekMessage / DispatchMessage / PostMessage -CreateCompatibleDC -CreateCompatibleBitmap -SelectObject -DeleteObject -BitBlt -PatBlt -FillRect -TextOut -``` - -A GDI bitmap may use a kernel surface as backing, but kernel surfaces remain generic pixel storage rather than becoming HBITMAP/HDC/window objects. - -Only introduce `winsrv.exe` if genuinely shared Win32-only server state appears. - -### X11 compatibility service - -Add X11 as a userspace compatibility service over the native desktop rather than teaching DWM or the kernel X semantics. `x11srv.exe` is a sibling of `posixsrv.exe`: `posixsrv` remains the POSIX process/filesystem/signal authority, while `x11srv` owns X protocol state such as XIDs, atoms, properties, selections, grabs, pixmaps, colormaps, and X event routing. - -Target layering: - -```text -X applications - | - libX11 - | - x11srv.exe - | - native DWM/RSSRV/surface protocols - | - kernel surfaces/display/input mechanisms -``` - -Do not require a socket merely to imitate Unix when both endpoints are local. An initial nt-lite `libX11` transport may use native Ports; add TCP/network-transparent X later only as an explicit transport. - -Support two explicit presentation modes rather than pretending they have identical window-manager semantics: - -**Rootless mode** - -- map each X top-level window to a native DWM top-level window; -- keep X child-window hierarchy and X-only resources inside `x11srv`; -- DWM remains authoritative for physical placement, activation, z-order, capture, and outer decorations; -- translate X configure/map/focus requests into native DWM requests and report resulting geometry/state back through X events; -- do not claim that a traditional X window manager is authoritative in this mode; X WM behavior may be constrained or redundant because the native DWM owns the real desktop. - -**Nested mode** - -- expose one native DWM window as the X root desktop; -- keep all X windows, stacking, focus, decorations, and workspace policy inside that root; -- allow traditional X window managers such as `twm` to operate normally within the nested X desktop; -- treat the entire nested X desktop as one ordinary native DWM client from the host desktop's perspective. - -Input remains native-session controlled: RSSRV/DWM choose the native focused/captured target, then `x11srv` translates delivered input into X events and implements X event masks/grabs within the authority granted by the native desktop. X grabs must not become unrestricted native input capture. - -Start rendering with userspace software X rasterization into kernel surfaces. CPU composition remains the correctness baseline; acceleration may replace selected rendering mechanisms later without moving X protocol policy into the kernel or DWM. - -`x11srv` holds whatever native desktop capabilities are required to represent X clients. Ordinary X clients do not receive DWM, physical scanout, input-injection, or display-manager authority merely because they can connect to the X server. - ---- - -## 43A. Native 2D graphics and text layers - -Applications may render directly into mapped surfaces, but ordinary applications should not have to hand-roll clipping, fills, blits, scaling, composition, or glyph-mask rendering. Add a userspace native 2D library without adopting GDI's kernel object/DC model. - -The native 2D context is userspace state only: target surface, clip, transform, and blend mode. Initial operations should cover clear/fill, copy/blit, stretch, alpha composition, clipping, simple geometry, and glyph-mask/glyph-run composition. Direct pixel access remains first-class for games, emulators, X11, Mesa/software renderers, and custom engines. - -Text is a separate layer: UTF-8 decoding, font discovery/loading, metrics, shaping/layout, glyph rasterization, and glyph cache produce glyph runs/masks that the 2D layer composites. Do not make a byte-oriented `TextOutA`-style contract the native text architecture. - -CPU software rendering is the correctness baseline. Initial canonical surface formats should remain intentionally small (for example premultiplied BGRA8888, opaque BGRX8888, and A8 masks) until real requirements justify more. - -Future `gdi32.dll`, the native UI toolkit, DWM decorations, and `x11srv` may all consume these primitives, but none of them owns the underlying surface ABI. - -## 44. Display miniport/raster split - -Refactor the display-driver boundary so hardware miniports do not own generic raster policy. - -### Hardware/video miniport - -Owns: - -- adapter lifecycle; -- mode enumeration; -- mode set; -- framebuffer/VRAM mappings; -- scanout/present; -- vblank/interrupt/fence mechanism; -- hardware cursor; -- optional low-level acceleration/DMA primitives. - -### Generic raster/display layer - -Owns: - -- software copy/blit/fill/composite/stretch correctness paths; -- clipping and format conversion; -- generic raster/present helpers; -- capability selection and substitution of optional hardware acceleration. - -### Optional display/render engine - -Use an NT-3.x-inspired ownership split without importing GDI/DDI semantics. A hardware miniport owns adapter mechanisms; an optional display/render engine exposes acceleration capabilities such as copy, fill, blit, composition, synchronization/fences, and command submission. The generic layer chooses accelerated operations only when advertised and always retains the software fallback. - -Do not expose one syscall per drawing primitive. When real hardware acceleration requires kernel submission, prefer validated batched command buffers referencing explicit surface/context capabilities. Applications do not receive unrestricted GPU MMIO, DMA, VRAM, or physical scanout authority. - -Long-term GPU objects may include device/context, memory, GPU virtual-address space, command submission, and fence/synchronization capabilities. Display (modes, scanout, vblank, cursor) and render (memory, contexts, execution) are separate interfaces even when one driver module implements both. - -Target layering: - -```text -DWM / native gfx / future GDI / x11srv / Mesa - | - generic graphics/display class - / \ - software raster acceleration dispatch - | - display/render engine - | - video miniport - | - hardware -``` - -The compositor/window manager remains userspace and owns composition policy, not hardware mechanism. - -### Experimental modern-GPU ports - -Keep modern-GPU ports as consumers of the native GPU ABI rather than design authorities. A plausible NVIDIA experiment is to port the open NVIDIA kernel-module OS-abstraction boundary onto nt-lite and use GSP where supported, rather than emulate a Linux kernel. For native graphics userspace, prefer a Mesa/NVK path with an nt-lite `nvkmd`-style backend over making Linux DRM the native ABI. Proprietary NVIDIA userspace, CUDA, or media stacks may later use a deliberately boxed Linux/NVIDIA compatibility personality; that compatibility ABI must not leak into native nt-lite graphics interfaces. - ---- - -# Part X — remaining driver cleanup - -## 45. Driver source/module decomposition - -Split only where ownership/lifecycle boundaries are real. - -Priority candidates: - -1. USB into `xhci.sys`, `usb.sys`, `usbhid.sys`, `usbstor.sys` as specified above. -2. xHCI source further into controller/ring/transfer/interrupt pieces if the post-split HCD remains monolithic. -3. NTFS where ntfs-3g integration and nt-lite glue are unnecessarily entangled. -4. VMware SVGA into hardware miniport versus generic display/raster responsibilities. -5. Other very large driver translation units discovered during work. - -Do not split files merely to increase file count. - ---- - -## 46. Input cleanup - -Finish moving repeat/typematic policy into the logical input owner so physical keyboard drivers deliver physical state and RSSRV owns logical repeat behavior consistently. - -Add personality wrappers for thread naming when useful: - -```text -kernel32 SetThreadDescription/GetThreadDescription -pthread_setname_np/pthread_getname_np -``` - -These are small cleanup items and should not block the larger lifecycle work. - ---- - -# Part X-A — native networking and compatibility-driver experiments - -## Native network-device model remains canonical - -The native network stack talks to a neutral `NET_DEVICE`/packet abstraction. Compatibility drivers adapt to it; they do not redefine native packet, DMA, PnP, or network policy. Networking eventually moves protocol processing such as lwIP out of the kernel where practical, using kernel-managed capability/waitable endpoints and efficient shared data transfer analogous to pipes. `SysResolveHost` remains an acknowledged temporary shortcut until that redesign is implemented. - -## NDIS subset - -A useful NDIS subset is reasonable as a compatibility layer. Target NDIS 6 Ethernet **miniport** behavior first; do not initially implement Windows protocol drivers or filter drivers. Translate miniport lifecycle, NBL/NB/MDL packet views, basic OIDs, interrupts, DMA, PCI/MMIO, timers/work, and status indication onto native nt-lite mechanisms. Start with copying packet translation; optimize later. - -Binary `.sys` compatibility additionally requires a boxed Win32 kernel-driver environment (`ntoskrnl`/HAL/NDIS imports, PE driver loading, observable IRQL constraints, configuration/registry projection, and possibly framework dependencies). Keep that namespace under a compatibility subsystem rather than exporting fake Windows APIs as native kernel interfaces. - -Do not begin with Wi-Fi, protocol/filter drivers, SR-IOV, advanced offloads, or KMDF. A first success criterion is an unmodified direct NDIS 6 Ethernet miniport reaching DHCP/ping through the native stack. - -## Native Wi-Fi ports and reverse engineering - -Full binary compatibility for KMDF + NetAdapterCx + WiFiCx is a distant research target, not a prerequisite. A much more reasonable path for unsupported vendor hardware is machine-assisted reverse engineering followed by a clean native port: - -```text -vendor binary + metadata - -> hardware/firmware protocol specification - -> native nt-lite driver -``` - -Recover firmware loading/boot, register programming, DMA/ring descriptors, interrupts, firmware command/event formats, TX/RX queue mechanics, scan/connect/key operations, and reset/recovery. Do not mechanically transliterate decompiler output or preserve Windows framework object lifetimes. - -Keep Wi-Fi policy primarily in a future userspace `wifisrv`: discovery, saved networks, credentials, association/roaming policy, and UI-facing state. The kernel driver owns time-critical hardware/firmware mechanisms, TX/RX, primitive scan/radio/key operations, and events. The exact split may tighten where firmware timing requires it. - ---- - -# Part XI — implementation sequence - -## 47. Recommended execution order - -P0-P7 are complete; P7 is runtime-validated by the full suite. The active work is now the generic substrate that must exist before POSIX P8 can be correct. Do not land synthetic P8 architecture merely to keep the old stage numbering moving. - -### Active milestone — native ABI cleanup - -Execute UABI.1-UABI.4 as independent reviewable changes. UABI.1 is a whole-tree semantic type sweep, not merely public-syscall cosmetics; real MSVC build/runtime validation remains the gate. UABI.2 makes UTF-8 the explicit native text contract. UABI.3 removes the fixed-size process-builder storage. UABI.4 deletes stale duplicated namespaces/input paths after callers are migrated. - -### Active milestone after UABI — KFS then SEC - -Execute KFS.1-KFS.7 before filesystem ACL integration. Then execute the SEC sequence so file security attaches to coherent backing identity. Revisit rights taxonomy before freezing hard-link/open/security syscalls. - -### Resume POSIX after native prerequisites - -```text -P8 revised filesystem projection + FIFOs -P9 tty/termios -P10 leaf APIs/profile closure -``` - -Do not start the future Win32 GS shim, KD, NDIS compatibility, or experimental GPU ports during this sequence unless the active native milestone independently needs the same generic primitive. - -### Parked milestone A — `drivers.ini` v2 parser and graph manager - -- define public/internal config structures; -- extend `BOOT_INFO` with raw `drivers.ini` and explicit boot-driver identities; -- replace ordered-list UEFI parsing with minimal descriptor preload scanning; -- implement strict kernel parser; -- implement class and driver graph validation; -- implement state machines and deterministic scheduler; -- initialize current drivers through the graph; -- remove ordered `[boot]`/`[runtime]` initialization paths. - -### Parked milestone B — graceful driver/device shutdown - -- define Prepare/Commit shutdown interfaces; -- add module/device lifetime accounting required by shutdown; -- implement reverse effective-graph shutdown; -- implement runtime device-topology shutdown traversal; -- flush/freeze filesystems and caches; -- stop storage/network/audio/display/USB hardware and DMA; -- add bounded timeouts and shutdown summary diagnostics; -- call HAL S5 only after lifecycle shutdown completes. - -### Parked milestone C — USB split - -- extract xHCI HCD; -- establish neutral HCD device/interface contract; -- extract generic USB core; -- extract USB HID; -- extract USB storage; -- place all four into lifecycle classes; -- verify QEMU USB input/storage and real-hardware USB paths; -- verify shutdown ordering through `usb-storage -> usb -> usb-hcd`. - -### Parked milestone D — unload/reload substrate - -- module import/reference tracking; -- PnP/device ownership accounting by module; -- real `DriverUnload` callers; -- manual unload/reload; -- optional cascade reload; -- only then add `start=demand`. - -### Parked milestone E — finish native desktop migration - -- render RSSRV console through a surface/window; -- make desktop focus the normal console/GUI transition; -- delete stale direct application input/console/display paths; -- improve damage regions; -- add title/close/move/resize/decorations; -- define DWM restart/loss behavior. - -### Future milestone F — native status remainder, loader identity, and personality selection - -- complete non-POSIX KSTATUS/Win32 mapping cleanup; -- resolved-file module identity; -- import/reference tracking; -- PE subsystem personality selection; -- generic mutable user-GS persistence; -- userspace Win32 PEB/TEB bootstrap for subsystems 2/3; -- subsystem 7 POSIX marker with native layout; -- `crt.dll`/`posix.dll` and Win32-facing DLL ownership split. - -### Future milestone G — minimal serial KD - -- K0-K4 from Section 42; -- keep transport independent of ordinary COM1 logging/input; -- stop at basic WinDbg source/symbol debugging before considering KDNET or Windows-specific extension compatibility. - -### Future milestone H — richer USER/GDI - -- build USER/GDI over native DWM/RSSRV protocols; -- retain kernel surface distinction; -- optimize only after functionality/profiling establishes hot paths. - -### Future milestone I — X11 compatibility service - -- add `x11srv.exe` as a sibling userspace service rather than putting X protocol state in `posixsrv` or DWM; -- implement local `libX11` transport over native IPC first; -- implement nested mode with one native DWM window as the X root and full traditional X window-manager authority inside it; -- implement rootless mode mapping X top-levels to native DWM windows while retaining native DWM authority over physical desktop policy; -- keep X child windows/resources, selections, properties, grabs, pixmaps, and event routing inside `x11srv`; -- translate native input/capture into X semantics without giving X clients ambient native input authority; -- render through native surfaces with software rasterization as the initial correctness path; -- add network-transparent/TCP X only after the local server and security model are stable. - -### Future milestone J — display stack split and remaining driver cleanup - -- hardware miniport versus generic raster/display layer; -- VMware SVGA cleanup; -- NTFS/source decomposition where justified; -- remaining large-driver ownership cleanup. - -### Future milestone K — native 2D/text stack and accelerated graphics - -- userspace native 2D raster library with direct-surface escape hatch; -- native UTF-8 text/font/shaping/glyph stack; -- shared theme schema for DWM chrome and native UI toolkit; -- optional display/render acceleration interface with software fallback; -- batched validated command submission only when real accelerated hardware needs it; -- later Mesa/native Vulkan WSI work over the native GPU ABI. - -### Future milestone L — network-driver compatibility experiments - -- narrow NDIS 6 Ethernet miniport compatibility layer over native `NET_DEVICE`; -- prove one unmodified miniport through DHCP/ping before expanding; -- keep KMDF/NetAdapterCx/WiFiCx binary compatibility explicitly experimental; -- prefer clean native Wi-Fi ports derived from documented/reverse-engineered hardware/firmware protocols; -- introduce `wifisrv` when native Wi-Fi policy work begins. - -### Experimental milestone M — modern NVIDIA path - -- port the open NVIDIA kernel-module OS abstraction only if it fits the native GPU interfaces; -- bring up PCI/BAR/GSP/interrupt/modeset before render acceleration; -- prototype Mesa/NVK with an nt-lite kernel backend before attempting proprietary Linux graphics userspace; -- keep any Linux/NVIDIA proprietary userspace compatibility environment isolated from the native GPU ABI. - ---- - -# Part XII — validation matrix - -## 48. Driver-manager tests - -Add deterministic unit/kernel tests for parsing, graph validation, state semantics, and boot handoff: - -- valid empty dependency lists; -- duplicate section/key rejection; -- unknown key rejection; -- malformed IDs; -- class-after-driver rejection; -- missing key rejection; -- class/driver/combined cycles; -- missing or disabled providers; -- boot dependency available only at runtime; -- provider alternatives with mixed success/failure; -- `requires` versus `after` settlement behavior; -- critical blocked-driver startup failure; -- configured boot blob identity matching and raw-config handoff. - ---- - -## 49. Shutdown tests - -Create synthetic drivers for successful prepare/commit, prepare failure/timeout, commit failure, draining workers, outstanding IRPs, and DMA/interrupt state. - -Verify reverse graph order, storage flush-before-controller shutdown, USB-storage/HID/core before HCD, network stack before adapters, and eventual HAL shutdown after bounded noncritical failures. - ---- - -## 50. Reload tests - -Before enabling reload generally, test: - -- unload with no devices; -- unload with attached devices rejected or properly removed; -- unload with outstanding IRP; -- unload with active import dependent rejected; -- reload success and DriverEntry failure; -- dependent closure calculation/cascade order; -- class availability changes and rebinding. - ---- - -## Current runtime regression investigation — DOOM first-frame stall - -A current DOOM run reaches `doom: entering game loop` and the asynchronous SoundFont loader reaches `doom: MIDI synthesizer ready`, but no frame becomes visible afterward. The first game-loop path contains three blocking boundaries worth isolating before changing architecture: display-clock pacing, the first RSSRV logical-input query, and acquisition of the audio/core mutex. Use one-shot first-frame diagnostics and a bounded display-clock wait to identify the exact boundary. Do not attribute the stall to MIDI merely because the last log line comes from the asynchronous synth-loader thread. - -## 51. Desktop tests - -Extend desktop tests to cover console-window visibility/focus, close, move/resize, capture, relative lock, damage clipping, overlapping composition, disconnect cleanup, and the chosen DWM crash/restart contract. - -### X11 compatibility tests - -When `x11srv` is implemented, cover: - -- nested X root creation inside one native DWM window; -- a traditional X window manager moving/decorating/stacking clients correctly in nested mode; -- rootless top-level creation, map/unmap, configure, focus, and close translation through DWM; -- X child windows remaining server-internal rather than becoming native windows; -- X grabs bounded by native DWM/RSSRV capture authority; -- selections/properties and disconnect resource cleanup; -- software raster output and damage propagation to native surfaces; -- rootless mode retaining DWM authority when an X client or X WM requests conflicting geometry/z-order; -- X clients having no physical scanout, global input-injection, or unrelated native-window authority. - ---- - -## 51A. UABI/KFS/security tests - -UABI gates include standalone compilation of every owned public header, ABI-layout assertions, compiler-warning-clean builds across changed callers, explicit checks that compatibility/vendor ABIs were not mechanically rewritten, and the full runtime suite after each ABI break. UTF-8 tests cover malformed sequences, boundary validation, embedded-NUL policy, and byte-exact namespace behavior. - -KFS tests cover independent-open/cache/mmap coherence, unlink with live opens, real hard-link counts/data identity, rename replacement/cross-device errors, timestamp mutation, and directory reset. Security tests cover order-independent deny-wins ACL evaluation, owner security-mutation authority without implicit data authority, creation-time inheritance, existing-capability survival across ACL changes, explicit revocation, principal/group materialization, provider translation, restrictions/privileges, and audit records. - -## 51B. Graphics/network compatibility tests - -Graphics tests retain CPU software rendering as the reference result and compare accelerated fill/copy/composite paths against it. Validate that ordinary applications cannot acquire physical scanout/MMIO/DMA authority, and that DWM policy remains unchanged when the backend changes. - -NDIS experiments must prove lifecycle, PCI/MMIO, interrupt, DMA, RX/TX, basic OIDs, link state, clean halt/unload, and DHCP/ping through the native stack before adding wider compatibility. Native Wi-Fi ports stage firmware boot, event/command transport, scan, RX/TX, association, key installation, DHCP/ping, reset, and suspend/resume. - -## 52. POSIX tests - -The POSIX suite is stage-gated, not one final smoke test. - -### Process identity and construction - -- stable POSIX PID independent of native PID; -- `getppid` launch association; -- spawn environment and file actions; -- inherited descriptor/open-description sharing; -- `wait`/`waitpid`/`WNOHANG` and child collection; -- failed spawn cleanup; -- successful exec retains POSIX PID while changing native generation; -- failed exec leaves the old image usable; -- repeated exec; -- `FD_CLOEXEC` filtering; -- `vfork()+execve` and `vfork()+_exit`; -- explicit `fork()` unsupported behavior if exported. - -### Signals - -- per-thread masks and pending sets; -- process-directed target selection; -- process-group routing; -- handler/default/ignore transitions; -- signal interruption of waits/I/O; -- exec disposition/mask behavior; -- exception translation. - -### Filesystem facade - -- POSIX owner/group/mode projection over native principals/ACLs; -- umask creation-time restriction; -- native hard-link aliases and link counts; -- open-after-unlink and rename-replacement behavior; -- timestamp mutation and directory reset; -- pathconf/fpathconf; -- FIFO open/rendezvous, nonblocking, EOF, broken-pipe, cleanup. - -### Tty/termios - -- canonical/raw behavior; -- VMIN/VTIME; -- echo/editing/output postprocessing; -- foreground process groups; -- VINTR/VQUIT signal routing. - -### End-to-end integration - -Run a shell-like scenario: - -```text -parent POSIX process - -> pipe - -> spawn producer with envp + dup2 file action - -> spawn consumer in a process group - -> signal the group - -> waitpid children - -> exec replacement retaining parent POSIX PID -``` - -The full native `all_tests` suite must still pass after every POSIX stage. - ---- - -## 53. PE subsystem/personality tests - -Future personality tests must verify subsystem 1 native layout, subsystem 2/3 Win32 `gs:[60h]`, GS persistence across preemption/migration, independent secondary-thread TEBs, subsystem 7 native-layout POSIX selection, and deliberate rejection of unsupported subsystem values. - ---- - -## 54. KD tests - -Future minimal KD validation must cover: - -- packet checksum/ACK/resend/reset unit tests; -- COM1 exclusive ownership in KD mode; -- WinDbg serial attach on one-vCPU QEMU; -- register read/write; -- valid and invalid virtual-memory access; -- software breakpoint add/remove; -- single-step/continue; -- host break-in; -- panic entry; -- reconnect after debugger reset where supported; -- SMP stop/release with workload on every CPU; -- no dependency on the ordinary serial input/logging driver while stopped. - ---- - -# Part XIII — architectural acceptance criteria - -## 55. Driver subsystem - -The driver lifecycle overhaul is complete when file order cannot affect startup semantics, the bootloader only preloads boot images/raw config, the kernel owns validation/scheduling, startup reverses into graceful shutdown, shutdown waits are bounded, and unload/reload/demand loading use the same graph/lifetime model. - -## 56. Desktop/session subsystem - -The desktop architecture reaches its next stable point when console is rendered through DWM as native desktop content, RSSRV remains console/input/session authority, DWM remains graphical policy, stale ambient application paths are gone, and DWM crash/restart behavior is explicit. - -## 57. POSIX subsystem - -The POSIX personality is complete for this roadmap when: - -- `posixsrv` owns stable POSIX PIDs and all genuinely shared POSIX policy; -- native PID/process generations are replaceable implementation details; -- `fork()` is deliberately unsupported and no COW/process-clone substrate exists for it; -- `vfork()` and `execve()` use the server-owned model in Section 31; -- `posix_spawn`, wait, environment, and descriptor file actions are complete; -- inherited open descriptions remain coherent across processes; -- signals are asynchronous, per-thread-mask aware, and process-group routable without kernel POSIX semantics; -- tty, identity, and process groups/sessions have one coherent POSIX owner while filesystem identity/links/security remain native KFS/SEC authority; -- POSIX filesystem permission/link/time APIs project native semantics and FIFOs remain coherently owned by `posixsrv`; -- the POSIX profile manifest accurately describes every exposed API; -- full native and POSIX runtime suites pass. - -## 58. Runtime/personality subsystem - -The future loader/runtime cleanup is complete when: - -- module identity is backing-file/object based; -- imports create real provider references; -- PE subsystem selects native, Win32 GUI/CUI, or POSIX personality intentionally; -- subsystems 2/3 receive userspace Win64 PEB/TEB state with persistent user GS; -- subsystem 7 remains native-layout POSIX unless deliberately redesigned; -- aliases resolve to one module instance; -- kernel module unloading can prove no import/lifetime references remain; -- `crt.dll`, `posix.dll`, and Win32-facing DLLs have distinct ownership boundaries. - -## 59. Minimal KD subsystem - -Basic KD support is complete when WinDbg can attach to SMP QEMU over an exclusively-owned polling serial transport, break in, freeze all CPUs, inspect/modify AMD64 registers and virtual memory, set/clear a software breakpoint, single-step, continue, and enter on panic while nt-lite retains its own kernel object layouts. - - -## 60. X11 compatibility subsystem - -X11 compatibility is complete for this roadmap when `x11srv` remains a userspace translation service over the native desktop, nested mode supports a conventional X root and traditional X window manager, rootless mode integrates individual X top-levels without surrendering native DWM authority, X-specific state remains out of the kernel/DWM, native input/capture authority is preserved, and local X clients can render through native surfaces without receiving ambient desktop/display authority. - ---- - -## 61. Native filesystem/security prerequisite subsystem - -The KFS/security prerequisite reaches its stable point when independent opens and aliases share one coherent backing identity; unlink/open lifetime, real hard links, rename replacement, timestamps, and directory reset are native provider contracts; file security attaches to backing identity; ACL evaluation and principal contexts are generic kernel concepts; account secrets/names remain userspace-owned; and POSIX/NTFS/ext4 all translate into one native security flavor rather than defining competing kernel models. - -## 62. Native graphics subsystem - -The graphics architecture reaches its stable point when mapped surfaces remain the fundamental client rendering ABI, the optional native 2D/text libraries do not require kernel GDI objects, software rendering is a complete correctness path, hardware miniports expose mechanisms rather than UI policy, accelerated render/display engines are optional substitutions, DWM remains userspace policy, and future Mesa/GDI/X11 backends can target the same native primitives. - -## 63. Compatibility-driver experiments - -NDIS, Linux/NVIDIA userspace, KMDF-class frameworks, and reverse-engineered vendor drivers remain compatibility or porting projects layered over stable native DMA/PnP/network/GPU primitives. Success in one compatibility target must not make its foreign ABI the native nt-lite contract. - -# Target architecture - -```text -UEFI loader - | - | loads kernel/HAL + configured boot driver blobs - | passes raw drivers.ini without dependency policy - v -kernel - | - | generic mechanisms only: - | process/thread/VM/handles/Ports/APCs/waits - | driver/device lifecycle - | native exception/trap context - | - +------------------------- init.exe -------------------------+ - | | - | rssrv.exe - | / | \ - | / | \ - | posixsrv.exe x11srv.exe dwm.exe - | | / \ | - | stable POSIX policy libX11 native DWM -----+ - | | | | | - | posix.dll X/POSIX native gfx/text | - | | apps | | - | POSIX apps native UI future USER/GDI - | - +-- future auth/account service -> principal names/secrets/groups -> kernel SECURITY_CONTEXT creation - +-- future wifisrv -> Wi-Fi discovery/association/credential policy -> native Wi-Fi device class - | - +-- userspace PE loader - | | - | +-- subsystem 1 -> native SYS_PEB/SYS_TEB - | +-- subsystem 2 -> Win32 GUI shim -> Win64 PEB/TEB via WRGSBASE - | +-- subsystem 3 -> Win32 CUI shim -> Win64 PEB/TEB via WRGSBASE - | +-- subsystem 7 -> POSIX marker, native PEB/TEB layout - | - +-- optional KD core - | - +-- exclusive polling 16550 serial transport - +-- WinDbg compatibility packets/context view - +-- SMP debugger stop/release -``` - -The important separation is: - -```text -configuration graph = module lifecycle policy -runtime device graph = actual hardware/device relationships -services graph = user-mode service lifecycle -RSSRV session state = native session authority -DWM = replaceable graphical policy -posixsrv = stable POSIX identity and shared POSIX policy -KFS backing = native file identity/cache/lifetime/security attachment point -SECURITY_CONTEXT/ACL = kernel-mediated principal authority, not account-name/password storage -DWM = native window/composition policy; native gfx/text remains optional userspace rendering support -native NET_DEVICE/GPU = canonical hardware abstractions; NDIS/NVIDIA/Linux compatibility stays outside them -PE subsystem selector = userspace personality bootstrap policy -KD compatibility view = debugger protocol metadata, not Windows kernel architecture -``` - -Each graph/state domain has one owner and one purpose. Personality implementations may request generic native mechanisms, but they do not move their policy into the kernel. diff --git a/readme.md b/readme.md index c57fae9..0720307 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,5 @@ # NT-Lite -- an x86-64 kernel -“Not only is UNIX dead, it’s starting to smell really bad.” - Rob Pike - “UNIX has three operations: read, write, and lie through ioctl.” ## what is it? @@ -10,55 +8,22 @@ NT-Lite is a 64-bit operating system kernel written from scratch. It boots into a shell, launches programs, runs Doom, talks TCP/IP, and occasionally reminds you that it is still a hobby kernel. -## features - -- UEFI bootloader -- real SMP -- VAD-based virtual memory, file-backed and anonymous mmap, CoW, reclaim -- single address-space design with user/supervisor page permissions -- preemptive priority scheduler -- capability-style handles with rights checks, derivation, and revocation -- rich synchronization primitives -- IRQL, DPCs, kernel APCs, and user APCs -- user exception handling with structured and vectored handlers -- async file I/O with events, IOSBs, APC callbacks, and cancellation -- capability-relative VFS with IRPs -- refcounted mounted volumes with partition arrival/removal -- page cache with dirty writeback and invalidation -- USB bus, xHCI, mass storage, hubs, hotplug, and HID -- audio -- VMware SVGA display driver -- dynamic driver loading -- native BSD-ish socket syscalls -- DNS resolver syscall -- four-register syscall ABI with sized request structs for larger calls -- partial Win32 personality -- partial (microsoft-level) POSIX personality -- Doom -- video playback -- fully GDPR-compliant -- everything but the kitchen sink - ## paths You thought it would be easy, would you? ```text -"apps/shell_native.exe" -> current working directory, resolved in userspace -"/SystemRoot/apps/shell_native.exe" -> system volume through the resource namespace -"/Device/HarddiskVolume1/boot.ini" -> system volume through the resource namespace +"apps/shell_native.exe" -> current working directory +"/SystemRoot/apps/shell_native.exe" -> system volume, symlink to /Device/HarddiskVolumeN +"/Device/HarddiskVolume1/boot.ini" -> system volume "C:\apps\doom.exe" -> /SystemRoot/apps/doom.exe through win32.dll "\apps\doom.exe" -> /SystemRoot/apps/doom.exe through win32.dll "/apps/video.exe" -> /SystemRoot/apps/video.exe through posix.dll ``` -Kernel filesystem syscalls are capability-relative; userspace owns the current working directory and resolves named resources through `rssrv`. - -`/SystemRoot` is the configured boot volume. Other mounted volumes are discovered through the volume manager and opened by capability. - ## networking -The socket syscalls are BSD-shaped, not BSD-compatible. Same nouns (AF_INET, SOCK_STREAM, IPPROTO_TCP), none of the baggage: +The socket syscalls are BSD-shaped, but not BSD-compatible. Same nouns (AF_INET, SOCK_STREAM, IPPROTO_TCP), none of the baggage: one fixed-size tagged address struct instead of the sockaddr_in/sockaddr_storage casting dance, no htons/ntohl conversions, and DNS resolution is a syscall rather than a userspace library call. That's right microkernel purists, cry some more. Ergonomics. diff --git a/roadmap.md b/roadmap.md new file mode 100644 index 0000000..ace5c10 --- /dev/null +++ b/roadmap.md @@ -0,0 +1,225 @@ +# nt-lite — remaining roadmap + +**Status:** working plan; remaining implementation, integration, and validation only. +**Scope:** lean-boot verification, the Linux-derived USB/SCSI/HID/input replacement, parallel driver/services startup, then deferred native filesystem/security, POSIX, desktop, personality, debugging, graphics, and driver compatibility work. + +## Current state and evidence + +- **Latest boot result:** user reports roughly **8 seconds**, **with tests enabled**. This is a substantial improvement over the earlier approximately 96-second ThinkPad run. Exact build flags and measured interval were not supplied; retain the report as an observed result, not a verified per-stage benchmark or proof that all regressions are fixed. +- **Implemented foundation:** POSIX P0–P7 (including server-owned `execve()` and `vfork()`), native UABI.1–UABI.4 cleanup, `drivers.ini` v2 class/exact-dependency parsing and driver-state management, driver/device prepare/commit shutdown, and prior USB/display shutdown-lifetime fixes. Preserve and test these contracts rather than rewriting them. +- **Lean-boot work:** a patch implementing reduced/gated boot checks, deletion of the unconditional SMP scheduler self-test, central monotonic `DbgPrint` timestamps, lightweight default debug flags, and opt-in test packaging was prepared (`ntlite_lean_boot_after_usb_lifetime.patch`). Before marking every component integrated, compare it against the *actual current tree*; the user-reported eight-second result is not a substitute for checking what was applied. +- **Graphical system:** native DWM/surfaces and DOOM integration work, but native console/window management, shell policy, and restart semantics are still incomplete. +- **Desktop PC unresolved:** AMD xHCI `1022:43d5`/`1022:149c` stalls with both external hubs connected. Latest supplied build marker `phase2-usb-rundown-20261022` reached `root hub started ... scan=queued` and `initial scan wake returned` for `28:00.3` before the log ended; source patches have yet to establish a hardware fix. +- **ThinkPad T14 Gen 5 (AMD, machine `21MDS30F00`):** four xHCI PCI functions (`1022:15b9`, `15ba`, `15c0`, `15c1`), recurring USB control-transfer timeouts and hub-attached child descriptor failures. A directly connected `09da:2268` boot keyboard works despite a secondary nonboot HID interface returning PnP unsupported; the built-in keyboard/TrackPoint provide no confirmed input events. Linux identifies i8042/serio keyboard and TrackPoint, plus `ELAN0676` HID-over-I²C on `AMDI0010:01`. The Unifying receiver (`046d:c52b`) has boot mouse capability, so report parsing alone does not explain its missing input. ACPI fixed power button is unavailable and AML event handling remains open. +- **No claim of a real integration test** follows from syntax checks, patch apply checks, or host models. Use the current real-hardware logs and fresh checkout for the next change. + +## Operating decisions + +1. Favor one clean owner and one native interface per concern. Breaking obsolete APIs is acceptable; delete stale callers and compatibility paths as part of a change. +2. **Normal boot is not a regression harness.** Runtime failure checks remain mandatory, but deterministic regression, synchronization stress, and intrusive diagnostics must be explicit. Avoid creating a new standalone test for every feature. +3. Use centrally timestamped `DbgPrint` records to find waits before adding profiling frameworks or blindly parallelizing work. Times must be monotonic elapsed values, prefixed once per logical line in both console/file output, safely under concurrent writers. Pre-clock logging may remain unprefixed. +4. No permanent vendor/model shortcut in place of standard hardware protocols. Use imported Linux state machines where practicable; otherwise implement the narrowest native component to Linux/spec behavior, with tests and recorded provenance. Protocol-defined fallback remains legitimate. +5. The driver/module dependency graph and the runtime PnP device topology are distinct. `requires` needs provider **success**; `after` needs provider **settlement**, regardless of success. Reverse dependency order governs graceful shutdown. +6. The user-mode service manager should be *generic*: **RSSRV is an ordinary critical service with declared bootstrap resources and authority**, not `type=resource` and not selected by hard-coded executable name. No service gains privileges merely because it uses a familiar image/ID. +7. A service's privilege mask and granted capabilities are separate: use minimal named ambient privileges where unavoidable; delegate scoped handles for actual resources. The current shared-address-space/SASOS model does not promise hostile-user-process memory isolation. +8. Prioritize a usable, correct system. Do not keep testing, scaffolding, or instrumentation merely because it already exists. Preserve only focused coverage for genuinely subtle regressions. + +--- + +# Active modernization: three phases + +## Phase 1 — Close lean boot and make delays observable + +**Status:** substantially progressed; verification and any measured residual stalls remain. + +**Work** + +- Verify that the current source contains the intended self-test cleanup: + - Rename `KiRunCoreHardeningSelfTests()` to a modest `KiCheckBootState()` (or equivalent), containing only cheap indispensable consistency checks, gated by the existing explicit diagnostic flag. + - Remove `KiRunEarlyKernelSelfTests()` and its unconditional call; do not run 64 rounds of AP-affine scheduler workers as part of boot. + - Heavily prune debug-only `KiRunKernelSelfTests()`; retain at most focused, uniquely valuable concurrency/memory tests (the prepared patch keeps APC wait-race and COW coverage). Remove in-kernel `STRINGVIEW`, trap-frame, filesystem/path, async-file, extensive scheduler, PFN/DMA/PTE/VAD and similar redundant tests instead of blindly relocating everything. + - Keep actual failure handling and initialization assertions where correctness requires them. Compile-time ABI layout checks belong in compile-time assertions. +- Verify the central `DbgPrint` monotonic timestamp implementation, across logical lines and persisted `kernel.log`; guard early/pre-clock and concurrent output without allocation or blocking hot paths. +- Default to light diagnostics (`debug=0x004` for file logs, optionally `0x005` with console mirroring); reserve intrusive flags and `0x002` regression diagnostics for explicit investigations. Do not assume a user must always use one particular mask. +- Normal build must not compile/package the fleet of test executables into `/tests` by default. The proposed build option is `--with-tests`. Prune duplicate/low-value tests selectively; preserve a runnable development suite. +- Use timestamps to distinguish firmware/EFI loading, CPU admission, PCI enumeration, storage readiness, USB timeouts, and service READY latency. Measure cold boots on both QEMU and real machines. Profile 256-bus PCI probing before replacing it; prefer real bridge-topology discovery when justified, while preserving access to buses `c4` and `c6`. + +**Done when:** ordinary boot invokes no regression/stress suite; debug-mode tests are opt-in; logs identify meaningful delay intervals; production images exclude test programs by default; the ~8-second result is reproducible with a defined start/end point and actual flags. This phase is **not** blocked on achieving an arbitrary sub-second boot time. + +## Phase 2 — Linux-derived USB, SCSI, block, HID and input replacement + +**Status:** architecture approved in principle; source import, compatibility runtime, new module graph, and hardware validation remain open. Existing `usb.sys` is a diagnostic baseline, **not** the implementation to extend feature by feature. The code already booted with direct USB storage and boot keyboards on some runs, but desktop hub hangs, ThinkPad full-/low-speed control timeouts, hub-child enumeration, generic HID coverage, and built-in input remain unresolved. Earlier patch application and syntax checks do not establish hardware fixes. + +**Implementation rule:** prefer a pinned, upstream Linux implementation for USB, xHCI, HID, input, SCSI and its necessary block support. Port closely where possible, adapt through private runtime/platform layers where needed, and rewrite the *smallest problematic dependency or native boundary* against Linux's semantics if a direct port costs more than a tested native implementation. Preserve provenance, specification-level behavior and the relevant licensing obligations in all cases. Avoid keeping a parallel bespoke USB core or two owners of USB device state. + +**P2.0 — Source freeze, dependency closure and legal provenance.** Pin an exact upstream Linux revision and imported-file hash manifest; derive the selected sources and transitive symbols using upstream Kconfig, Makefiles and actual code, across `drivers/usb/core`, `drivers/usb/host/xhci*`, `drivers/usb/storage`, `drivers/hid`, `drivers/hid/usbhid`, `drivers/input`, `drivers/scsi` and the required `block/` support. Exclude USB gadget, unrelated HCDs and class drivers until demanded by a real feature. Stage third-party notices, original per-file SPDX/copyright, GPLv2 text, modifications, complete corresponding-source/build-script distribution procedures; a notice alone does not discharge all license obligations. Record a port/adapt/rewrite decision per component based on dependency cost and testable semantics. Maintain the last bootable source as a regression baseline, not an ongoing duplicate stack. + +**P2.1 — Compiler and module ABI feasibility gate.** Build, link and execute representative upstream units from xHCI, USB core, HID/input and SCSI/block. Explicitly audit MSVC x64's LLP64 model and Microsoft calling convention versus Linux x86-64 LP64 and System V; audit layout, atomics, bitmaps, pointer-sized fields, inline asm, object relocation and compiler builtins. Compare native PE/COFF with a contained alternative internal Linux ABI/module format. Choose by measured total integration and maintenance cost; retain nt-lite's kernel/public ABI at the external boundary. A successful syntax-only build cannot close this gate. + +**P2.2 — Shared Linux compatibility runtime.** Supply exactly the used allocator/page, coherent and streaming DMA, SG, atomic/refcount, spinlock/IRQ masking, mutex/completion/wait, worker/workqueue, delayed work, timer, device/refcount and driver-binding, PCI/IRQ/MMIO, clock, error and logging contracts. Their cancellation, concurrency, memory-order and sleepability semantics must match the imported components. Avoid a second general native device manager: native PnP owns PCI discovery; Linux's device graph owns imported USB/HID/SCSI children. Test allocation failures, work cancellation, timer teardown, interrupt/thread context, DMA rundown and exactly-once callbacks independently before relying on the runtime. + +**P2.3 — External object/authority boundary.** Native PnP initiates physical xHCI controller binding; the imported USB graph owns root hubs, downstream devices, interfaces, endpoints and URBs. Translate only at deliberately narrow native publication boundaries: Linux input event -> native KeInput/RSSRV and Linux SCSI disk -> native `DEVICE_OBJECT`/disk I/O. Keep Linux URB/HID/SCSI/block structures private. Derive errors and rights explicitly; assign references on both sides; stop publication and new I/O before draining callbacks on removal. Avoid native filesystem dependencies on Linux `bio`, `gendisk`, or SCSI command internals. + +**P2.4 — Native disk substrate prerequisite.** Remove the eight-disk registry limit and `uint8_t` identity constraint; adopt reusable generation-safe device identities. Publish logical/physical sector geometry, capacity, alignment, flush/removal and transfer limits. Remove 512-byte assumptions from disk I/O and GPT/MBR discovery; verify native AHCI/ATA remains functional. Test 512/4096-byte devices, partitions, disappearing devices, simultaneous operations and stable system-volume selection. + +**P2.5 — Linux USB core and xHCI together.** Retain upstream HCD/URB, command and transfer ring bookkeeping, TD completion matching, endpoint state transitions, cancellation, command timeout/recovery, protocol-based USB2/USB3 root hubs, external hubs, hub routing and TTs, composite/alternate configurations, interrupt/bulk/control/isoc and streams where enabled. Integrate native PCI, DMA, MSI/IRQ and module lifecycle beneath the Linux USB model. Avoid placing the old nt-lite URB model between Linux USB and Linux xHCI. Begin with QEMU controllers and hub topology, then enumerate/transfer/fail/recover on both AMD machines. Use traced TRB/completion and device path evidence rather than timing workarounds. + +**P2.6 — Linux SCSI and block.** Port the SCSI host/device/LUN midlayer, command lifecycle, sense/error/timeout recovery, SCSI disk upper layer (`sd`) and required blk-mq/request infrastructure. Prefer upstream block privately inside the imported storage stack. If blk-mq imports disproportionate unrelated kernel machinery, rewrite only the block-facing queue adapter while preserving SCSI ordering, asynchronous completion, timeout and recovery guarantees. Validate first with a synthetic SCSI host: INQUIRY, TEST UNIT READY, READ CAPACITY, READ/WRITE, SYNCHRONIZE CACHE, multi-LUN, injected errors and hot-remove. Publish a disk through the native adapter only after this path works end to end. + +**P2.7 — USB-storage and UAS.** Import the Linux Bulk-Only Transport drivers, transport helpers, device quirks and UAS where stream/queue semantics are ready. Feed Linux SCSI rather than a parallel native command implementation. Test direct and hub-attached media; multiple disks/LUNs; read/write/flush; disconnect while busy; reconnect; reset; shutdown. Boot-critical SCSI, USB and host-controller modules must be available from EFI/bootstrap media before the configured system volume is mounted. + +**P2.8 — Linux HID and input.** Port the HID core and generic report parser, `usbhid`, necessary input event core and required key/mouse/multitouch mappings. Decode real report descriptors and composite interfaces rather than relying solely on boot protocol or QEMU device shortcuts. Map Linux input codes to nt-lite's native usage/event representations at one boundary; retain RSSRV's focus/capture/session and typematic policy. Support LEDs/output reports, mouse motion/buttons/wheel, disconnect release of pressed state and multi-device use. The secondary interface of a working USB keyboard must have an explicit supported or correctly scoped outcome, without making the functioning boot interface fail. + +**P2.9 — Physical input follow-on.** Evaluate importing `i8042`, `serio`, `atkbd` and `psmouse` for the ThinkPad's internal keyboard and TrackPoint; rewrite only a narrow host/interrupt adaptation when useful. Evaluate Linux I²C host/core, `i2c-hid` and HID multitouch for `AMDI0010:01` / `ELAN0676`; use ACPI-described resources and interrupt/power methods. These devices feed the *same* canonical input path. Retain the ACPI `PNP0C0C`/AML power-button gap as independent platform work; consider ACPICA before authoring a new AML engine. No xHCI patch can fix that firmware-control path. + +**P2.10 — Module graph, builds and distribution.** Provide real dynamic ownership boundaries: internal Linux runtime, USB core/HCD, xHCI, HID, input, SCSI/required block support, `usbhid`, USB-storage/UAS. Combine units when upstream ownership and linkage make a separate DLL artificial; do not force module count for its own sake. Update imports/exports, driver `requires`/`after`, source builds, packaging, runtime discovery, bootstrap preload, shutdown and unload rundown together. Linux device enumeration is an asynchronous runtime event, distinct from driver `DriverEntry` readiness. No hidden dependency cycle through the USB boot volume. + +**P2.11 — Verification and deliberate cutover.** Require synthetic concurrency tests, QEMU enumeration/hub/transfer tests, hardware tests on the desktop `03:00.0` (`1022:43d5`) and `28:00.3` (`1022:149c`), and all four ThinkPad xHCI functions (`1022:15b9/15ba/15c0/15c1`). Validate physical/USB2/USB3 mapping, mixed-speed hubs, the desktop's two hubs with wired keyboard, Logitech receiver, Bluetooth dongle and storage, and ThinkPad direct/hub-connected input/storage. Stress concurrent URBs, timeout, endpoint reset, hotplug, I/O cancellation, sleepable/IRQ context and shutdown. Measure throughput and tail latency rather than presuming an upstream port will automatically be faster. Delete bespoke USB enumeration, controller, HID and storage code and stale APIs after the Linux-derived path passes acceptance. Retain no permanent shadow enumeration path. + +**P2.12 — Upstream maintenance.** Record original source hashes, local adaptation patches, component decisions, test ownership and imported licenses. Track security fixes, protocol fixes and AMD controller quirks against the pinned release; review dependency changes when updating. Keep the imported source materially recognizable to upstream wherever possible. + +**Phase 2 acceptance:** QEMU boots and enumerates keyboard/tablet/storage; both real machines boot reliably with the previously failing hub topologies; direct and behind-hub control/bulk/interrupt transfers work; generic HID/Unifying input reaches RSSRV; storage survives multiple LUNs and abort/reconnect tests; native disk geometry/partition discovery is correct; shutdown/reboot drain owned DMA and callbacks. ThinkPad PS/2/TrackPoint, I²C touchpad and AML power button retain their separately tracked hardware/platform gates. A release with only syntax checks, successful module loading or direct-device enumeration leaves this phase open. + +## Phase 3 — Parallel graph startup and native service manager + +**Status:** dependency-graph **foundation implemented** for drivers; concurrency, service policy and remaining lifecycle work open. Its service design work may proceed in parallel with Phase 2 import feasibility, while shared module/runtime and authorization decisions are coordinated. + +**Driver initialization** + +- Evolve the existing `drivers.ini` v2 manager—not another parser—into a **bounded graph worker scheduler**. Start independent drivers concurrently, with exact- and class-level `requires`/`after` semantics unchanged. Deterministic eligibility and reporting do **not** require deterministic completion order. +- Protect image loading, import/export/module registries and module-state transitions with precisely scoped synchronization. Distinguish module `DriverEntry` completion from runtime device readiness. Preserve boot-critical storage availability, PnP/device ownership, failure propagation, and reverse-order shutdown. Recheck imported xHCI/USB/SCSI module startup, AHCI, PS/2, HDA, PCI and network on SMP hardware. +- Preserve the soft NIC-first behavior: a network stack may wait for the adapter class to **settle** (`after=network-adapter`) rather than fail if no NIC works. No file-order coupling. +- Driver `start=demand`, unload/reload, and optional cascade reload are **later extensions**, not prerequisites to making service demand startup work. Before unloading images: block new binding, quiesce owned device stacks, drain IRPs/DPCs/timers/threads/IRQs/DMA, and prove no import/executable references remain. + +**Simpler `services.ini`** + +- Adopt the useful dependency model of `drivers.ini`: stable service IDs (`[service.id]`), optional display `name`, `image`, `requires`, `after`, optional classes, strict whole-file validation and cycle rejection. Defaults should eliminate mandatory noise; omit `type` entirely. Services may have no published endpoint. +- Support `start=auto|delayed|demand|disabled`. `delayed` becomes eligible **after the interactive session is usable**, not after an arbitrary sleep. Demand services start through explicit authorized management requests and optional declared resource/connection activation. Coalesce simultaneous activations into one startup attempt; never start a disabled service through dependency resolution. +- Startup is dependency-driven and concurrent. Launch all currently eligible services before waiting for READY; collect asynchronous READY/exit/timeout events. Do not bind the long-term manager to one fixed 32-handle `WaitMany` array. Failed `requires` blocks dependents; `after` only waits for settlement. Shutdown stops in reverse graph order and prevents new demand activation. +- Per-service restart policy: `never|on-failure|always` plus optional retry limit, time window, initial delay, backoff scheme and maximum delay. Avoid hard-coding one 3-in-5-minutes policy for all services. Prevent restart storms, exclude deliberate stops, and define failure behavior for exhausted budgets. DWM restarts only once window/client reconstruction or explicit-loss semantics are defined. +- Expose human-readable `name`, stable service identity, status, reason for waiting/failure, and explicit service administration. The service manager's control plane is separate from ordinary client access to a service Port. +- Resource publication: `/Services` is an RSSRV-owned **resource namespace**, not a disk directory. Declare publication/activation endpoints explicitly where useful; the desktop session endpoint is separate (e.g. `/Sessions/0/Desktop`). Document this in a dedicated short service/namespace specification, not misleading comments in the INI. +- **RSSRV remains generic:** configuration declares a namespace-provider role/resource contract, `critical=true`, and narrowly defined bootstrap capabilities. Init gives the designated provider the primordial namespace/bootstrap handles via a generic descriptor-driven mechanism. No magic `[service.rssrv]`, `type=resource`, or privileged image-name comparison. Resolve the one-provider/bootstrap-cycle invariant explicitly in the schema/validator. +- Privileges: parse named `privileges` into a derived immutable service security context; separately grant handles/capabilities with scoped rights (storage, Port publication, display/session, network where needed). An ordinary database service must not inherit RSSRV authority. Enforce who may configure, activate, query, stop, or reconfigure services. Integrate later with SEC principals/ACLs rather than inventing an alternate security framework now. +- Move the session image selection out of `[init] commandline` in `boot.ini` into init-owned configuration (for example `[session] image=...`). Define executable path separately from future command-line arguments. Keep `boot.ini` for EFI/bootstrap selection and kernel diagnostics; no Windows Boot Manager clone. + +**Illustrative policy only; final grammar needs a small design review:** + +```ini +[session] +image=apps/shell_native.exe + +[service.namespace] +name=Resource Service +image=system/rssrv.exe +class=resource-namespace +critical=true +privileges=resource-admin + +[service.posix] +name=POSIX Server +image=system/posixsrv.exe +requires=namespace + +[service.dwm] +name=Desktop Window Manager +image=system/dwm.exe +requires=namespace + +[service.mysql] +name=MySQL Server +image=apps/mysql.exe +start=demand +requires=posix +privileges=network +``` + +`mysql` is an example of service policy, **not** a claim that the program already runs; its writable database storage should be delegated as a scoped resource. The example `resource-admin` and role syntax are provisional: use existing native privilege/capability vocabulary rather than freeze arbitrary new policy names. + +**Acceptance:** independent drivers and services visibly overlap in timestamped logs; dependency failures and shutdown remain correct; services can be auto/delayed/demand/disabled; resource activation deduplicates; per-service restarts obey their own budgets; RSSRV and ordinary services share one lifecycle mechanism but different delegated authority; configuration is shorter and self-explanatory. + +--- + +# Deferred work after the three active phases + +These are **still outstanding**. The previous roadmap's detailed designs should not be mistaken for implemented behavior. Resume in dependency order, not by the age of its section numbers. + +## D1 — Native filesystem substrate: KFS.1–KFS.7 + +Do this before POSIX P8 and before attaching durable ACL identity. + +- **KFS.1:** refcounted native file-backing identity separate from each `FILE_OBJECT`/open description; key by mounted volume + stable provider file ID + generation where available; coherent shared file size/page cache/mapping/security across aliases/opens. +- **KFS.2:** unlink removes a directory link while live open/mapping references retain backing until final release. +- **KFS.3:** genuine capability-relative hard links, stable link counts; native cross-volume status; NTFS/ext4 providers where supported; FAT may report unsupported. +- **KFS.4:** atomic rename replacement where supported and canonical cross-volume failures. +- **KFS.5:** generic metadata setter and UTC persistent timestamps, without repurposing system-clock/provenance structures. +- **KFS.6:** native directory enumeration reset/restart without pathname reopen. +- **KFS.7:** focused provider/runtime validation of independent opens, aliases, mmap/page-cache coherence, unlink lifetime, hard links, replacement, timestamps and directory iteration. + +## D2 — Native security: SEC.0–SEC.15 + +Native authorization remains **capability-first**. Keep userspace account identities separate from kernel principals; do not claim full inter-process memory protection under shared-PML4 SASOS. + +- SEC0/1: threat boundary and rights taxonomy; stable opaque principal IDs and immutable/materialized security contexts with groups, privileges, restrictions and audit identity. +- SEC2/3/4: one native security descriptor with owner, owning group, allow/deny ACL, creation templates; matching denies win independent of ACE order; authorize capability acquisition through the object manager. Ownership grants authority to mutate security policy, not implicit read/write of data. +- SEC5–9: provider contract, ext4/NTFS translation, creation-time inheritance, owner/security mutation; metadata lives with coherent file backing. Existing granted capabilities survive ACL changes unless explicitly revoked. +- SEC10/11: separate named privileges and derived restricted contexts; don't introduce Unix root as a kernel special case. +- SEC12/13: user-mode accounts/authentication/password storage and kernel-stamped audit with userspace persistence/filtering. +- SEC14/15: process/device/IPC/service authorization sweep and POSIX/Win32 personality mapping. + +Phase 3's service-specific privilege/capability grants must be designed to extend naturally into SEC rather than build a second authorization regime. + +## D3 — Finish POSIX personality: P8–P10 + +**P0–P7 complete**; preserve the existing `posixsrv`-owned stable PID, non-fork `execve`, `vfork`, spawn/wait and signals design. `fork()` stays deliberately unsupported; do not add Unix kernel process semantics. + +- **P8 (after KFS/SEC):** project native principal/ACL ownership and permissions into POSIX uid/gid/mode, `chmod/chown/access`; preserve `umask` as creation-time restriction; native hard-link/rename/timestamp/directory-reset APIs; FIFO namespace and rendezvous policy in `posixsrv` with native pipe data transport; correct errno and path/directory APIs. +- **P9:** POSIX tty/termios as userspace adapter over RSSRV console/session, canonical/raw input, echo, drain/flow, VMIN/VTIME, controlling tty and foreground process-group signaling (`VINTR`/`VQUIT`). +- **P10:** remaining leaf/profile APIs (`uname`, `times`, `sysconf`, `pathconf/fpathconf`, etc.), canonical status mappings, header/export/profile audit, removal of obsolete helpers. Validate the end-to-end spawn/pipe/signal/wait/exec scenario and deliberately unsupported operations. + +## D4 — Native desktop/session completion + +- Represent the RSSRV-owned console as a DWM-managed surface/window. RSSRV keeps console state and session input authority; DWM owns graphical geometry, clipping, composition and physical presentation. +- Add native decorations, close lifecycle, move/resize, title/app identity, pointer capture, activation/focus/z-order rules, minimize/maximize policy, damage rectangles, occlusion and incremental composition; keep the CPU compositor as reference. +- Shell (taskbar/launcher/notifications/work area) is **not** DWM policy. Use explicit capabilities for privileged window enumeration/capture/input injection/secure surfaces and display control. +- Delete legacy application direct input/console/display APIs after the canonical RSSRV/DWM route is complete; choose an explicit DWM crash/restart model (reconstruct state or invalidate/reconnect clients). Native UI toolkit/theme can follow. + +## D5 — Loader, native runtime, and personalities + +- Finish cross-personality `KSTATUS` to POSIX errno / Win32 LastError mapping; keep one mapping owner per personality. +- Identify loaded modules by resolved backing-file/object identity, with aliases and import/reference ownership; prerequisite to reliable kernel module unload and CRT aliases. +- Use PE32+ subsystem to select native (1), Win32 GUI/CUI (2/3), or POSIX marker (7). Win32 userspace bootstrap creates its PEB/TEB shim and uses generic persistent per-thread FS/GS bases; native and POSIX remain on their existing TEB layout. Do not introduce personality-specific kernel scheduler state. +- Split `crt.dll`, `posix.dll`, and Win32-facing libraries along true ownership boundaries; add thin USER/GDI translation to native DWM/surfaces only when the native desktop is mature. + +## D6 — Driver on-demand lifecycle, observability, and cleanup + +- Complete module import/reference accounting and PnP-owned device/worker/DPC/IRP/IRQ/DMA quiescence before any actual unload; then exact-driver reload and optional cascade reload over the same graph. +- Add `start=demand` for drivers, activated by PnP IDs, authorized management requests, or dependency demand, not by a parallel private graph. +- Structured native read-only driver/class status and wait/failure causes; no requirement to scrape `DbgPrint` for management. +- Complete justified driver decomposition (upstream-derived USB submodules, NTFS glue, VMware SVGA hardware vs generic raster), remaining input typematic cleanup, and future power/hotplug lifecycle testing. + +## D7 — Debugger and compatibility/graphics research + +These are intentionally **not prerequisites** for boot, service cleanup, KFS, or POSIX completion. + +- **Minimal serial KD:** debugger-owned polling UART independent of normal serial driver; KD packet/ACK/resend, context/memory/breakpoint/single-step, panic/break-in, SMP stop/release, minimal module/PDB metadata, first verified against WinDbg/QEMU. No need for KDNET initially. +- **Native 2D/text and graphics split:** mapped kernel surfaces remain canonical. Userspace 2D and Unicode font/shaping/raster layers, software reference raster; hardware miniports own modes, VRAM, scanout, DMA/fences; optional validated batched acceleration stays behind generic graphics abstractions. +- **USER/GDI and X11:** future Win32 USER/GDI wrappers map to native DWM/RSSRV/surfaces; `x11srv` is a userspace X protocol server (nested and rootless modes) with native capture/focus authority preserved. Neither redefines kernel surfaces. +- **Networking and compatibility drivers:** neutral native `NET_DEVICE`/packet contract; eventual protocol stack in userspace where practical; NDIS 6 **Ethernet miniport** experiment only after native mechanics stabilize; proprietary/vendor Wi-Fi work preferably as clean native hardware/firmware ports with userspace `wifisrv`. A future NVIDIA/GSP/Mesa/NVK experiment must consume nt-lite GPU APIs rather than impose Linux DRM as the native ABI. + +--- + +# Verification and handoff rules + +- **Boot:** record source revision/patch set, machine, firmware, debug mask, `--with-tests` mode, wall-clock start/end definition, and timestamped kernel log. Keep cold/warm comparisons distinct. The ~8-second result is the latest user report, not a universal target or a subsystem timing profile. +- **Hardware:** validate each controller/port/hub/interface separately and capture the first failing stage. For imported USB/xHCI/SCSI/HID ensure timeout, disconnect, cancellation, DPC, I/O, remove, shutdown and reboot ownership. Include both desktop external hubs and ThinkPad `046d:c52b`/`09da:2268`, plus USB storage directly and behind hubs. +- **Concurrency:** stress dependency cycles/failure/settlement, deterministic selection, ready-event races, service activation coalescing, cancellation and reverse shutdown—not only a clean boot. Do not restore mandatory stress tests to the boot path. +- **Services/security:** prove a minimally privileged database-like service cannot obtain RSSRV namespace management, display, input, raw storage or shutdown capabilities; explicit granted handles work; only authorized callers can control services. +- **KFS/SEC/POSIX:** preserve file/provider identity across rename/unlink and native capability rights; run the focused filesystem, security and POSIX integration suite in development builds. +- **Code baseline:** use a newly provided current source tree when starting implementation. Earlier chat archives, `code(5).zip`, and prior cumulative patches may not represent the user's latest checkout. Never call a patch 'built' when only `git apply --check`, a syntax check, or a standalone model has passed. + +## Next work item — `docs/services.md` contract review + +Review the revised service architecture against `init/service_config.{h,c}`, `init.c`, `services.ini`, `service_control.h` and RSSRV namespace/publication code. Agree the minimal grammar and authority/bootstrap contracts before changing the service ABI. First choices: how a service declares the unique namespace-provider role, how config requests audited named privileges versus capability grants, and how READY exports name resources without service-type adapters. Then implement a strict parser and deterministic graph validator, followed by event-driven parallel startup, demand activation, restart budgets and management authorization. Keep service boot independent from the Phase 2 imported USB device model: drivers remain driver-manager/PnP objects, not ordinary user-mode services. + +**Parallel Phase 2 first action:** pin Linux source and complete the compiler/ABI + minimal-runtime feasibility gate before large-scale driver import.