redo boot around an explicit system volume and fixed kernel/HAL images, redo volumes and filesystem paths around capabilities instead of drive numbers and kernel CWD state, add a capability/security model with derived and revocable handles and security contexts, add port IPC and rssrv to manage the userspace resource namespace and publish the system volume, move process, dll and path loading to userspace and add process builder syscalls, init now supervises rssrv and the session process, redo the syscall ABI around request structs and four arguments, add a volume manager and proper partition arrival/removal handling so FAT and NTFS mount dynamically, clean up the HAL/DDK boundary so drivers don't depend on HAL internals, save fpu state properly now, the serial driver doesn't hang on a missing port, fix AHCI startup races, fix timeout accounting, fix shared mmap writeback, more smoke tests

This commit is contained in:
maksw committed 2026-08-20 12:33:17 +02:00
1 parent 4add6789ba
commit f882d339fb
232 files changed
+20934 -8898

No files matched your search

Binary file not shown.
Binary file not shown.
+9 -3
View File
@@ -1,6 +1,12 @@
[boot]
; Structural system-volume selector consumed by the bootloader and kernel storage stack.
system_volume = mbr/4E544C31/2
[kernel]
; bitmask: 1 = debug prints, 2 = self check, 4 = file log, 4096 = checked intrusive lists
; 0x8000 reports MTRRs. Add 0x10000 for one temporary framebuffer WC experiment.
debug = 0xffff
; Debug bitmask. Values are defined canonically in ntlite/ddk/debug.h.
; 0x001 console output, 0x002 boot self-tests, 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
[init]
commandline = apps/shell_native.exe
+482
View File
@@ -0,0 +1,482 @@
// Portable fixed-image PE32+ loader. This file has no UEFI dependencies so firmware glue stays out of image parsing.
#include "boot_pe.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#define BOOT_PE_DOS_MAGIC 0x5A4Du
#define BOOT_PE_NT_MAGIC 0x00004550u
#define BOOT_PE_MACHINE_AMD64 0x8664u
#define BOOT_PE_OPTIONAL_MAGIC_PE32_PLUS 0x020Bu
#define BOOT_PE_ORDINAL_FLAG64 0x8000000000000000ull
#define BOOT_PE_DIRECTORY_EXPORT 0u
#define BOOT_PE_DIRECTORY_IMPORT 1u
#define BOOT_PE_DIRECTORY_RELOCATION 5u
#define BOOT_PE_OPTIONAL_MIN_BYTES 112u
#define BOOT_PE_SECTION_BYTES 40u
#define BOOT_PE_IMPORT_DESCRIPTOR_BYTES 20u
#define BOOT_PE_PAGE_SIZE 4096u
typedef struct BOOT_PE_VIEW {
const uint8_t* File;
size_t FileSize;
size_t NtOffset;
size_t OptionalOffset;
size_t SectionOffset;
uint16_t SectionCount;
uint16_t OptionalSize;
uint32_t DirectoryCount;
uint64_t ImageBase;
uint32_t SizeOfImage;
uint32_t SizeOfHeaders;
uint32_t EntryRva;
uint32_t Checksum;
} BOOT_PE_VIEW;
// Returns whether adding two sizes would overflow size_t.
static bool BootPeSizeAdd(size_t a, size_t b, size_t* out) {
if (a > SIZE_MAX - b)
return false;
*out = a + b;
return true;
}
// Returns whether multiplying two sizes would overflow size_t.
static bool BootPeSizeMultiply(size_t a, size_t b, size_t* out) {
if (a != 0 && b > SIZE_MAX / a)
return false;
*out = a * b;
return true;
}
// Returns whether one byte range is fully contained in a buffer.
static bool BootPeRangeValid(size_t offset, size_t length, size_t bufferSize) {
return offset <= bufferSize && length <= bufferSize - offset;
}
// Reads one little-endian 16-bit field without assuming alignment.
static uint16_t BootPeReadU16(const void* address) {
uint16_t value;
memcpy(&value, address, sizeof(value));
return value;
}
// Reads one little-endian 32-bit field without assuming alignment.
static uint32_t BootPeReadU32(const void* address) {
uint32_t value;
memcpy(&value, address, sizeof(value));
return value;
}
// Reads one little-endian 64-bit field without assuming alignment.
static uint64_t BootPeReadU64(const void* address) {
uint64_t value;
memcpy(&value, address, sizeof(value));
return value;
}
// Writes one little-endian 64-bit field without assuming alignment.
static void BootPeWriteU64(void* address, uint64_t value) {
memcpy(address, &value, sizeof(value));
}
// Parses the fixed PE header fields shared by file and materialized-image validation.
static BOOT_PE_STATUS BootPeParseHeaders(const void* buffer, size_t bufferSize,
bool requireRawSections, BOOT_PE_VIEW* viewOut) {
if (!buffer || !viewOut || bufferSize < 64u)
return BootPeInvalidImage;
const uint8_t* bytes = (const uint8_t*)buffer;
if (BootPeReadU16(bytes) != BOOT_PE_DOS_MAGIC)
return BootPeInvalidImage;
size_t ntOffset = (size_t)BootPeReadU32(bytes + 0x3Cu);
if (!BootPeRangeValid(ntOffset, 24u, bufferSize))
return BootPeInvalidImage;
if (BootPeReadU32(bytes + ntOffset) != BOOT_PE_NT_MAGIC)
return BootPeInvalidImage;
if (BootPeReadU16(bytes + ntOffset + 4u) != BOOT_PE_MACHINE_AMD64)
return BootPeWrongMachine;
uint16_t sectionCount = BootPeReadU16(bytes + ntOffset + 6u);
uint16_t optionalSize = BootPeReadU16(bytes + ntOffset + 20u);
size_t optionalOffset = ntOffset + 24u;
if (optionalSize < BOOT_PE_OPTIONAL_MIN_BYTES ||
!BootPeRangeValid(optionalOffset, optionalSize, bufferSize)) {
return BootPeInvalidImage;
}
if (BootPeReadU16(bytes + optionalOffset) != BOOT_PE_OPTIONAL_MAGIC_PE32_PLUS)
return BootPeInvalidImage;
uint32_t entryRva = BootPeReadU32(bytes + optionalOffset + 16u);
uint64_t imageBase = BootPeReadU64(bytes + optionalOffset + 24u);
uint32_t sizeOfImage = BootPeReadU32(bytes + optionalOffset + 56u);
uint32_t sizeOfHeaders = BootPeReadU32(bytes + optionalOffset + 60u);
uint32_t checksum = BootPeReadU32(bytes + optionalOffset + 64u);
uint32_t directoryCount = BootPeReadU32(bytes + optionalOffset + 108u);
if (imageBase == 0 || (imageBase & (BOOT_PE_PAGE_SIZE - 1u)) != 0 ||
sizeOfImage == 0 || sizeOfHeaders == 0 || sizeOfHeaders > sizeOfImage ||
entryRva >= sizeOfImage) {
return BootPeInvalidImage;
}
size_t directoryBytes = 0;
uint32_t availableDirectories = (uint32_t)((optionalSize - BOOT_PE_OPTIONAL_MIN_BYTES) / 8u);
if (directoryCount > availableDirectories)
return BootPeInvalidImage;
if (!BootPeSizeMultiply((size_t)directoryCount, 8u, &directoryBytes) ||
!BootPeRangeValid(optionalOffset + BOOT_PE_OPTIONAL_MIN_BYTES, directoryBytes, bufferSize)) {
return BootPeInvalidImage;
}
size_t sectionOffset = 0;
size_t sectionBytes = 0;
if (!BootPeSizeAdd(optionalOffset, optionalSize, &sectionOffset) ||
!BootPeSizeMultiply(sectionCount, BOOT_PE_SECTION_BYTES, &sectionBytes) ||
!BootPeRangeValid(sectionOffset, sectionBytes, bufferSize)) {
return BootPeInvalidImage;
}
if (requireRawSections) {
if (sizeOfHeaders > bufferSize)
return BootPeInvalidImage;
for (uint16_t index = 0; index < sectionCount; ++index) {
const uint8_t* section = bytes + sectionOffset + (size_t)index * BOOT_PE_SECTION_BYTES;
uint32_t virtualSize = BootPeReadU32(section + 8u);
uint32_t virtualAddress = BootPeReadU32(section + 12u);
uint32_t rawSize = BootPeReadU32(section + 16u);
uint32_t rawOffset = BootPeReadU32(section + 20u);
uint32_t mappedSize = virtualSize > rawSize ? virtualSize : rawSize;
if (virtualAddress > sizeOfImage || mappedSize > sizeOfImage - virtualAddress)
return BootPeInvalidImage;
if (rawSize != 0 && !BootPeRangeValid(rawOffset, rawSize, bufferSize))
return BootPeInvalidImage;
}
}
BOOT_PE_VIEW view = {0};
view.File = bytes;
view.FileSize = bufferSize;
view.NtOffset = ntOffset;
view.OptionalOffset = optionalOffset;
view.SectionOffset = sectionOffset;
view.SectionCount = sectionCount;
view.OptionalSize = optionalSize;
view.DirectoryCount = directoryCount;
view.ImageBase = imageBase;
view.SizeOfImage = sizeOfImage;
view.SizeOfHeaders = sizeOfHeaders;
view.EntryRva = entryRva;
view.Checksum = checksum;
*viewOut = view;
return BootPeSuccess;
}
// Reads one PE data-directory entry when the optional header declares it.
static bool BootPeGetDirectory(const BOOT_PE_VIEW* view, uint32_t index,
uint32_t* rvaOut, uint32_t* sizeOut) {
if (!view || index >= view->DirectoryCount)
return false;
const uint8_t* directory = view->File + view->OptionalOffset + BOOT_PE_OPTIONAL_MIN_BYTES + (size_t)index * 8u;
if (rvaOut) *rvaOut = BootPeReadU32(directory);
if (sizeOut) *sizeOut = BootPeReadU32(directory + 4u);
return true;
}
// Computes the standard PE checksum while treating the stored CheckSum field as zero.
static uint32_t BootPeComputeChecksum(const uint8_t* file, size_t fileSize, size_t checksumOffset) {
uint64_t sum = 0;
size_t offset = 0;
while (offset + 1u < fileSize) {
if (offset == checksumOffset || offset == checksumOffset + 2u) {
offset += 2u;
continue;
}
sum += BootPeReadU16(file + offset);
sum = (sum & 0xFFFFu) + (sum >> 16);
offset += 2u;
}
if (offset < fileSize)
sum += file[offset];
sum = (sum & 0xFFFFu) + (sum >> 16);
sum += sum >> 16;
sum = (sum & 0xFFFFu) + fileSize;
return (uint32_t)sum;
}
// Returns whether an image RVA range is fully mapped inside SizeOfImage.
static bool BootPeImageRangeValid(uint32_t rva, size_t length, size_t imageSize) {
return (size_t)rva <= imageSize && length <= imageSize - (size_t)rva;
}
// Returns whether an image-relative string has a NUL terminator before the image ends.
static bool BootPeImageStringValid(const uint8_t* image, size_t imageSize, uint32_t rva) {
if ((size_t)rva >= imageSize)
return false;
for (size_t offset = (size_t)rva; offset < imageSize; ++offset) {
if (image[offset] == 0)
return true;
}
return false;
}
// Compares two ASCII module names without case sensitivity.
static bool BootPeAsciiEqualInsensitive(const char* a, const char* b) {
if (!a || !b)
return false;
while (*a && *b) {
char ca = *a++;
char cb = *b++;
if (ca >= 'A' && ca <= 'Z') ca = (char)(ca + ('a' - 'A'));
if (cb >= 'A' && cb <= 'Z') cb = (char)(cb + ('a' - 'A'));
if (ca != cb)
return false;
}
return *a == *b;
}
// Finds one named export RVA and rejects forwarded exports.
static BOOT_PE_STATUS BootPeFindExport(const uint8_t* provider, size_t providerSize,
const BOOT_PE_VIEW* providerView, const char* functionName,
uint32_t* functionRvaOut) {
uint32_t exportRva = 0;
uint32_t exportSize = 0;
if (!BootPeGetDirectory(providerView, BOOT_PE_DIRECTORY_EXPORT, &exportRva, &exportSize) ||
exportRva == 0 || exportSize < 40u || !BootPeImageRangeValid(exportRva, exportSize, providerSize)) {
return BootPeImportNotFound;
}
const uint8_t* exports = provider + exportRva;
uint32_t functionCount = BootPeReadU32(exports + 20u);
uint32_t nameCount = BootPeReadU32(exports + 24u);
uint32_t functionsRva = BootPeReadU32(exports + 28u);
uint32_t namesRva = BootPeReadU32(exports + 32u);
uint32_t ordinalsRva = BootPeReadU32(exports + 36u);
if (!BootPeImageRangeValid(functionsRva, (size_t)functionCount * 4u, providerSize) ||
!BootPeImageRangeValid(namesRva, (size_t)nameCount * 4u, providerSize) ||
!BootPeImageRangeValid(ordinalsRva, (size_t)nameCount * 2u, providerSize)) {
return BootPeInvalidImage;
}
for (uint32_t index = 0; index < nameCount; ++index) {
uint32_t nameRva = BootPeReadU32(provider + namesRva + (size_t)index * 4u);
if (!BootPeImageStringValid(provider, providerSize, nameRva))
return BootPeInvalidImage;
if (strcmp((const char*)(provider + nameRva), functionName) != 0)
continue;
uint16_t ordinal = BootPeReadU16(provider + ordinalsRva + (size_t)index * 2u);
if (ordinal >= functionCount)
return BootPeInvalidImage;
uint32_t functionRva = BootPeReadU32(provider + functionsRva + (size_t)ordinal * 4u);
if (!BootPeImageRangeValid(functionRva, 1u, providerSize))
return BootPeInvalidImage;
if (functionRva >= exportRva && (uint64_t)functionRva < (uint64_t)exportRva + exportSize)
return BootPeUnsupportedImport;
*functionRvaOut = functionRva;
return BootPeSuccess;
}
return BootPeImportNotFound;
}
// Validates one fixed AMD64 PE32+ file and returns the image metadata required by the bootloader.
BOOT_PE_STATUS BootPeInspect(const void* file, size_t fileSize, BOOT_PE_INFO* infoOut) {
BOOT_PE_VIEW view;
BOOT_PE_STATUS status = BootPeParseHeaders(file, fileSize, true, &view);
if (status != BootPeSuccess)
return status;
uint32_t relocationRva = 0;
uint32_t relocationSize = 0;
if (BootPeGetDirectory(&view, BOOT_PE_DIRECTORY_RELOCATION, &relocationRva, &relocationSize) &&
(relocationRva != 0 || relocationSize != 0)) {
return BootPeRelocationsPresent;
}
if (view.Checksum == 0)
return BootPeChecksumMissing;
size_t checksumOffset = view.OptionalOffset + 64u;
uint32_t computed = BootPeComputeChecksum(view.File, view.FileSize, checksumOffset);
if (computed != view.Checksum)
return BootPeChecksumMismatch;
if (infoOut) {
infoOut->ImageBase = view.ImageBase;
infoOut->SizeOfImage = view.SizeOfImage;
infoOut->SizeOfHeaders = view.SizeOfHeaders;
infoOut->EntryRva = view.EntryRva;
infoOut->Checksum = view.Checksum;
}
return BootPeSuccess;
}
// Copies a validated fixed image into a zeroed image-sized destination.
BOOT_PE_STATUS BootPeMaterialize(const void* file, size_t fileSize, void* image,
size_t imageCapacity, BOOT_PE_INFO* infoOut) {
BOOT_PE_INFO info;
BOOT_PE_STATUS status = BootPeInspect(file, fileSize, &info);
if (status != BootPeSuccess)
return status;
if (!image || imageCapacity < info.SizeOfImage)
return BootPeBufferTooSmall;
BOOT_PE_VIEW view;
status = BootPeParseHeaders(file, fileSize, true, &view);
if (status != BootPeSuccess)
return status;
memset(image, 0, info.SizeOfImage);
memcpy(image, file, info.SizeOfHeaders);
for (uint16_t index = 0; index < view.SectionCount; ++index) {
const uint8_t* section = view.File + view.SectionOffset + (size_t)index * BOOT_PE_SECTION_BYTES;
uint32_t virtualAddress = BootPeReadU32(section + 12u);
uint32_t rawSize = BootPeReadU32(section + 16u);
uint32_t rawOffset = BootPeReadU32(section + 20u);
if (rawSize != 0)
memcpy((uint8_t*)image + virtualAddress, view.File + rawOffset, rawSize);
}
if (infoOut)
*infoOut = info;
return BootPeSuccess;
}
// Returns whether a materialized image declares any import descriptors.
BOOT_PE_STATUS BootPeRequireNoImports(const void* image, size_t imageSize) {
BOOT_PE_VIEW view;
BOOT_PE_STATUS status = BootPeParseHeaders(image, imageSize, false, &view);
if (status != BootPeSuccess)
return status;
uint32_t importRva = 0;
uint32_t importSize = 0;
if (BootPeGetDirectory(&view, BOOT_PE_DIRECTORY_IMPORT, &importRva, &importSize) &&
(importRva != 0 || importSize != 0)) {
return BootPeImportsPresent;
}
return BootPeSuccess;
}
// Resolves one consumer's named imports against a single fixed provider image.
BOOT_PE_STATUS BootPeResolveImports(void* consumerImage, size_t consumerSize,
const void* providerImage, size_t providerSize,
const char* providerName, uint64_t providerVirtualBase) {
if (!consumerImage || !providerImage || !providerName)
return BootPeInvalidImage;
BOOT_PE_VIEW consumerView;
BOOT_PE_VIEW providerView;
BOOT_PE_STATUS status = BootPeParseHeaders(consumerImage, consumerSize, false, &consumerView);
if (status != BootPeSuccess)
return status;
status = BootPeParseHeaders(providerImage, providerSize, false, &providerView);
if (status != BootPeSuccess)
return status;
uint32_t importRva = 0;
uint32_t importSize = 0;
if (!BootPeGetDirectory(&consumerView, BOOT_PE_DIRECTORY_IMPORT, &importRva, &importSize) ||
importRva == 0 || importSize == 0) {
return BootPeSuccess;
}
if (importSize < BOOT_PE_IMPORT_DESCRIPTOR_BYTES ||
!BootPeImageRangeValid(importRva, importSize, consumerSize)) {
return BootPeInvalidImage;
}
uint32_t descriptorCount = importSize / BOOT_PE_IMPORT_DESCRIPTOR_BYTES;
bool terminated = false;
uint8_t* consumer = (uint8_t*)consumerImage;
const uint8_t* provider = (const uint8_t*)providerImage;
for (uint32_t descriptorIndex = 0; descriptorIndex < descriptorCount; ++descriptorIndex) {
uint8_t* descriptor = consumer + importRva + (size_t)descriptorIndex * BOOT_PE_IMPORT_DESCRIPTOR_BYTES;
uint32_t originalThunkRva = BootPeReadU32(descriptor);
uint32_t nameRva = BootPeReadU32(descriptor + 12u);
uint32_t firstThunkRva = BootPeReadU32(descriptor + 16u);
if (originalThunkRva == 0 && nameRva == 0 && firstThunkRva == 0) {
terminated = true;
break;
}
if (!BootPeImageStringValid(consumer, consumerSize, nameRva) ||
!BootPeAsciiEqualInsensitive((const char*)(consumer + nameRva), providerName)) {
return BootPeUnsupportedImport;
}
uint32_t lookupRva = originalThunkRva ? originalThunkRva : firstThunkRva;
if (!BootPeImageRangeValid(lookupRva, 8u, consumerSize) ||
!BootPeImageRangeValid(firstThunkRva, 8u, consumerSize)) {
return BootPeInvalidImage;
}
size_t lookupCapacity = (consumerSize - lookupRva) / 8u;
size_t writeCapacity = (consumerSize - firstThunkRva) / 8u;
size_t thunkCapacity = lookupCapacity < writeCapacity ? lookupCapacity : writeCapacity;
bool thunkTerminated = false;
for (size_t thunkIndex = 0; thunkIndex < thunkCapacity; ++thunkIndex) {
uint64_t lookup = BootPeReadU64(consumer + lookupRva + thunkIndex * 8u);
if (lookup == 0) {
thunkTerminated = true;
break;
}
if ((lookup & BOOT_PE_ORDINAL_FLAG64) != 0 || lookup > UINT32_MAX)
return BootPeUnsupportedImport;
uint32_t importByNameRva = (uint32_t)lookup;
if (!BootPeImageRangeValid(importByNameRva, 3u, consumerSize) ||
!BootPeImageStringValid(consumer, consumerSize, importByNameRva + 2u)) {
return BootPeInvalidImage;
}
const char* functionName = (const char*)(consumer + importByNameRva + 2u);
uint32_t functionRva = 0;
status = BootPeFindExport(provider, providerSize, &providerView, functionName, &functionRva);
if (status != BootPeSuccess)
return status;
if (providerVirtualBase > UINT64_MAX - functionRva)
return BootPeInvalidImage;
BootPeWriteU64(consumer + firstThunkRva + thunkIndex * 8u,
providerVirtualBase + functionRva);
}
if (!thunkTerminated)
return BootPeInvalidImage;
}
return terminated ? BootPeSuccess : BootPeInvalidImage;
}
// Returns a concise diagnostic name for one portable loader status.
const char* BootPeStatusName(BOOT_PE_STATUS status) {
switch (status) {
case BootPeSuccess: return "success";
case BootPeInvalidImage: return "invalid image";
case BootPeWrongMachine: return "wrong machine";
case BootPeChecksumMissing: return "checksum missing";
case BootPeChecksumMismatch: return "checksum mismatch";
case BootPeRelocationsPresent: return "relocations present";
case BootPeBufferTooSmall: return "buffer too small";
case BootPeImportsPresent: return "imports present";
case BootPeUnsupportedImport: return "unsupported import";
case BootPeImportNotFound: return "import not found";
default: return "unknown PE status";
}
}
+45
View File
@@ -0,0 +1,45 @@
// Portable fixed-image PE32+ loader declarations used by the UEFI bootloader.
#pragma once
#include <stddef.h>
#include <stdint.h>
typedef enum BOOT_PE_STATUS {
BootPeSuccess = 0,
BootPeInvalidImage,
BootPeWrongMachine,
BootPeChecksumMissing,
BootPeChecksumMismatch,
BootPeRelocationsPresent,
BootPeBufferTooSmall,
BootPeImportsPresent,
BootPeUnsupportedImport,
BootPeImportNotFound,
} BOOT_PE_STATUS;
typedef struct BOOT_PE_INFO {
uint64_t ImageBase;
uint32_t SizeOfImage;
uint32_t SizeOfHeaders;
uint32_t EntryRva;
uint32_t Checksum;
} BOOT_PE_INFO;
// Validates one fixed AMD64 PE32+ file and returns the image metadata required by the bootloader.
BOOT_PE_STATUS BootPeInspect(const void* file, size_t fileSize, BOOT_PE_INFO* infoOut);
// Copies a validated fixed image into a zeroed image-sized destination.
BOOT_PE_STATUS BootPeMaterialize(const void* file, size_t fileSize, void* image,
size_t imageCapacity, BOOT_PE_INFO* infoOut);
// Returns whether a materialized image declares any import descriptors.
BOOT_PE_STATUS BootPeRequireNoImports(const void* image, size_t imageSize);
// Resolves one consumer's named imports against a single fixed provider image.
BOOT_PE_STATUS BootPeResolveImports(void* consumerImage, size_t consumerSize,
const void* providerImage, size_t providerSize,
const char* providerName, uint64_t providerVirtualBase);
// Returns a concise diagnostic name for one portable loader status.
const char* BootPeStatusName(BOOT_PE_STATUS status);
+1397 -781
View File
File diff suppressed because it is too large. Load diff
+226 -142
View File
@@ -14,30 +14,33 @@
#define USER_HEAP_OBJS_LITERAL "obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj"
#define NATIVE_RUNTIME_OBJS_LITERAL "obj\\runtime\\native_exception.obj obj\\runtime\\native_start.obj"
#define NATIVE_EXE_LIBS_LITERAL "bin\\syscall.lib obj\\libc.obj " USER_HEAP_OBJS_LITERAL " " NATIVE_RUNTIME_OBJS_LITERAL
#define NATIVE_INIT_LIBS_LITERAL "bin\\syscall.lib obj\\libc.obj " USER_HEAP_OBJS_LITERAL " obj\\runtime\\native_exception.obj"
#define NATIVE_INIT_LIBS_LITERAL "bin\\syscall_s.lib obj\\libc.obj obj\\runtime\\native_exception.obj"
#define NATIVE_SERVICE_LIBS_LITERAL "bin\\syscall_s.lib obj\\libc.obj " USER_HEAP_OBJS_LITERAL " obj\\runtime\\native_exception.obj"
#define WIN32_EXE_LIBS_LITERAL "bin\\win32.lib bin\\syscall.lib obj\\libc.obj obj\\runtime\\native_start.obj"
#define POSIX_EXE_LIBS_LITERAL "bin\\posix.lib bin\\syscall.lib obj\\libc.obj obj\\runtime\\posix_start.obj obj\\runtime\\native_exception.obj"
#define NATIVE_EXE_ENTRY_LITERAL "/ENTRY:NtlNativeStart"
#define USER_IMAGE_FLAGS_LITERAL "/DYNAMICBASE /HIGHENTROPYVA"
#define LWIP_CFLAGS_LITERAL DRIVER_CFLAGS_LITERAL " /Icode\\drivers\\net\\lwip\\include /Icode\\drivers\\net\\lwip\\third_party\\lwip\\src\\include /wd4005 /wd4100 /wd4103 /wd4127 /wd4244 /wd4267 /wd4706"
#define LFLAGS_LITERAL "/nologo /NODEFAULTLIB /DEBUG"
// Partition offsets inside disk.vhd
#define VHD_PART1 "disk.vhd@@1M" // ESP / Partition 1 (Offset 1MB)
#define VHD_PART2 "disk.vhd@@65M" // Test/Data / Partition 2 (Offset 65MB)
#define DEFAULT_VHD_DISK_SIGNATURE 0x4E544C31u
const char *CommonCflags = COMMON_CFLAGS_LITERAL;
const char *KernelCflags = KERNEL_CFLAGS_LITERAL;
const char *UserCflags = USER_CFLAGS_LITERAL;
const char *BootCflags = BOOT_CFLAGS_LITERAL;
const char *LwipCflags = LWIP_CFLAGS_LITERAL;
const char *LFLAGS = LFLAGS_LITERAL;
const char* CommonCflags = COMMON_CFLAGS_LITERAL;
const char* KernelCflags = KERNEL_CFLAGS_LITERAL;
const char* UserCflags = USER_CFLAGS_LITERAL;
const char* BootCflags = BOOT_CFLAGS_LITERAL;
const char* LwipCflags = LWIP_CFLAGS_LITERAL;
const char* LFLAGS = LFLAGS_LITERAL;
typedef struct Driver_Target {
const char *name;
const char *obj_dir;
const char *sources;
const char *cflags;
const char *libs;
const char* name;
const char* obj_dir;
const char* sources;
const char* cflags;
const char* libs;
} Driver_Target;
#define DRIVER_TARGET(name_, obj_dir_, sources_) { (name_), (obj_dir_), (sources_), DRIVER_CFLAGS_LITERAL, "" }
@@ -63,13 +66,13 @@ static const Driver_Target STANDALONE_DRIVERS[] = {
// Creates layout.
void create_layout() {
const char *dirs[] = {
const char* dirs[] = {
"obj", "bin", "obj\\boot", "obj\\kernel", "obj\\lwip", "obj\\hal",
"obj\\syscall", "obj\\runtime", "obj\\win32", "obj\\posix", "obj\\drivers", "obj\\init",
"obj\\syscall", "obj\\runtime", "obj\\win32", "obj\\posix", "obj\\drivers", "obj\\init", "obj\\rssrv",
"obj\\shell_win32", "obj\\shell_posix", "obj\\shell_native",
"obj\\async_apc", "obj\\async_cancel", "obj\\apc_return_smoke", "obj\\edit", "obj\\httpget",
"obj\\tcpserve", "obj\\udp", "obj\\resolve", "obj\\ping", "obj\\doom", "obj\\video", "obj\\memtop",
"obj\\file_stat_smoke", "obj\\handle_dup_smoke", "obj\\named_queue_lifetime_smoke", "obj\\input_api_smoke", "obj\\input_model_smoke", "obj\\timer_smoke", "obj\\stats_abi_smoke", "obj\\realpath_smoke", "obj\\file_lock_smoke", "obj\\process_protection_smoke", "obj\\socket_addr_smoke", "obj\\syscall_contract_smoke", "obj\\would_block_smoke", "obj\\audio_stream_smoke", "obj\\winmm_waveout_smoke", "obj\\win32_condition_variable_smoke", "obj\\win32_mutex_smoke", "obj\\win32_thread_suspend_smoke", "obj\\win32_module_lookup_smoke", "obj\\cwd_peb_smoke", "obj\\mmap_smoke", "obj\\pcache_smoke", "obj\\pcache_pressure", "obj\\dirty_writeback", "obj\\shared_mmap",
"obj\\file_stat_smoke", "obj\\handle_dup_smoke", "obj\\security_smoke", "obj\\port_ipc_smoke", "obj\\resource_service_smoke", "obj\\named_queue_lifetime_smoke", "obj\\input_api_smoke", "obj\\input_model_smoke", "obj\\timer_smoke", "obj\\stats_abi_smoke", "obj\\realpath_smoke", "obj\\file_lock_smoke", "obj\\process_protection_smoke", "obj\\process_exit_multithread_smoke", "obj\\process_builder_smoke", "obj\\socket_addr_smoke", "obj\\syscall_contract_smoke", "obj\\would_block_smoke", "obj\\audio_stream_smoke", "obj\\winmm_waveout_smoke", "obj\\win32_condition_variable_smoke", "obj\\win32_mutex_smoke", "obj\\win32_thread_suspend_smoke", "obj\\win32_module_lookup_smoke", "obj\\cwd_userspace_smoke", "obj\\mmap_smoke", "obj\\pcache_smoke", "obj\\pcache_pressure", "obj\\dirty_writeback", "obj\\shared_mmap",
"obj\\pcache_invalidate", "obj\\pcache_mmap_read_reuse", "obj\\pcache_multithread",
"obj\\all_tests", "obj\\surface_smoke", "obj\\surface_invalid_rect", "obj\\surface_multithread_present", "obj\\net_irp_smoke", "obj\\module_load_smoke", "obj\\posix_module_smoke", "obj\\exception_dispatcher_smoke", "obj\\native_exception_runtime_smoke", "obj\\native_raise_exception_smoke", "obj\\exception_smoke", "obj\\win32_exception_smoke", "obj\\win32_seh_smoke"
};
@@ -79,7 +82,7 @@ void create_layout() {
}
// Runs a shell command and waits for it to finish.
bool run_sync(const char *cmd_str) {
bool run_sync(const char* cmd_str) {
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "cmd.exe", "/c", cmd_str);
bool ok = nob_cmd_run_sync(cmd);
@@ -88,7 +91,7 @@ bool run_sync(const char *cmd_str) {
}
// Launches a shell command while the build graph continues.
Nob_Proc run_async(const char *cmd_str) {
Nob_Proc run_async(const char* cmd_str) {
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "cmd.exe", "/c", cmd_str);
Nob_Proc proc = nob_cmd_run_async(cmd);
@@ -97,20 +100,20 @@ Nob_Proc run_async(const char *cmd_str) {
}
// Deletes stale object files from a target directory before compiling into it.
bool clean_object_directory(const char *obj_dir) {
const char *cmd = nob_temp_sprintf("if exist %s\\*.obj del /q %s\\*.obj", obj_dir, obj_dir);
bool clean_object_directory(const char* obj_dir) {
const char* cmd = nob_temp_sprintf("if exist %s\\*.obj del /q %s\\*.obj", obj_dir, obj_dir);
return run_sync(cmd);
}
// Cleans object directories that are linked through wildcards.
bool clean_build_object_directories(void) {
const char *dirs[] = {
const char* dirs[] = {
"obj\\boot", "obj\\kernel", "obj\\lwip", "obj\\hal",
"obj\\syscall", "obj\\runtime", "obj\\win32", "obj\\posix",
"obj\\init", "obj\\shell_win32", "obj\\shell_posix", "obj\\shell_native",
"obj\\init", "obj\\rssrv", "obj\\shell_win32", "obj\\shell_posix", "obj\\shell_native",
"obj\\async_apc", "obj\\async_cancel", "obj\\apc_return_smoke", "obj\\edit", "obj\\httpget",
"obj\\tcpserve", "obj\\udp", "obj\\resolve", "obj\\ping",
"obj\\doom", "obj\\video", "obj\\memtop", "obj\\file_stat_smoke", "obj\\handle_dup_smoke", "obj\\named_queue_lifetime_smoke", "obj\\input_api_smoke", "obj\\input_model_smoke", "obj\\timer_smoke", "obj\\stats_abi_smoke", "obj\\realpath_smoke", "obj\\file_lock_smoke", "obj\\process_protection_smoke", "obj\\socket_addr_smoke", "obj\\syscall_contract_smoke", "obj\\would_block_smoke", "obj\\audio_stream_smoke", "obj\\winmm_waveout_smoke", "obj\\win32_condition_variable_smoke", "obj\\win32_mutex_smoke", "obj\\win32_thread_suspend_smoke", "obj\\win32_module_lookup_smoke", "obj\\cwd_peb_smoke", "obj\\mmap_smoke", "obj\\pcache_smoke", "obj\\pcache_pressure", "obj\\dirty_writeback", "obj\\shared_mmap",
"obj\\doom", "obj\\video", "obj\\memtop", "obj\\file_stat_smoke", "obj\\handle_dup_smoke", "obj\\security_smoke", "obj\\port_ipc_smoke", "obj\\resource_service_smoke", "obj\\named_queue_lifetime_smoke", "obj\\input_api_smoke", "obj\\input_model_smoke", "obj\\timer_smoke", "obj\\stats_abi_smoke", "obj\\realpath_smoke", "obj\\file_lock_smoke", "obj\\process_protection_smoke", "obj\\process_exit_multithread_smoke", "obj\\process_builder_smoke", "obj\\socket_addr_smoke", "obj\\syscall_contract_smoke", "obj\\would_block_smoke", "obj\\audio_stream_smoke", "obj\\winmm_waveout_smoke", "obj\\win32_condition_variable_smoke", "obj\\win32_mutex_smoke", "obj\\win32_thread_suspend_smoke", "obj\\win32_module_lookup_smoke", "obj\\cwd_userspace_smoke", "obj\\mmap_smoke", "obj\\pcache_smoke", "obj\\pcache_pressure", "obj\\dirty_writeback", "obj\\shared_mmap",
"obj\\pcache_invalidate", "obj\\pcache_mmap_read_reuse", "obj\\pcache_multithread",
"obj\\all_tests", "obj\\surface_smoke", "obj\\surface_invalid_rect", "obj\\surface_multithread_present", "obj\\net_irp_smoke", "obj\\module_load_smoke", "obj\\posix_module_smoke", "obj\\exception_dispatcher_smoke", "obj\\native_exception_runtime_smoke", "obj\\native_raise_exception_smoke", "obj\\exception_smoke", "obj\\win32_exception_smoke", "obj\\win32_seh_smoke"
};
@@ -132,21 +135,43 @@ bool create_usbfatroot_if_missing(void) {
return nob_mkdir_if_not_exists("usbfatroot");
}
// Utility to copy a local file into a FAT image using mcopy without admin rights.
bool mcopy_file(const char *src, const char *vhd_target, const char *dst_path) {
const char *cmd = nob_temp_sprintf("mcopy -o -i %s %s ::%s", vhd_target, src, dst_path);
// Copies one local file into a FAT image using mcopy without admin rights.
bool mcopy_file(const char* src, const char* vhd_target, const char* dst_path) {
const char* cmd = nob_temp_sprintf("mcopy -o -i %s %s ::%s", vhd_target, src, dst_path);
return run_sync(cmd);
}
// Copies several same-name files into one FAT directory with a single mcopy process.
bool mcopy_files_to_dir(const char* vhd_target,
const char* dst_dir,
const char* const* sources,
size_t source_count) {
if (!vhd_target || !dst_dir || (!sources && source_count))
return false;
if (!source_count)
return true;
Nob_String_Builder command = {0};
nob_sb_appendf(&command, "mcopy -o -i %s", vhd_target);
for (size_t i = 0; i < source_count; ++i)
nob_sb_appendf(&command, " %s", sources[i]);
nob_sb_appendf(&command, " ::%s", dst_dir);
nob_sb_append_null(&command);
bool ok = run_sync(command.items);
nob_sb_free(command);
return ok;
}
// Utility to create a directory in a FAT image using mmd.
bool mmd_dir(const char *vhd_target, const char *dir_path) {
const char *cmd = nob_temp_sprintf("mmd -i %s ::%s 2>nul", vhd_target, dir_path);
bool mmd_dir(const char* vhd_target, const char* dir_path) {
const char* cmd = nob_temp_sprintf("mmd -i %s ::%s 2>nul", vhd_target, dir_path);
run_sync(cmd);
return true;
}
// Writes a 512-byte Connectix VHD Fixed Disk Footer at the end of the file.
static void write_vpc_footer(FILE *f, uint64_t disk_size_bytes) {
static void write_vpc_footer(FILE* f, uint64_t disk_size_bytes) {
uint8_t footer[512] = {0};
memcpy(&footer[0], "conectix", 8);
@@ -208,14 +233,30 @@ static void write_vpc_footer(FILE *f, uint64_t disk_size_bytes) {
fwrite(footer, 1, 512, f);
}
// Ensures the managed development VHD carries the stable identity used by boot.ini.
static bool ensure_disk_vhd_signature(void) {
FILE* f = fopen("disk.vhd", "r+b");
if (!f) return false;
uint32_t signature = DEFAULT_VHD_DISK_SIGNATURE;
if (fseek(f, 440, SEEK_SET) != 0 ||
fwrite(&signature, sizeof(signature), 1, f) != 1) {
fclose(f);
return false;
}
fclose(f);
return true;
}
// Creates a 512 MB partitioned disk.vhd image with a valid VPC footer.
bool create_disk_vhd_if_missing(void) {
if (nob_file_exists("disk.vhd")) return true;
if (nob_file_exists("disk.vhd")) return ensure_disk_vhd_signature();
nob_log(NOB_INFO, "disk.vhd missing, creating new 512 MB fixed VHD image...");
const uint64_t disk_size = (uint64_t)512 * 1024 * 1024; // 512 MB Payload
FILE *f = fopen("disk.vhd", "wb");
FILE* f = fopen("disk.vhd", "wb");
if (!f) {
nob_log(NOB_ERROR, "Failed to create disk.vhd file!");
return false;
@@ -230,11 +271,13 @@ bool create_disk_vhd_if_missing(void) {
// Build standard MBR partition table (Sector 0)
uint8_t mbr[512] = {0};
uint32_t disk_signature = DEFAULT_VHD_DISK_SIGNATURE;
memcpy(&mbr[440], &disk_signature, sizeof(disk_signature));
mbr[510] = 0x55;
mbr[511] = 0xAA;
// Partition 1 Entry: 64MB FAT32 @ LBA 2048 (1MB offset)
uint8_t *p1 = &mbr[0x01BE];
uint8_t* p1 = &mbr[0x01BE];
p1[0] = 0x80; // Active / Bootable
p1[1] = 0x00; p1[2] = 0x02; p1[3] = 0x00; // CHS Start
p1[4] = 0x0C; // FAT32 LBA
@@ -245,7 +288,7 @@ bool create_disk_vhd_if_missing(void) {
memcpy(&p1[12], &p1_sectors, 4);
// Partition 2 Entry: 256MB FAT32 @ LBA 133120 (65MB offset)
uint8_t *p2 = &mbr[0x01CE];
uint8_t* p2 = &mbr[0x01CE];
p2[0] = 0x00; // Inactive
p2[1] = 0x00; p2[2] = 0x01; p2[3] = 0x08; // CHS Start
p2[4] = 0x0C; // FAT32 LBA
@@ -272,7 +315,7 @@ bool create_disk_vhd_if_missing(void) {
}
// Runs the requested operation.
int main(int argc, char **argv) {
int main(int argc, char** argv) {
NOB_GO_REBUILD_URSELF(argc, argv);
if (!create_disk_vhd_if_missing()) return 1;
@@ -282,14 +325,25 @@ int main(int argc, char **argv) {
nob_log(NOB_INFO, "--- Building Core Subsystems ---");
const char *core_tasks[] = {
const char* core_tasks[] = {
"ml64 /nologo /c /Foobj\\kernel\\ke_switch.obj code\\kernel\\ke_switch.asm",
"ml64 /nologo /c /Foobj\\kernel\\ke_syscall_asm.obj code\\kernel\\ke_syscall.asm",
"ml64 /nologo /c /Foobj\\kernel\\ke_gdt_reload.obj code\\kernel\\ke_gdt_reload.asm",
"ml64 /nologo /c /Foobj\\syscall\\stubs.obj code\\user\\syscall\\syscall_stubs.asm",
"ml64 /nologo /DNTL_SYSCALL_STATIC /c /Foobj\\syscall\\stubs_s.obj code\\user\\syscall\\syscall_stubs.asm",
nob_temp_sprintf("cl %s /DNTL_SYSCALL_BUILD /c /Foobj\\syscall\\pe_loader.obj code\\user\\syscall\\pe_loader.c", UserCflags),
nob_temp_sprintf("cl %s /DNTL_SYSCALL_STATIC /c /Foobj\\syscall\\pe_loader_s.obj code\\user\\syscall\\pe_loader.c", UserCflags),
nob_temp_sprintf("cl %s /DNTL_SYSCALL_BUILD /c /Foobj\\syscall\\module_loader.obj code\\user\\syscall\\module_loader.c", UserCflags),
nob_temp_sprintf("cl %s /DNTL_SYSCALL_STATIC /c /Foobj\\syscall\\module_loader_s.obj code\\user\\syscall\\module_loader.c", UserCflags),
nob_temp_sprintf("cl %s /DNTL_SYSCALL_BUILD /c /Foobj\\syscall\\process_loader.obj code\\user\\syscall\\process_loader.c", UserCflags),
nob_temp_sprintf("cl %s /DNTL_SYSCALL_STATIC /c /Foobj\\syscall\\process_loader_s.obj code\\user\\syscall\\process_loader.c", UserCflags),
nob_temp_sprintf("cl %s /DNTL_SYSCALL_BUILD /c /Foobj\\syscall\\resource.obj code\\user\\syscall\\resource.c", UserCflags),
nob_temp_sprintf("cl %s /DNTL_SYSCALL_STATIC /c /Foobj\\syscall\\resource_s.obj code\\user\\syscall\\resource.c", UserCflags),
nob_temp_sprintf("cl %s /DNTL_SYSCALL_BUILD /c /Foobj\\syscall\\path.obj code\\user\\syscall\\path.c", UserCflags),
nob_temp_sprintf("cl %s /DNTL_SYSCALL_STATIC /c /Foobj\\syscall\\path_s.obj code\\user\\syscall\\path.c", UserCflags),
"for %f in (code\\boot\\uefi\\*.asm) do ml64 /nologo /c /Fo\"obj\\boot\\%~nf.obj\" \"%f\"",
nob_temp_sprintf("cl %s /MP /c /Foobj\\ code\\common\\printf.c code\\common\\ini_parser.c", CommonCflags),
nob_temp_sprintf("cl %s /MP /c /Foobj\\ code\\common\\printf.c", CommonCflags),
"cl /nologo /c /Foobj\\libc.obj code\\common\\libc.c",
nob_temp_sprintf("cl %s /c /Foobj\\runtime\\user_heap.obj code\\user\\runtime\\heap.c", UserCflags),
nob_temp_sprintf("cl %s /c /Foobj\\runtime\\user_heap_exports.obj code\\user\\runtime\\heap_exports.c", UserCflags),
@@ -298,18 +352,19 @@ int main(int argc, char **argv) {
nob_temp_sprintf("cl %s /c /Foobj\\runtime\\posix_start.obj code\\user\\runtime\\posix_start.c", UserCflags),
// Bootloader.
nob_temp_sprintf("cl %s /MP /c /Icode\\boot\\uefi\\include /Foobj\\boot\\ code\\boot\\uefi\\*.c && link %s /FORCE:MULTIPLE /SUBSYSTEM:EFI_APPLICATION /ENTRY:UefiMain /OUT:bin\\bootx64.efi obj\\libc.obj obj\\ini_parser.obj code\\boot\\uefi\\efilib.lib obj\\boot\\*.obj", BootCflags, LFLAGS),
nob_temp_sprintf("cl %s /MP /c /Icode\\boot\\uefi\\include /Foobj\\boot\\ code\\boot\\uefi\\*.c code\\common\\boot_volume_selector.c && link %s /FORCE:MULTIPLE /SUBSYSTEM:EFI_APPLICATION /ENTRY:UefiMain /OUT:bin\\bootx64.efi obj\\libc.obj code\\boot\\uefi\\efilib.lib obj\\boot\\*.obj", BootCflags, LFLAGS),
// HAL.
nob_temp_sprintf("cl %s /MP /c /Foobj\\hal\\ code\\kernel\\hal\\*.c && link %s /DLL /OUT:bin\\hal.dll /ENTRY:HalInitialize /IGNORE:4216 obj\\printf.obj obj\\libc.obj obj\\hal\\*.obj", KernelCflags, LFLAGS),
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 && link %s /SUBSYSTEM:NATIVE /ENTRY:KernelMain /OUT:bin\\kernel.exe bin\\hal.lib obj\\ini_parser.obj obj\\printf.obj obj\\libc.obj obj\\kernel\\*.obj", KernelCflags, LFLAGS),
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),
// Subsystem DLLs.
"link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\syscall.dll obj\\syscall\\stubs.obj",
nob_temp_sprintf("cl %s /DUSER_HEAP_EXPORTS /DNTL_EXCEPTION_EXPORTS /MP /c /Foobj\\win32\\ code\\user\\runtime\\heap.c code\\user\\runtime\\heap_exports.c code\\user\\runtime\\native_exception.c code\\user\\personalities\\win32\\*.c && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\win32.dll bin\\syscall.lib obj\\libc.obj obj\\win32\\*.obj", UserCflags),
nob_temp_sprintf("cl %s /DUSER_HEAP_EXPORTS /MP /c /Foobj\\posix\\ code\\user\\runtime\\heap.c code\\user\\runtime\\heap_exports.c code\\user\\personalities\\posix\\*.c && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\posix.dll bin\\syscall.lib obj\\libc.obj obj\\posix\\*.obj", UserCflags)
"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\\path.obj obj\\libc.obj",
"lib /nologo /OUT:bin\\syscall_s.lib obj\\syscall\\stubs_s.obj obj\\syscall\\pe_loader_s.obj obj\\syscall\\module_loader_s.obj obj\\syscall\\process_loader_s.obj obj\\syscall\\resource_s.obj obj\\syscall\\path_s.obj",
nob_temp_sprintf("cl %s /DUSER_HEAP_EXPORTS /DNTL_EXCEPTION_EXPORTS /MP /c /Foobj\\win32\\ code\\user\\runtime\\heap.c code\\user\\runtime\\heap_exports.c code\\user\\runtime\\native_exception.c code\\user\\personalities\\win32\\*.c && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL " USER_IMAGE_FLAGS_LITERAL " /OUT:bin\\win32.dll bin\\syscall.lib obj\\libc.obj obj\\win32\\*.obj", UserCflags),
nob_temp_sprintf("cl %s /DUSER_HEAP_EXPORTS /MP /c /Foobj\\posix\\ code\\user\\runtime\\heap.c code\\user\\runtime\\heap_exports.c code\\user\\personalities\\posix\\*.c && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL " USER_IMAGE_FLAGS_LITERAL " /OUT:bin\\posix.dll bin\\syscall.lib obj\\libc.obj obj\\posix\\*.obj", UserCflags)
};
for (size_t i = 0; i < NOB_ARRAY_LEN(core_tasks); i++) {
@@ -317,8 +372,8 @@ int main(int argc, char **argv) {
}
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\\hal.lib bin\\kernel.lib obj\\printf.obj obj\\libc.obj %s\\*.obj",
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",
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
);
@@ -328,9 +383,9 @@ int main(int argc, char **argv) {
Nob_Procs procs = {0};
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\\hal.lib bin\\kernel.lib %s obj\\libc.obj %s\\*.obj",
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",
driver->cflags, driver->obj_dir, driver->sources, driver->name,
driver->libs ? driver->libs : "", driver->obj_dir
);
@@ -349,11 +404,12 @@ 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\\hal.lib 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 /ENTRY:DriverEntry bin\\kernel.lib obj\\libc.obj obj\\lwip\\*.obj", LwipCflags, lwip_files.items)));
}
const char *apps[][4] = {
const char* apps[][4] = {
{"system\\init", NATIVE_INIT_LIBS_LITERAL, "init", "/ENTRY:_start"},
{"system\\rssrv", NATIVE_SERVICE_LIBS_LITERAL, "rssrv", "/ENTRY:_start"},
{"tests\\all_tests", NATIVE_EXE_LIBS_LITERAL, "all_tests", NATIVE_EXE_ENTRY_LITERAL},
{"apps\\shell_win32", WIN32_EXE_LIBS_LITERAL, "shell_win32", NATIVE_EXE_ENTRY_LITERAL},
{"apps\\shell_posix", POSIX_EXE_LIBS_LITERAL, "shell_posix", "/ENTRY:PosixStart"},
@@ -363,6 +419,9 @@ int main(int argc, char **argv) {
{"tests\\async_cancel", NATIVE_EXE_LIBS_LITERAL, "async_cancel", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\file_stat_smoke", NATIVE_EXE_LIBS_LITERAL, "file_stat_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\handle_dup_smoke", NATIVE_EXE_LIBS_LITERAL, "handle_dup_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\security_smoke", NATIVE_EXE_LIBS_LITERAL, "security_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\port_ipc_smoke", NATIVE_EXE_LIBS_LITERAL, "port_ipc_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\resource_service_smoke", NATIVE_EXE_LIBS_LITERAL, "resource_service_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\named_queue_lifetime_smoke", NATIVE_EXE_LIBS_LITERAL, "named_queue_lifetime_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\input_api_smoke", NATIVE_EXE_LIBS_LITERAL, "input_api_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\input_model_smoke", NATIVE_EXE_LIBS_LITERAL, "input_model_smoke", NATIVE_EXE_ENTRY_LITERAL},
@@ -371,6 +430,8 @@ int main(int argc, char **argv) {
{"tests\\realpath_smoke", NATIVE_EXE_LIBS_LITERAL, "realpath_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\file_lock_smoke", NATIVE_EXE_LIBS_LITERAL, "file_lock_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\process_protection_smoke", NATIVE_EXE_LIBS_LITERAL, "process_protection_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\process_exit_multithread_smoke", NATIVE_EXE_LIBS_LITERAL, "process_exit_multithread_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\process_builder_smoke", "bin\\syscall_s.lib obj\\libc.obj " NATIVE_RUNTIME_OBJS_LITERAL, "process_builder_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\socket_addr_smoke", NATIVE_EXE_LIBS_LITERAL, "socket_addr_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\net_irp_smoke", NATIVE_EXE_LIBS_LITERAL, "net_irp_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\module_load_smoke", NATIVE_EXE_LIBS_LITERAL, "module_load_smoke", NATIVE_EXE_ENTRY_LITERAL},
@@ -389,7 +450,7 @@ int main(int argc, char **argv) {
{"tests\\win32_mutex_smoke", WIN32_EXE_LIBS_LITERAL, "win32_mutex_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\win32_thread_suspend_smoke", WIN32_EXE_LIBS_LITERAL, "win32_thread_suspend_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\win32_module_lookup_smoke", WIN32_EXE_LIBS_LITERAL, "win32_module_lookup_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\cwd_peb_smoke", NATIVE_EXE_LIBS_LITERAL, "cwd_peb_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\cwd_userspace_smoke", NATIVE_EXE_LIBS_LITERAL, "cwd_userspace_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\mmap_smoke", NATIVE_EXE_LIBS_LITERAL, "mmap_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\pcache_smoke", NATIVE_EXE_LIBS_LITERAL, "pcache_smoke", NATIVE_EXE_ENTRY_LITERAL},
{"tests\\pcache_pressure", NATIVE_EXE_LIBS_LITERAL, "pcache_pressure", NATIVE_EXE_ENTRY_LITERAL},
@@ -413,8 +474,8 @@ int main(int argc, char **argv) {
};
for (size_t i = 0; i < NOB_ARRAY_LEN(apps); i++) {
const char *cmd = nob_temp_sprintf(
"cl %s /MP /c /Foobj\\%s\\ code\\user\\%s\\*.c && link %s /SUBSYSTEM:NATIVE %s /OUT:bin\\%s.exe /PDB:bin\\%s.pdb %s obj\\%s\\*.obj",
const char* cmd = nob_temp_sprintf(
"cl %s /MP /c /Foobj\\%s\\ code\\user\\%s\\*.c && link %s /SUBSYSTEM:NATIVE " USER_IMAGE_FLAGS_LITERAL " %s /OUT:bin\\%s.exe /PDB:bin\\%s.pdb %s obj\\%s\\*.obj",
UserCflags, apps[i][2], apps[i][0], LFLAGS, apps[i][3], apps[i][2], apps[i][2], apps[i][1], apps[i][2]
);
nob_da_append(&procs, run_async(cmd));
@@ -431,106 +492,129 @@ int main(int argc, char **argv) {
run_sync("mformat -i " VHD_PART1 " -F");
run_sync("mformat -i " VHD_PART2 " -F");
mmd_dir(VHD_PART1, "apps");
mmd_dir(VHD_PART1, "system");
mmd_dir(VHD_PART1, "drivers");
mmd_dir(VHD_PART1, "EFI");
mmd_dir(VHD_PART1, "EFI/BOOT");
mmd_dir(VHD_PART1, "EFI/NTLite");
mmd_dir(VHD_PART1, "EFI/NTLite/fs");
mmd_dir(VHD_PART2, "apps");
mmd_dir(VHD_PART2, "system");
mmd_dir(VHD_PART2, "drivers");
mmd_dir(VHD_PART2, "tests");
const char *copies[][3] = {
{"bin\\bootx64.efi", VHD_PART1, "EFI/BOOT/bootx64.efi"},
{"bin\\kernel.exe", VHD_PART1, "system/colonel.exe"},
{"bin\\hal.dll", VHD_PART1, "system/hal.dll"},
{"bin\\syscall.dll", VHD_PART1, "system/syscall.dll"},
{"bin\\win32.dll", VHD_PART1, "system/win32.dll"},
{"bin\\posix.dll", VHD_PART1, "system/posix.dll"},
{"code\\settings.ini", VHD_PART1, "system/settings.ini"},
{"bin\\init.exe", VHD_PART1, "system/init.exe"},
{"bin\\pci.sys", VHD_PART1, "drivers/pci.sys"},
{"bin\\ps2.sys", VHD_PART1, "drivers/ps2.sys"},
{"bin\\serial.sys", VHD_PART1, "drivers/serial.sys"},
{"bin\\hda.sys", VHD_PART1, "drivers/hda.sys"},
{"bin\\ata.sys", VHD_PART1, "drivers/ata.sys"},
{"bin\\ahci.sys", VHD_PART1, "drivers/ahci.sys"},
{"bin\\fat.sys", VHD_PART1, "drivers/fat.sys"},
{"bin\\ntfs.sys", VHD_PART1, "drivers/ntfs.sys"},
{"bin\\vmwaresvga.sys", VHD_PART1, "drivers/vmwaresvga.sys"},
{"bin\\lwip.sys", VHD_PART1, "drivers/lwip.sys"},
{"bin\\usb.sys", VHD_PART1, "drivers/usb.sys"},
{"bin\\rtl816x.sys", VHD_PART1, "drivers/rtl816x.sys"},
{"bin\\rtl8139.sys", VHD_PART1, "drivers/rtl8139.sys"},
{"code\\drivers\\drivers.ini", VHD_PART1, "drivers/drivers.ini"},
{"code\\boot.ini", VHD_PART1, "boot.ini"},
// 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;
if (!mcopy_file("code\\boot.ini", VHD_PART1, "boot.ini")) return 1;
if (!run_sync("if exist code\\boot\\uefi\\fs\\*.efi mcopy -o -i " VHD_PART1 " code\\boot\\uefi\\fs\\*.efi ::EFI/NTLite/fs/")) return 1;
{"bin\\shell_win32.exe", VHD_PART1, "apps/shell_win32.exe"},
{"bin\\shell_posix.exe", VHD_PART1, "apps/shell_posix.exe"},
{"bin\\shell_native.exe", VHD_PART1, "apps/shell_native.exe"},
{"bin\\edit.exe", VHD_PART1, "apps/edit.exe"},
{"bin\\httpget.exe", VHD_PART1, "apps/httpget.exe"},
{"bin\\tcpserve.exe", VHD_PART1, "apps/tcpserve.exe"},
{"bin\\udp.exe", VHD_PART1, "apps/udp.exe"},
{"bin\\resolve.exe", VHD_PART1, "apps/resolve.exe"},
{"bin\\ping.exe", VHD_PART1, "apps/ping.exe"},
{"bin\\doom.exe", VHD_PART1, "apps/doom.exe"},
{"bin\\video.exe", VHD_PART1, "apps/video.exe"},
{"bin\\memtop.exe", VHD_PART1, "apps/memtop.exe"},
if (!mcopy_file("bin\\kernel.exe", VHD_PART2, "system/colonel.exe")) return 1;
// Partition 2 Apps
{"bin\\all_tests.exe", VHD_PART2, "tests/all_tests.exe"},
{"bin\\async_apc.exe", VHD_PART2, "tests/async_apc.exe"},
{"bin\\async_cancel.exe", VHD_PART2, "tests/async_cancel.exe"},
{"bin\\apc_return_smoke.exe", VHD_PART2, "tests/apc_return_smoke.exe"},
{"bin\\file_stat_smoke.exe", VHD_PART2, "tests/file_stat_smoke.exe"},
{"bin\\handle_dup_smoke.exe", VHD_PART2, "tests/handle_dup_smoke.exe"},
{"bin\\named_queue_lifetime_smoke.exe", VHD_PART2, "tests/named_queue_lifetime_smoke.exe"},
{"bin\\input_api_smoke.exe", VHD_PART2, "tests/input_api_smoke.exe"},
{"bin\\input_model_smoke.exe", VHD_PART2, "tests/input_model_smoke.exe"},
{"bin\\timer_smoke.exe", VHD_PART2, "tests/timer_smoke.exe"},
{"bin\\stats_abi_smoke.exe", VHD_PART2, "tests/stats_abi_smoke.exe"},
{"bin\\realpath_smoke.exe", VHD_PART2, "tests/realpath_smoke.exe"},
{"bin\\file_lock_smoke.exe", VHD_PART2, "tests/file_lock_smoke.exe"},
{"bin\\process_protection_smoke.exe", VHD_PART2, "tests/process_protection_smoke.exe"},
{"bin\\socket_addr_smoke.exe", VHD_PART2, "tests/socket_addr_smoke.exe"},
{"bin\\net_irp_smoke.exe", VHD_PART2, "tests/net_irp_smoke.exe"},
{"bin\\module_load_smoke.exe", VHD_PART2, "tests/module_load_smoke.exe"},
{"bin\\posix_module_smoke.exe", VHD_PART2, "tests/posix_module_smoke.exe"},
{"bin\\exception_dispatcher_smoke.exe", VHD_PART2, "tests/exception_dispatcher_smoke.exe"},
{"bin\\native_exception_runtime_smoke.exe", VHD_PART2, "tests/native_exception_runtime_smoke.exe"},
{"bin\\native_raise_exception_smoke.exe", VHD_PART2, "tests/native_raise_exception_smoke.exe"},
{"bin\\exception_smoke.exe", VHD_PART2, "tests/exception_smoke.exe"},
{"bin\\win32_exception_smoke.exe", VHD_PART2, "tests/win32_exception_smoke.exe"},
{"bin\\win32_seh_smoke.exe", VHD_PART2, "tests/win32_seh_smoke.exe"},
{"bin\\syscall_contract_smoke.exe", VHD_PART2, "tests/syscall_contract_smoke.exe"},
{"bin\\would_block_smoke.exe", VHD_PART2, "tests/would_block_smoke.exe"},
{"bin\\audio_stream_smoke.exe", VHD_PART2, "tests/audio_stream_smoke.exe"},
{"bin\\winmm_waveout_smoke.exe", VHD_PART2, "tests/winmm_waveout_smoke.exe"},
{"bin\\win32_condition_variable_smoke.exe", VHD_PART2, "tests/win32_condition_variable_smoke.exe"},
{"bin\\win32_mutex_smoke.exe", VHD_PART2, "tests/win32_mutex_smoke.exe"},
{"bin\\win32_thread_suspend_smoke.exe", VHD_PART2, "tests/win32_thread_suspend_smoke.exe"},
{"bin\\win32_module_lookup_smoke.exe", VHD_PART2, "tests/win32_module_lookup_smoke.exe"},
{"bin\\cwd_peb_smoke.exe", VHD_PART2, "tests/cwd_peb_smoke.exe"},
{"bin\\mmap_smoke.exe", VHD_PART2, "tests/mmap_smoke.exe"},
{"bin\\pcache_smoke.exe", VHD_PART2, "tests/pcache_smoke.exe"},
{"bin\\pcache_pressure.exe", VHD_PART2, "tests/pcache_pressure.exe"},
{"bin\\dirty_writeback.exe", VHD_PART2, "tests/dirty_writeback.exe"},
{"bin\\shared_mmap.exe", VHD_PART2, "tests/shared_mmap.exe"},
{"bin\\pcache_invalidate.exe", VHD_PART2, "tests/pcache_invalidate.exe"},
{"bin\\pcache_mmap_read_reuse.exe", VHD_PART2, "tests/pcache_mmap_read_reuse.exe"},
{"bin\\pcache_multithread.exe", VHD_PART2, "tests/pcache_multithread.exe"},
{"bin\\surface_smoke.exe", VHD_PART2, "tests/surface_smoke.exe"},
{"bin\\surface_invalid_rect.exe", VHD_PART2, "tests/surface_invalid_rect.exe"},
{"bin\\surface_multithread_present.exe", VHD_PART2, "tests/surface_multithread_present.exe"}
const char* system_files[] = {
"bin\\hal.dll",
"bin\\syscall.dll",
"bin\\win32.dll",
"bin\\posix.dll",
"code\\settings.ini",
"bin\\init.exe",
"bin\\rssrv.exe",
};
if (!mcopy_files_to_dir(VHD_PART2, "system/", system_files, NOB_ARRAY_LEN(system_files))) return 1;
for (size_t i = 0; i < NOB_ARRAY_LEN(copies); i++) {
mcopy_file(copies[i][0], copies[i][1], copies[i][2]);
}
const char* driver_files[] = {
"bin\\pci.sys",
"bin\\ps2.sys",
"bin\\serial.sys",
"bin\\hda.sys",
"bin\\ata.sys",
"bin\\ahci.sys",
"bin\\fat.sys",
"bin\\ntfs.sys",
"bin\\vmwaresvga.sys",
"bin\\lwip.sys",
"bin\\usb.sys",
"bin\\rtl816x.sys",
"bin\\rtl8139.sys",
"code\\drivers\\drivers.ini",
};
if (!mcopy_files_to_dir(VHD_PART2, "drivers/", driver_files, NOB_ARRAY_LEN(driver_files))) return 1;
if (nob_file_exists("code\\user\\apps\\video\\bink2w64.dll")) mcopy_file("code\\user\\apps\\video\\bink2w64.dll", VHD_PART1, "apps/bink2w64.dll");
if (nob_file_exists("code\\user\\apps\\video\\video.bk2")) mcopy_file("code\\user\\apps\\video\\video.bk2", VHD_PART1, "apps/video.bk2");
if (nob_file_exists("code\\user\\apps\\doom\\doom1.wad")) mcopy_file("code\\user\\apps\\doom\\doom1.wad", VHD_PART1, "apps/doom1.wad");
const char* app_files[] = {
"bin\\shell_win32.exe",
"bin\\shell_posix.exe",
"bin\\shell_native.exe",
"bin\\edit.exe",
"bin\\httpget.exe",
"bin\\tcpserve.exe",
"bin\\udp.exe",
"bin\\resolve.exe",
"bin\\ping.exe",
"bin\\doom.exe",
"bin\\video.exe",
"bin\\memtop.exe",
};
if (!mcopy_files_to_dir(VHD_PART2, "apps/", app_files, NOB_ARRAY_LEN(app_files))) return 1;
const char* test_files[] = {
"bin\\all_tests.exe",
"bin\\async_apc.exe",
"bin\\async_cancel.exe",
"bin\\apc_return_smoke.exe",
"bin\\file_stat_smoke.exe",
"bin\\handle_dup_smoke.exe",
"bin\\security_smoke.exe",
"bin\\port_ipc_smoke.exe",
"bin\\resource_service_smoke.exe",
"bin\\named_queue_lifetime_smoke.exe",
"bin\\input_api_smoke.exe",
"bin\\input_model_smoke.exe",
"bin\\timer_smoke.exe",
"bin\\stats_abi_smoke.exe",
"bin\\realpath_smoke.exe",
"bin\\file_lock_smoke.exe",
"bin\\process_protection_smoke.exe",
"bin\\process_exit_multithread_smoke.exe",
"bin\\process_builder_smoke.exe",
"bin\\socket_addr_smoke.exe",
"bin\\net_irp_smoke.exe",
"bin\\module_load_smoke.exe",
"bin\\posix_module_smoke.exe",
"bin\\exception_dispatcher_smoke.exe",
"bin\\native_exception_runtime_smoke.exe",
"bin\\native_raise_exception_smoke.exe",
"bin\\exception_smoke.exe",
"bin\\win32_exception_smoke.exe",
"bin\\win32_seh_smoke.exe",
"bin\\syscall_contract_smoke.exe",
"bin\\would_block_smoke.exe",
"bin\\audio_stream_smoke.exe",
"bin\\winmm_waveout_smoke.exe",
"bin\\win32_condition_variable_smoke.exe",
"bin\\win32_mutex_smoke.exe",
"bin\\win32_thread_suspend_smoke.exe",
"bin\\win32_module_lookup_smoke.exe",
"bin\\cwd_userspace_smoke.exe",
"bin\\mmap_smoke.exe",
"bin\\pcache_smoke.exe",
"bin\\pcache_pressure.exe",
"bin\\dirty_writeback.exe",
"bin\\shared_mmap.exe",
"bin\\pcache_invalidate.exe",
"bin\\pcache_mmap_read_reuse.exe",
"bin\\pcache_multithread.exe",
"bin\\surface_smoke.exe",
"bin\\surface_invalid_rect.exe",
"bin\\surface_multithread_present.exe",
};
if (!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;
if (nob_file_exists("code\\user\\apps\\video\\bink2w64.dll"))
optional_app_files[optional_app_count++] = "code\\user\\apps\\video\\bink2w64.dll";
if (nob_file_exists("code\\user\\apps\\video\\video.bk2"))
optional_app_files[optional_app_count++] = "code\\user\\apps\\video\\video.bk2";
if (nob_file_exists("code\\user\\apps\\doom\\doom1.wad"))
optional_app_files[optional_app_count++] = "code\\user\\apps\\doom\\doom1.wad";
if (!mcopy_files_to_dir(VHD_PART2, "apps/", optional_app_files, optional_app_count)) return 1;
nob_log(NOB_INFO, "Build complete!");
return 0;
+99
View File
@@ -0,0 +1,99 @@
// Shared parser for the canonical structural boot-volume selector grammar.
#include <common/boot_volume_selector.h>
#include <string.h>
// Converts one hexadecimal character without accepting non-hexadecimal text.
static bool BootVolumeHexDigit(char c, uint8_t* digitOut) {
if (!digitOut)
return false;
if (c >= '0' && c <= '9') {
*digitOut = (uint8_t)(c - '0');
return true;
}
if (c >= 'a' && c <= 'f') {
*digitOut = (uint8_t)(10 + c - 'a');
return true;
}
if (c >= 'A' && c <= 'F') {
*digitOut = (uint8_t)(10 + c - 'A');
return true;
}
return false;
}
// Parses an exact hexadecimal integer span into a 32-bit value.
static bool BootVolumeParseHex32(const char* text, size_t length, uint32_t* valueOut) {
if (!text || !valueOut || length == 0 || length > 8)
return false;
uint32_t value = 0;
for (size_t i = 0; i < length; ++i) {
uint8_t digit = 0;
if (!BootVolumeHexDigit(text[i], &digit))
return false;
value = (value << 4) | digit;
}
*valueOut = value;
return true;
}
// Parses one exact GUID hexadecimal group.
static bool BootVolumeParseGuidGroup(const char* text, size_t length, uint32_t* valueOut) {
return BootVolumeParseHex32(text, length, valueOut);
}
// Parses a canonical 8-4-4-4-12 GUID into EFI/GPT field layout.
static bool BootVolumeParseGuid(const char* text, size_t length, BOOT_VOLUME_GUID* guidOut) {
if (!text || !guidOut || length != 36 ||
text[8] != '-' || text[13] != '-' || text[18] != '-' || text[23] != '-') {
return false;
}
BOOT_VOLUME_GUID guid = {0};
uint32_t value = 0;
if (!BootVolumeParseGuidGroup(text, 8, &guid.Data1) ||
!BootVolumeParseGuidGroup(text + 9, 4, &value)) {
return false;
}
guid.Data2 = (uint16_t)value;
if (!BootVolumeParseGuidGroup(text + 14, 4, &value))
return false;
guid.Data3 = (uint16_t)value;
const size_t byteOffsets[8] = {19, 21, 24, 26, 28, 30, 32, 34};
for (size_t i = 0; i < 8; ++i) {
if (!BootVolumeParseGuidGroup(text + byteOffsets[i], 2, &value))
return false;
guid.Data4[i] = (uint8_t)value;
}
*guidOut = guid;
return true;
}
// Parses one canonical structural system-volume selector.
bool BootVolumeSelectorParse(const char* text, size_t length, BOOT_VOLUME_SELECTOR* selectorOut) {
if (!text || !selectorOut)
return false;
BOOT_VOLUME_SELECTOR selector = {0};
if (length == 14 && memcmp(text, "mbr/", 4) == 0 && text[12] == '/' &&
BootVolumeParseHex32(text + 4, 8, &selector.Identity.Mbr.DiskSignature) &&
text[13] >= '1' && text[13] <= '4') {
selector.Kind = BootVolumeSelectorMbr;
selector.Identity.Mbr.PartitionNumber = (uint32_t)(text[13] - '0');
*selectorOut = selector;
return true;
}
if (length == 40 && memcmp(text, "gpt/", 4) == 0 &&
BootVolumeParseGuid(text + 4, 36, &selector.Identity.Gpt.PartitionGuid)) {
selector.Kind = BootVolumeSelectorGpt;
*selectorOut = selector;
return true;
}
return false;
}
@@ -0,0 +1,36 @@
// Shared structural boot-volume selector declarations.
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef enum BOOT_VOLUME_SELECTOR_KIND {
BootVolumeSelectorInvalid = 0,
BootVolumeSelectorMbr,
BootVolumeSelectorGpt,
} BOOT_VOLUME_SELECTOR_KIND;
typedef struct BOOT_VOLUME_GUID {
uint32_t Data1;
uint16_t Data2;
uint16_t Data3;
uint8_t Data4[8];
} BOOT_VOLUME_GUID;
typedef struct BOOT_VOLUME_SELECTOR {
BOOT_VOLUME_SELECTOR_KIND Kind;
union {
struct {
uint32_t DiskSignature;
uint32_t PartitionNumber;
} Mbr;
struct {
BOOT_VOLUME_GUID PartitionGuid;
} Gpt;
} Identity;
} BOOT_VOLUME_SELECTOR;
// Parses one canonical structural system-volume selector.
bool BootVolumeSelectorParse(const char* text, size_t length, BOOT_VOLUME_SELECTOR* selectorOut);
-6
View File
@@ -1,6 +0,0 @@
// Shared INI parser declarations for boot and kernel configuration readers.
#pragma once
typedef void (*IniCallback)(const char* section, const char* key,
const char* value, void* userdata);
void IniParse(char* buf, IniCallback cb, void* userdata);
-55
View File
@@ -1,55 +0,0 @@
// Minimal INI parser. This file accepts the small configuration dialect used by boot.ini and drivers.ini.
#include <string.h>
#include <common/ini_parser.h>
// Checks whether ws.
static inline int IsWs(char c) { return c == ' ' || c == '\t' || c == '\r'; }
// Implements the trim Left operation.
static char* TrimLeft(char* s) {
while (*s && IsWs(*s)) s++;
return s;
}
// Implements the trim Right operation.
static void TrimRight(char* s) {
char* e = s + strlen(s);
while (e > s && IsWs(*(e-1))) *--e = '\0';
}
// Parse 'buf' (null-terminated) for [section] / key=value.
// Calls cb(section, key, value, userdata) for each key found.
void IniParse(char* buf, IniCallback cb, void* userdata) {
char section[64] = "";
char* line = buf;
while (*line) {
char* end = line;
while (*end && *end != '\n') end++;
char saved = *end;
*end = '\0';
char* p = TrimLeft(line);
if (*p == '[') {
p++;
char* close = strchr(p, ']');
if (close) { *close = '\0'; strncpy(section, p, 63); section[63] = '\0'; }
} else if (*p && *p != ';' && *p != '#') {
char* eq = strchr(p, '=');
if (eq) {
*eq = '\0';
char* key = p; TrimRight(key);
char* val = TrimLeft(eq + 1); TrimRight(val);
cb(section, key, val, userdata);
} else {
// Key with no value (e.g., a driver filename in drivers.ini).
TrimRight(p);
if (*p) cb(section, p, "", userdata);
}
}
*end = saved;
line = end + (*end ? 1 : 0);
}
}
+21 -9
View File
@@ -20,15 +20,15 @@ char* strncpy(char* dest, const char* src, size_t n) {
return dest;
}
char *strstr(const char *haystack, const char *needle) {
char* strstr(const char* haystack, const char* needle) {
// An empty needle matches the beginning of haystack
if (!*needle) {
return (char *)haystack;
return (char*)haystack;
}
for (; *haystack != '\0'; haystack++) {
const char *h = haystack;
const char *n = needle;
const char* h = haystack;
const char* n = needle;
// Match as many characters as possible
while (*h != '\0' && *n != '\0' && *h == *n) {
@@ -38,7 +38,7 @@ char *strstr(const char *haystack, const char *needle) {
// If we reached the end of needle, we found a match
if (*n == '\0') {
return (char *)haystack;
return (char*)haystack;
}
}
@@ -83,13 +83,13 @@ int strcmp(const char* s1, const char* s2) {
}
// Implements the strncmp operation.
int strncmp(const char *s1, const char *s2, size_t n) {
int strncmp(const char* s1, const char* s2, size_t n) {
while (n && *s1 && (*s1 == *s2)) {
s1++;
s2++;
n--;
}
return n == 0 ? 0 : *(unsigned char *)s1 - *(unsigned char *)s2;
return n == 0 ? 0 : *(unsigned char*)s1 - *(unsigned char*)s2;
}
// Implements the stricmp operation.
@@ -109,7 +109,7 @@ size_t strlen(const char* s) {
}
// Implements the strnlen operation.
size_t strnlen(const char *s, size_t maxlen) {
size_t strnlen(const char* s, size_t maxlen) {
size_t len;
for (len = 0; len < maxlen; len++) {
if (s[len] == '\0') {
@@ -122,7 +122,7 @@ size_t strnlen(const char *s, size_t maxlen) {
// Implements the strchr operation.
char* strchr(const char* s, int c) {
while (*s) {
if (*s == (char)c) return (char *)s;
if (*s == (char)c) return (char*)s;
s++;
}
return NULL;
@@ -138,6 +138,18 @@ char* strrchr(const char* s, int c) {
return (char*)last;
}
void *memchr(const void *s, int c, size_t n) {
const unsigned char *p = s;
unsigned char ch = (unsigned char)c;
while (n--) {
if (*p == ch) {
return (void *)p;
}
p++;
}
return NULL;
}
// Implements the memset operation.
void* memset(void* dest, int c, size_t n) {
unsigned char* d = (unsigned char*)dest;
+10 -18
View File
@@ -611,15 +611,6 @@ __declspec(dllexport) KSTATUS PciEnableMsi(DEVICE_OBJECT* pdo, uint8_t irqSlot)
if (!cap)
return NTLSTATUS_NOT_SUPPORTED;
uint32_t bspLapic = gDdk->Platform->GetBspLapicId();
uint8_t x2Apic = gDdk->Platform->IsX2ApicEnabled();
if (!x2Apic && bspLapic >= 256u)
return NTLSTATUS_NOT_SUPPORTED;
if (x2Apic && bspLapic >= 256u) {
// Extended x2APIC MSI destinations need interrupt-remapping awareness.
return NTLSTATUS_NOT_SUPPORTED;
}
uint8_t bus = ext->Info.Address.Bus;
uint8_t dev = ext->Info.Address.Device;
uint8_t fn = ext->Info.Address.Function;
@@ -627,18 +618,19 @@ __declspec(dllexport) KSTATUS PciEnableMsi(DEVICE_OBJECT* pdo, uint8_t irqSlot)
uint16_t control = PciRawRead16(bus, dev, fn, (uint16_t)(cap + 2u));
bool is64 = (control & PCI_MSI_CTRL_64BIT) != 0;
bool pvm = (control & PCI_MSI_CTRL_PVM) != 0;
uint8_t vector = (uint8_t)(PCI_IRQ_VECTOR_BASE + irqSlot);
uint32_t address = PCI_MSI_ADDRESS_BASE | ((bspLapic & 0xFFu) << 12);
uint16_t data = vector;
NTL_DDK_MSI_MESSAGE message;
KSTATUS messageStatus = gDdk->Interrupt->ComposeMsiMessage(irqSlot, &message);
if (!KSUCCESS(messageStatus))
return messageStatus;
PciRawWrite32(bus, dev, fn, (uint16_t)(cap + 4u), address);
PciRawWrite32(bus, dev, fn, (uint16_t)(cap + 4u), (uint32_t)message.Address);
if (is64) {
PciRawWrite32(bus, dev, fn, (uint16_t)(cap + 8u), 0);
PciRawWrite16(bus, dev, fn, (uint16_t)(cap + 12u), data);
PciRawWrite32(bus, dev, fn, (uint16_t)(cap + 8u), (uint32_t)(message.Address >> 32));
PciRawWrite16(bus, dev, fn, (uint16_t)(cap + 12u), (uint16_t)message.Data);
if (pvm)
PciRawWrite32(bus, dev, fn, (uint16_t)(cap + 16u), 0);
} else {
PciRawWrite16(bus, dev, fn, (uint16_t)(cap + 8u), data);
PciRawWrite16(bus, dev, fn, (uint16_t)(cap + 8u), (uint16_t)message.Data);
if (pvm)
PciRawWrite32(bus, dev, fn, (uint16_t)(cap + 12u), 0);
}
@@ -651,8 +643,8 @@ __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 dest=%u %s\n",
bus, dev, fn, cap, vector, irqSlot, bspLapic, is64 ? "64-bit" : "32-bit");
gDdk->Debug->Print("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;
}
-1
View File
@@ -37,7 +37,6 @@
#define PCI_MSI_CTRL_64BIT 0x0080u
#define PCI_MSI_CTRL_PVM 0x0100u
#define PCI_MSI_ADDRESS_BASE 0xFEE00000u
#define PCI_IRQ_VECTOR_BASE 0x20u
typedef struct PCI_PDO_EXTENSION {
+352 -562
View File
File diff suppressed because it is too large. Load diff
+23 -21
View File
@@ -21,25 +21,27 @@
typedef struct FAT_VOL_S FAT_VOL;
KSTATUS FatOpen(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx);
KSTATUS FatFlush(IO_FILESYSTEM *fs, void *fileCtx);
void FatClose(IO_FILESYSTEM *fs, void *fileCtx);
KSTATUS FatRead(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, void *buf, size_t size, size_t *bytesRead);
KSTATUS FatWrite(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, const void *buf, size_t size, size_t *bytesWritten);
KSTATUS FatReadAsync(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, void *buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void *completeContext);
KSTATUS FatWriteAsync(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, const void *buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void *completeContext);
KSTATUS FatCreate(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx);
KSTATUS FatCreateDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path);
KSTATUS FatRemoveDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path);
KSTATUS FatDelete(IO_FILESYSTEM *fs, SYS_STRING_VIEW path);
KSTATUS FatTruncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize, uint32_t fileFlags);
uint64_t FatGetSize(IO_FILESYSTEM *fs, void *fileCtx);
KSTATUS FatQueryInfo(IO_FILESYSTEM *fs, void *fileCtx, SYS_FILE_STAT *out);
KSTATUS FatStat(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, SYS_FILE_STAT *out);
KSTATUS FatQueryIdentity(IO_FILESYSTEM *fs, void *fileCtx, IO_FILE_IDENTITY *out);
KSTATUS FatOpenDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW dirPath, void **dirCtx);
KSTATUS FatReadDir(IO_FILESYSTEM *fs, void *dirCtx, KDIR_ENTRY *out);
void FatCloseDir(IO_FILESYSTEM *fs, void *dirCtx);
void FatQueryStats(IO_FILESYSTEM *fs, IO_FILESYSTEM_STATS *out);
KSTATUS FatOpen(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, void** fileCtx);
KSTATUS FatFlush(IO_VOLUME_PROVIDER* fs, void* fileCtx);
void FatClose(IO_VOLUME_PROVIDER* fs, void* fileCtx);
KSTATUS FatRead(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t offset, void* buf, size_t size, size_t* bytesRead);
KSTATUS FatWrite(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t offset, const void* buf, size_t size, size_t* bytesWritten);
KSTATUS FatReadAsync(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t offset, void* buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void* completeContext);
KSTATUS FatWriteAsync(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t offset, const void* buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void* completeContext);
KSTATUS FatCreate(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, void** fileCtx);
KSTATUS FatCreateDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path);
KSTATUS FatRemoveDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path);
KSTATUS FatDelete(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path);
KSTATUS FatTruncate(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t newSize, uint32_t fileFlags);
uint64_t FatGetSize(IO_VOLUME_PROVIDER* fs, void* fileCtx);
KSTATUS FatQueryInfo(IO_VOLUME_PROVIDER* fs, void* fileCtx, SYS_FILE_STAT* out);
KSTATUS FatStat(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, SYS_FILE_STAT* out);
KSTATUS FatQueryIdentity(IO_VOLUME_PROVIDER* fs, void* fileCtx, IO_FILE_IDENTITY* out);
KSTATUS FatOpenDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW dirPath, void** dirCtx);
KSTATUS FatReadDir(IO_VOLUME_PROVIDER* fs, void* dirCtx, KDIR_ENTRY* out);
void FatCloseDir(IO_VOLUME_PROVIDER* fs, void* dirCtx);
void FatQueryStats(IO_VOLUME_PROVIDER* fs, IO_FILESYSTEM_STATS* out);
void FatInitVtable(IO_FILESYSTEM* fs, FAT_VOL* vol);
void FatInitProvider(IO_VOLUME_PROVIDER* provider, FAT_VOL* vol);
void FatVolumeDead(IO_VOLUME_PROVIDER* provider, IO_VOLUME_REMOVAL_REASON reason);
void FatDestroyVolumeProvider(IO_VOLUME_PROVIDER* provider);
-33
View File
@@ -63,39 +63,6 @@ typedef struct {
uint16_t Name3[2];
} LFN_ENTRY;
typedef struct {
uint32_t Data1;
uint16_t Data2;
uint16_t Data3;
uint8_t Data4[8];
} EFI_GUID;
typedef struct {
char Signature[8];
uint32_t Revision;
uint32_t HeaderSize;
uint32_t HeaderCRC32;
uint32_t Reserved;
uint64_t MyLBA;
uint64_t AlternateLBA;
uint64_t FirstUsableLBA;
uint64_t LastUsableLBA;
EFI_GUID DiskGUID;
uint64_t PartitionEntryLBA;
uint32_t NumberOfPartitionEntries;
uint32_t SizeOfPartitionEntry;
uint32_t PartitionEntryArrayCRC32;
} GPT_HEADER;
typedef struct {
EFI_GUID PartitionTypeGUID;
EFI_GUID UniquePartitionGUID;
uint64_t StartingLBA;
uint64_t EndingLBA;
uint64_t Attributes;
uint16_t PartitionName[36];
} GPT_ENTRY;
#pragma pack(pop)
#define ATTR_VOLUME_ID 0x08
+5 -3
View File
@@ -2,8 +2,8 @@
#include "fat.h"
// Initializes vtable.
void FatInitVtable(IO_FILESYSTEM* fs, FAT_VOL* vol) {
// Initializes the FAT volume provider dispatch.
void FatInitProvider(IO_VOLUME_PROVIDER* fs, FAT_VOL* vol) {
memset(fs, 0, sizeof(*fs));
fs->Open = FatOpen;
fs->Read = FatRead;
@@ -21,9 +21,11 @@ void FatInitVtable(IO_FILESYSTEM* fs, FAT_VOL* vol) {
fs->QueryIdentity = FatQueryIdentity;
fs->Flush = FatFlush;
fs->Close = FatClose;
fs->DriverCtx = vol;
fs->Context = vol;
fs->OpenDir = FatOpenDir;
fs->ReadDir = FatReadDir;
fs->CloseDir = FatCloseDir;
fs->QueryStats = FatQueryStats;
fs->VolumeDead = FatVolumeDead;
fs->Destroy = FatDestroyVolumeProvider;
}
+195 -298
View File
@@ -1,6 +1,6 @@
// Native read-only NTFS filesystem driver for NTLite.
// Portions are based on ntfs-efi by Mark Harmstone (2023), GPLv2-or-later.
// The port removes UEFI protocol/header dependencies and targets NTLite's IO_FILESYSTEM ABI.
// The port removes UEFI protocol/header dependencies and targets NTLite's IO_VOLUME_PROVIDER ABI.
#include <stdint.h>
#include <stddef.h>
@@ -13,9 +13,8 @@
#include <ntlite/ddk/debug.h>
#include <ntlite/ddk/list.h>
#include <ntlite/ddk/sync.h>
#include <ntlite/ddk/mutex.h>
#define NTFS_MAX_VOLUMES 8u
#define NTFS_MAX_PARTITIONS_PER_DISK 32u
#define NTFS_SECTOR_SIZE 512u
#define NTFS_FS_NAME "NTFS "
#define NTFS_UPCASE_TABLE_CHARS 0x10000u
@@ -203,48 +202,6 @@ typedef struct NTFS_FILE_NAME_ATTR {
uint16_t FileName[1];
} NTFS_FILE_NAME_ATTR;
typedef struct NTFS_GPT_GUID {
uint32_t Data1;
uint16_t Data2;
uint16_t Data3;
uint8_t Data4[8];
} NTFS_GPT_GUID;
typedef struct NTFS_GPT_HEADER {
char Signature[8];
uint32_t Revision;
uint32_t HeaderSize;
uint32_t HeaderCRC32;
uint32_t Reserved;
uint64_t MyLBA;
uint64_t AlternateLBA;
uint64_t FirstUsableLBA;
uint64_t LastUsableLBA;
NTFS_GPT_GUID DiskGUID;
uint64_t PartitionEntryLBA;
uint32_t NumberOfPartitionEntries;
uint32_t SizeOfPartitionEntry;
uint32_t PartitionEntryArrayCRC32;
} NTFS_GPT_HEADER;
typedef struct NTFS_GPT_ENTRY {
NTFS_GPT_GUID PartitionTypeGUID;
NTFS_GPT_GUID UniquePartitionGUID;
uint64_t StartingLBA;
uint64_t EndingLBA;
uint64_t Attributes;
uint16_t PartitionName[36];
} NTFS_GPT_ENTRY;
typedef struct NTFS_MBR_PARTITION {
uint8_t BootIndicator;
uint8_t StartingChs[3];
uint8_t OsType;
uint8_t EndingChs[3];
uint32_t StartingLba;
uint32_t SizeInLba;
} NTFS_MBR_PARTITION;
#pragma pack(pop)
typedef struct NTFS_RUN {
@@ -257,7 +214,6 @@ typedef struct NTFS_RUN {
typedef struct NTFS_VOLUME {
uint8_t DriveIndex;
uint8_t VolumeIndex;
uint64_t PartitionLba;
uint64_t PartitionSize;
uint64_t SerialNumber;
@@ -269,6 +225,8 @@ typedef struct NTFS_VOLUME {
uint64_t FileRecordSize;
LIST_ENTRY MftRuns;
uint16_t* Upcase;
IO_VOLUME* Volume; // Active mount-list reference.
LIST_ENTRY MountLink;
} NTFS_VOLUME;
typedef struct NTFS_FILE {
@@ -306,11 +264,6 @@ typedef struct NTFS_DIR_CTX {
uint32_t Position;
} NTFS_DIR_CTX;
typedef struct NTFS_PARTITION_CANDIDATE {
uint64_t StartLba;
uint64_t SectorCount;
} NTFS_PARTITION_CANDIDATE;
typedef struct NTFS_ATTR_VIEW {
const NTFS_ATTRIBUTE_RECORD_HEADER* Attr;
const uint8_t* ResidentData;
@@ -332,13 +285,11 @@ typedef struct NTFS_COLLECT_CTX {
bool HideNtfsMetadata;
} NTFS_COLLECT_CTX;
static NTFS_VOLUME gVolumes[NTFS_MAX_VOLUMES];
static IO_FILESYSTEM gFilesystems[NTFS_MAX_VOLUMES];
static bool gVolumeMounted[NTFS_MAX_VOLUMES];
static bool gVolumeBusy[NTFS_MAX_VOLUMES];
static bool gDiskScanned[NTFS_MAX_VOLUMES];
static SPINLOCK gNtfsMountLock = SPINLOCK_INIT;
static LIST_ENTRY gNtfsMounts;
static uint32_t gMountedCount;
static MUTEX gNtfsMountMutex;
static SPINLOCK gNtfsMountMutexInitLock = SPINLOCK_INIT;
static int gNtfsMountMutexReady;
static uint64_t NtfsAlignUp(uint64_t value, uint64_t alignment) {
if (alignment == 0) return value;
@@ -937,7 +888,6 @@ static void NtfsFillStat(NTFS_FILE* file, SYS_FILE_STAT* out) {
out->Attributes = NtfsSysAttrsFromNtfs(file->Attributes, file->IsDirectory);
out->LinkCount = 1;
out->FileSize = file->Size;
out->VolumeId = file->Vol->SerialNumber;
out->FileId = file->Inode;
NtfsWinTimeToSystemTime(file->StandardInfo.CreationTime, &out->CreationTime);
NtfsWinTimeToSystemTime(file->StandardInfo.LastAccessTime, &out->LastAccessTime);
@@ -1139,9 +1089,9 @@ static bool NtfsCollectEntryCallback(const NTFS_FILE_NAME_ATTR* fn, uint64_t ino
return true;
}
static KSTATUS NtfsOpen(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, void** fileCtx) {
static KSTATUS NtfsOpen(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, void** fileCtx) {
if (!fs || !fileCtx || !SysSvIsValid(path)) return NTLSTATUS_INVALID_PARAMETER;
NTFS_VOLUME* vol = (NTFS_VOLUME*)fs->DriverCtx;
NTFS_VOLUME* vol = (NTFS_VOLUME*)fs->Context;
if (!vol) return NTLSTATUS_INVALID_PARAMETER;
uint64_t inode;
@@ -1156,7 +1106,7 @@ static KSTATUS NtfsOpen(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, void** fileCtx)
return NTLSTATUS_SUCCESS;
}
static void NtfsClose(IO_FILESYSTEM* fs, void* fileCtx) {
static void NtfsClose(IO_VOLUME_PROVIDER* fs, void* fileCtx) {
(void)fs;
NTFS_FILE* file = (NTFS_FILE*)fileCtx;
if (!file) return;
@@ -1164,46 +1114,46 @@ static void NtfsClose(IO_FILESYSTEM* fs, void* fileCtx) {
MmFreePool(file);
}
static KSTATUS NtfsRead(IO_FILESYSTEM* fs, void* fileCtx, uint64_t offset, void* buf, size_t size, size_t* bytesRead) {
static KSTATUS NtfsRead(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t offset, void* buf, size_t size, size_t* bytesRead) {
(void)fs;
return NtfsReadFileData((NTFS_FILE*)fileCtx, offset, buf, size, bytesRead);
}
static KSTATUS NtfsWrite(IO_FILESYSTEM* fs, void* fileCtx, uint64_t offset, const void* buf, size_t size, size_t* bytesWritten) {
static KSTATUS NtfsWrite(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t offset, const void* buf, size_t size, size_t* bytesWritten) {
(void)fs; (void)fileCtx; (void)offset; (void)buf; (void)size;
if (bytesWritten) *bytesWritten = 0;
return NTLSTATUS_NOT_SUPPORTED;
}
static KSTATUS NtfsCreate(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, void** fileCtx) {
static KSTATUS NtfsCreate(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, void** fileCtx) {
(void)fs; (void)path; (void)fileCtx;
return NTLSTATUS_NOT_SUPPORTED;
}
static KSTATUS NtfsDelete(IO_FILESYSTEM* fs, SYS_STRING_VIEW path) {
static KSTATUS NtfsDelete(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path) {
(void)fs; (void)path;
return NTLSTATUS_NOT_SUPPORTED;
}
static KSTATUS NtfsTruncate(IO_FILESYSTEM* fs, void* fileCtx, uint64_t newSize, uint32_t fileFlags) {
static KSTATUS NtfsTruncate(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t newSize, uint32_t fileFlags) {
(void)fs; (void)fileCtx; (void)newSize; (void)fileFlags;
return NTLSTATUS_NOT_SUPPORTED;
}
static uint64_t NtfsGetSize(IO_FILESYSTEM* fs, void* fileCtx) {
static uint64_t NtfsGetSize(IO_VOLUME_PROVIDER* fs, void* fileCtx) {
(void)fs;
NTFS_FILE* file = (NTFS_FILE*)fileCtx;
return file ? file->Size : 0;
}
static KSTATUS NtfsQueryInfo(IO_FILESYSTEM* fs, void* fileCtx, SYS_FILE_STAT* out) {
static KSTATUS NtfsQueryInfo(IO_VOLUME_PROVIDER* fs, void* fileCtx, SYS_FILE_STAT* out) {
(void)fs;
if (!fileCtx || !out) return NTLSTATUS_INVALID_PARAMETER;
NtfsFillStat((NTFS_FILE*)fileCtx, out);
return NTLSTATUS_SUCCESS;
}
static KSTATUS NtfsStat(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, SYS_FILE_STAT* out) {
static KSTATUS NtfsStat(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, SYS_FILE_STAT* out) {
if (!fs || !out || !SysSvIsValid(path)) return NTLSTATUS_INVALID_PARAMETER;
void* ctx = NULL;
KSTATUS s = NtfsOpen(fs, path, &ctx);
@@ -1213,27 +1163,27 @@ static KSTATUS NtfsStat(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, SYS_FILE_STAT*
return s;
}
static KSTATUS NtfsQueryIdentity(IO_FILESYSTEM* fs, void* fileCtx, IO_FILE_IDENTITY* out) {
static KSTATUS NtfsQueryIdentity(IO_VOLUME_PROVIDER* fs, void* fileCtx, IO_FILE_IDENTITY* out) {
(void)fs;
if (!fileCtx || !out) return NTLSTATUS_INVALID_PARAMETER;
NTFS_FILE* file = (NTFS_FILE*)fileCtx;
out->VolumeId = file->Vol->SerialNumber;
out->FileId = file->Inode;
out->Generation = (uint64_t)file->StandardInfo.LastWriteTime ^ ((uint64_t)file->StandardInfo.ChangeTime << 1u);
out->Size = file->Size;
return NTLSTATUS_SUCCESS;
}
static KSTATUS NtfsOpenDir(IO_FILESYSTEM* fs, SYS_STRING_VIEW dirPath, void** dirCtx) {
static KSTATUS NtfsOpenDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW dirPath, void** dirCtx) {
if (!fs || !dirCtx || !SysSvIsValid(dirPath)) return NTLSTATUS_INVALID_PARAMETER;
NTFS_VOLUME* vol = (NTFS_VOLUME*)fs->DriverCtx;
NTFS_VOLUME* vol = (NTFS_VOLUME*)fs->Context;
if (!vol) return NTLSTATUS_INVALID_PARAMETER;
uint64_t inode;
KSTATUS s = NtfsLookupPath(vol, dirPath, &inode);
if (!KSUCCESS(s)) {
NtfsTrace("NTFS: opendir: lookup failed drive=%u pathLen=%llu st=0x%x\n",
vol->VolumeIndex, (unsigned long long)dirPath.Length, s);
NtfsTrace("NTFS: opendir: lookup failed disk=%u lba=%llu pathLen=%llu st=0x%x\n",
vol->DriveIndex, (unsigned long long)vol->PartitionLba,
(unsigned long long)dirPath.Length, s);
return s;
}
@@ -1295,7 +1245,7 @@ static KSTATUS NtfsOpenDir(IO_FILESYSTEM* fs, SYS_STRING_VIEW dirPath, void** di
return NTLSTATUS_SUCCESS;
}
static KSTATUS NtfsReadDir(IO_FILESYSTEM* fs, void* dirCtx, KDIR_ENTRY* out) {
static KSTATUS NtfsReadDir(IO_VOLUME_PROVIDER* fs, void* dirCtx, KDIR_ENTRY* out) {
(void)fs;
if (!dirCtx || !out) return NTLSTATUS_INVALID_PARAMETER;
NTFS_DIR_CTX* ctx = (NTFS_DIR_CTX*)dirCtx;
@@ -1311,7 +1261,7 @@ static KSTATUS NtfsReadDir(IO_FILESYSTEM* fs, void* dirCtx, KDIR_ENTRY* out) {
return NTLSTATUS_SUCCESS;
}
static void NtfsCloseDir(IO_FILESYSTEM* fs, void* dirCtx) {
static void NtfsCloseDir(IO_VOLUME_PROVIDER* fs, void* dirCtx) {
(void)fs;
NTFS_DIR_CTX* ctx = (NTFS_DIR_CTX*)dirCtx;
if (!ctx) return;
@@ -1319,12 +1269,15 @@ static void NtfsCloseDir(IO_FILESYSTEM* fs, void* dirCtx) {
MmFreePool(ctx);
}
static void NtfsQueryStats(IO_FILESYSTEM* fs, IO_FILESYSTEM_STATS* out) {
static void NtfsQueryStats(IO_VOLUME_PROVIDER* fs, IO_FILESYSTEM_STATS* out) {
(void)fs;
if (out) memset(out, 0, sizeof(*out));
}
static void NtfsInitVtable(IO_FILESYSTEM* fs, NTFS_VOLUME* vol) {
static void NtfsVolumeDead(IO_VOLUME_PROVIDER* provider, IO_VOLUME_REMOVAL_REASON reason);
static void NtfsDestroyVolumeProvider(IO_VOLUME_PROVIDER* provider);
static void NtfsInitProvider(IO_VOLUME_PROVIDER* fs, NTFS_VOLUME* vol) {
memset(fs, 0, sizeof(*fs));
fs->Open = NtfsOpen;
fs->Read = NtfsRead;
@@ -1338,15 +1291,12 @@ static void NtfsInitVtable(IO_FILESYSTEM* fs, NTFS_VOLUME* vol) {
fs->QueryIdentity = NtfsQueryIdentity;
fs->Close = NtfsClose;
fs->QueryStats = NtfsQueryStats;
fs->DriverCtx = vol;
fs->Context = vol;
fs->OpenDir = NtfsOpenDir;
fs->ReadDir = NtfsReadDir;
fs->CloseDir = NtfsCloseDir;
}
static bool NtfsGuidIsZero(const NTFS_GPT_GUID* guid) {
static const NTFS_GPT_GUID zeroGuid = {0};
return memcmp(guid, &zeroGuid, sizeof(*guid)) == 0;
fs->VolumeDead = NtfsVolumeDead;
fs->Destroy = NtfsDestroyVolumeProvider;
}
static bool NtfsIsValidBootSector(const NTFS_BOOT_SECTOR* bs) {
@@ -1425,6 +1375,22 @@ static void NtfsCleanupVolume(NTFS_VOLUME* vol) {
}
}
// Provider death blocks new I/O in the I/O core; NTFS state is retained for safe close teardown.
static void NtfsVolumeDead(IO_VOLUME_PROVIDER* provider, IO_VOLUME_REMOVAL_REASON reason) {
(void)provider;
(void)reason;
}
// Releases one dynamically allocated NTFS mount after every volume reference has drained.
static void NtfsDestroyVolumeProvider(IO_VOLUME_PROVIDER* provider) {
if (!provider || !provider->Context)
return;
NTFS_VOLUME* volume = (NTFS_VOLUME*)provider->Context;
NtfsCleanupVolume(volume);
memset(volume, 0, sizeof(*volume));
MmFreePool(volume);
}
static void NtfsTryLoadUpcase(NTFS_VOLUME* vol) {
NTFS_FILE upcase;
if (!KSUCCESS(NtfsLoadFile(vol, NTFS_UPCASE_INODE, &upcase))) return;
@@ -1443,6 +1409,7 @@ static KSTATUS NtfsProbeAndPrepareVolume(NTFS_VOLUME* vol, uint8_t disk, uint64_
vol->PartitionSize = sectors;
vol->BytesPerSector = NTFS_SECTOR_SIZE;
NtfsInitRunList(&vol->MftRuns);
LIST_INIT(&vol->MountLink);
uint8_t sector[NTFS_SECTOR_SIZE];
memset(sector, 0, sizeof(sector));
@@ -1472,252 +1439,182 @@ static KSTATUS NtfsProbeAndPrepareVolume(NTFS_VOLUME* vol, uint8_t disk, uint64_
return NTLSTATUS_SUCCESS;
}
static uint32_t NtfsEnumerateDiskPartitions(uint8_t disk, NTFS_PARTITION_CANDIDATE* parts, uint32_t maxParts, bool* mediaReadable) {
if (mediaReadable) *mediaReadable = false;
if (!parts || maxParts == 0) return 0;
uint8_t sector[NTFS_SECTOR_SIZE];
NTFS_VOLUME raw;
memset(&raw, 0, sizeof(raw));
raw.DriveIndex = disk;
raw.PartitionLba = 0;
raw.PartitionSize = IoDiskSectorCount(disk);
raw.BytesPerSector = NTFS_SECTOR_SIZE;
NtfsInitRunList(&raw.MftRuns);
// Initializes the dynamic NTFS mount-list mutex without allocating kernel objects.
static void NtfsMountEnsureMutex(void) {
if (gNtfsMountMutexReady)
return;
if (!KSUCCESS(NtfsReadSectors(&raw, 0, 1, sector))) return 0;
if (mediaReadable) *mediaReadable = true;
uint32_t count = 0;
NTFS_BOOT_SECTOR* vbr = (NTFS_BOOT_SECTOR*)sector;
if (NtfsIsValidBootSector(vbr)) {
parts[count].StartLba = 0;
parts[count].SectorCount = vbr->TotalSectors;
count++;
if (count >= maxParts) return count;
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountMutexInitLock);
if (!gNtfsMountMutexReady) {
memset(&gNtfsMountMutex, 0, sizeof(gNtfsMountMutex));
ObInitializeObjectHeader(&gNtfsMountMutex.Header, ObjectTypeMutex);
gNtfsMountMutex.Lock = (SPINLOCK)SPINLOCK_INIT;
gNtfsMountMutex.Owner = (THREAD*)0;
LIST_INIT(&gNtfsMountMutex.WaitList);
LIST_INIT(&gNtfsMountMutex.OwnerLink);
gNtfsMountMutex.DonationClass = KeSchedulingNormal;
gNtfsMountMutex.DonationPriority = PriorityLow;
gNtfsMountMutex.HasDonation = 0;
gNtfsMountMutex.Abandoned = 0;
gNtfsMountMutexReady = 1;
}
if (sector[510] != 0x55u || sector[511] != 0xaau) return count;
NTFS_MBR_PARTITION* mbr = (NTFS_MBR_PARTITION*)(sector + 446u);
bool protectiveMbr = false;
for (uint32_t i = 0; i < 4u; i++) if (mbr[i].OsType == 0xeeu) protectiveMbr = true;
if (protectiveMbr) {
uint8_t gptSector[NTFS_SECTOR_SIZE];
if (!KSUCCESS(NtfsReadSectors(&raw, 1, 1, gptSector))) return count;
NTFS_GPT_HEADER* hdr = (NTFS_GPT_HEADER*)gptSector;
if (memcmp(hdr->Signature, "EFI PART", 8) != 0 || hdr->SizeOfPartitionEntry < sizeof(NTFS_GPT_ENTRY)) return count;
uint32_t entries = hdr->NumberOfPartitionEntries;
if (entries > 256u) entries = 256u;
uint32_t entrySize = hdr->SizeOfPartitionEntry;
uint8_t entrySector[NTFS_SECTOR_SIZE];
for (uint32_t i = 0; i < entries && count < maxParts; i++) {
uint64_t byteOff = (uint64_t)i * entrySize;
uint64_t lba = hdr->PartitionEntryLBA + byteOff / NTFS_SECTOR_SIZE;
uint32_t inSector = (uint32_t)(byteOff % NTFS_SECTOR_SIZE);
if (inSector + sizeof(NTFS_GPT_ENTRY) > NTFS_SECTOR_SIZE) continue;
if (!KSUCCESS(NtfsReadSectors(&raw, lba, 1, entrySector))) break;
NTFS_GPT_ENTRY* e = (NTFS_GPT_ENTRY*)(entrySector + inSector);
if (NtfsGuidIsZero(&e->PartitionTypeGUID)) continue;
if (e->EndingLBA < e->StartingLBA) continue;
parts[count].StartLba = e->StartingLBA;
parts[count].SectorCount = e->EndingLBA - e->StartingLBA + 1ull;
count++;
}
return count;
}
for (uint32_t i = 0; i < 4u && count < maxParts; i++) {
if (mbr[i].OsType == 0 || mbr[i].SizeInLba == 0) continue;
parts[count].StartLba = mbr[i].StartingLba;
parts[count].SectorCount = mbr[i].SizeInLba;
count++;
}
return count;
KeReleaseSpinlock(&gNtfsMountMutexInitLock, flags);
}
static int NtfsReserveFreeVolumeIndex(void) {
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock);
for (uint8_t i = 0; i < NTFS_MAX_VOLUMES; i++) {
if (!gVolumeMounted[i] && !gVolumeBusy[i] && !IoFilesystemPresent(i)) {
gVolumeBusy[i] = true;
KeReleaseSpinlock(&gNtfsMountLock, flags);
return (int)i;
// Returns one active NTFS mount for an exact partition extent while the mount mutex is held.
static NTFS_VOLUME* NtfsFindMountLocked(const IO_PARTITION_INFO* partition) {
if (!partition)
return (NTFS_VOLUME*)0;
for (LIST_ENTRY* entry = gNtfsMounts.Fwd; entry != &gNtfsMounts; entry = entry->Fwd) {
NTFS_VOLUME* volume = CONTAINING_RECORD(entry, NTFS_VOLUME, MountLink);
if (volume->DriveIndex == partition->DiskIndex &&
volume->PartitionLba == partition->StartLba) {
return volume;
}
}
KeReleaseSpinlock(&gNtfsMountLock, flags);
return -1;
return (NTFS_VOLUME*)0;
}
static void NtfsReleaseReservedVolumeIndex(uint8_t volumeIndex) {
if (volumeIndex >= NTFS_MAX_VOLUMES) return;
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock);
gVolumeBusy[volumeIndex] = false;
KeReleaseSpinlock(&gNtfsMountLock, flags);
}
// Probes one I/O-core partition extent and mounts it only when its VBR is valid NTFS.
static bool NtfsTryMountPartition(const IO_PARTITION_INFO* partition) {
if (!partition || partition->SectorCount == 0 || !IoDiskDevicePresent(partition->DiskIndex))
return false;
static bool NtfsBeginDiskScan(uint8_t disk) {
if (disk >= NTFS_MAX_VOLUMES) return false;
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock);
if (gDiskScanned[disk]) {
KeReleaseSpinlock(&gNtfsMountLock, flags);
NtfsMountEnsureMutex();
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(&gNtfsMountMutex, KE_WAIT_INFINITE)))
return false;
if (NtfsFindMountLocked(partition)) {
KeReleaseMutex(&gNtfsMountMutex);
return false;
}
gDiskScanned[disk] = true;
KeReleaseSpinlock(&gNtfsMountLock, flags);
NTFS_VOLUME* mount = (NTFS_VOLUME*)MmAllocatePool(sizeof(NTFS_VOLUME));
if (!mount) {
KeReleaseMutex(&gNtfsMountMutex);
return false;
}
KSTATUS status = NtfsProbeAndPrepareVolume(mount,
partition->DiskIndex,
partition->StartLba,
partition->SectorCount);
if (!KSUCCESS(status)) {
NtfsCleanupVolume(mount);
MmFreePool(mount);
KeReleaseMutex(&gNtfsMountMutex);
return false;
}
IO_VOLUME_PROVIDER provider;
NtfsInitProvider(&provider, mount);
IO_VOLUME_SOURCE params;
params.Type = IoVolumeSourcePartition;
params.DiskIndex = partition->DiskIndex;
memset(params.Reserved, 0, sizeof(params.Reserved));
params.StartLba = partition->StartLba;
params.SectorCount = mount->PartitionSize;
IO_VOLUME* volume = (IO_VOLUME*)0;
status = IoCreateVolume(&provider, &params, &volume);
if (!KSUCCESS(status)) {
if (volume) {
(void)IoRemoveVolume(volume, IoVolumeUnmounted);
IoReleaseVolume(volume);
} else {
NtfsCleanupVolume(mount);
MmFreePool(mount);
}
DbgPrint("NTFS: disk=%u lba=%llu mount failed status=0x%x\n",
partition->DiskIndex, (unsigned long long)partition->StartLba, status);
KeReleaseMutex(&gNtfsMountMutex);
return false;
}
// Retain the IoCreateVolume caller reference while this provider remains on the active mount list.
mount->Volume = volume;
ListInsertTail(&gNtfsMounts, &mount->MountLink);
gMountedCount++;
uint32_t mountedCount = gMountedCount;
uint32_t instanceId = IoGetVolumeInstanceId(volume);
uint64_t recordSize = mount->FileRecordSize;
DbgPrint("NTFS: disk=%u lba=%llu mounted volume=%u (mft_record=%llu); total=%u\n",
partition->DiskIndex, (unsigned long long)partition->StartLba,
instanceId, (unsigned long long)recordSize, mountedCount);
KeReleaseMutex(&gNtfsMountMutex);
return true;
}
static void NtfsEndDiskScan(uint8_t disk, bool completed) {
if (disk >= NTFS_MAX_VOLUMES) return;
if (completed) return;
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock);
gDiskScanned[disk] = false;
KeReleaseSpinlock(&gNtfsMountLock, flags);
// Unmounts one exact dynamic NTFS provider instance when its backing partition disappears.
static void NtfsUnmountPartition(const IO_PARTITION_INFO* partition, IO_PARTITION_CHANGE change) {
if (!partition)
return;
NtfsMountEnsureMutex();
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(&gNtfsMountMutex, KE_WAIT_INFINITE)))
return;
NTFS_VOLUME* mount = NtfsFindMountLocked(partition);
if (!mount) {
KeReleaseMutex(&gNtfsMountMutex);
return;
}
IO_VOLUME* volume = mount->Volume;
uint32_t instanceId = IoGetVolumeInstanceId(volume);
ListRemove(&mount->MountLink);
mount->Volume = (IO_VOLUME*)0;
if (gMountedCount)
gMountedCount--;
uint32_t mountedCount = gMountedCount;
KeReleaseMutex(&gNtfsMountMutex);
(void)IoRemoveVolume(volume, IoVolumeBackingRemoved);
IoReleaseVolume(volume); // Drop the active mount-list reference.
DbgPrint("NTFS: disk=%u lba=%llu %s; unmounted volume=%u; total=%u\n",
partition->DiskIndex, (unsigned long long)partition->StartLba,
change == IoPartitionSurpriseRemoved ? "surprise removed" : "removed",
instanceId, mountedCount);
}
static uint32_t NtfsTryMountDisk(uint8_t disk) {
if (disk >= NTFS_MAX_VOLUMES) return 0;
if (!IoDiskDevicePresent(disk)) return 0;
if (!NtfsBeginDiskScan(disk)) return 0;
bool scanCompleted = false;
bool mediaReadable = false;
NTFS_PARTITION_CANDIDATE parts[NTFS_MAX_PARTITIONS_PER_DISK];
uint32_t partCount = NtfsEnumerateDiskPartitions(disk, parts, NTFS_MAX_PARTITIONS_PER_DISK, &mediaReadable);
if (!mediaReadable) {
DbgPrint("NTFS: disk %u: media probe deferred; sector 0 unreadable\n", disk);
NtfsEndDiskScan(disk, false);
return 0;
}
scanCompleted = true;
uint32_t mounted = 0;
for (uint32_t p = 0; p < partCount; p++) {
int freeVolume = NtfsReserveFreeVolumeIndex();
if (freeVolume < 0) break;
uint8_t volumeIndex = (uint8_t)freeVolume;
KSTATUS s = NtfsProbeAndPrepareVolume(&gVolumes[volumeIndex], disk, parts[p].StartLba, parts[p].SectorCount);
if (!KSUCCESS(s)) {
NtfsCleanupVolume(&gVolumes[volumeIndex]);
memset(&gVolumes[volumeIndex], 0, sizeof(gVolumes[volumeIndex]));
NtfsReleaseReservedVolumeIndex(volumeIndex);
continue;
}
gVolumes[volumeIndex].VolumeIndex = volumeIndex;
NtfsInitVtable(&gFilesystems[volumeIndex], &gVolumes[volumeIndex]);
IO_VOLUME_PARAMS params;
params.DiskIndex = disk;
params.PartitionLba = parts[p].StartLba;
params.PartitionSize = gVolumes[volumeIndex].PartitionSize;
s = IoRegisterFilesystem(volumeIndex, &gFilesystems[volumeIndex], &params);
if (KSUCCESS(s)) {
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock);
gVolumeMounted[volumeIndex] = true;
gVolumeBusy[volumeIndex] = false;
gMountedCount++;
KeReleaseSpinlock(&gNtfsMountLock, flags);
mounted++;
DbgPrint("NTFS: disk %u part %u mounted as drive %u (lba=%llu mft_record=%llu).\n",
disk, p, volumeIndex,
(unsigned long long)parts[p].StartLba,
(unsigned long long)gVolumes[volumeIndex].FileRecordSize);
} else {
NtfsCleanupVolume(&gVolumes[volumeIndex]);
memset(&gVolumes[volumeIndex], 0, sizeof(gVolumes[volumeIndex]));
memset(&gFilesystems[volumeIndex], 0, sizeof(gFilesystems[volumeIndex]));
NtfsReleaseReservedVolumeIndex(volumeIndex);
DbgPrint("NTFS: disk %u part %u mount failed status=0x%x.\n", disk, p, s);
}
}
NtfsEndDiskScan(disk, scanCompleted);
if (partCount && !mounted && gMountedCount >= NTFS_MAX_VOLUMES)
DbgPrint("NTFS: disk %u: volume table full\n", disk);
return mounted;
}
static void NtfsUnmountDisk(uint8_t diskIndex, IO_DEVICE_CHANGE change) {
uint32_t removed = 0;
for (uint8_t i = 0; i < NTFS_MAX_VOLUMES; i++) {
if (!gVolumeMounted[i] || gVolumes[i].DriveIndex != diskIndex) continue;
(void)IoUnregisterFilesystem(i);
NtfsCleanupVolume(&gVolumes[i]);
memset(&gVolumes[i], 0, sizeof(gVolumes[i]));
memset(&gFilesystems[i], 0, sizeof(gFilesystems[i]));
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock);
gVolumeMounted[i] = false;
gVolumeBusy[i] = false;
if (gMountedCount) gMountedCount--;
KeReleaseSpinlock(&gNtfsMountLock, flags);
removed++;
}
if (diskIndex < NTFS_MAX_VOLUMES) {
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock);
gDiskScanned[diskIndex] = false;
KeReleaseSpinlock(&gNtfsMountLock, flags);
}
if (removed) {
DbgPrint("NTFS: disk %u %s; unmounted %u volume(s); total=%u\n",
diskIndex,
change == IoDeviceSurpriseRemoved ? "surprise removed" : "removed",
removed,
gMountedCount);
}
}
static void NtfsDiskArrival(uint8_t driveIndex, DEVICE_OBJECT* device, void* context) {
(void)device;
(void)context;
uint32_t mounted = NtfsTryMountDisk(driveIndex);
if (mounted) DbgPrint("NTFS: hotplug scan mounted %u new volume(s); total=%u\n", mounted, gMountedCount);
}
static void NtfsDiskChange(uint8_t driveIndex, DEVICE_OBJECT* device, IO_DEVICE_CHANGE change, void* context) {
(void)device;
// Receives immutable partition extents from the I/O core; NTFS never parses partition tables.
static void NtfsPartitionChange(const IO_PARTITION_INFO* partition,
IO_PARTITION_CHANGE change,
void* context) {
(void)context;
switch (change) {
case IoDeviceArrived:
NtfsDiskArrival(driveIndex, device, context);
case IoPartitionArrived:
(void)NtfsTryMountPartition(partition);
break;
case IoDeviceSurpriseRemoved:
case IoDeviceRemoved:
NtfsUnmountDisk(driveIndex, change);
case IoPartitionSurpriseRemoved:
case IoPartitionRemoved:
NtfsUnmountPartition(partition, change);
break;
case IoDeviceQueryRemove:
default:
break;
}
}
// Initializes NTFS and subscribes to the authoritative kernel partition stream.
KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) {
DbgPrint("NTFS: Init\n");
if (!ddk) return NTLSTATUS_INVALID_PARAMETER;
if (!ddk)
return NTLSTATUS_INVALID_PARAMETER;
(void)init;
memset(gVolumes, 0, sizeof(gVolumes));
memset(gFilesystems, 0, sizeof(gFilesystems));
memset(gVolumeMounted, 0, sizeof(gVolumeMounted));
memset(gVolumeBusy, 0, sizeof(gVolumeBusy));
memset(gDiskScanned, 0, sizeof(gDiskScanned));
LIST_INIT(&gNtfsMounts);
gMountedCount = 0;
NtfsMountEnsureMutex();
KSTATUS cbStatus = IoRegisterDiskChangeCallback(NtfsDiskChange, (void*)0);
bool callbackRegistered = KSUCCESS(cbStatus);
if (!callbackRegistered)
DbgPrint("NTFS: disk-change callback registration failed st=0x%x\n", cbStatus);
KSTATUS enumStatus = IoEnumerateDiskDevices(NtfsDiskArrival, (void*)0);
if (!KSUCCESS(enumStatus))
DbgPrint("NTFS: initial disk enumeration failed st=0x%x\n", enumStatus);
if (!gMountedCount && !callbackRegistered) {
DbgPrint("NTFS: no mountable NTFS volumes found and disk-change callback unavailable.\n");
return cbStatus;
KSTATUS status = IoRegisterPartitionChangeCallback(NtfsPartitionChange, NULL);
if (!KSUCCESS(status)) {
DbgPrint("NTFS: partition callback registration failed status=0x%x\n", status);
return status;
}
if (!gMountedCount)
DbgPrint("NTFS: no mountable NTFS volumes found; driver resident for disk changes.\n");
DbgPrint("NTFS: no mountable NTFS partitions found; driver resident for partition changes.\n");
DbgPrint("NTFS: Init complete; mounted %u volume(s).\n", gMountedCount);
return NTLSTATUS_SUCCESS;
}
+38 -6
View File
@@ -22,6 +22,7 @@
#define SERIAL_REG_LCR 3u
#define SERIAL_REG_MCR 4u
#define SERIAL_REG_LSR 5u
#define SERIAL_REG_SCR 7u
#define SERIAL_LSR_DATA_READY 0x01u
#define SERIAL_LSR_TX_EMPTY 0x20u
@@ -37,6 +38,7 @@
#define SERIAL_FCR_TRIGGER_14 0xC0u
#define SERIAL_RX_QUEUE_SIZE 1024u
#define SERIAL_RX_DRAIN_LIMIT 256u
#define SERIAL_RX_QUEUE_MASK (SERIAL_RX_QUEUE_SIZE - 1u)
typedef enum SERIAL_ESCAPE_STATE {
@@ -65,6 +67,19 @@ static void SerialWrite(uint16_t reg, uint8_t value) {
__outbyte((unsigned short)(SERIAL_COM1_BASE + reg), value);
}
// Probes the 16550 scratch register so an absent COM1 device is rejected before any receive loop is entered.
static bool SerialProbeCom1(void) {
uint8_t original = SerialRead(SERIAL_REG_SCR);
SerialWrite(SERIAL_REG_SCR, 0x5Au);
uint8_t first = SerialRead(SERIAL_REG_SCR);
SerialWrite(SERIAL_REG_SCR, 0xA5u);
uint8_t second = SerialRead(SERIAL_REG_SCR);
SerialWrite(SERIAL_REG_SCR, original);
return first == 0x5Au && second == 0xA5u;
}
// Submits one input key transition.
static void SerialSubmitTransition(INPUT_KEY key, bool pressed) {
INPUT_KEY_EVENT event;
@@ -301,8 +316,8 @@ static void SerialIsr(void) {
}
}
// Initializes COM1 as an 8N1 115200 baud input device.
static void SerialInitializeCom1(void) {
// Configures COM1 as an 8N1 115200 baud input device while leaving receive interrupts disabled.
static KSTATUS SerialConfigureCom1(void) {
SerialWrite(SERIAL_REG_IER, 0x00u);
SerialWrite(SERIAL_REG_LCR, SERIAL_LCR_DLAB);
SerialWrite(SERIAL_REG_DATA, 0x01u);
@@ -312,10 +327,16 @@ static void SerialInitializeCom1(void) {
SERIAL_FCR_CLEAR_TX | SERIAL_FCR_TRIGGER_14);
SerialWrite(SERIAL_REG_MCR, SERIAL_MCR_DTR | SERIAL_MCR_RTS | SERIAL_MCR_OUT2);
while (SerialRead(SERIAL_REG_LSR) & SERIAL_LSR_DATA_READY)
for (uint32_t i = 0; i < SERIAL_RX_DRAIN_LIMIT; i++) {
uint8_t lsr = SerialRead(SERIAL_REG_LSR);
if (lsr == 0xFFu)
return NTLSTATUS_NOT_FOUND;
if ((lsr & SERIAL_LSR_DATA_READY) == 0)
return NTLSTATUS_SUCCESS;
(void)SerialRead(SERIAL_REG_DATA);
}
SerialWrite(SERIAL_REG_IER, SERIAL_IER_RX_READY);
return NTLSTATUS_DEVICE_ERROR;
}
// DriverEntry registers COM1 as an input source.
@@ -323,8 +344,19 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) {
(void)ddk;
(void)init;
if (!SerialProbeCom1()) {
DbgPrint("serial: COM1 not present; input driver disabled\n");
return NTLSTATUS_NOT_FOUND;
}
KSTATUS st = SerialConfigureCom1();
if (!KSUCCESS(st)) {
DbgPrint("serial: COM1 did not become idle during initialization, status=0x%08X\n", st);
return st;
}
uint32_t deviceId = 0;
KSTATUS st = KeInputRegisterDevice("serial-com1", 0, &deviceId);
st = KeInputRegisterDevice("serial-com1", 0, &deviceId);
if (!KSUCCESS(st))
return st;
@@ -336,8 +368,8 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) {
gSerialRxTail = 0;
gSerialRxDropped = 0;
SerialInitializeCom1();
KeIrqRegister(SERIAL_COM1_IRQ, SerialIsr);
SerialWrite(SERIAL_REG_IER, SERIAL_IER_RX_READY);
DbgPrint("serial: initialized COM1 input device=%u irq=%u\n", deviceId, SERIAL_COM1_IRQ);
return NTLSTATUS_SUCCESS;
+2 -2
View File
@@ -349,10 +349,10 @@ sys_thread_t sys_thread_new(const char* name, lwip_thread_fn thread, void* arg,
}
sys_prot_t sys_arch_protect(void) {
return (sys_prot_t)HalDisableInterrupts();
return (sys_prot_t)KeDisableInterrupts();
}
// Restores the interrupt state saved by sys_arch_protect.
void sys_arch_unprotect(sys_prot_t pval) {
HalRestoreInterrupts((uintptr_t)pval);
KeRestoreInterrupts((uintptr_t)pval);
}
+84 -73
View File
@@ -29,28 +29,28 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl);
static void AhciUnregisterController(AHCI_CONTROLLER* ctl);
// Accessors for port structure memory at known CPU and device-address offsets.
static inline HBA_CMD_HEADER *cmd_list(AHCI_DRIVE *drv) {
return (HBA_CMD_HEADER *)((uint8_t *)drv->structVirt + PORT_OFF_CMDLIST);
static inline HBA_CMD_HEADER* cmd_list(AHCI_DRIVE* drv) {
return (HBA_CMD_HEADER*)((uint8_t*)drv->structVirt + PORT_OFF_CMDLIST);
}
// Returns the command-list device address.
static inline uint64_t cmd_list_device_address(AHCI_DRIVE *drv) {
static inline uint64_t cmd_list_device_address(AHCI_DRIVE* drv) {
return drv->structDeviceAddress + PORT_OFF_CMDLIST;
}
// Returns the received-FIS device address.
static inline uint64_t fis_device_address(AHCI_DRIVE *drv) {
static inline uint64_t fis_device_address(AHCI_DRIVE* drv) {
return drv->structDeviceAddress + PORT_OFF_FIS;
}
static inline HBA_CMD_TBL *cmd_tbl(AHCI_DRIVE *drv, int slot) {
return (HBA_CMD_TBL *)((uint8_t *)drv->structVirt
static inline HBA_CMD_TBL* cmd_tbl(AHCI_DRIVE* drv, int slot) {
return (HBA_CMD_TBL*)((uint8_t*)drv->structVirt
+ PORT_OFF_CMDTBLS
+ (uint32_t)slot * PORT_CMDTBL_SIZE);
}
// Returns one command-table device address.
static inline uint64_t cmd_tbl_device_address(AHCI_DRIVE *drv, int slot) {
static inline uint64_t cmd_tbl_device_address(AHCI_DRIVE* drv, int slot) {
return drv->structDeviceAddress + PORT_OFF_CMDTBLS
+ (uint64_t)slot * PORT_CMDTBL_SIZE;
}
@@ -118,7 +118,7 @@ static void AhciReleaseDriveDma(AHCI_DRIVE* drv) {
}
// Allocates and programs one port's command, FIS, and bounce buffers.
static KSTATUS port_init_memory(AHCI_DRIVE *drv) {
static KSTATUS port_init_memory(AHCI_DRIVE* drv) {
if (!drv || !drv->controller || !drv->controller->dmaAdapter)
return NTLSTATUS_INVALID_PARAMETER;
@@ -142,7 +142,7 @@ static KSTATUS port_init_memory(AHCI_DRIVE *drv) {
drv->bounceVirt = gAhciDdk->Dma->GetCommonBufferAddress(drv->bounceDma);
drv->bounceDeviceAddress = gAhciDdk->Dma->GetCommonBufferDeviceAddress(drv->bounceDma);
HBA_PORT *port = drv->port;
HBA_PORT* port = drv->port;
port_stop(port);
port->clb = (uint32_t)(cmd_list_device_address(drv) & 0xFFFFFFFF);
@@ -150,7 +150,7 @@ static KSTATUS port_init_memory(AHCI_DRIVE *drv) {
port->fb = (uint32_t)(fis_device_address(drv) & 0xFFFFFFFF);
port->fbu = (uint32_t)(fis_device_address(drv) >> 32);
HBA_CMD_HEADER *hdrs = cmd_list(drv);
HBA_CMD_HEADER* hdrs = cmd_list(drv);
for (int i = 0; i < NUM_CMD_SLOTS; i++) {
hdrs[i].prdtl = PORT_PRDT_COUNT;
uint64_t table = cmd_tbl_device_address(drv, i);
@@ -168,8 +168,8 @@ static KSTATUS port_init_memory(AHCI_DRIVE *drv) {
// before the scheduler and interrupt handler are live).
// Returns NTLSTATUS_SUCCESS, NTLSTATUS_DEVICE_ERROR, or NTLSTATUS_TIMEOUT.
static KSTATUS port_issue_cmd_poll(AHCI_DRIVE *drv) {
HBA_PORT *port = drv->port;
static KSTATUS port_issue_cmd_poll(AHCI_DRIVE* drv) {
HBA_PORT* port = drv->port;
// Clear pending interrupts before issuing.
port->is = (uint32_t)-1;
@@ -214,11 +214,11 @@ static KSTATUS port_issue_cmd_poll(AHCI_DRIVE *drv) {
}
// Recovers a port after a command that cannot be trusted to have retired.
static void AhciRecoverPort(AHCI_DRIVE *drv) {
static void AhciRecoverPort(AHCI_DRIVE* drv) {
if (!drv || !drv->port)
return;
HBA_PORT *port = drv->port;
HBA_PORT* port = drv->port;
uint32_t savedIe = port->ie;
// Keep reset-generated FIS traffic from satisfying a command waiter.
@@ -240,13 +240,13 @@ static void AhciRecoverPort(AHCI_DRIVE *drv) {
// Interrupt bottom half. The ISR owns hardware acknowledgement and status
// capture; this DPC wakes the worker thread after the hard IRQ has unwound.
static void AhciCompletionDpc(KDPC *dpc, void *context,
void *systemArgument1, void *systemArgument2) {
static void AhciCompletionDpc(KDPC* dpc, void* context,
void* systemArgument1, void* systemArgument2) {
(void)dpc;
(void)systemArgument1;
(void)systemArgument2;
AHCI_DRIVE *drv = (AHCI_DRIVE *)context;
AHCI_DRIVE* drv = (AHCI_DRIVE*)context;
if (drv && drv->completionEvent)
KeSignalEvent(drv->completionEvent);
}
@@ -267,7 +267,7 @@ static void AhciIsr(void) {
continue;
for (int i = 0; i < ctl->driveCount; i++) {
AHCI_DRIVE *drv = &ctl->drives[i];
AHCI_DRIVE* drv = &ctl->drives[i];
uint32_t portBit = (1u << drv->portNo);
if (!(pending & portBit)) continue;
@@ -286,7 +286,7 @@ static void AhciIsr(void) {
// command completion. Only wake the worker for D2H completion or
// an error that terminates the command.
if (portIs & AHCI_PORT_TERMINAL_MASK)
KeInsertQueueDpc(&drv->completionDpc, (void *)0, (void *)0);
KeInsertQueueDpc(&drv->completionDpc, (void*)0, (void*)0);
}
}
}
@@ -295,11 +295,11 @@ static void AhciIsr(void) {
// Caller has already built slot 0 via build_rw_cmd / port_identify.
// Blocks the current thread until the ISR signals completionEvent.
static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) {
static KSTATUS port_issue_cmd(AHCI_DRIVE* drv) {
if (drv->controller && drv->controller->pollingMode)
return port_issue_cmd_poll(drv);
HBA_PORT *port = drv->port;
HBA_PORT* port = drv->port;
// A serialized slot must be idle before reuse. A stale active bit means a
// prior command escaped normal completion and must be recovered first.
@@ -330,10 +330,10 @@ static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) {
// Issue slot 0, then block with a finite deadline.
port->ci = (1u << 0);
EVENT *events[1] = { drv->completionEvent };
EVENT* events[1] = { drv->completionEvent };
KSTATUS waitStatus = KeWaitForMultipleEvents(events, 1, 0,
AHCI_COMMAND_TIMEOUT_MS,
(uint32_t *)0);
(uint32_t*)0);
// Merge any live status that was not consumed by the ISR. This lets a
// missed IRQ still report the hardware error that accompanied it.
@@ -378,10 +378,10 @@ static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) {
// Build a standard DMA read or write command in slot 0.
static void build_rw_cmd(AHCI_DRIVE *drv, uint64_t lba, uint32_t sectors,
static void build_rw_cmd(AHCI_DRIVE* drv, uint64_t lba, uint32_t sectors,
uint64_t bufferDeviceAddress, bool write) {
HBA_CMD_HEADER *hdr = cmd_list(drv); // slot 0
HBA_CMD_TBL *tbl = cmd_tbl(drv, 0);
HBA_CMD_HEADER* hdr = cmd_list(drv); // slot 0
HBA_CMD_TBL* tbl = cmd_tbl(drv, 0);
memset(tbl, 0, PORT_CMDTBL_SIZE);
@@ -411,7 +411,7 @@ static void build_rw_cmd(AHCI_DRIVE *drv, uint64_t lba, uint32_t sectors,
}
// Build H2D FIS.
FIS_REG_H2D *fis = (FIS_REG_H2D *)tbl->cfis;
FIS_REG_H2D* fis = (FIS_REG_H2D*)tbl->cfis;
memset(fis, 0, sizeof(*fis));
fis->fis_type = FIS_TYPE_REG_H2D;
fis->c = 1;
@@ -456,12 +456,12 @@ static KSTATUS do_flush(AHCI_DRIVE* drv) {
// IDENTIFY command - fills a 512-byte buffer and returns it.
static KSTATUS port_identify(AHCI_DRIVE *drv) {
static KSTATUS port_identify(AHCI_DRIVE* drv) {
// Reuse bounce buffer for IDENTIFY data (512 bytes).
memset(drv->bounceVirt, 0, 512);
HBA_CMD_HEADER *hdr = cmd_list(drv);
HBA_CMD_TBL *tbl = cmd_tbl(drv, 0);
HBA_CMD_HEADER* hdr = cmd_list(drv);
HBA_CMD_TBL* tbl = cmd_tbl(drv, 0);
memset(tbl, 0, PORT_CMDTBL_SIZE);
@@ -475,7 +475,7 @@ static KSTATUS port_identify(AHCI_DRIVE *drv) {
tbl->prdt_entry[0].dbc = 512 - 1;
tbl->prdt_entry[0].i = 1;
FIS_REG_H2D *fis = (FIS_REG_H2D *)tbl->cfis;
FIS_REG_H2D* fis = (FIS_REG_H2D*)tbl->cfis;
memset(fis, 0, sizeof(*fis));
fis->fis_type = FIS_TYPE_REG_H2D;
fis->c = 1;
@@ -487,7 +487,7 @@ static KSTATUS port_identify(AHCI_DRIVE *drv) {
if (!KSUCCESS(st)) return st;
// Parse sector count from IDENTIFY data.
const uint16_t *id = (const uint16_t *)drv->bounceVirt;
const uint16_t* id = (const uint16_t*)drv->bounceVirt;
if (id[ATA_IDENT_W83] & (1u << 10)) {
// 48-bit LBA supported.
@@ -505,9 +505,9 @@ static KSTATUS port_identify(AHCI_DRIVE *drv) {
}
// Core read logic.
static KSTATUS do_read(AHCI_DRIVE *drv, uint64_t lba,
uint32_t count, void *buf) {
uint8_t *out = (uint8_t *)buf;
static KSTATUS do_read(AHCI_DRIVE* drv, uint64_t lba,
uint32_t count, void* buf) {
uint8_t* out = (uint8_t*)buf;
while (count > 0) {
uint32_t batch = (count > BOUNCE_SECTORS) ? BOUNCE_SECTORS : count;
@@ -525,9 +525,9 @@ static KSTATUS do_read(AHCI_DRIVE *drv, uint64_t lba,
}
// Core write logic.
static KSTATUS do_write(AHCI_DRIVE *drv, uint64_t lba,
uint32_t count, const void *buf) {
const uint8_t *in = (const uint8_t *)buf;
static KSTATUS do_write(AHCI_DRIVE* drv, uint64_t lba,
uint32_t count, const void* buf) {
const uint8_t* in = (const uint8_t*)buf;
while (count > 0) {
uint32_t batch = (count > BOUNCE_SECTORS) ? BOUNCE_SECTORS : count;
@@ -571,9 +571,9 @@ static KSTATUS AhciGetIrpReadWrite(IRP* irp, bool* outWrite, uint64_t* outLba,
// Accepted IRPs are queued to the per-drive worker and completed later with
// IoCompleteRequest(). Rejected IRPs are returned synchronously and are not
// completed.
static KSTATUS AhciDispatchReadWrite(DEVICE_OBJECT *device, IRP *irp) {
static KSTATUS AhciDispatchReadWrite(DEVICE_OBJECT* device, IRP* irp) {
if (!device || !irp) return NTLSTATUS_INVALID_PARAMETER;
AHCI_DRIVE *drv = (AHCI_DRIVE *)device->DeviceExtension;
AHCI_DRIVE* drv = (AHCI_DRIVE*)device->DeviceExtension;
if (!drv) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS st = IoAcquireRemoveLock(&drv->removeLock, irp);
@@ -762,7 +762,7 @@ static KSTATUS AhciDispatchPnp(DEVICE_OBJECT* device, IRP* irp) {
// record slotIrp[slot] = irp, and issue via port->sact + port->ci.
// 5. Return immediately so the ISR can drive all completions.
static void AhciWorker(uintptr_t arg) {
AHCI_DRIVE *drv = (AHCI_DRIVE *)arg;
AHCI_DRIVE* drv = (AHCI_DRIVE*)arg;
while (drv && drv->workerRunning) {
// Block until AHCI dispatch signals a new IRP.
@@ -770,7 +770,7 @@ static void AhciWorker(uintptr_t arg) {
if (!drv->workerRunning)
break;
IRP *irp = IrpQueueDequeue(&drv->irpQueue);
IRP* irp = IrpQueueDequeue(&drv->irpQueue);
if (!irp)
continue;
@@ -954,11 +954,17 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) {
return st;
}
// IoRegisterDiskDevice publishes arrival synchronously, and partition/filesystem
// consumers may issue reads from that callback. Install the controller ISR before
// any disk is published so an interrupt-driven drive can complete those reads.
if (!ctl->pollingMode)
KeIrqRegister(ctl->irq, AhciIsr);
uint32_t pi = ctl->hba->pi;
for (uint32_t portno = 0; portno < 32 && ctl->driveCount < MAX_AHCI_DRIVES; portno++) {
if (!(pi & (1u << portno))) continue;
HBA_PORT *port = &ctl->hba->ports[portno];
HBA_PORT* port = &ctl->hba->ports[portno];
// Phase 1: physical link check.
if (!port_link_up(port)) {
@@ -969,7 +975,7 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) {
DbgPrint("AHCI: port %u: link up, initializing...\n", portno);
int driveIdx = ctl->driveCount;
AHCI_DRIVE *drv = &ctl->drives[driveIdx];
AHCI_DRIVE* drv = &ctl->drives[driveIdx];
memset(drv, 0, sizeof(*drv));
drv->magic = AHCI_DRIVE_MAGIC;
drv->controller = ctl;
@@ -1013,7 +1019,7 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) {
// Phase 5b: create the IRP queue semaphore and initialise the queue.
// The semaphore starts at 0; each submitted IRP increments it by one.
SEMAPHORE *irpSem = (SEMAPHORE *)0;
SEMAPHORE* irpSem = (SEMAPHORE*)0;
st = KeSemaphoreCreate(0, 0x7FFFFFFF, &irpSem);
if (!KSUCCESS(st)) {
DbgPrint("AHCI: port %u: semaphore create failed (0x%x)\n", portno, st);
@@ -1031,7 +1037,7 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) {
}
drv->removeLockReady = true;
// Phase 6: IDENTIFY (polled - scheduler not yet running).
// Phase 6: IDENTIFY remains polled until the disk runtime path is ready.
st = port_identify(drv);
if (!KSUCCESS(st)) {
DbgPrint("AHCI: port %u: IDENTIFY failed (0x%x)\n", portno, st);
@@ -1045,12 +1051,8 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) {
(unsigned long long)drv->sectorCount,
(unsigned long long)(drv->sectorCount / 2048));
// Phase 7: enable per-port interrupts.
// Clear any stale IS first so we don't get a spurious signal immediately.
port->is = (uint32_t)-1;
port->ie = AHCI_PORT_TERMINAL_MASK;
// Phase 8: create and register a disk DEVICE_OBJECT.
// Phase 7: create the disk DEVICE_OBJECT, but do not publish it until the
// asynchronous runtime path is fully operational.
st = IoCreateDevice(&g_driver, 0, &drv->deviceObject);
if (!KSUCCESS(st)) {
DbgPrint("AHCI: drive %d: IoCreateDevice failed (0x%x)\n", g_nextDiskIndex, st);
@@ -1063,24 +1065,9 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) {
drv->deviceObject->u.Disk.SectorSize = 512;
drv->deviceObject->AlignmentRequirement = 512;
uint8_t diskIndex = g_nextDiskIndex;
st = IoRegisterDiskDevice(diskIndex, drv->deviceObject);
if (KSUCCESS(st)) {
DbgPrint("AHCI: drive %u registered\n", diskIndex);
ctl->driveCount++;
g_nextDiskIndex++;
} else {
DbgPrint("AHCI: IoRegisterDiskDevice(%u) failed (0x%x)\n", diskIndex, st);
IoDeleteDevice(drv->deviceObject);
drv->deviceObject = (DEVICE_OBJECT*)0;
AhciReleaseDriveRuntime(drv);
AhciReleaseDriveDma(drv);
continue;
}
// Phase 9: spawn the per-drive IRP worker thread.
// The worker owns do_read / do_write exclusively once the scheduler
// is live; it is the only thread that may call port_issue_cmd().
// Phase 8: start the per-drive IRP worker before publishing the disk.
// IoRegisterDiskDevice synchronously notifies partition consumers, which may
// immediately submit reads that must have a live worker and completion path.
st = KeEventCreate(EventManualReset, 0, &drv->workerFinished);
if (KSUCCESS(st)) {
drv->workerRunning = true;
@@ -1092,9 +1079,34 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) {
}
if (!KSUCCESS(st)) {
drv->workerRunning = false;
DbgPrint("AHCI: drive %u: worker creation failed (0x%x)\n", diskIndex, st);
(void)IoUnregisterDiskDevice(diskIndex);
DbgPrint("AHCI: drive %u: worker creation failed (0x%x)\n", g_nextDiskIndex, st);
IoDeleteDevice(drv->deviceObject);
drv->deviceObject = (DEVICE_OBJECT*)0;
AhciReleaseDriveRuntime(drv);
AhciReleaseDriveDma(drv);
continue;
}
// Make the initialized drive visible to the controller ISR, then arm its
// interrupt source. No consumer can submit I/O until publication below.
ctl->driveCount++;
port->is = (uint32_t)-1;
port->ie = AHCI_PORT_TERMINAL_MASK;
// Phase 9: publish only after reads and writes can complete synchronously.
uint8_t diskIndex = g_nextDiskIndex;
st = IoRegisterDiskDevice(diskIndex, drv->deviceObject);
if (KSUCCESS(st)) {
DbgPrint("AHCI: drive %u registered\n", diskIndex);
g_nextDiskIndex++;
} else {
DbgPrint("AHCI: IoRegisterDiskDevice(%u) failed (0x%x)\n", diskIndex, st);
port->ie = 0;
ctl->driveCount--;
IoDeleteDevice(drv->deviceObject);
drv->deviceObject = (DEVICE_OBJECT*)0;
AhciReleaseDriveRuntime(drv);
AhciReleaseDriveDma(drv);
continue;
}
}
@@ -1106,7 +1118,6 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) {
ctl->pciInfo.Address.Function);
if (!ctl->pollingMode) {
KeIrqRegister(ctl->irq, AhciIsr);
DbgPrint("AHCI: ISR registered on IRQ %u (%s); controller drives=%d\n",
(unsigned)ctl->irq, ctl->usingMsi ? "MSI" : "INTx", ctl->driveCount);
} else {
+8 -8
View File
@@ -317,7 +317,7 @@ typedef struct AHCI_CONTROLLER AHCI_CONTROLLER;
typedef struct {
uint32_t magic; // AHCI_DRIVE_MAGIC
AHCI_CONTROLLER* controller; // owning AHCI controller FDO extension
HBA_PORT *port; // MMIO port registers (virtual)
HBA_PORT* port; // MMIO port registers (virtual)
uint64_t sectorCount; // Total LBA sectors on device
uint8_t portNo; // Physical port index (for ISR dispatch)
@@ -333,7 +333,7 @@ typedef struct {
// Signaled by the completion DPC when the port raises an interrupt.
// Auto-reset: one Wait consumes one completion.
EVENT *completionEvent;
EVENT* completionEvent;
// Last IS snapshot captured by the ISR (for error checking post-wait).
volatile uint32_t lastIs;
@@ -389,10 +389,10 @@ extern const NTL_DDK* gAhciDdk;
extern DRIVER_OBJECT g_driver;
KSTATUS ahci_map_mmio(uint64_t physBase, uint64_t length);
void port_stop(HBA_PORT *port);
void port_start(HBA_PORT *port);
KSTATUS hba_reset(HBA_MEM *hba);
bool port_link_up(HBA_PORT *port);
bool port_is_sata_disk(HBA_PORT *port);
void port_stop(HBA_PORT* port);
void port_start(HBA_PORT* port);
KSTATUS hba_reset(HBA_MEM* hba);
bool port_link_up(HBA_PORT* port);
bool port_is_sata_disk(HBA_PORT* port);
void ahci_delay_ms(uint32_t ms);
KSTATUS port_comreset(AHCI_DRIVE *drv);
KSTATUS port_comreset(AHCI_DRIVE* drv);
+7 -7
View File
@@ -26,7 +26,7 @@ KSTATUS ahci_map_mmio(uint64_t physBase, uint64_t length) {
}
// Implements the port stop operation.
void port_stop(HBA_PORT *port) {
void port_stop(HBA_PORT* port) {
// Clear ST (bit 0).
port->cmd &= ~PORT_CMD_ST;
// Wait for CR (bit 15) to clear (command list DMA stops).
@@ -42,7 +42,7 @@ void port_stop(HBA_PORT *port) {
}
// Implements the port start operation.
void port_start(HBA_PORT *port) {
void port_start(HBA_PORT* port) {
// Wait until CR is clear before setting ST.
for (int i = 0; i < POLL_TIMEOUT; i++) {
if (!(port->cmd & PORT_CMD_CR)) break;
@@ -52,7 +52,7 @@ void port_start(HBA_PORT *port) {
}
// Implements the hba reset operation.
KSTATUS hba_reset(HBA_MEM *hba) {
KSTATUS hba_reset(HBA_MEM* hba) {
// BIOS/OS handoff (spec §10.6.3).
if (hba->cap2 & CAP2_BOH) {
hba->bohc |= BOHC_OOS;
@@ -86,14 +86,14 @@ KSTATUS hba_reset(HBA_MEM *hba) {
}
// Implements the port link up operation.
bool port_link_up(HBA_PORT *port) {
bool port_link_up(HBA_PORT* port) {
uint32_t det = port->ssts & 0x0F;
uint32_t ipm = (port->ssts >> 8) & 0x0F;
return (det == PORT_DET_PRESENT && ipm == PORT_IPM_ACTIVE);
}
// Implements the port is sata disk operation.
bool port_is_sata_disk(HBA_PORT *port) {
bool port_is_sata_disk(HBA_PORT* port) {
return port->sig == SATA_SIG_ATA;
}
@@ -104,8 +104,8 @@ void ahci_delay_ms(uint32_t ms) {
}
// Implements the port comreset operation.
KSTATUS port_comreset(AHCI_DRIVE *drv) {
HBA_PORT *port = drv->port;
KSTATUS port_comreset(AHCI_DRIVE* drv) {
HBA_PORT* port = drv->port;
port->cmd &= ~PORT_CMD_ST;
for (int i = 0; i < POLL_TIMEOUT; i++)
+62 -56
View File
@@ -1,15 +1,16 @@
// HAL APIC support. This file owns local APIC, I/O APIC, and interrupt-controller setup.
#include <kernel/hal/hal_apic.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_internal.h>
#include <kernel/hal/hal_api.h>
#include <kernel/hal/hal_timer.h>
#include <stdint.h>
#include <intrin.h>
extern HAL_API gApi;
// ---- Local APIC ----.
static volatile uint32_t* gLapic = (volatile uint32_t *)0;
#define LAPIC_REG(off) (*(volatile uint32_t *)((uint8_t *)gLapic + (off)))
static volatile uint32_t* gLapic = (volatile uint32_t*)0;
#define LAPIC_REG(off) (*(volatile uint32_t*)((uint8_t*)gLapic + (off)))
#define LAPIC_ID 0x020
#define LAPIC_EOI 0x0B0
#define LAPIC_SPURIOUS 0x0F0
@@ -27,10 +28,9 @@ static volatile uint32_t* gLapic = (volatile uint32_t *)0;
#define CPUID_FEATURE_ECX_X2APIC (1u << 21)
#define APIC_VECTOR_RESCHEDULE 0xF0u
// ---- I/O APIC ----.
static volatile uint32_t* gIoapic = (volatile uint32_t *)0;
static volatile uint32_t* gIoapic = (volatile uint32_t*)0;
static uint32_t gBspLapicId = 0;
static uint8_t gX2ApicSupported = 0;
static uint8_t gX2ApicEnabled = 0;
@@ -39,13 +39,13 @@ static uint8_t gX2ApicEnabled = 0;
// Implements the ioapic Read operation.
static uint32_t IoapicRead(uint8_t reg) {
*(volatile uint32_t *)((uint8_t *)gIoapic + IOAPIC_IDX) = reg;
return *(volatile uint32_t *)((uint8_t *)gIoapic + IOAPIC_DATA);
*(volatile uint32_t*)((uint8_t*)gIoapic + IOAPIC_IDX) = reg;
return *(volatile uint32_t*)((uint8_t*)gIoapic + IOAPIC_DATA);
}
// Implements the ioapic Write operation.
static void IoapicWrite(uint8_t reg, uint32_t val) {
*(volatile uint32_t *)((uint8_t *)gIoapic + IOAPIC_IDX) = reg;
*(volatile uint32_t *)((uint8_t *)gIoapic + IOAPIC_DATA) = val;
*(volatile uint32_t*)((uint8_t*)gIoapic + IOAPIC_IDX) = reg;
*(volatile uint32_t*)((uint8_t*)gIoapic + IOAPIC_DATA) = val;
}
// Implements the apic Detect Mode operation.
@@ -58,17 +58,9 @@ static void HalApicDetectMode(void) {
gX2ApicEnabled = (gX2ApicSupported && (apicBase & IA32_APIC_BASE_X2APIC)) ? 1u : 0u;
}
// Implements the apic Stall Fallback operation.
static void HalApicStallFallback(uint32_t loops) {
for (volatile uint32_t i = 0; i < loops; i++) {
_mm_pause();
}
}
// Implements the apic Stall Us operation.
// Stalls for the requested bootstrap interval using the HAL timer fallback chain.
static void HalApicStallUs(uint32_t us) {
if (gApi.Timer.Stall) gApi.Timer.Stall(us);
else HalApicStallFallback(us * 128u + 1u);
HalTimerStall(us);
}
// Implements the apic Wait Icr Idle operation.
@@ -102,25 +94,25 @@ static void HalApicSendIpi(uint32_t lapicId, uint32_t low) {
// Interrupt vector base: IRQ 0 -> vector 0x20, IRQ 1 -> vector 0x21, ...
#define IRQ_VECTOR_BASE 0x20
// Returns bsp Lapic Id.
__declspec(dllexport) uint32_t HalGetBspLapicId(void) {
// Returns the local interrupt-controller hardware ID of the boot processor.
uint32_t HalApicGetBootProcessorId(void) {
return gBspLapicId;
}
// Implements the apic Using X2 Apic operation.
__declspec(dllexport) uint8_t HalApicUsingX2Apic(void) {
// Returns whether the local interrupt controller is using x2APIC mode.
uint8_t HalApicUsingX2Apic(void) {
return gX2ApicEnabled;
}
// Implements the apic Can Address Id operation.
__declspec(dllexport) uint8_t HalApicCanAddressId(uint32_t lapicId) {
// Returns whether the active local interrupt-controller mode can address one hardware ID.
uint8_t HalApicCanAddressId(uint32_t hardwareId) {
if (gX2ApicEnabled)
return 1u;
return (lapicId < 256u) ? 1u : 0u;
return (hardwareId < 256u) ? 1u : 0u;
}
// Implements the apic Enable Current Cpu operation.
__declspec(dllexport) void HalApicEnableCurrentCpu(void) {
// Initializes the local interrupt controller on the current processor.
void HalApicInitializeCurrentProcessor(void) {
uint64_t apicBase = __readmsr(IA32_APIC_BASE) | IA32_APIC_BASE_ENABLE;
if (gX2ApicEnabled) {
@@ -135,8 +127,8 @@ __declspec(dllexport) void HalApicEnableCurrentCpu(void) {
LAPIC_REG(LAPIC_SPURIOUS) = LAPIC_ENABLE | 0xFF;
}
// Implements the apic Send Panic Ipi operation.
__declspec(dllexport) void HalApicSendPanicIpi(void) {
// Broadcasts an NMI to every processor except the caller.
void HalApicBroadcastNmi(void) {
if (!gLapic && !gX2ApicEnabled) return;
// Stop-the-world panic IPI. Use NMI delivery instead of a normal fixed
@@ -146,42 +138,56 @@ __declspec(dllexport) void HalApicSendPanicIpi(void) {
HalApicSendIpi(0, (3u << 18) | (4u << 8)); // all-excluding-self, NMI
}
// Implements the apic Send Reschedule Ipi operation.
__declspec(dllexport) void HalApicSendRescheduleIpi(uint32_t lapicId) {
if (!gLapic && !gX2ApicEnabled) return;
HalApicSendIpi(lapicId, APIC_VECTOR_RESCHEDULE); // fixed delivery, physical destination
// Sends one fixed-vector processor interrupt without exposing APIC mechanics.
void HalApicSendProcessorInterrupt(uint32_t hardwareId, uint8_t vector) {
if ((!gLapic && !gX2ApicEnabled) || vector < 0x20u || !HalApicCanAddressId(hardwareId))
return;
HalApicSendIpi(hardwareId, vector);
}
// Implements the apic Send Tlb Shootdown Ipi operation.
__declspec(dllexport) void HalApicSendTlbShootdownIpi(uint32_t lapicId) {
if (!gLapic && !gX2ApicEnabled) return;
// TLB shootdown must reach CPUs that may be spinning with IF=0 inside
// high-IRQL/spinlock regions, so use NMI delivery rather than a fixed
// vector. The NMI stub handles active shootdowns and returns; unexpected
// NMIs still go through the normal fault/panic path.
HalApicSendIpi(lapicId, (4u << 8)); // NMI delivery, physical destination
// Sends one non-maskable processor interrupt.
void HalApicSendProcessorNmi(uint32_t hardwareId) {
if ((!gLapic && !gX2ApicEnabled) || !HalApicCanAddressId(hardwareId))
return;
HalApicSendIpi(hardwareId, (4u << 8));
}
// Starts application Processor.
__declspec(dllexport) KSTATUS HalStartApplicationProcessor(uint32_t lapicId, uint8_t startupVector) {
if ((!gLapic && !gX2ApicEnabled) || !startupVector || lapicId == gBspLapicId)
// Starts one secondary processor through the local interrupt-controller startup protocol.
KSTATUS HalApicStartProcessor(uint32_t hardwareId, uintptr_t startPhysical) {
if ((!gLapic && !gX2ApicEnabled) || !startPhysical ||
(startPhysical & 0xFFFu) != 0 || startPhysical >= 0x100000u ||
hardwareId == gBspLapicId) {
return NTLSTATUS_INVALID_PARAMETER;
if (!HalApicCanAddressId(lapicId))
}
if (!HalApicCanAddressId(hardwareId))
return NTLSTATUS_NOT_SUPPORTED;
// INIT assert/deassert, then two SIPIs. The target begins at startupVector << 12.
HalApicSendIpi(lapicId, 0x0000C500u); // INIT, level assert, level-triggered
uint8_t startupVector = (uint8_t)(startPhysical >> 12);
// INIT assert/deassert, then two SIPIs. The target begins at startPhysical.
HalApicSendIpi(hardwareId, 0x0000C500u); // INIT, level assert, level-triggered
HalApicStallUs(10000);
HalApicSendIpi(lapicId, 0x00008500u); // INIT, level deassert
HalApicSendIpi(hardwareId, 0x00008500u); // INIT, level deassert
HalApicStallUs(200);
HalApicSendIpi(lapicId, 0x00000600u | startupVector); // SIPI
HalApicSendIpi(hardwareId, 0x00000600u | startupVector); // SIPI
HalApicStallUs(200);
HalApicSendIpi(lapicId, 0x00000600u | startupVector); // SIPI retry
HalApicSendIpi(hardwareId, 0x00000600u | startupVector); // SIPI retry
HalApicStallUs(200);
return NTLSTATUS_SUCCESS;
}
// Composes an MSI message targeting the boot processor without exposing APIC mode.
KSTATUS HalApicComposeMsi(uint8_t vector, HAL_MSI_MESSAGE* messageOut) {
if (!messageOut || vector < 0x20u)
return NTLSTATUS_INVALID_PARAMETER;
if (gBspLapicId >= 256u)
return NTLSTATUS_NOT_SUPPORTED;
messageOut->Address = 0xFEE00000ull | ((uint64_t)(gBspLapicId & 0xFFu) << 12);
messageOut->Data = vector;
messageOut->Reserved = 0;
return NTLSTATUS_SUCCESS;
}
// Implements the apic Route Irq operation.
void HalApicRouteIrq(uint8_t irq) {
// Full initial setup of the redirection entry.
@@ -220,8 +226,8 @@ void HalApicSendEoi(void) {
void HalApicInitialize(uintptr_t lapicPhys, uintptr_t ioapicPhys) {
HalApicDetectMode();
gLapic = (volatile uint32_t *)(gApi.PhysToVirt(lapicPhys));
gIoapic = (volatile uint32_t *)(gApi.PhysToVirt(ioapicPhys));
gLapic = (volatile uint32_t*)(HalPhysicalToVirtual(lapicPhys));
gIoapic = (volatile uint32_t*)(HalPhysicalToVirtual(ioapicPhys));
// The BSP is the CPU executing HAL init. Keep all external/device IRQs
// routed to this LAPIC; CPU-local IPIs use the local APIC directly.
@@ -230,7 +236,7 @@ void HalApicInitialize(uintptr_t lapicPhys, uintptr_t ioapicPhys) {
else
gBspLapicId = (LAPIC_REG(LAPIC_ID) >> 24) & 0xFF;
HalApicEnableCurrentCpu();
HalApicInitializeCurrentProcessor();
// Mask all IOAPIC lines initially.
uint32_t maxRedir = (IoapicRead(1) >> 16) & 0xFF;
+76 -55
View File
@@ -1,8 +1,11 @@
// HAL initialization entry point. This file discovers platform tables and publishes the HAL API to the kernel.
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
#include <kernel/hal/hal_apic.h>
#include <kernel/hal/hal_output.h>
#include <kernel/hal/hal_processor.h>
#include <kernel/hal/hal_internal.h>
#include <kernel/hal/hal_timer.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
@@ -11,7 +14,6 @@
void HalScreenInitialize(void* (*AllocatePool)(size_t size), uintptr_t fb, int w, int h, int stride,
HAL_SCREEN* out);
KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *out);
void HalPciInitialize(HAL_PCI* out);
void HalRtcInitialize(HAL_RTC* out);
void HalRtcSetCenturyRegister(uint8_t centuryRegister);
@@ -20,9 +22,34 @@ extern void HalSetDebugEnabled(int v);
// Defined in mm_virt.c - HAL needs it for MMIO window access.
static uint64_t gPhysBase = 0xFFFF880000000000ULL;
HAL_API gApi;
static KSTATUS HalQueryProcessorTopology(HAL_PROCESSOR_TOPOLOGY* out);
static HAL_API gApi = {
.Size = sizeof(HAL_API),
.Version = HAL_API_VERSION,
.Processor = {
.DisableInterrupts = HalDisableInterrupts,
.RestoreInterrupts = HalRestoreInterrupts,
.EnableInterrupts = HalEnableInterrupts,
.WaitForInterrupt = HalWaitForInterrupt,
.Halt = HalHalt,
.Stop = HalStopProcessor,
.QueryTopology = HalQueryProcessorTopology,
.Start = HalApicStartProcessor,
},
.InterruptController = {
.RouteIrq = HalApicRouteIrq,
.MaskIrq = HalApicMaskIrq,
.UnmaskIrq = HalApicUnmaskIrq,
.SendEoi = HalApicSendEoi,
.InitializeCurrentProcessor = HalApicInitializeCurrentProcessor,
.SendProcessorInterrupt = HalApicSendProcessorInterrupt,
.SendProcessorNmi = HalApicSendProcessorNmi,
.BroadcastNmi = HalApicBroadcastNmi,
.ComposeMsi = HalApicComposeMsi,
},
};
static int gInitialised = 0;
extern void (*gTimerCallback)(void);
// ACPI table walker - minimal, just enough to find APIC and HPET addresses.
#define ACPI_SIG(a,b,c,d) ((uint32_t)(a)|((uint32_t)(b)<<8)|((uint32_t)(c)<<16)|((uint32_t)(d)<<24))
@@ -81,7 +108,7 @@ typedef struct {
static uintptr_t gLapicPhys = 0xFEE00000;
static uintptr_t gIoapicPhys = 0xFEC00000;
static uintptr_t gHpetPhys = 0xFED00000;
static HAL_CPU_TOPOLOGY gCpuTopology;
static HAL_PROCESSOR_TOPOLOGY gCpuTopology;
// ACPI power-off state (filled in by ParseFadt / ScanDsdtForS5).
// Do not guess SLP_TYP on real hardware: these values are firmware-specific.
@@ -122,8 +149,8 @@ void HalDbgPrint(const char* fmt, ...) {
gApi.Output.Write(&record);
}
static void *HalPhysToVirt(uint64_t phys) {
return (void *)(phys + gPhysBase);
void* HalPhysicalToVirtual(uint64_t phys) {
return (void*)(phys + gPhysBase);
}
// FADT (Fixed ACPI Description Table) offsets used here. ACPI 2.0+
@@ -360,18 +387,18 @@ static void ParseFadt(const ACPISDTHeader* header) {
// Implements the add Cpu Record operation.
static void HalAddCpuRecord(uint32_t uid, uint32_t lapicId, uint32_t flags) {
if (gCpuTopology.CpuCount >= HAL_MAX_CPUS)
if (gCpuTopology.Count >= HAL_MAX_PROCESSORS)
return;
for (uint32_t i = 0; i < gCpuTopology.CpuCount; i++) {
if (gCpuTopology.Cpus[i].LapicId == lapicId)
for (uint32_t i = 0; i < gCpuTopology.Count; i++) {
if (gCpuTopology.Processors[i].HardwareId == lapicId)
return;
}
HAL_CPU_INFO* cpu = &gCpuTopology.Cpus[gCpuTopology.CpuCount++];
HAL_PROCESSOR_INFO* cpu = &gCpuTopology.Processors[gCpuTopology.Count++];
memset(cpu, 0, sizeof(*cpu));
cpu->ProcessorUid = uid;
cpu->LapicId = lapicId;
cpu->FirmwareId = uid;
cpu->HardwareId = lapicId;
cpu->Flags = flags;
cpu->Enabled = (flags & 1u) ? 1u : 0u;
}
@@ -379,8 +406,8 @@ static void HalAddCpuRecord(uint32_t uid, uint32_t lapicId, uint32_t flags) {
// Parses madt.
static void ParseMadt(MADT* madt) {
gLapicPhys = madt->LocalAPICAddress;
uint8_t* p = (uint8_t *)(madt + 1);
uint8_t* end = (uint8_t *)madt + madt->Header.Length;
uint8_t* p = (uint8_t*)(madt + 1);
uint8_t* end = (uint8_t*)madt + madt->Header.Length;
while (p + 2 <= end) {
uint8_t type = p[0], len = p[1];
if (!len || p + len > end) break;
@@ -388,17 +415,17 @@ static void ParseMadt(MADT* madt) {
if (type == 0 && len >= 8) { // Processor Local APIC
uint32_t uid = p[2];
uint32_t lapicId = p[3];
uint32_t flags = *(uint32_t *)(p + 4);
uint32_t flags = *(uint32_t*)(p + 4);
if (flags & 1u)
HalAddCpuRecord(uid, lapicId, flags);
} else if (type == 1 && len >= 12) { // I/O APIC entry
gIoapicPhys = *(uint32_t *)(p + 4);
gIoapicPhys = *(uint32_t*)(p + 4);
} else if (type == 5 && len >= 12) { // Local APIC Address Override
gLapicPhys = (uintptr_t)*(uint64_t *)(p + 4);
gLapicPhys = (uintptr_t)*(uint64_t*)(p + 4);
} else if (type == 9 && len >= 16) { // Processor Local x2APIC
uint32_t lapicId = *(uint32_t *)(p + 4);
uint32_t flags = *(uint32_t *)(p + 8);
uint32_t uid = *(uint32_t *)(p + 12);
uint32_t lapicId = *(uint32_t*)(p + 4);
uint32_t flags = *(uint32_t*)(p + 8);
uint32_t uid = *(uint32_t*)(p + 12);
if (flags & 1u)
HalAddCpuRecord(uid, lapicId, flags);
}
@@ -512,47 +539,52 @@ static void HalShutdown(void) {
HalStopProcessor();
}
__declspec(dllexport) KSTATUS HalGetProcessorTopology(HAL_CPU_TOPOLOGY* out) {
static KSTATUS HalQueryProcessorTopology(HAL_PROCESSOR_TOPOLOGY* out) {
if (!out) return NTLSTATUS_INVALID_PARAMETER;
memset(out, 0, sizeof(*out));
*out = gCpuTopology;
if (!out->CpuCount) {
out->CpuCount = 1;
out->BspLapicId = HalGetBspLapicId();
out->Cpus[0].LapicId = out->BspLapicId;
out->Cpus[0].ProcessorUid = 0;
out->Cpus[0].Flags = 1;
out->Cpus[0].Enabled = 1;
out->Cpus[0].IsBsp = 1;
if (!out->Count) {
out->Count = 1;
out->BootProcessorHardwareId = HalApicGetBootProcessorId();
out->Processors[0].HardwareId = out->BootProcessorHardwareId;
out->Processors[0].FirmwareId = 0;
out->Processors[0].Flags = 1;
out->Processors[0].Enabled = 1;
out->Processors[0].IsBootProcessor = 1;
out->Processors[0].Startable = 1;
return NTLSTATUS_SUCCESS;
}
uint32_t bsp = HalGetBspLapicId();
out->BspLapicId = bsp;
uint32_t bsp = HalApicGetBootProcessorId();
out->BootProcessorHardwareId = bsp;
int foundBsp = 0;
for (uint32_t i = 0; i < out->CpuCount; i++) {
out->Cpus[i].IsBsp = (out->Cpus[i].LapicId == bsp) ? 1u : 0u;
if (out->Cpus[i].IsBsp) foundBsp = 1;
for (uint32_t i = 0; i < out->Count; i++) {
out->Processors[i].IsBootProcessor = (out->Processors[i].HardwareId == bsp) ? 1u : 0u;
out->Processors[i].Startable = out->Processors[i].IsBootProcessor || HalApicCanAddressId(out->Processors[i].HardwareId);
if (out->Processors[i].IsBootProcessor) foundBsp = 1;
}
if (!foundBsp && out->CpuCount < HAL_MAX_CPUS) {
HAL_CPU_INFO* cpu = &out->Cpus[out->CpuCount++];
if (!foundBsp && out->Count < HAL_MAX_PROCESSORS) {
HAL_PROCESSOR_INFO* cpu = &out->Processors[out->Count++];
memset(cpu, 0, sizeof(*cpu));
cpu->LapicId = bsp;
cpu->ProcessorUid = bsp;
cpu->HardwareId = bsp;
cpu->FirmwareId = bsp;
cpu->Flags = 1;
cpu->Enabled = 1;
cpu->IsBsp = 1;
cpu->IsBootProcessor = 1;
cpu->Startable = 1;
}
return NTLSTATUS_SUCCESS;
}
__declspec(dllexport) KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) {
HAL_ENTRY KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) {
if (!initParams || initParams->Size != sizeof(HAL_INIT_PARAMS) || initParams->Flags != 0 ||
!initParams->PhysicalWindowBase || !initParams->AllocatePool)
return NTLSTATUS_INVALID_PARAMETER;
if (gInitialised) return NTLSTATUS_ALREADY_EXISTS;
gPhysBase = initParams->PhysicalWindowBase;
gApi.PhysToVirt = HalPhysToVirt;
debugEnabled = initParams->Debug;
HalOutputInitialize(&gApi.Output);
@@ -577,21 +609,15 @@ __declspec(dllexport) KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) {
HalApicInitialize(gLapicPhys, gIoapicPhys);
HalDbgPrint("HAL: local APIC mode=%s BSP_ID=%u\n",
HalApicUsingX2Apic() ? "x2APIC" : "xAPIC",
HalGetBspLapicId());
HalApicGetBootProcessorId());
KSTATUS ts = HalTimerInitialize(gLapicPhys, gHpetPhys, &gApi.Timer);
if (!KSUCCESS(ts)) HalDbgPrint("HAL: HPET unavailable, using fallback\n");
HalRtcInitialize(&gApi.Rtc);
HalPciInitialize(&gApi.Pci);
gApi.Intc.RouteIrq = HalApicRouteIrq;
gApi.Intc.MaskIrq = HalApicMaskIrq;
gApi.Intc.UnmaskIrq = HalApicUnmaskIrq;
gApi.Intc.SendEoi = HalApicSendEoi;
gApi.Power.Shutdown = HalShutdown;
gApi.Version = HAL_API_VERSION;
gInitialised = 1;
// The 8042 can leave output bytes pending while the keyboard/mouse drivers
@@ -609,10 +635,5 @@ __declspec(dllexport) KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) {
return NTLSTATUS_SUCCESS;
}
// Registers scheduler.
__declspec(dllexport) void HalRegisterScheduler(void (*pKeSchedulerTick)(void)) {
gTimerCallback = pKeSchedulerTick;
}
// Returns api.
__declspec(dllexport) const HAL_API* HalGetApi(void) { return &gApi; }
HAL_ENTRY const HAL_API* HalGetApi(void) { return &gApi; }
+1 -1
View File
@@ -3,7 +3,7 @@
#include <stdint.h>
#include <string.h>
#include <intrin.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
#include <kernel/hal/hal_output.h>
#define HAL_OUTPUT_ENDPOINT_CAPACITY 8u
+2 -1
View File
@@ -1,7 +1,8 @@
// HAL PCI configuration access. This file owns legacy mechanism-1 PCI config port I/O.
#include <stdint.h>
#include <ntlite/ddi/hal_api.h>
#include <intrin.h>
#include <kernel/hal/hal_api.h>
#define PCI_CONFIG_ADDR 0xCF8
#define PCI_CONFIG_DATA 0xCFC
+8 -7
View File
@@ -1,41 +1,42 @@
// HAL processor control exports. This file owns interrupt masking and processor halt instructions.
#include <stdint.h>
#include <stdbool.h>
#include <intrin.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_processor.h>
#pragma intrinsic(_disable)
#pragma intrinsic(_enable)
// Masks interrupts and returns the previous RFLAGS value.
__declspec(dllexport) uintptr_t HalDisableInterrupts(void) {
uintptr_t HalDisableInterrupts(void) {
uintptr_t flags = __readeflags();
_disable();
return flags;
}
// Restores a previously saved RFLAGS value.
__declspec(dllexport) void HalRestoreInterrupts(uintptr_t flags) {
void HalRestoreInterrupts(uintptr_t flags) {
__writeeflags(flags);
}
// Enables maskable interrupts.
__declspec(dllexport) void HalEnableInterrupts(void) {
void HalEnableInterrupts(void) {
_enable();
}
// Enables interrupts and halts until an interrupt arrives.
__declspec(dllexport) void HalWaitForInterrupt(void) {
void HalWaitForInterrupt(void) {
_enable();
__halt();
}
// Halts once without changing interrupt state.
__declspec(dllexport) void HalHalt(void) {
void HalHalt(void) {
__halt();
}
// Masks interrupts and permanently stops the current processor.
__declspec(dllexport) _Noreturn void HalStopProcessor(void) {
_Noreturn void HalStopProcessor(void) {
_disable();
while (true)
__halt();
+1 -1
View File
@@ -5,7 +5,7 @@
#include <string.h>
#include <intrin.h>
#include <ntlite/status.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
#include <kernel/hal/hal_debug.h>
#define CMOS_INDEX_PORT 0x70
+4 -4
View File
@@ -5,12 +5,12 @@
// each byte is the leftmost pixel.
#include <stdint.h>
#include <string.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
#include <kernel/mm_virt.h>
#include <kernel/hal/hal_output.h>
static uint32_t* gFb = (uint32_t *)0;
static uint32_t* gBackbuffer = (uint32_t *)0;
static uint32_t* gFb = (uint32_t*)0;
static uint32_t* gBackbuffer = (uint32_t*)0;
static int gWidth = 0;
static int gHeight = 0;
static int gStride = 0; // pixels per row
@@ -343,7 +343,7 @@ void HalScreenRegisterOutputEndpoint(HAL_OUTPUT_ROUTER* output) {
// Populate the HAL_SCREEN vtable.
void HalScreenInitialize(void* (*AllocatePool)(size_t size), uintptr_t fb, int w, int h, int stride,
HAL_SCREEN* out) {
gFb = (uint32_t *)fb;
gFb = (uint32_t*)fb;
gWidth = w;
gHeight = h;
gStride = stride;
+56 -16
View File
@@ -1,26 +1,51 @@
// HAL serial output endpoint. This file owns polling COM1 writes and subscribes serial to HAL text streams.
#include <stdint.h>
#include <stdbool.h>
#include <intrin.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
#include <kernel/hal/hal_output.h>
#include <kernel/hal/hal_processor.h>
#define HAL_COM1_DATA_PORT 0x03F8u
#define HAL_COM1_STATUS_PORT 0x03FDu
#define HAL_COM1_TX_READY 0x20u
#define HAL_COM1_BASE 0x03F8u
#define HAL_COM1_DATA_PORT (HAL_COM1_BASE + 0u)
#define HAL_COM1_STATUS_PORT (HAL_COM1_BASE + 5u)
#define HAL_COM1_SCRATCH_PORT (HAL_COM1_BASE + 7u)
#define HAL_COM1_TX_READY 0x20u
#define HAL_SERIAL_TX_SPIN_LIMIT 1000000u
static volatile long gSerialWriteLock;
static volatile long gSerialPresent;
// Waits until COM1 can accept one byte.
static void HalSerialWaitForTransmit(void) {
while ((__inbyte(HAL_COM1_STATUS_PORT) & HAL_COM1_TX_READY) == 0)
_mm_pause();
// Probes the 16550 scratch register without relying on line-status values from an absent port.
static bool HalSerialProbeCom1(void) {
uint8_t original = __inbyte(HAL_COM1_SCRATCH_PORT);
__outbyte(HAL_COM1_SCRATCH_PORT, 0x5Au);
uint8_t first = __inbyte(HAL_COM1_SCRATCH_PORT);
__outbyte(HAL_COM1_SCRATCH_PORT, 0xA5u);
uint8_t second = __inbyte(HAL_COM1_SCRATCH_PORT);
__outbyte(HAL_COM1_SCRATCH_PORT, original);
return first == 0x5Au && second == 0xA5u;
}
// Writes one byte to COM1.
static void HalSerialWriteByte(uint8_t value) {
HalSerialWaitForTransmit();
// Waits for COM1 transmit readiness without allowing broken or removed hardware to stall the machine forever.
static bool HalSerialWaitForTransmit(void) {
for (uint32_t spin = 0; spin < HAL_SERIAL_TX_SPIN_LIMIT; spin++) {
if ((__inbyte(HAL_COM1_STATUS_PORT) & HAL_COM1_TX_READY) != 0)
return true;
_mm_pause();
}
return false;
}
// Writes one byte to COM1 when the device remains responsive.
static bool HalSerialWriteByte(uint8_t value) {
if (!HalSerialWaitForTransmit())
return false;
__outbyte(HAL_COM1_DATA_PORT, value);
return true;
}
// Acquires the normal serial writer lock.
@@ -37,7 +62,8 @@ static void HalSerialReleaseWriteLock(void) {
// Writes one output record through polling COM1.
static void HalSerialWriteRecord(void* context, const HAL_OUTPUT_RECORD* record) {
(void)context;
if (!record || !record->Buffer)
if (!record || !record->Buffer ||
_InterlockedCompareExchange(&gSerialPresent, 0, 0) == 0)
return;
bool emergency = (record->Flags & HAL_OUTPUT_WRITE_EMERGENCY) != 0;
@@ -47,24 +73,36 @@ static void HalSerialWriteRecord(void* context, const HAL_OUTPUT_RECORD* record)
HalSerialAcquireWriteLock();
}
for (size_t i = 0; i < record->Length; i++) {
bool responsive = true;
for (size_t i = 0; i < record->Length && responsive; i++) {
char c = record->Buffer[i];
if (c == '\n')
HalSerialWriteByte('\r');
HalSerialWriteByte((uint8_t)c);
responsive = HalSerialWriteByte('\r');
if (responsive)
responsive = HalSerialWriteByte((uint8_t)c);
}
if (!responsive)
_InterlockedExchange(&gSerialPresent, 0);
if (!emergency) {
HalSerialReleaseWriteLock();
HalRestoreInterrupts(interruptFlags);
}
}
// Registers the polling serial endpoint.
// Registers the polling serial endpoint only when a 16550-compatible COM1 device is present.
void HalSerialInitialize(HAL_OUTPUT_ROUTER* output) {
if (!output || !output->RegisterEndpoint)
return;
gSerialWriteLock = 0;
gSerialPresent = 0;
if (!HalSerialProbeCom1())
return;
gSerialPresent = 1;
HAL_OUTPUT_ENDPOINT endpoint;
endpoint.Name = "serial0";
endpoint.StreamMask = HAL_OUTPUT_STREAM_ALL;
@@ -75,4 +113,6 @@ void HalSerialInitialize(HAL_OUTPUT_ROUTER* output) {
uint32_t endpointId = HAL_OUTPUT_ENDPOINT_INVALID;
if (KSUCCESS(output->RegisterEndpoint(&endpoint, &endpointId)))
output->SerialEndpointId = endpointId;
else
gSerialPresent = 0;
}
+19 -21
View File
@@ -2,8 +2,9 @@
#include <stdint.h>
#include <intrin.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_apic.h>
#include <kernel/hal/hal_api.h>
#include <kernel/hal/hal_timer.h>
#include <kernel/hal/hal_internal.h>
#include <kernel/hal/hal_debug.h>
#define LAPIC_TIMER_LVT 0x320
@@ -19,27 +20,24 @@
#define TIMER_PERIOD_MS 10
#define PIT_HZ 1193182UL
extern HAL_API gApi;
static volatile uint32_t* gLapic = NULL;
static uint32_t gApicTicksPerUs = 0;
static uint32_t gApicTicksPerQuantum = 0;
static uint64_t gHpetTicksPerUs = 0;
static uint64_t gHpetHz = 0;
static volatile uint64_t* gHpet = NULL;
#define LAPIC_REG(off) (*(volatile uint32_t *)((uint8_t *)gLapic + (off)))
#define LAPIC_REG(off) (*(volatile uint32_t*)((uint8_t*)gLapic + (off)))
static volatile uint64_t gTicks = 0;
void (* volatile gTimerCallback)(void) = NULL;
// Implements the outb operation.
static inline void outb(uint16_t port, uint8_t val) { __outbyte(port, val); }
// Implements the inb operation.
static inline uint8_t inb(uint16_t port) { return __inbyte(port); }
// Implements the timer Isr operation.
static void TimerIsr(void) {
// Records one local timer interrupt for HAL timekeeping fallback.
static void TimerInterrupt(void) {
gTicks++;
if (gTimerCallback) gTimerCallback();
// EOI is sent by KeIrqDispatch after this returns.
}
@@ -87,7 +85,7 @@ static uint64_t QueryTickFrequency(void) {
}
// Implements the stall operation.
static void Stall(uint32_t us) {
void HalTimerStall(uint32_t us) {
// Priority 1: Use HPET (Highest Precision).
if (gHpet && gHpetTicksPerUs > 0) {
uint64_t start = gHpet[30]; // Offset 0xF0 is the Main Counter
@@ -128,7 +126,7 @@ static void Stall(uint32_t us) {
// Returns HPET ticks/second, or 0 if HPET is unavailable.
static uint64_t HpetInit(uintptr_t hpetPhys) {
// HPET register block, accessed through physical window.
volatile uint64_t *hpet = (volatile uint64_t *)gApi.PhysToVirt(hpetPhys);
volatile uint64_t* hpet = (volatile uint64_t*)HalPhysicalToVirtual(hpetPhys);
// General Capabilities register (offset 0):
// Bits 63:32 = counter clock period in femtoseconds.
@@ -167,7 +165,7 @@ static uint32_t CalibrateApicTimerViaPit(void) {
// Use HPET main counter to measure how many APIC ticks elapse in 10 ms, then return ticks/ms.
static uint32_t CalibrateApicTimerViaHpet(uintptr_t hpetPhys) {
volatile uint64_t *hpet = (volatile uint64_t *)gApi.PhysToVirt(hpetPhys);
volatile uint64_t* hpet = (volatile uint64_t*)HalPhysicalToVirtual(hpetPhys);
uint64_t hpetHz = HpetInit(hpetPhys);
if (!hpetHz) return 0;
@@ -187,7 +185,7 @@ static uint32_t CalibrateApicTimerViaHpet(uintptr_t hpetPhys) {
}
// Implements the timer Configure Current Cpu operation.
__declspec(dllexport) void HalTimerConfigureCurrentCpu(void) {
void HalTimerConfigureCurrentProcessor(void) {
if (!gLapic || !gApicTicksPerQuantum) return;
LAPIC_REG(LAPIC_TIMER_DIV) = TIMER_DIV16;
LAPIC_REG(LAPIC_TIMER_LVT) = TIMER_PERIODIC | TIMER_VECTOR;
@@ -195,8 +193,8 @@ __declspec(dllexport) void HalTimerConfigureCurrentCpu(void) {
}
// Implements the timer Initialize operation.
KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *out) {
gLapic = (volatile uint32_t *)gApi.PhysToVirt(lapicPhys);
KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER* out) {
gLapic = (volatile uint32_t*)HalPhysicalToVirtual(lapicPhys);
HalDbgPrint("HAL: lapicPhys=0x%016llX, lapicVirt=0x%016llX, hpetPhys=0x%016llX\n", lapicPhys, (uintptr_t)gLapic, hpetPhys);
uint32_t ticksPerMs = 0;
@@ -217,7 +215,7 @@ KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *o
gApicTicksPerQuantum = ticksPerMs * TIMER_PERIOD_MS;
if (hpetPhys) {
gHpet = (volatile uint64_t*)gApi.PhysToVirt(hpetPhys);
gHpet = (volatile uint64_t*)HalPhysicalToVirtual(hpetPhys);
uint64_t hpetHz = HpetInit(hpetPhys);
gHpetHz = hpetHz;
gHpetTicksPerUs = hpetHz / 1000000;
@@ -225,14 +223,14 @@ KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *o
HalDbgPrint("HAL: APIC timer: %u ticks/ms\n", ticksPerMs);
// Configure this CPU's LAPIC timer; APs call HalTimerConfigureCurrentCpu()
// after enabling their local APIC.
HalTimerConfigureCurrentCpu();
// Leave the BSP timer masked until the kernel registers its scheduler
// callback and IRQ route. APs arm their local timer after per-CPU setup.
out->QueryTicks = QueryTicks;
out->QueryTickFrequency = QueryTickFrequency;
out->Stall = Stall;
out->Isr = TimerIsr;
out->IrqNumber = 0;
out->Stall = HalTimerStall;
out->HandleInterrupt = TimerInterrupt;
out->ConfigureCurrentProcessor = HalTimerConfigureCurrentProcessor;
out->IrqNumber = 0;
return NTLSTATUS_SUCCESS;
}
@@ -1,13 +1,50 @@
// Public NTLite hal api ABI declarations shared between kernel, drivers, and user mode.
// Private kernel-to-HAL interface table. This ABI is not exposed to drivers or user mode.
#pragma once
// hal_api.h - HAL interface table exported as HalGetApi().
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <ntlite/status.h>
typedef struct BOOT_INFO BOOT_INFO;
#ifdef NTL_HAL_BUILD
#define HAL_ENTRY __declspec(dllexport)
#else
#define HAL_ENTRY __declspec(dllimport)
#endif
#define HAL_MAX_PROCESSORS 32u
typedef struct HAL_PROCESSOR_INFO {
uint32_t FirmwareId;
uint32_t HardwareId;
uint32_t Flags;
uint8_t Enabled;
uint8_t IsBootProcessor;
uint8_t Startable;
uint8_t Reserved;
} HAL_PROCESSOR_INFO;
typedef struct HAL_PROCESSOR_TOPOLOGY {
uint32_t Count;
uint32_t BootProcessorHardwareId;
HAL_PROCESSOR_INFO Processors[HAL_MAX_PROCESSORS];
} HAL_PROCESSOR_TOPOLOGY;
typedef struct HAL_PROCESSOR {
uintptr_t (*DisableInterrupts)(void);
void (*RestoreInterrupts)(uintptr_t flags);
void (*EnableInterrupts)(void);
void (*WaitForInterrupt)(void);
void (*Halt)(void);
void (*Stop)(void);
KSTATUS (*QueryTopology)(HAL_PROCESSOR_TOPOLOGY* out);
KSTATUS (*Start)(uint32_t hardwareId, uintptr_t startPhysical);
} HAL_PROCESSOR;
typedef struct HAL_MSI_MESSAGE {
uint64_t Address;
uint32_t Data;
uint32_t Reserved;
} HAL_MSI_MESSAGE;
typedef struct HAL_FRAMEBUFFER_INFO {
uintptr_t Base; // kernel virtual address of the visible framebuffer
@@ -91,9 +128,10 @@ typedef struct HAL_OUTPUT_ROUTER {
typedef struct HAL_TIMER {
uint64_t (*QueryTicks)(void);
uint64_t (*QueryTickFrequency)(void);
void (*Stall)(uint32_t us);
void (*Isr)(void);
uint8_t IrqNumber;
void (*Stall)(uint32_t us);
void (*HandleInterrupt)(void);
void (*ConfigureCurrentProcessor)(void);
uint8_t IrqNumber;
} HAL_TIMER;
#define HAL_RTC_TIME_FLAG_VALID 0x0001u
@@ -133,12 +171,17 @@ typedef struct HAL_PCI {
uint32_t value);
} HAL_PCI;
typedef struct HAL_INTC {
typedef struct HAL_INTERRUPT_CONTROLLER {
void (*RouteIrq)(uint8_t irq);
void (*MaskIrq)(uint8_t irq);
void (*UnmaskIrq)(uint8_t irq);
void (*SendEoi)(void);
} HAL_INTC;
void (*InitializeCurrentProcessor)(void);
void (*SendProcessorInterrupt)(uint32_t hardwareId, uint8_t vector);
void (*SendProcessorNmi)(uint32_t hardwareId);
void (*BroadcastNmi)(void);
KSTATUS (*ComposeMsi)(uint8_t vector, HAL_MSI_MESSAGE* messageOut);
} HAL_INTERRUPT_CONTROLLER;
typedef struct HAL_POWER {
// ACPI S5 soft-off. Disables interrupts, writes SLP_EN to PM1a/b_CNT,
@@ -147,18 +190,22 @@ typedef struct HAL_POWER {
} HAL_POWER;
typedef struct HAL_API {
uint32_t Version;
void *(*PhysToVirt)(uint64_t physAddr);
HAL_SCREEN Screen;
uint32_t Size;
uint32_t Version;
HAL_PROCESSOR Processor;
HAL_SCREEN Screen;
HAL_OUTPUT_ROUTER Output;
HAL_TIMER Timer;
HAL_RTC Rtc;
HAL_PCI Pci;
HAL_INTC Intc;
HAL_POWER Power;
HAL_TIMER Timer;
HAL_RTC Rtc;
HAL_PCI Pci;
HAL_INTERRUPT_CONTROLLER InterruptController;
HAL_POWER Power;
} HAL_API;
typedef struct HAL_INIT_PARAMS {
uint32_t Size;
uint32_t Flags;
// Framebuffer.
uintptr_t FramebufferBase;
uint32_t FramebufferWidth;
@@ -177,27 +224,11 @@ typedef struct HAL_INIT_PARAMS {
void* (*AllocatePool)(size_t size);
} HAL_INIT_PARAMS;
// Increment when the HAL API layout or semantics change when we go from indev to alpha.
#define HAL_API_VERSION 1
// Increment whenever the kernel/HAL table ABI changes.
#define HAL_API_VERSION 2u
__declspec(dllexport) const HAL_API* HalGetApi(void);
__declspec(dllexport) void HalRegisterScheduler(void (*pKeSchedulerTick)(void));
__declspec(dllexport) KSTATUS HalInitialize(const HAL_INIT_PARAMS* params);
// Returns the HAL table. Processor interrupt-state operations are valid before HalInitialize().
HAL_ENTRY const HAL_API* HalGetApi(void);
// Masks interrupts and returns the previous RFLAGS value.
__declspec(dllexport) uintptr_t HalDisableInterrupts(void);
// Restores the RFLAGS value returned by HalDisableInterrupts.
__declspec(dllexport) void HalRestoreInterrupts(uintptr_t flags);
// Enables maskable interrupts.
__declspec(dllexport) void HalEnableInterrupts(void);
// Enables interrupts and halts until the processor receives an interrupt.
__declspec(dllexport) void HalWaitForInterrupt(void);
// Halts once without changing the interrupt state.
__declspec(dllexport) void HalHalt(void);
// Masks interrupts and permanently stops the current processor.
__declspec(dllexport) _Noreturn void HalStopProcessor(void);
// Initializes platform discovery and the runtime portions of the HAL table.
HAL_ENTRY KSTATUS HalInitialize(const HAL_INIT_PARAMS* params);
+12 -28
View File
@@ -1,38 +1,22 @@
// Kernel hal apic declarations shared by implementation files and kernel clients.
// HAL APIC implementation declarations. APIC details do not cross the HAL ABI.
#pragma once
#include <stdint.h>
#include <ntlite/status.h>
#include <kernel/hal/hal_api.h>
void HalApicRouteIrq(uint8_t irq);
void HalApicMaskIrq(uint8_t irq);
void HalApicUnmaskIrq(uint8_t irq);
void HalApicSendEoi(void);
void HalApicInitialize(uintptr_t lapicPhys, uintptr_t ioapicPhys);
#define HAL_MAX_CPUS 32u
typedef struct HAL_CPU_INFO {
uint32_t ProcessorUid;
uint32_t LapicId;
uint32_t Flags;
uint8_t Enabled;
uint8_t IsBsp;
} HAL_CPU_INFO;
typedef struct HAL_CPU_TOPOLOGY {
uint32_t CpuCount;
uint32_t BspLapicId;
HAL_CPU_INFO Cpus[HAL_MAX_CPUS];
} HAL_CPU_TOPOLOGY;
__declspec(dllexport) KSTATUS HalGetProcessorTopology(HAL_CPU_TOPOLOGY* out);
__declspec(dllexport) uint32_t HalGetBspLapicId(void);
__declspec(dllexport) uint8_t HalApicUsingX2Apic(void);
__declspec(dllexport) uint8_t HalApicCanAddressId(uint32_t lapicId);
__declspec(dllexport) void HalApicEnableCurrentCpu(void);
__declspec(dllexport) void HalApicSendPanicIpi(void);
__declspec(dllexport) void HalApicSendRescheduleIpi(uint32_t lapicId);
__declspec(dllexport) void HalApicSendTlbShootdownIpi(uint32_t lapicId);
__declspec(dllexport) void HalTimerConfigureCurrentCpu(void);
__declspec(dllexport) KSTATUS HalStartApplicationProcessor(uint32_t lapicId, uint8_t startupVector);
uint32_t HalApicGetBootProcessorId(void);
uint8_t HalApicUsingX2Apic(void);
uint8_t HalApicCanAddressId(uint32_t hardwareId);
void HalApicInitializeCurrentProcessor(void);
void HalApicBroadcastNmi(void);
void HalApicSendProcessorInterrupt(uint32_t hardwareId, uint8_t vector);
void HalApicSendProcessorNmi(uint32_t hardwareId);
KSTATUS HalApicStartProcessor(uint32_t hardwareId, uintptr_t startPhysical);
KSTATUS HalApicComposeMsi(uint8_t vector, HAL_MSI_MESSAGE* messageOut);
@@ -0,0 +1,7 @@
// Shared HAL implementation helpers that are not part of the kernel/HAL ABI.
#pragma once
#include <stdint.h>
void* HalPhysicalToVirtual(uint64_t physicalAddress);
+1 -1
View File
@@ -2,7 +2,7 @@
#pragma once
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
// Initializes the fixed HAL output endpoint table.
void HalOutputInitialize(HAL_OUTPUT_ROUTER* output);
@@ -0,0 +1,12 @@
// HAL processor-control declarations used within hal.dll.
#pragma once
#include <stdint.h>
uintptr_t HalDisableInterrupts(void);
void HalRestoreInterrupts(uintptr_t flags);
void HalEnableInterrupts(void);
void HalWaitForInterrupt(void);
void HalHalt(void);
_Noreturn void HalStopProcessor(void);
@@ -0,0 +1,10 @@
// Internal HAL timer declarations shared by HAL implementation files.
#pragma once
#include <stdint.h>
#include <ntlite/status.h>
#include <kernel/hal/hal_api.h>
KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER* out);
void HalTimerStall(uint32_t microseconds);
+2 -1
View File
@@ -1,6 +1,7 @@
// Kernel DDK-table provider declarations.
#pragma once
#include <stdint.h>
#include <ntlite/ddk/ddk.h>
@@ -8,7 +9,7 @@ struct HAL_API;
typedef struct HAL_API HAL_API;
// Initializes the kernel-owned DDK table from platform services.
void IoInitializeDdk(const HAL_API* hal);
void IoInitializeDdk(const HAL_API* hal, uintptr_t physicalWindowBase);
// Returns the kernel-owned DDK table passed to driver entrypoints.
const NTL_DDK* IoGetDdk(void);
+13 -13
View File
@@ -1,26 +1,26 @@
// Kernel I/O file declarations shared by filesystem, cache, and syscall code.
#pragma once
// io_file.h - VFS and filesystem provider interface.
// Current kernel-mode filesystem drivers still use the legacy file-provider
// layout. The shared declarations live in the DDK while the real provider split
// is being designed; this private header adds kernel-only async and lock entry
// points without redefining the shared types.
// The public DDK defines IO_VOLUME and provider dispatch. This private header
// adds kernel-only async and file-lock entry points.
#include <ntlite/ddk/file.h>
#include <kernel/io_async.h>
struct PROCESS;
// Initializes filesystem namespace change notification before drivers register volumes.
KSTATUS IoInitializeVolumeNamespace(void);
// Capability-relative opens rooted at an already-open directory.
KSTATUS IoOpenFileAtDirectory(DIR_OBJECT* directory, SYS_STRING_VIEW path, FILE_OBJECT** out);
KSTATUS IoCreateFileAtDirectory(DIR_OBJECT* directory, SYS_STRING_VIEW path, FILE_OBJECT** out);
KSTATUS IoOpenDirAtDirectory(DIR_OBJECT* directory, SYS_STRING_VIEW path, DIR_OBJECT** out);
// Returns the generation of the mounted-volume namespace.
uint64_t IoGetVolumeNamespaceGeneration(void);
// Waits until the mounted-volume namespace changes or the timeout expires.
KSTATUS IoWaitForVolumeNamespaceChange(uint64_t observedGeneration,
uint64_t timeoutMilliseconds);
// Generic capability-rooted namespace operations used by the syscall layer.
KSTATUS IoStatAtRoot(OBJECT* root, SYS_STRING_VIEW path, SYS_FILE_STAT* out);
KSTATUS IoDeleteFileAtRoot(OBJECT* root, SYS_STRING_VIEW path);
KSTATUS IoRenameFileAtRoots(OBJECT* oldRoot, SYS_STRING_VIEW oldPath,
OBJECT* newRoot, SYS_STRING_VIEW newPath);
KSTATUS IoCreateDirAtRoot(OBJECT* root, SYS_STRING_VIEW path);
KSTATUS IoRemoveDirAtRoot(OBJECT* root, SYS_STRING_VIEW path);
__declspec(dllexport) KSTATUS IoReadFileAsyncKernel(FILE_OBJECT* file,
void* buf,
+12
View File
@@ -0,0 +1,12 @@
// Kernel partition discovery declarations. This file keeps structural partition identity private to the I/O core.
#pragma once
#include <ntlite/status.h>
#include <ntlite/ddi/io_driver.h>
// Initializes dynamic partition discovery and the mandatory structural system-volume selector.
KSTATUS IoInitializePartitionManager(const char* systemVolumeSelector);
// Returns the uniquely selected system partition, or NOT_FOUND/COLLISION while discovery is incomplete or ambiguous.
KSTATUS IoQuerySystemPartition(IO_PARTITION_INFO* partitionOut);
+111
View File
@@ -0,0 +1,111 @@
// Kernel mounted-volume core declarations.
#pragma once
#include <ntlite/ddk/file.h>
#include <kernel/ke_list.h>
#include <ntlite/ddi/io_driver.h>
typedef enum IO_VOLUME_STATE {
IoVolumeMounted = 1,
IoVolumeDead,
} IO_VOLUME_STATE;
typedef struct IO_VOLUME_MANAGER {
OBJECT Header;
} IO_VOLUME_MANAGER;
typedef struct IO_VOLUME_SNAPSHOT_ENTRY {
uint32_t InstanceId;
} IO_VOLUME_SNAPSHOT_ENTRY;
typedef struct IO_VOLUME_STATS {
uint64_t CreatedVolumes;
uint64_t MountedVolumes;
uint64_t RemovedVolumes;
uint64_t DeadIoRejects;
uint64_t ProviderDestroys;
} IO_VOLUME_STATS;
typedef struct IO_VOLUME_MANAGER_STATS {
uint64_t Snapshots;
uint64_t VolumeOpens;
uint64_t Waits;
uint64_t Wakeups;
} IO_VOLUME_MANAGER_STATS;
struct IO_VOLUME {
OBJECT Header;
LIST_ENTRY RegistryLink;
SPINLOCK Lock;
uint32_t InstanceId;
uint32_t State;
uint32_t RemovalReason;
uint8_t RegistryLinked;
IO_VOLUME_SOURCE Source;
DEVICE_OBJECT* BackingDevice;
IO_VOLUME_PROVIDER Provider;
};
// Initializes dynamic mounted-volume discovery notifications before providers start.
KSTATUS IoInitializeVolumeManager(void);
// References the persistent kernel volume-manager authority object.
KSTATUS IoReferenceVolumeManager(IO_VOLUME_MANAGER** managerOut);
// Returns a stable value snapshot of every currently mounted volume. The caller frees the returned array with MmFreePool.
KSTATUS IoSnapshotVolumes(IO_VOLUME_SNAPSHOT_ENTRY** entriesOut, uint32_t* countOut, uint64_t* generationOut);
// References one mounted volume by its monotonic runtime instance identity.
KSTATUS IoReferenceVolumeByInstanceId(uint32_t instanceId, IO_VOLUME** volumeOut);
// Returns the current dynamic mounted-volume registry generation.
uint64_t IoGetVolumeGeneration(void);
// Waits until the dynamic mounted-volume registry changes or the timeout expires.
KSTATUS IoWaitForVolumeChange(uint64_t observedGeneration, uint64_t timeoutMilliseconds);
// Acquires a reference only while the volume is still mounted.
bool IoVolumeTryReferenceMounted(IO_VOLUME* volume);
// References the unique mounted volume backed by one exact disk extent start.
KSTATUS IoReferenceVolumeByBacking(uint8_t diskIndex, uint64_t startLba, IO_VOLUME** volumeOut);
// Returns whether provider I/O is still permitted on this volume.
bool IoVolumeIsMounted(const IO_VOLUME* volume);
// Returns whether the mounted provider implements native async file I/O.
bool IoVolumeSupportsReadAsync(const IO_VOLUME* volume);
bool IoVolumeSupportsWriteAsync(const IO_VOLUME* volume);
void IoQueryVolumeStats(IO_VOLUME_STATS* out);
void IoQueryVolumeManagerStats(IO_VOLUME_MANAGER_STATS* out);
// Provider dispatch. All operations except Close/CloseDir reject dead volumes.
KSTATUS IoVolumeOpen(IO_VOLUME* volume, SYS_STRING_VIEW path, void** fileCtx);
KSTATUS IoVolumeRead(IO_VOLUME* volume, void* fileCtx, uint64_t offset,
void* buffer, size_t size, size_t* bytesRead);
KSTATUS IoVolumeWrite(IO_VOLUME* volume, void* fileCtx, uint64_t offset,
const void* buffer, size_t size, size_t* bytesWritten);
KSTATUS IoVolumeReadAsync(IO_VOLUME* volume, void* fileCtx, uint64_t offset,
void* buffer, size_t size, IO_FS_ASYNC_COMPLETE complete,
void* completeContext);
KSTATUS IoVolumeWriteAsync(IO_VOLUME* volume, void* fileCtx, uint64_t offset,
const void* buffer, size_t size, IO_FS_ASYNC_COMPLETE complete,
void* completeContext);
KSTATUS IoVolumeCreate(IO_VOLUME* volume, SYS_STRING_VIEW path, void** fileCtx);
KSTATUS IoVolumeDelete(IO_VOLUME* volume, SYS_STRING_VIEW path);
KSTATUS IoVolumeRename(IO_VOLUME* volume, SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath);
KSTATUS IoVolumeCreateDir(IO_VOLUME* volume, SYS_STRING_VIEW path);
KSTATUS IoVolumeRemoveDir(IO_VOLUME* volume, SYS_STRING_VIEW path);
KSTATUS IoVolumeTruncate(IO_VOLUME* volume, void* fileCtx, uint64_t newSize, uint32_t fileFlags);
KSTATUS IoVolumeGetSize(IO_VOLUME* volume, void* fileCtx, uint64_t* sizeOut);
KSTATUS IoVolumeQueryInfo(IO_VOLUME* volume, void* fileCtx, SYS_FILE_STAT* out);
KSTATUS IoVolumeStat(IO_VOLUME* volume, SYS_STRING_VIEW path, SYS_FILE_STAT* out);
KSTATUS IoVolumeQueryIdentity(IO_VOLUME* volume, void* fileCtx, IO_FILE_IDENTITY* out);
KSTATUS IoVolumeFlush(IO_VOLUME* volume, void* fileCtx);
void IoVolumeClose(IO_VOLUME* volume, void* fileCtx);
void IoVolumeQueryStats(IO_VOLUME* volume, IO_FILESYSTEM_STATS* out);
KSTATUS IoVolumeOpenDir(IO_VOLUME* volume, SYS_STRING_VIEW path, void** dirCtx);
KSTATUS IoVolumeReadDir(IO_VOLUME* volume, void* dirCtx, KDIR_ENTRY* out);
void IoVolumeCloseDir(IO_VOLUME* volume, void* dirCtx);
+5 -8
View File
@@ -1,4 +1,4 @@
// Kernel audio-core declarations shared by the mixer, syscall layer, and selftests.
// Kernel audio-core declarations shared by the mixer and syscall layer.
#pragma once
@@ -52,7 +52,7 @@ typedef struct KE_AUDIO_STREAM KE_AUDIO_STREAM;
// Reports one endpoint snapshot without exposing provider-owned pointers.
typedef struct KE_AUDIO_ENDPOINT_INFO {
uint64_t EndpointId;
uint32_t EndpointId;
IO_AUDIO_ENDPOINT_KIND Kind;
KE_AUDIO_ENDPOINT_STATE State;
uint32_t Capabilities;
@@ -123,10 +123,10 @@ KSTATUS KeAudioQueryEndpoints(KE_AUDIO_ENDPOINT_INFO* endpoints,
uint32_t* countOut);
// Returns the currently selected endpoint identifier, or zero when none is usable.
uint64_t KeAudioGetDefaultEndpointId(void);
uint32_t KeAudioGetDefaultEndpointId(void);
// Creates one fixed-format logical render stream and returns a caller-owned reference.
KSTATUS KeAudioCreateStream(uint64_t endpointId,
KSTATUS KeAudioCreateStream(uint32_t endpointId,
const IO_AUDIO_FORMAT* format,
KE_AUDIO_STREAM** outStream);
@@ -174,10 +174,7 @@ KSTATUS KeAudioSetStreamVolume(KE_AUDIO_STREAM* stream,
bool muted);
// Updates endpoint master software gain and separate logical mute state.
KSTATUS KeAudioSetMasterVolume(uint64_t endpointId, float volume, bool muted);
KSTATUS KeAudioSetMasterVolume(uint32_t endpointId, float volume, bool muted);
// Copies one coherent stream snapshot.
KSTATUS KeAudioQueryStream(KE_AUDIO_STREAM* stream, KE_AUDIO_STREAM_INFO* infoOut);
// Runs the final render path selftest behind KE_DEBUG_AUDIO_SELFTEST.
void KiRunAudioFinalPathSelfTest(void);
@@ -0,0 +1,20 @@
// Kernel boot configuration declarations for parsing the raw mandatory boot.ini handoff.
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <ntlite/status.h>
#define KE_BOOT_COMMAND_LINE_MAX 256u
#define KE_BOOT_SYSTEM_VOLUME_MAX 128u
typedef struct KE_BOOT_CONFIG {
uint32_t Debug;
// Opaque system-volume selector interpreted by storage policy, never as a legacy drive slot.
char SystemVolume[KE_BOOT_SYSTEM_VOLUME_MAX];
char CommandLine[KE_BOOT_COMMAND_LINE_MAX];
} KE_BOOT_CONFIG;
// Parses the kernel-owned boot.ini settings from the raw bootloader string.
KSTATUS KeParseBootConfiguration(const char* text, size_t length, KE_BOOT_CONFIG* configOut);
+4 -4
View File
@@ -33,15 +33,15 @@ typedef struct KCPU {
struct KCPU* Self;
uint32_t CpuIndex;
uint32_t LapicId;
uint32_t AcpiProcessorId;
uint32_t HardwareId;
uint32_t FirmwareId;
uint32_t BootProcessorIndex;
volatile long State;
uint8_t IsBsp;
uint8_t IsBootProcessor;
// CPUID/MADT-derived SMT topology. PackageId/CoreId/ThreadId are decoded
// from the APIC ID using CPUID leaf 0x1F/0x0B when available, with a
// from the processor hardware ID using CPUID leaf 0x1F/0x0B when available, with a
// conservative CPUID(1)/CPUID(4) fallback. PhysicalCoreIndex is a compact
// kernel-local core number used by the scheduler. CoreSiblingMask contains
// all logical CPUs that share this physical core. PackageMask contains all
+2 -2
View File
@@ -7,14 +7,14 @@
#include <stddef.h>
#include <ntlite/status.h>
#include <ntlite/string_view.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
#include <ntlite/ddk/debug.h>
void KeInitDebug(const HAL_API* hal, uint32_t flags);
uint32_t KeGetDebugFlags(void);
__declspec(dllexport) void DbgPrint(const char* fmt, ...);
__declspec(dllexport) void KePrintConsole(const char* buf, size_t len);
__declspec(dllexport) KSTATUS KeFlushDebugLogToFile(SYS_STRING_VIEW path);
KSTATUS KeFlushDebugLogToSystemRoot(const char* relativePath);
__declspec(dllexport) _Noreturn void KePanic(int code, const char* fmt, ...);
__declspec(dllexport) int KeHandleNmiIpi(void);
+1 -1
View File
@@ -2,7 +2,7 @@
#pragma once
#include <stdint.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
#ifndef DISPLAY_API
#ifdef KE_DISPLAY_BUILD
+23
View File
@@ -0,0 +1,23 @@
// Kernel HAL binding. This file keeps direct hal.dll imports out of kernel subsystems.
#pragma once
#include <stdint.h>
#include <ntlite/status.h>
#include <ntlite/ddk/irql.h>
#include <kernel/hal/hal_api.h>
KSTATUS KeBindBootstrapHal(void);
KSTATUS KeInitializeHal(const HAL_INIT_PARAMS* params);
const HAL_API* KeGetHal(void);
void KeEnableInterrupts(void);
void KeWaitForInterrupt(void);
void KeHaltProcessor(void);
_Noreturn void KeStopProcessor(void);
KSTATUS KeQueryProcessorTopology(HAL_PROCESSOR_TOPOLOGY* topologyOut);
KSTATUS KeStartProcessor(uint32_t hardwareId, uintptr_t startPhysical);
void KeInitializeCurrentProcessorInterruptController(void);
void KeSendProcessorInterrupt(uint32_t hardwareId, uint8_t vector);
void KeSendProcessorNmi(uint32_t hardwareId);
void KeBroadcastProcessorNmi(void);
void KeConfigureCurrentProcessorTimer(void);
+85
View File
@@ -0,0 +1,85 @@
// Kernel capability handle declarations shared by processes and syscall code.
#pragma once
#include <stdint.h>
#include <ntlite/handle.h>
#include <ntlite/status.h>
#include <ntlite/ddk/object.h>
#include <kernel/ke_spinlock.h>
#define KE_HANDLE_BUCKET_COUNT 256u
#define KE_CAPABILITY_MAX_DEPTH 16u
struct PROCESS;
struct KE_HANDLE_ENTRY;
struct KE_CAPTURED_HANDLE;
typedef struct KE_HANDLE_TABLE {
struct KE_HANDLE_ENTRY* Buckets[KE_HANDLE_BUCKET_COUNT];
SYS_HANDLE NextHandle;
uint32_t Count;
uint32_t Rundown;
SPINLOCK Lock;
} KE_HANDLE_TABLE;
// Initializes an empty process-local handle namespace.
void KeInitializeHandleTable(KE_HANDLE_TABLE* table);
// Releases every handle and capability grant owned by one process.
void KeRundownHandleTable(struct PROCESS* process);
// Creates one root capability handle for an object.
KSTATUS KeCreateHandle(struct PROCESS* process, OBJECT* object, SYS_RIGHTS access, SYS_HANDLE* handleOut);
// Closes one process-local handle without reusing its numeric identity.
KSTATUS KeCloseHandle(struct PROCESS* process, SYS_HANDLE handle);
// References the target object after validating type, revocation, and rights.
KSTATUS KeReferenceHandle(struct PROCESS* process,
SYS_HANDLE handle,
OBJECT_TYPE expectedType,
SYS_RIGHTS requiredRights,
OBJECT** objectOut);
// Duplicates one alias of the same capability grant into another process.
KSTATUS KeDuplicateHandle(struct PROCESS* sourceProcess,
SYS_HANDLE sourceHandle,
struct PROCESS* targetProcess,
SYS_RIGHTS desiredRights,
uint32_t duplicateFlags,
SYS_HANDLE* targetHandleOut);
// Derives one child grant whose maximum rights are an attenuated subset.
KSTATUS KeDeriveHandle(struct PROCESS* process,
SYS_HANDLE sourceHandle,
SYS_RIGHTS desiredRights,
SYS_HANDLE* derivedHandleOut);
// Revokes one grant and all grants derived from it.
KSTATUS KeRevokeHandle(struct PROCESS* process, SYS_HANDLE handle);
#define KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS 0x00000001u
#define KE_HANDLE_TRANSFER_FLAG_DERIVE 0x00000002u
// Captures one transferable capability independently of the sender's handle lifetime.
KSTATUS KeCaptureHandleForTransfer(struct PROCESS* sourceProcess,
SYS_HANDLE sourceHandle,
SYS_RIGHTS desiredRights,
uint32_t flags,
struct KE_CAPTURED_HANDLE** capturedOut);
// Releases one captured capability that was not materialized.
void KeReleaseCapturedHandle(struct KE_CAPTURED_HANDLE* captured);
// Atomically materializes captured capabilities as new handles in one target process.
KSTATUS KeMaterializeCapturedHandles(struct PROCESS* targetProcess,
struct KE_CAPTURED_HANDLE** captured,
uint32_t count,
SYS_HANDLE* handlesOut);
// Returns the number of currently live handles in one process.
uint32_t KeGetHandleCount(struct PROCESS* process);
// Returns the object-type-specific rights mask accepted by the handle core.
SYS_RIGHTS KeGetValidHandleRights(OBJECT_TYPE type);
+15
View File
@@ -0,0 +1,15 @@
// Kernel monotonic identifier allocator declarations.
#pragma once
#include <stdint.h>
#include <ntlite/status.h>
typedef struct KE_ID_ALLOCATOR {
volatile long long Next;
} KE_ID_ALLOCATOR;
#define KE_ID_ALLOCATOR_INIT(first_) { (long long)(uint32_t)(first_) }
// Allocates one 32-bit identifier and never reuses an issued value during the boot.
KSTATUS KeAllocateId32(KE_ID_ALLOCATOR* allocator, uint32_t* idOut);
+1 -1
View File
@@ -52,5 +52,5 @@ extern void KeFaultUD(void); // #UD invalid opcode
extern void KeNmiIpiStub(void); // returning NMI SMP-control stub
// CPU-local IPI vector. This uses the common interrupt entry machinery but is
// not an external/device IRQ and is never routed through the I/O APIC.
// not an external/device IRQ and is never routed through the external interrupt controller.
#define KE_VECTOR_RESCHEDULE_IPI 0xF0u
+1 -1
View File
@@ -4,7 +4,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <ntlite/input.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
#include <ntlite/status.h>
#include <kernel/ob_object.h>
#include <kernel/ke_event.h>
+2 -2
View File
@@ -2,10 +2,10 @@
#pragma once
#include <stdint.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
#include <ntlite/status.h>
void KeIrqInitialize(const HAL_API *hal);
void KeIrqInitialize(const HAL_API* hal);
__declspec(dllexport) KSTATUS KeIrqAllocateMsiVector(uint8_t* outIrq);
__declspec(dllexport) void KeIrqRegister(uint8_t irq, void (*handler)(void));
void KeIrqDispatch(uint8_t irq); // called from ke_syscall.asm
+2 -1
View File
@@ -3,9 +3,10 @@
#pragma once
#include <stdint.h>
#include <ntlite/handle.h>
struct PROCESS;
struct OBJECT;
// Removes registry entries owned by the given process handle/object pair.
void KeNamedQueueUnpublishHandle(struct PROCESS* process, uint32_t handle, struct OBJECT* object);
void KeNamedQueueUnpublishHandle(struct PROCESS* process, SYS_HANDLE handle, struct OBJECT* object);
+98
View File
@@ -0,0 +1,98 @@
// Kernel Port IPC declarations.
#pragma once
#include <stdint.h>
#include <kernel/ob_object.h>
#include <kernel/ke_event.h>
#include <ntlite/port.h>
struct PROCESS;
struct THREAD;
typedef struct PORT PORT;
typedef struct KE_PORT_STATS {
uint64_t ConnectionsCreated;
uint64_t DirectDeliveries;
uint64_t DirectBytes;
uint64_t QueuedMessages;
uint64_t QueuedBytes;
uint64_t QueuedDeliveries;
uint64_t RepliesCompleted;
uint64_t Disconnects;
uint64_t ProcessQuotaRejects;
} KE_PORT_STATS;
// Copies a lock-free diagnostic snapshot of Port transport counters.
void KePortQueryStats(KE_PORT_STATS* statsOut);
// Creates one waitable connection-listener Port.
KSTATUS KePortCreate(uint32_t backlog, PORT** portOut);
// Returns whether one Port object is a listener rather than a connected endpoint.
bool KePortIsListener(const PORT* port);
// Returns the passive wait event for a listener or endpoint.
EVENT* KePortWaitEvent(PORT* port);
// Connects one process to a listener and returns its new endpoint handle.
KSTATUS KePortConnect(PORT* listener,
struct PROCESS* clientProcess,
struct THREAD* clientThread,
SYS_HANDLE* endpointHandleOut);
// Accepts one queued connection and returns a new server endpoint handle.
KSTATUS KePortAccept(PORT* listener,
struct PROCESS* serverProcess,
uint64_t timeoutMilliseconds,
SYS_HANDLE* endpointHandleOut,
SYS_PORT_CONNECTION_INFO* infoOut);
// Sends one copied one-way notification, with direct user-to-user delivery when a receiver is already waiting.
KSTATUS KePortSend(PORT* endpoint,
struct PROCESS* senderProcess,
struct THREAD* senderThread,
const void* payload,
uint32_t payloadLength,
const SYS_PORT_HANDLE_TRANSFER* transfers,
uint32_t transferCount,
uint64_t timeoutMilliseconds);
// Receives one request or notification and materializes transferred capabilities in the receiving process.
KSTATUS KePortReceive(PORT* endpoint,
struct PROCESS* receiverProcess,
void* payload,
uint32_t payloadCapacity,
SYS_HANDLE* handlesOut,
uint32_t handleCapacity,
SYS_PORT_MESSAGE_INFO* infoOut,
uint64_t timeoutMilliseconds);
// Performs one synchronous copied request/reply exchange.
KSTATUS KePortCall(PORT* endpoint,
struct PROCESS* callerProcess,
struct THREAD* callerThread,
const void* requestPayload,
uint32_t requestLength,
const SYS_PORT_HANDLE_TRANSFER* requestTransfers,
uint32_t requestTransferCount,
void* replyPayload,
uint32_t replyCapacity,
SYS_HANDLE* replyHandlesOut,
uint32_t replyHandleCapacity,
SYS_PORT_MESSAGE_INFO* replyInfoOut,
uint64_t timeoutMilliseconds);
// Replies to one request previously delivered on this endpoint.
KSTATUS KePortReply(PORT* endpoint,
struct PROCESS* senderProcess,
struct THREAD* senderThread,
uint32_t messageId,
const void* payload,
uint32_t payloadLength,
const SYS_PORT_HANDLE_TRANSFER* transfers,
uint32_t transferCount);
// Disconnects one endpoint and wakes peer waiters and pending synchronous callers.
KSTATUS KePortDisconnect(PORT* endpoint);
+34 -57
View File
@@ -8,48 +8,26 @@
#include <kernel/ke_list.h>
#include <kernel/ke_spinlock.h>
#include <kernel/ke_event.h>
#include <kernel/ke_handle.h>
#include <stddef.h>
#include <stdint.h>
#include <ntlite/string_view.h>
#include <kernel/ke_userenv.h>
#define PROCESS_NAME_MAX 64
#define HANDLE_TABLE_SIZE 256
#define PROCESS_MODULE_MAX 16
#define PROCESS_MODULE_NAME_MAX 64
#define PROCESS_CWD_MAX 512
#define KE_PROCESS_FLAG_SYSTEM 0x00000001u
#define KE_PROCESS_FLAG_IDLE 0x00000002u
#define KE_PROCESS_FLAG_TERMINATION_PROTECTED 0x00000004u
#define HANDLE_RIGHT_READ 0x00000001u
#define HANDLE_RIGHT_WRITE 0x00000002u
#define HANDLE_RIGHT_WAIT 0x00000004u
#define HANDLE_RIGHT_SIGNAL 0x00000008u
#define HANDLE_RIGHT_DUPLICATE 0x00000010u
#define HANDLE_RIGHT_QUERY 0x00000020u
#define HANDLE_RIGHT_TERMINATE 0x00000040u
#define HANDLE_RIGHT_LOCK 0x00000080u
#define HANDLE_RIGHT_RESUME 0x00000100u
#define HANDLE_RIGHT_ALL 0xFFFFFFFFu
#define KE_PROCESS_BUILD_STARTED 0l
#define KE_PROCESS_BUILD_CONSTRUCTING 1l
#define KE_PROCESS_BUILD_STARTING 2l
#define KE_PROCESS_BUILD_DEAD 3l
struct SYS_EXCEPTION_STATS;
struct SECURITY_CONTEXT;
typedef struct HANDLE_ENTRY {
OBJECT* Object;
uint32_t Rights;
uint32_t Flags;
} HANDLE_ENTRY;
typedef struct PROCESS_MODULE {
char Name[PROCESS_MODULE_NAME_MAX];
uintptr_t Base;
size_t Size;
uintptr_t EntryPoint;
uint32_t Flags;
uint32_t PublicRefCount;
uint32_t ImportRefCount;
} PROCESS_MODULE;
typedef struct PROCESS {
OBJECT Header; // must be first
@@ -58,27 +36,36 @@ typedef struct PROCESS {
uint32_t Flags;
char Name[PROCESS_NAME_MAX]; // UTF-8
char CurrentDirectory[PROCESS_CWD_MAX];
size_t CurrentDirectoryLength;
uint8_t CurrentDrive;
SPINLOCK CwdLock;
// Live thread set. Dead THREAD objects may outlive this membership while
// retained handles remain waitable and queryable.
LIST_ENTRY ThreadList; // links THREAD.ProcessLink
uint32_t ThreadCount;
SPINLOCK ThreadLock;
// Handle table.
// Index 0 is always empty (the invalid handle).
// Each live entry holds one object reference plus rights metadata.
HANDLE_ENTRY Handles[HANDLE_TABLE_SIZE];
SPINLOCK HandleLock;
// Process-local capability namespace. Handle IDs are monotonic and never reused.
KE_HANDLE_TABLE HandleTable;
// Port IPC kernel-resource accounting. Direct and queued messages are charged to the sender.
SPINLOCK PortQuotaLock;
uint32_t PortOutstandingMessages;
uint32_t PortOutstandingBytes;
uint32_t PortOutstandingHandles;
uint32_t PortOutstandingCalls;
// Immutable process authority. Address-space reachability is not security authority.
struct SECURITY_CONTEXT* SecurityContext;
int32_t ExitCode;
volatile long Terminating;
volatile long Exited;
volatile long ResourcesReleased;
// Userspace image construction is authorized by SYS_PROCESS_RIGHT_BUILD.
// BuildOperations closes the race between memory construction and the one-way start transition.
volatile long BuildState;
volatile long BuildOperations;
// Main executable image. Used for cleanup.
uintptr_t ImageBase;
size_t ImageSize;
@@ -90,8 +77,8 @@ typedef struct PROCESS {
size_t UserImagePathSize;
uintptr_t UserCmdlineBase;
size_t UserCmdlineSize;
uintptr_t UserCwdBase;
size_t UserCwdSize;
uintptr_t UserStartupCapabilitiesBase;
size_t UserStartupCapabilitiesSize;
// Per-process user virtual regions. The actual VA arena is global because
// NT-Lite currently uses one shared address space; this list tracks
@@ -99,11 +86,6 @@ typedef struct PROCESS {
LIST_ENTRY VadList;
SPINLOCK VadLock;
// Per-process loaded modules (user processes only).
PROCESS_MODULE Modules[PROCESS_MODULE_MAX];
uint32_t ModuleCount;
SPINLOCK ModuleLock;
// Native structured-exception dispatcher registration. The user-mode
// personality owns VEH, SEH, signal, and unwind policy behind this hook.
uintptr_t ExceptionDispatcher;
@@ -129,12 +111,16 @@ typedef struct PROCESS {
LIST_ENTRY GlobalLink; // node in the global process list
} PROCESS;
KSTATUS KeCreateProcess(const char* name, PROCESS **out);
KSTATUS KeCreateProcess(const char* name, struct SECURITY_CONTEXT* securityContext, PROCESS** out);
KSTATUS KeCreateSystemProcess(PROCESS** out);
KSTATUS KeSetupUserProcessEnvironment(PROCESS* process,
SYS_STRING_VIEW imagePath,
SYS_STRING_VIEW commandLine,
SYS_STRING_VIEW currentDirectory);
SYS_STRING_VIEW commandLine);
KSTATUS KeSetProcessStartupCapabilities(PROCESS* process,
const SYS_STARTUP_CAPABILITY* capabilities,
uint32_t count);
KSTATUS KeSetProcessLoaderData(PROCESS* process, const void* loaderData);
void KeStopProcessConstruction(PROCESS* process);
KSTATUS KeExitProcess(PROCESS* process, int32_t exitCode);
KSTATUS KeTerminateProcess(PROCESS* process, int32_t exitCode);
void KeCompleteProcessExitIfLastThread(PROCESS* process);
@@ -143,15 +129,6 @@ int KeIsProcessTerminationProtected(PROCESS* process);
KSTATUS KeGetProcessExitCode(PROCESS* process, int32_t* exitCodeOut);
void KeQueryExceptionStats(struct SYS_EXCEPTION_STATS* statsOut);
// Handle table operations.
KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut);
KSTATUS KeAllocateHandleWithRights(PROCESS* process, OBJECT* object, uint32_t rights, uint32_t* handleOut);
void KeCloseHandle(PROCESS* process, uint32_t handle);
OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle);
OBJECT* KeLookupHandleWithRights(PROCESS* process, uint32_t handle, uint32_t requiredRights);
uint32_t KeGetHandleRights(PROCESS* process, uint32_t handle);
KSTATUS KeDuplicateHandleToProcess(PROCESS* sourceProcess, uint32_t sourceHandle, PROCESS* targetProcess, uint32_t desiredRights, uint32_t duplicateFlags, uint32_t* targetHandleOut);
// Return the global process list head (kernel-internal).
LIST_ENTRY* KeGetProcessList(void);
PROCESS* KeGetCurrentProcess(void);
+1 -1
View File
@@ -33,6 +33,6 @@ typedef struct QUEUE {
EVENT* NotFullEvent;
} QUEUE;
KSTATUS KeQueueCreate(QUEUE **out);
KSTATUS KeQueueCreate(QUEUE** out);
KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, uint64_t timeoutMilliseconds);
KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, uint64_t timeoutMilliseconds);
+2 -2
View File
@@ -2,7 +2,7 @@
#pragma once
// The scheduler combines dynamic normal queues with static real-time queues.
// LAPIC timer IRQs drive KeSchedulerTick.
// Per-processor timer IRQs drive KeSchedulerTick.
#include <kernel/ke_thread.h>
struct KCPU;
@@ -97,7 +97,7 @@ void KeReadyThread(THREAD* thread);
void KeUnreadyThread(THREAD* thread);
// Called from each CPU timer ISR every SCHED_TICK_MS. Every CPU performs local
// quantum accounting and may request a local reschedule. Only the BSP advances
// quantum accounting and may request a local reschedule. Only the boot processor advances
// global scheduler time and services sleepers, waitable timers, input repeat,
// and priority aging. The actual context switch happens from KiExitInterrupt
// after the IRQ handler has returned and EOI has been sent.
+1 -1
View File
@@ -3,7 +3,7 @@
#pragma once
// ke_selftest.h - kernel bring-up and regression self-tests.
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
void KiRunCoreHardeningSelfTests(void);
void KiRunEarlyKernelSelfTests(void);
+1 -1
View File
@@ -16,6 +16,6 @@ typedef struct SEMAPHORE {
LIST_ENTRY WaitList;
} SEMAPHORE;
__declspec(dllexport) KSTATUS KeSemaphoreCreate(int32_t initial, int32_t max, SEMAPHORE **out);
__declspec(dllexport) KSTATUS KeSemaphoreCreate(int32_t initial, int32_t max, SEMAPHORE** out);
__declspec(dllexport) KSTATUS KeWaitSemaphore(SEMAPHORE* semaphore, uint64_t timeoutMilliseconds);
__declspec(dllexport) void KeSignalSemaphore(SEMAPHORE* semaphore);
+1 -1
View File
@@ -5,7 +5,7 @@
#include <stddef.h>
#include <ntlite/status.h>
#define KE_SETTINGS_PATH "system\\settings.ini"
#define KE_SETTINGS_PATH "system/settings.ini"
__declspec(dllexport) KSTATUS KeGetSettingString(const char* section,
const char* key,
+1 -1
View File
@@ -22,5 +22,5 @@ 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 BSP-affine unless their affinity is explicitly widened.
// threads remain boot-processor-affine unless their affinity is explicitly widened.
KSTATUS KeRunApplicationProcessorSchedulerSelfTests(void);
+2 -2
View File
@@ -3,11 +3,11 @@
#pragma once
#include <stdint.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
// Set the HAL API pointer for syscalls.
void KeSyscallInit(const HAL_API* hal);
// Syscall dispatcher, called from assembly entry point.
uint64_t KeSyscallDispatch(uint64_t nr, uint64_t a0, uint64_t a1,
uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5);
uint64_t a2, uint64_t a3);
+7 -12
View File
@@ -1,25 +1,20 @@
// System root volume declarations. This file exposes the one selected kernel root volume to loader and process code.
// System root volume declarations. This file exposes the one selected mounted volume to bootstrap code.
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <ntlite/status.h>
#include <kernel/io_file.h>
#define KE_SYSTEM_ROOT_MAX_DRIVES 8
// Selects the mounted volume that contains the kernel/system tree.
// Selects the mounted volume backed by the configured structural system partition.
KSTATUS KeInitializeSystemRootVolume(void);
// Returns whether the system root volume has been selected.
bool KeSystemRootReady(void);
// Returns the selected system root drive, or zero before discovery.
uint8_t KeGetSystemRootDrive(void);
// References the selected mounted system volume. The caller releases the returned object reference.
KSTATUS KeReferenceSystemRootVolume(IO_VOLUME** volumeOut);
// Builds an absolute path rooted at the selected system volume.
bool KeBuildSystemRootPath(const char* relative, char* out, size_t outSize);
// Opens a file rooted at the selected system volume.
// Opens, creates, or deletes canonical paths rooted directly at the selected system volume.
KSTATUS KeOpenSystemRootFile(const char* relative, FILE_OBJECT** out);
KSTATUS KeCreateSystemRootFile(const char* relative, FILE_OBJECT** out);
KSTATUS KeDeleteSystemRootFile(const char* relative);
@@ -0,0 +1,24 @@
// Kernel well-known system-service registry declarations.
#pragma once
#include <ntlite/service.h>
#include <ntlite/status.h>
#include <ntlite/security.h>
struct PORT;
// Registers one Port listener under a numeric well-known service identity.
KSTATUS KeSystemServiceRegister(SYS_SERVICE_ID serviceId, struct PORT* listener);
// Removes a registration only when it still refers to the supplied listener.
KSTATUS KeSystemServiceUnregister(SYS_SERVICE_ID serviceId, struct PORT* listener);
// References the listener currently registered for one service identity.
KSTATUS KeSystemServiceReference(SYS_SERVICE_ID serviceId, struct PORT** listenerOut);
// 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);
+3
View File
@@ -20,6 +20,9 @@
typedef struct THREAD THREAD;
struct MUTEX;
// Allocates one boot-unique thread identifier.
KSTATUS KeAllocateThreadId(uint32_t* threadIdOut);
#ifndef NTL_THREAD_PRIORITY_DEFINED
typedef enum THREAD_PRIORITY {
PriorityLow = 0,
+6
View File
@@ -14,3 +14,9 @@ __declspec(dllexport) uint64_t KeQueryUnixTimeSeconds(void);
// Returns the monotonic platform counter in microseconds.
uint64_t KeQueryMonotonicTicks(void);
// Returns a saturated monotonic deadline for a relative millisecond interval.
uint64_t KeMonotonicDeadlineAfterMilliseconds(uint64_t milliseconds);
// Returns the remaining whole milliseconds, rounded up, until a monotonic deadline.
uint64_t KeMonotonicMillisecondsUntil(uint64_t deadlineMicroseconds);
+2 -2
View File
@@ -13,8 +13,8 @@ typedef struct KTIMER {
OBJECT Header;
EVENT* Event;
LIST_ENTRY Link;
uint64_t DueTick;
uint64_t PeriodTicks;
uint64_t DueMicroseconds;
uint64_t PeriodMicroseconds;
uint8_t Active;
uint8_t Linked;
} KTIMER;
+1 -2
View File
@@ -82,6 +82,5 @@ void KiExitTrapFrame(KTRAP_FRAME* frame);
KTRAP_FRAME* KeGetCurrentTrapFrame(void);
int KeTrapFrameFromUser(const KTRAP_FRAME* frame);
void KiValidateSyscallTrapFrame(uint64_t nr, uint64_t a0, uint64_t a1,
uint64_t a2, uint64_t a3, uint64_t a4,
uint64_t a5);
uint64_t a2, uint64_t a3);
KSTATUS KeQueryCurrentTrapFrame(SYS_TRAP_FRAME_INFO_K* out);
+18 -3
View File
@@ -7,15 +7,26 @@
#include <stdint.h>
#include <stddef.h>
#include <ntlite/string_view.h>
#include <ntlite/handle.h>
#define SYS_PEB_MAGIC 0x30424550u // "PEB0"
#define SYS_TEB_MAGIC 0x30424554u // "TEB0"
#define SYS_ENV_VERSION 1u
#define SYS_CWD_MAX 520u
#define SYS_ENV_VERSION 4u
#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_CAPABILITY_MAX 32u
typedef struct SYS_STARTUP_CAPABILITY {
uint32_t Tag;
SYS_HANDLE Handle;
} SYS_STARTUP_CAPABILITY;
typedef struct SYS_PEB {
uint32_t Magic;
uint32_t Version;
@@ -23,7 +34,11 @@ typedef struct SYS_PEB {
SYS_STRING_VIEW ImagePath;
SYS_STRING_VIEW CommandLine;
SYS_STRING_VIEW CurrentDirectory;
const SYS_STARTUP_CAPABILITY* StartupCapabilities;
uint32_t StartupCapabilityCount;
uint32_t Reserved;
const void* LoaderData;
} SYS_PEB;
typedef struct SYS_TEB {
+7 -7
View File
@@ -25,25 +25,25 @@ typedef struct {
#define _ARR_INIT_CAPACITY 2
#define _arr_header(arr) ((_ArrHeader *)(arr) - 1)
#define _arr_header(arr) ((_ArrHeader*)(arr) - 1)
#define arr_len(arr) ((arr) ? _arr_header(arr)->count : 0u)
#define _arr_maybe_grow(arr) \
do { \
if (!(arr)) { \
_ArrHeader *_h = MmAllocatePool(sizeof(_ArrHeader) + \
_ArrHeader* _h = MmAllocatePool(sizeof(_ArrHeader) + \
sizeof(*(arr)) * _ARR_INIT_CAPACITY); \
_h->count = 0; \
_h->capacity = _ARR_INIT_CAPACITY; \
(arr) = (void *)(_h + 1); \
(arr) = (void*)(_h + 1); \
} \
_ArrHeader *_h = _arr_header(arr); \
_ArrHeader* _h = _arr_header(arr); \
if (_h->count >= _h->capacity) { \
_h->capacity *= 2; \
_h = MmReallocatePool(_h, sizeof(_ArrHeader) + \
sizeof(*(arr)) * _h->capacity); \
(arr) = (void *)(_h + 1); \
(arr) = (void*)(_h + 1); \
} \
} while (0)
@@ -56,7 +56,7 @@ typedef struct {
#define arr_push_front(arr, x) \
do { \
_arr_maybe_grow(arr); \
_ArrHeader *_h = _arr_header(arr); \
_ArrHeader* _h = _arr_header(arr); \
memmove(&(arr)[1], &(arr)[0], sizeof(*(arr)) * _h->count); \
(arr)[0] = (x); \
_h->count++; \
@@ -65,7 +65,7 @@ typedef struct {
#define arr_remove(arr, index) \
do { \
if ((arr) && (size_t)(index) < arr_len(arr)) { \
_ArrHeader *_h = _arr_header(arr); \
_ArrHeader* _h = _arr_header(arr); \
memmove(&(arr)[index], &(arr)[(index) + 1], \
sizeof(*(arr)) * (_h->count - (size_t)(index) - 1)); \
_h->count--; \
+6 -19
View File
@@ -10,7 +10,6 @@
struct PROCESS;
struct FILE_OBJECT;
struct SYS_MODULE_INFO;
// Map a PE32+ image from a bootloader/driver raw buffer into kernel space.
// User images must be loaded from FILE_OBJECTs so image sections have stable identity.
@@ -22,7 +21,7 @@ size_t LdrGetImageSize(uintptr_t base);
size_t LdrGetMappedImageSize(struct PROCESS* process, uintptr_t base);
// Look up a named export in a loaded module.
// 'moduleName' must match a name in BOOT_INFO.Modules (e.g., "hal.dll").
// 'moduleName' must match a registered kernel module name such as "hal.dll".
void* LdrGetExport(const char* moduleName, const char* functionName);
// Get the entry point address of a loaded image.
@@ -31,26 +30,14 @@ uintptr_t LdrGetEntryPoint(uintptr_t base);
// Resolve the import table of an already-loaded image using the global registry.
KSTATUS LdrResolveImports(uintptr_t imageBase);
// Resolve imports using a process-local module registry.
KSTATUS LdrResolveImportsForProcess(struct PROCESS* process, uintptr_t imageBase);
// Requires that one mapped bootstrap user image declare no import table.
KSTATUS LdrRequireNoImports(uintptr_t imageBase);
// Apply final page permissions for a loaded image after relocations/imports.
KSTATUS LdrProtectImageForProcess(struct PROCESS* process, uintptr_t imageBase, bool user);
// Load a DLL for a specific process, resolve it against that process's
// loaded modules, and register it in the process-local registry.
#define LDR_MODULE_LOAD_IMPORT 0x00000001u
#define LDR_MODULE_LOAD_PUBLIC 0x00000002u
KSTATUS LdrLoadModuleForProcess(struct PROCESS* process, const char* filename, uint32_t loadFlags, uintptr_t* baseOut);
KSTATUS LdrUnloadModuleForProcess(struct PROCESS* process, uintptr_t imageBase);
KSTATUS LdrQueryModuleForProcess(struct PROCESS* process, uintptr_t imageBase, struct SYS_MODULE_INFO* infoOut);
KSTATUS LdrCopyProcessModuleInfo(struct PROCESS* process, struct SYS_MODULE_INFO* infoOut, size_t maxCount, size_t* countOut);
void* LdrGetModuleExportForProcess(struct PROCESS* process, uintptr_t imageBase, const char* functionName);
// Called by KernelMain to register all modules from BOOT_INFO so that
// LdrGetExport can find them.
// Called by KernelMain to register the fixed kernel/HAL bootstrap images so LdrGetExport can find them.
struct BOOT_INFO; // forward declaration
void LdrRegisterModulesFromBootInfo(const struct BOOT_INFO* bootInfo);
void LdrRegisterModule(const char* name, uintptr_t base);
KSTATUS LdrRegisterModulesFromBootInfo(const struct BOOT_INFO* bootInfo);
KSTATUS LdrRegisterModule(const char* name, uintptr_t base);
const char* LdrFindKernelModuleByAddress(uintptr_t address, uintptr_t* baseOut, size_t* sizeOut);
@@ -10,6 +10,8 @@
struct FILE_OBJECT;
typedef struct FILE_OBJECT FILE_OBJECT;
struct IO_VOLUME;
typedef struct IO_VOLUME IO_VOLUME;
typedef struct MM_FILE_CACHE_PAGE {
FILE_OBJECT* File;
@@ -74,6 +76,7 @@ KSTATUS MmFileCacheFlushFile(FILE_OBJECT* file);
// by callers before destructive metadata operations.
void MmFileCacheInvalidate(FILE_OBJECT* file, uint64_t offset, size_t size);
void MmFileCacheInvalidateAll(FILE_OBJECT* file);
void MmFileCacheDiscardVolume(IO_VOLUME* volume);
// Reclaims one cache-owned page if possible. Safe to call under memory pressure;
// it never writes back because v1 stores only clean file data.
+1 -2
View File
@@ -5,7 +5,6 @@
#include <stdint.h>
#include <stddef.h>
#include <ntlite/status.h>
#include <ntlite/string_view.h>
#include <kernel/mm_phys.h>
typedef struct MM_SWAP_STATS {
@@ -23,7 +22,7 @@ typedef struct MM_SWAP_STATS {
uint64_t SlotAllocationFailures;
} MM_SWAP_STATS;
KSTATUS MmSwapInitialize(SYS_STRING_VIEW path, uint64_t sizeBytes);
KSTATUS MmSwapInitialize(const char* relativePath, uint64_t sizeBytes);
int MmSwapIsEnabled(void);
KSTATUS MmSwapPageOut(PHYSICAL_ADDRESS frame, uint32_t* slotOut);
KSTATUS MmSwapPageIn(uint32_t slot, PHYSICAL_ADDRESS frame);
+3
View File
@@ -56,6 +56,9 @@ __declspec(dllexport) KSTATUS MmProbeUserWrite(void* ptr, size_t size);
// kernel-owned buffers, not raw user pointers, unless explicitly documented.
__declspec(dllexport) KSTATUS CopyFromUser(void* kernelDst, const void* userSrc, size_t size);
__declspec(dllexport) KSTATUS CopyToUser(void* userDst, const void* kernelSrc, size_t size);
// Copies bytes directly between two process-owned user ranges while pinning both sides.
__declspec(dllexport) KSTATUS MmCopyUserToUser(struct PROCESS* sourceProcess, const void* source,
struct PROCESS* targetProcess, void* target, size_t size);
typedef struct MM_PINNED_USER_PAGE {
uintptr_t UserPage;
+1 -1
View File
@@ -54,7 +54,7 @@ typedef struct NET_ASYNC_SOCKET_SUBMIT {
size_t OptionValueSize;
size_t* OptionActualSizeOut;
uint32_t* HandleOut;
SYS_HANDLE* HandleOut;
uint32_t* PollReturnedEventsOut;
IO_STATUS_BLOCK* Iosb;
+42
View File
@@ -0,0 +1,42 @@
// Kernel security context declarations.
#pragma once
#include <stdbool.h>
#include <ntlite/ddk/object.h>
#include <ntlite/security.h>
struct PROCESS;
typedef struct SECURITY_CONTEXT {
OBJECT Header;
uint32_t SecurityId;
SYS_PRIVILEGES EffectivePrivileges;
SYS_PRIVILEGES DelegablePrivileges;
SYS_SERVICE_CLAIMS ServiceClaims;
SYS_SERVICE_CLAIMS DelegableServiceClaims;
uint32_t Flags;
} SECURITY_CONTEXT;
// Initializes the immutable root security context used during bootstrap.
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.
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);
+160 -142
View File
@@ -4,167 +4,185 @@
// INT 0x40 calling convention, MS x64.
// RAX selects the syscall number. RCX, RDX, R8, and R9 carry args 0-3.
// R10 and R11 carry args 4-5 and are loaded by six-argument stubs.
// Syscalls use at most four register arguments; larger operations use request structures.
// RAX carries the return value.
// Process, threads, modules, waits, APC, and exceptions.
// Process, threads, waits, APC, and exceptions.
#define SYS_EXIT_PROCESS 0
#define SYS_EXIT_THREAD 1
#define SYS_CREATE_PROCESS 2
#define SYS_OPEN_PROCESS 3
#define SYS_GET_PROCESS_EXIT_CODE 4
#define SYS_KILL_PROCESS 5
#define SYS_GET_PROCESS_LIST 6
#define SYS_GET_PROCESS_PID 7
#define SYS_CREATE_THREAD 8
#define SYS_RESUME_THREAD 9
#define SYS_GET_THREAD_LIST 10
#define SYS_LOAD_MODULE 11
#define SYS_UNLOAD_MODULE 12
#define SYS_GET_MODULE_INFO 13
#define SYS_GET_MODULE_EXPORT 14
#define SYS_GET_MODULE_LIST 15
#define SYS_GET_CURRENT_TID 16
#define SYS_GET_CURRENT_PID 17
#define SYS_SET_THREAD_PRIORITY 18
#define SYS_YIELD 19
#define SYS_SLEEP 20
#define SYS_WAIT_SINGLE 21
#define SYS_WAIT_MANY 22
#define SYS_SET_USER_APC_DISPATCHER 23
#define SYS_APC_RETURN 24
#define SYS_SET_EXCEPTION_DISPATCHER 25
#define SYS_GET_EXCEPTION_DISPATCHER 26
#define SYS_CONTINUE_EXCEPTION 27
#define SYS_RAISE_EXCEPTION 28
#define SYS_CREATE_PROCESS_OBJECT 2
#define SYS_ALLOC_PROCESS_VM 3
#define SYS_PROTECT_PROCESS_VM 4
#define SYS_START_PROCESS 5
#define SYS_OPEN_PROCESS 6
#define SYS_GET_PROCESS_EXIT_CODE 7
#define SYS_KILL_PROCESS 8
#define SYS_GET_PROCESS_LIST 9
#define SYS_GET_PROCESS_PID 10
#define SYS_CREATE_THREAD 11
#define SYS_RESUME_THREAD 12
#define SYS_GET_THREAD_LIST 13
#define SYS_GET_CURRENT_TID 14
#define SYS_GET_CURRENT_PID 15
#define SYS_SET_THREAD_PRIORITY 16
#define SYS_YIELD 17
#define SYS_SLEEP 18
#define SYS_WAIT_SINGLE 19
#define SYS_WAIT_MANY 20
#define SYS_SET_USER_APC_DISPATCHER 21
#define SYS_APC_RETURN 22
#define SYS_SET_EXCEPTION_DISPATCHER 23
#define SYS_GET_EXCEPTION_DISPATCHER 24
#define SYS_CONTINUE_EXCEPTION 25
#define SYS_RAISE_EXCEPTION 26
// Virtual memory, mappings, and handles.
#define SYS_ALLOC_VM 29
#define SYS_FREE_VM 30
#define SYS_PROTECT_VM 31
#define SYS_QUERY_VM 32
#define SYS_CLOSE_HANDLE 33
#define SYS_DUPLICATE_HANDLE 34
#define SYS_CREATE_FILE_MAPPING 35
#define SYS_MAP_FILE 36
#define SYS_ALLOC_VM 27
#define SYS_FREE_VM 28
#define SYS_PROTECT_VM 29
#define SYS_QUERY_VM 30
#define SYS_CLOSE_HANDLE 31
#define SYS_DUPLICATE_HANDLE 32
#define SYS_DERIVE_HANDLE 33
#define SYS_REVOKE_HANDLE 34
#define SYS_DERIVE_SECURITY_CONTEXT 35
#define SYS_QUERY_SECURITY_CONTEXT 36
#define SYS_CREATE_FILE_MAPPING 37
#define SYS_MAP_FILE 38
// Synchronization objects.
#define SYS_CREATE_MUTEX 37
#define SYS_TRY_ACQUIRE_MUTEX 38
#define SYS_RELEASE_MUTEX 39
#define SYS_CREATE_SEM 40
#define SYS_TRY_WAIT_SEM 41
#define SYS_SIGNAL_SEM 42
#define SYS_CREATE_EVENT 43
#define SYS_SIGNAL_EVENT 44
#define SYS_CLEAR_EVENT 45
#define SYS_CREATE_MUTEX 39
#define SYS_TRY_ACQUIRE_MUTEX 40
#define SYS_RELEASE_MUTEX 41
#define SYS_CREATE_SEM 42
#define SYS_TRY_WAIT_SEM 43
#define SYS_SIGNAL_SEM 44
#define SYS_CREATE_EVENT 45
#define SYS_SIGNAL_EVENT 46
#define SYS_CLEAR_EVENT 47
// Queues and named queues.
#define SYS_CREATE_QUEUE 46
#define SYS_SEND_QUEUE 47
#define SYS_RECV_QUEUE 48
#define SYS_PUBLISH_QUEUE 49
#define SYS_OPEN_NAMED_QUEUE 50
#define SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS 51
#define SYS_CREATE_QUEUE 48
#define SYS_SEND_QUEUE 49
#define SYS_RECV_QUEUE 50
#define SYS_PUBLISH_QUEUE 51
#define SYS_OPEN_NAMED_QUEUE 52
#define SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS 53
#define SYS_UNPUBLISH_QUEUE 54
// Files, directories, and current directory.
#define SYS_OPEN_FILE 52
#define SYS_READ_FILE 53
#define SYS_WRITE_FILE 54
#define SYS_READ_FILE_ASYNC 55
#define SYS_WRITE_FILE_ASYNC 56
#define SYS_CANCEL_IO 57
#define SYS_SEEK_FILE 58
#define SYS_GET_FILE_POS 59
#define SYS_GET_FILE_SIZE 60
#define SYS_GET_FILE_STAT 61
#define SYS_STAT_FILE 62
#define SYS_TRUNCATE_FILE 63
#define SYS_FLUSH_FILE 64
#define SYS_DELETE_FILE 65
#define SYS_RENAME_FILE 66
#define SYS_LOCK_FILE 67
#define SYS_UNLOCK_FILE 68
#define SYS_OPEN_DIR 69
#define SYS_READ_DIR 70
#define SYS_CREATE_DIR 71
#define SYS_REMOVE_DIR 72
#define SYS_SET_CWD 73
#define SYS_REAL_PATH 74
// LPC-like Ports and well-known system services.
#define SYS_CREATE_PORT 55
#define SYS_CONNECT_PORT 56
#define SYS_ACCEPT_PORT 57
#define SYS_SEND_PORT 58
#define SYS_RECEIVE_PORT 59
#define SYS_CALL_PORT 60
#define SYS_REPLY_PORT 61
#define SYS_DISCONNECT_PORT 62
#define SYS_REGISTER_SYSTEM_SERVICE 63
#define SYS_UNREGISTER_SYSTEM_SERVICE 64
#define SYS_CONNECT_SYSTEM_SERVICE 65
// Mounted-volume manager control plane.
#define SYS_QUERY_VOLUMES 66
#define SYS_OPEN_VOLUME 67
#define SYS_WAIT_VOLUME_CHANGE 68
// Files and directories.
#define SYS_OPEN_FILE_AT 69
#define SYS_OPEN_DIR_AT 70
#define SYS_STAT_AT 71
#define SYS_DELETE_AT 72
#define SYS_RENAME_AT 73
#define SYS_CREATE_DIR_AT 74
#define SYS_REMOVE_DIR_AT 75
#define SYS_READ_FILE 76
#define SYS_WRITE_FILE 77
#define SYS_READ_FILE_ASYNC 78
#define SYS_WRITE_FILE_ASYNC 79
#define SYS_CANCEL_IO 80
#define SYS_SEEK_FILE 81
#define SYS_GET_FILE_POS 82
#define SYS_GET_FILE_SIZE 83
#define SYS_GET_FILE_STAT 84
#define SYS_TRUNCATE_FILE 85
#define SYS_FLUSH_FILE 86
#define SYS_LOCK_FILE 87
#define SYS_UNLOCK_FILE 88
#define SYS_READ_DIR 89
// Console, display, and surfaces.
#define SYS_WRITE_CONSOLE 75
#define SYS_GET_CONSOLE_INFO 76
#define SYS_SET_CONSOLE_CURSOR 77
#define SYS_FILL_CONSOLE_CHARACTER 78
#define SYS_FILL_CONSOLE_ATTRIBUTE 79
#define SYS_PUT_SCREEN_CHAR 80
#define SYS_CLEAR_SCREEN 81
#define SYS_GET_SCREEN_SIZE 82
#define SYS_QUERY_DISPLAY_INFO 83
#define SYS_OPEN_DISPLAY_CLOCK 84
#define SYS_CREATE_SURFACE 85
#define SYS_MAP_SURFACE 86
#define SYS_QUERY_SURFACE 87
#define SYS_PRESENT_SURFACE 88
#define SYS_WRITE_CONSOLE 90
#define SYS_GET_CONSOLE_INFO 91
#define SYS_SET_CONSOLE_CURSOR 92
#define SYS_FILL_CONSOLE_CHARACTER 93
#define SYS_FILL_CONSOLE_ATTRIBUTE 94
#define SYS_PUT_SCREEN_CHAR 95
#define SYS_CLEAR_SCREEN 96
#define SYS_GET_SCREEN_SIZE 97
#define SYS_QUERY_DISPLAY_INFO 98
#define SYS_OPEN_DISPLAY_CLOCK 99
#define SYS_CREATE_SURFACE 100
#define SYS_MAP_SURFACE 101
#define SYS_QUERY_SURFACE 102
#define SYS_PRESENT_SURFACE 103
// Native render audio.
#define SYS_AUDIO_ENUMERATE_ENDPOINTS 89
#define SYS_AUDIO_OPEN_STREAM 90
#define SYS_AUDIO_WRITE 91
#define SYS_AUDIO_ARM_WAIT 92
#define SYS_AUDIO_QUERY_STREAM 93
#define SYS_AUDIO_START 94
#define SYS_AUDIO_PAUSE 95
#define SYS_AUDIO_RESUME 96
#define SYS_AUDIO_DRAIN 97
#define SYS_AUDIO_STOP 98
#define SYS_AUDIO_RESET 99
#define SYS_AUDIO_SET_VOLUME 100
#define SYS_AUDIO_SET_MASTER_VOLUME 101
#define SYS_AUDIO_ENUMERATE_ENDPOINTS 104
#define SYS_AUDIO_OPEN_STREAM 105
#define SYS_AUDIO_WRITE 106
#define SYS_AUDIO_ARM_WAIT 107
#define SYS_AUDIO_QUERY_STREAM 108
#define SYS_AUDIO_START 109
#define SYS_AUDIO_PAUSE 110
#define SYS_AUDIO_RESUME 111
#define SYS_AUDIO_DRAIN 112
#define SYS_AUDIO_STOP 113
#define SYS_AUDIO_RESET 114
#define SYS_AUDIO_SET_VOLUME 115
#define SYS_AUDIO_SET_MASTER_VOLUME 116
// Input.
#define SYS_POLL_KEYBOARD 102
#define SYS_IS_KEY_DOWN 103
#define SYS_READ_KEYBOARD_CHAR 104
#define SYS_READ_KEY 105
#define SYS_GET_KEY_MODIFIERS 106
#define SYS_QUERY_GLOBAL_INPUT_STATE 107
#define SYS_OPEN_INPUT_QUEUE 108
#define SYS_READ_INPUT_EVENT 109
#define SYS_PEEK_INPUT_EVENT 110
#define SYS_PEEK_MOUSE 111
#define SYS_READ_MOUSE 112
#define SYS_GET_MOUSE_POS 113
#define SYS_POLL_KEYBOARD 117
#define SYS_IS_KEY_DOWN 118
#define SYS_READ_KEYBOARD_CHAR 119
#define SYS_READ_KEY 120
#define SYS_GET_KEY_MODIFIERS 121
#define SYS_QUERY_GLOBAL_INPUT_STATE 122
#define SYS_OPEN_INPUT_QUEUE 123
#define SYS_READ_INPUT_EVENT 124
#define SYS_PEEK_INPUT_EVENT 125
#define SYS_PEEK_MOUSE 126
#define SYS_READ_MOUSE 127
#define SYS_GET_MOUSE_POS 128
// Time, debug, and system information.
#define SYS_CREATE_TIMER 114
#define SYS_SET_TIMER 115
#define SYS_CANCEL_TIMER 116
#define SYS_GET_TICK 117
#define SYS_GET_TICK_FREQUENCY 118
#define SYS_GET_SYSTEM_TIME 119
#define SYS_PRINT_DEBUG 120
#define SYS_QUERY_TRAP_FRAME 121
#define SYS_GET_MEMORY_STATUS 122
#define SYS_QUERY_KERNEL_STATS 123
#define SYS_CREATE_TIMER 129
#define SYS_SET_TIMER 130
#define SYS_CANCEL_TIMER 131
#define SYS_GET_TICK 132
#define SYS_GET_TICK_FREQUENCY 133
#define SYS_GET_SYSTEM_TIME 134
#define SYS_PRINT_DEBUG 135
#define SYS_QUERY_TRAP_FRAME 136
#define SYS_GET_MEMORY_STATUS 137
#define SYS_QUERY_KERNEL_STATS 138
// Native sockets and polling.
#define SYS_CREATE_SOCKET 124
#define SYS_SOCKET_IO_ASYNC 125
#define SYS_BIND_SOCKET 126
#define SYS_LISTEN_SOCKET 127
#define SYS_ACCEPT_SOCKET 128
#define SYS_CONNECT_SOCKET 129
#define SYS_SEND_SOCKET 130
#define SYS_RECV_SOCKET 131
#define SYS_SHUTDOWN_SOCKET 132
#define SYS_SEND_TO_SOCKET 133
#define SYS_RECV_FROM_SOCKET 134
#define SYS_RESOLVE_HOST 135
#define SYS_SET_SOCKET_OPTION 136
#define SYS_GET_SOCKET_OPTION 137
#define SYS_POLL 138
#define SYS_UNPUBLISH_QUEUE 139
#define SYS_CREATE_SOCKET 139
#define SYS_SOCKET_IO_ASYNC 140
#define SYS_BIND_SOCKET 141
#define SYS_LISTEN_SOCKET 142
#define SYS_ACCEPT_SOCKET 143
#define SYS_CONNECT_SOCKET 144
#define SYS_SEND_SOCKET 145
#define SYS_RECV_SOCKET 146
#define SYS_SHUTDOWN_SOCKET 147
#define SYS_SEND_TO_SOCKET 148
#define SYS_RECV_FROM_SOCKET 149
#define SYS_RESOLVE_HOST 150
#define SYS_SET_SOCKET_OPTION 151
#define SYS_GET_SOCKET_OPTION 152
#define SYS_POLL 153
#define SYS_MAX 140
#define SYS_MAX 154
+24 -17
View File
@@ -2,8 +2,7 @@
#include <stdint.h>
#include <ntlite/ddk/ddk.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_apic.h>
#include <kernel/hal/hal_api.h>
#include <kernel/io_ddk.h>
#include <kernel/io_dma.h>
#include <kernel/ke_audio.h>
@@ -13,12 +12,13 @@
#include <kernel/mm_pte.h>
static const HAL_API* gIoDdkHal;
static uintptr_t gIoDdkPhysicalWindowBase;
// Returns the kernel direct-map alias for a physical address.
static void* IoDdkPhysToVirt(uint64_t physicalAddress) {
if (!gIoDdkHal || !gIoDdkHal->PhysToVirt)
if (!gIoDdkPhysicalWindowBase || physicalAddress > UINTPTR_MAX - gIoDdkPhysicalWindowBase)
return (void*)0;
return gIoDdkHal->PhysToVirt(physicalAddress);
return (void*)(gIoDdkPhysicalWindowBase + (uintptr_t)physicalAddress);
}
// Maps one kernel page through the DDK memory surface.
@@ -55,21 +55,28 @@ static void IoDdkPciWrite32(uint16_t segment,
gIoDdkHal->Pci.Write32(segment, bus, device, function, offset, value);
}
// Returns the BSP local APIC identifier.
static uint32_t IoDdkGetBspLapicId(void) {
return HalGetBspLapicId();
}
// Returns whether the platform interrupt controller is using x2APIC mode.
static uint8_t IoDdkIsX2ApicEnabled(void) {
return HalApicUsingX2Apic();
}
// Allocates one kernel IRQ slot for MSI delivery.
static KSTATUS IoDdkAllocateMsiVector(uint8_t* outIrq) {
return KeIrqAllocateMsiVector(outIrq);
}
// 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)
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;
return NTLSTATUS_SUCCESS;
}
static const NTL_DDK_DEBUG gIoDdkDebug = {
.Print = DbgPrint,
.GetFlags = KeGetDebugFlags,
@@ -77,6 +84,7 @@ static const NTL_DDK_DEBUG gIoDdkDebug = {
static const NTL_DDK_INTERRUPT gIoDdkInterrupt = {
.AllocateMsiVector = IoDdkAllocateMsiVector,
.ComposeMsiMessage = IoDdkComposeMsiMessage,
};
static const NTL_DDK_MEMORY gIoDdkMemory = {
@@ -87,8 +95,6 @@ static const NTL_DDK_MEMORY gIoDdkMemory = {
static const NTL_DDK_PLATFORM gIoDdkPlatform = {
.PciRead32 = IoDdkPciRead32,
.PciWrite32 = IoDdkPciWrite32,
.GetBspLapicId = IoDdkGetBspLapicId,
.IsX2ApicEnabled = IoDdkIsX2ApicEnabled,
};
static const NTL_DDK_SCHEDULER gIoDdkScheduler = {
@@ -114,8 +120,9 @@ static NTL_DDK gIoDdk = {
};
// Initializes the kernel-owned DDK table from platform services.
void IoInitializeDdk(const HAL_API* hal) {
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;
+356 -586
View File
File diff suppressed because it is too large. Load diff
+772
View File
@@ -0,0 +1,772 @@
// Dynamic partition discovery. This file owns MBR/GPT parsing and keeps structural identity out of filesystem providers.
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <common/boot_volume_selector.h>
#include <ntlite/ddi/io_driver.h>
#include <ntlite/ddk/mutex.h>
#include <kernel/io_partition.h>
#include <kernel/ke_debug.h>
#include <kernel/mm_pool.h>
#define IOP_SECTOR_SIZE 512u
#define IOP_GPT_SIGNATURE "EFI PART"
#define IOP_GPT_REVISION_1_0 0x00010000u
#define IOP_GPT_MIN_ENTRY_SIZE 128u
#pragma pack(push, 1)
typedef struct IOP_MBR_PARTITION_ENTRY {
uint8_t BootIndicator;
uint8_t StartingChs[3];
uint8_t Type;
uint8_t EndingChs[3];
uint32_t StartingLba;
uint32_t SectorCount;
} IOP_MBR_PARTITION_ENTRY;
typedef struct IOP_GPT_HEADER {
char Signature[8];
uint32_t Revision;
uint32_t HeaderSize;
uint32_t HeaderCrc32;
uint32_t Reserved;
uint64_t MyLba;
uint64_t AlternateLba;
uint64_t FirstUsableLba;
uint64_t LastUsableLba;
BOOT_VOLUME_GUID DiskGuid;
uint64_t PartitionEntryLba;
uint32_t NumberOfPartitionEntries;
uint32_t SizeOfPartitionEntry;
uint32_t PartitionEntryArrayCrc32;
} IOP_GPT_HEADER;
typedef struct IOP_GPT_ENTRY_PREFIX {
BOOT_VOLUME_GUID PartitionTypeGuid;
BOOT_VOLUME_GUID UniquePartitionGuid;
uint64_t StartingLba;
uint64_t EndingLba;
uint64_t Attributes;
} IOP_GPT_ENTRY_PREFIX;
#pragma pack(pop)
_Static_assert(sizeof(IOP_MBR_PARTITION_ENTRY) == 16u, "MBR partition entry layout must match disk format");
_Static_assert(sizeof(IOP_GPT_HEADER) == 92u, "GPT header prefix layout must match disk format");
_Static_assert(sizeof(IOP_GPT_ENTRY_PREFIX) == 56u, "GPT entry prefix layout must match disk format");
typedef struct IOP_PARTITION_RECORD {
struct IOP_PARTITION_RECORD* Next;
IO_PARTITION_INFO Info;
BOOT_VOLUME_SELECTOR Identity;
} IOP_PARTITION_RECORD;
typedef struct IOP_PARTITION_CALLBACK_ENTRY {
struct IOP_PARTITION_CALLBACK_ENTRY* Next;
IO_PARTITION_CHANGE_CALLBACK Callback;
void* Context;
} IOP_PARTITION_CALLBACK_ENTRY;
typedef struct IOP_GPT_VALIDATED_HEADER {
IOP_GPT_HEADER Header;
uint64_t EntryBytes;
uint64_t EntrySectors;
} IOP_GPT_VALIDATED_HEADER;
static MUTEX* gPartitionMutex;
static IOP_PARTITION_RECORD* gPartitions;
static IOP_PARTITION_CALLBACK_ENTRY* gPartitionCallbacks;
static BOOT_VOLUME_SELECTOR gSystemVolumeSelector;
static bool gPartitionManagerInitialized;
// Returns whether two shared GUID values are identical.
static bool IopGuidEquals(const BOOT_VOLUME_GUID* left, const BOOT_VOLUME_GUID* right) {
return left && right && memcmp(left, right, sizeof(*left)) == 0;
}
// Returns whether one shared GUID value is all zeroes.
static bool IopGuidIsZero(const BOOT_VOLUME_GUID* guid) {
static const BOOT_VOLUME_GUID zero = {0};
return guid && IopGuidEquals(guid, &zero);
}
// Returns whether one internal partition identity equals another.
static bool IopIdentityEquals(const BOOT_VOLUME_SELECTOR* left, const BOOT_VOLUME_SELECTOR* right) {
if (!left || !right || left->Kind != right->Kind)
return false;
if (left->Kind == BootVolumeSelectorMbr) {
return left->Identity.Mbr.DiskSignature == right->Identity.Mbr.DiskSignature &&
left->Identity.Mbr.PartitionNumber == right->Identity.Mbr.PartitionNumber;
}
if (left->Kind == BootVolumeSelectorGpt) {
return IopGuidEquals(&left->Identity.Gpt.PartitionGuid,
&right->Identity.Gpt.PartitionGuid);
}
return false;
}
// Acquires the partition-manager mutex for registry and callback serialization.
static bool IopAcquirePartitionMutex(void) {
if (!gPartitionMutex)
return false;
return KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(gPartitionMutex, KE_WAIT_INFINITE));
}
// Frees one dynamically discovered partition list.
static void IopFreePartitionList(IOP_PARTITION_RECORD* head) {
while (head) {
IOP_PARTITION_RECORD* next = head->Next;
MmFreePool(head);
head = next;
}
}
// Updates one CRC32 accumulator using the GPT/IEEE polynomial.
static uint32_t IopCrc32Update(uint32_t crc, const uint8_t* data, size_t length) {
for (size_t i = 0; i < length; ++i) {
crc ^= data[i];
for (uint32_t bit = 0; bit < 8; ++bit)
crc = (crc >> 1) ^ (0xedb88320u & (uint32_t)-(int32_t)(crc & 1u));
}
return crc;
}
// Computes a complete GPT CRC32 value.
static uint32_t IopCrc32(const void* data, size_t length) {
uint32_t crc = IopCrc32Update(0xffffffffu, (const uint8_t*)data, length);
return crc ^ 0xffffffffu;
}
// Reads an arbitrary byte span through the kernel's 512-byte block-device API.
static KSTATUS IopReadDiskBytes(uint8_t diskIndex,
uint64_t diskSectors,
uint64_t byteOffset,
void* buffer,
size_t length) {
if (!buffer || length == 0)
return NTLSTATUS_INVALID_PARAMETER;
if (diskSectors > UINT64_MAX / IOP_SECTOR_SIZE)
return NTLSTATUS_INVALID_PARAMETER;
uint64_t diskBytes = diskSectors * IOP_SECTOR_SIZE;
if (byteOffset > diskBytes || (uint64_t)length > diskBytes - byteOffset)
return NTLSTATUS_INVALID_PARAMETER;
uint8_t sector[IOP_SECTOR_SIZE];
uint8_t* out = (uint8_t*)buffer;
size_t remaining = length;
while (remaining) {
uint64_t lba = byteOffset / IOP_SECTOR_SIZE;
uint32_t inSector = (uint32_t)(byteOffset % IOP_SECTOR_SIZE);
size_t chunk = IOP_SECTOR_SIZE - inSector;
if (chunk > remaining)
chunk = remaining;
KSTATUS status = IoReadDiskSectors(diskIndex, lba, 1, sector);
if (!KSUCCESS(status))
return status;
memcpy(out, sector + inSector, chunk);
out += chunk;
byteOffset += chunk;
remaining -= chunk;
}
return NTLSTATUS_SUCCESS;
}
// Returns whether one MBR type is an extended-partition container we intentionally do not enumerate.
static bool IopMbrTypeIsExtended(uint8_t type) {
return type == 0x05u || type == 0x0fu || type == 0x85u;
}
// Allocates one immutable partition record.
static IOP_PARTITION_RECORD* IopAllocatePartition(uint8_t diskIndex,
uint64_t startLba,
uint64_t sectorCount,
const BOOT_VOLUME_SELECTOR* identity) {
if (!sectorCount || !identity)
return NULL;
IOP_PARTITION_RECORD* record = MmAllocatePool(sizeof(*record));
if (!record)
return NULL;
memset(record, 0, sizeof(*record));
record->Info.DiskIndex = diskIndex;
record->Info.StartLba = startLba;
record->Info.SectorCount = sectorCount;
record->Identity = *identity;
return record;
}
// Inserts one discovered partition in ascending LBA order and rejects overlap or duplicate identity.
static KSTATUS IopInsertDiscoveredPartition(IOP_PARTITION_RECORD** head,
IOP_PARTITION_RECORD* record) {
if (!head || !record || record->Info.SectorCount == 0)
return NTLSTATUS_INVALID_PARAMETER;
if (record->Info.StartLba > UINT64_MAX - record->Info.SectorCount)
return NTLSTATUS_INVALID_PARAMETER;
uint64_t recordEnd = record->Info.StartLba + record->Info.SectorCount;
IOP_PARTITION_RECORD* previous = NULL;
IOP_PARTITION_RECORD* current = *head;
while (current && current->Info.StartLba < record->Info.StartLba) {
if (IopIdentityEquals(&current->Identity, &record->Identity))
return NTLSTATUS_OBJECT_NAME_COLLISION;
previous = current;
current = current->Next;
}
for (IOP_PARTITION_RECORD* scan = current; scan; scan = scan->Next) {
if (IopIdentityEquals(&scan->Identity, &record->Identity))
return NTLSTATUS_OBJECT_NAME_COLLISION;
}
if (previous) {
uint64_t previousEnd = previous->Info.StartLba + previous->Info.SectorCount;
if (previousEnd > record->Info.StartLba)
return NTLSTATUS_INVALID_PARAMETER;
}
if (current && recordEnd > current->Info.StartLba)
return NTLSTATUS_INVALID_PARAMETER;
record->Next = current;
if (previous)
previous->Next = record;
else
*head = record;
return NTLSTATUS_SUCCESS;
}
// Validates the exact CRC32 of one GPT partition-entry array without imposing an entry-count cap.
static KSTATUS IopValidateGptEntryArrayCrc(uint8_t diskIndex,
const IOP_GPT_VALIDATED_HEADER* validated) {
uint8_t sector[IOP_SECTOR_SIZE];
uint64_t remaining = validated->EntryBytes;
uint64_t lba = validated->Header.PartitionEntryLba;
uint32_t crc = 0xffffffffu;
while (remaining) {
KSTATUS status = IoReadDiskSectors(diskIndex, lba++, 1, sector);
if (!KSUCCESS(status))
return status;
size_t chunk = remaining < IOP_SECTOR_SIZE ? (size_t)remaining : IOP_SECTOR_SIZE;
crc = IopCrc32Update(crc, sector, chunk);
remaining -= chunk;
}
crc ^= 0xffffffffu;
return crc == validated->Header.PartitionEntryArrayCrc32
? NTLSTATUS_SUCCESS
: NTLSTATUS_CHECKSUM_MISMATCH;
}
// Validates one primary or backup GPT header and its partition-entry-array CRC.
static KSTATUS IopValidateGptHeader(uint8_t diskIndex,
uint64_t diskSectors,
uint64_t headerLba,
uint64_t expectedAlternateLba,
IOP_GPT_VALIDATED_HEADER* validatedOut) {
if (!validatedOut || diskSectors < 3 || headerLba >= diskSectors ||
expectedAlternateLba >= diskSectors || headerLba == expectedAlternateLba) {
return NTLSTATUS_INVALID_PARAMETER;
}
uint8_t sector[IOP_SECTOR_SIZE];
KSTATUS status = IoReadDiskSectors(diskIndex, headerLba, 1, sector);
if (!KSUCCESS(status))
return status;
IOP_GPT_HEADER header;
memcpy(&header, sector, sizeof(header));
if (memcmp(header.Signature, IOP_GPT_SIGNATURE, 8) != 0 ||
header.Revision < IOP_GPT_REVISION_1_0 ||
header.HeaderSize < sizeof(IOP_GPT_HEADER) ||
header.HeaderSize > IOP_SECTOR_SIZE ||
header.Reserved != 0 ||
header.MyLba != headerLba ||
header.AlternateLba != expectedAlternateLba ||
header.FirstUsableLba > header.LastUsableLba ||
header.LastUsableLba >= diskSectors ||
IopGuidIsZero(&header.DiskGuid) ||
header.NumberOfPartitionEntries == 0 ||
header.SizeOfPartitionEntry < IOP_GPT_MIN_ENTRY_SIZE ||
(header.SizeOfPartitionEntry & 7u) != 0) {
return NTLSTATUS_INVALID_PARAMETER;
}
uint32_t expectedHeaderCrc = header.HeaderCrc32;
memset(sector + offsetof(IOP_GPT_HEADER, HeaderCrc32), 0, sizeof(header.HeaderCrc32));
if (IopCrc32(sector, header.HeaderSize) != expectedHeaderCrc)
return NTLSTATUS_CHECKSUM_MISMATCH;
if ((uint64_t)header.NumberOfPartitionEntries > UINT64_MAX / header.SizeOfPartitionEntry)
return NTLSTATUS_INVALID_PARAMETER;
uint64_t entryBytes = (uint64_t)header.NumberOfPartitionEntries * header.SizeOfPartitionEntry;
if (entryBytes > UINT64_MAX - (IOP_SECTOR_SIZE - 1u))
return NTLSTATUS_INVALID_PARAMETER;
uint64_t entrySectors = (entryBytes + IOP_SECTOR_SIZE - 1u) / IOP_SECTOR_SIZE;
if (entrySectors == 0 || header.PartitionEntryLba >= diskSectors ||
entrySectors > diskSectors - header.PartitionEntryLba) {
return NTLSTATUS_INVALID_PARAMETER;
}
uint64_t entryLastLba = header.PartitionEntryLba + entrySectors - 1u;
if (headerLba == 1u) {
if (header.PartitionEntryLba <= headerLba || entryLastLba >= header.FirstUsableLba)
return NTLSTATUS_INVALID_PARAMETER;
} else {
if (header.PartitionEntryLba <= header.LastUsableLba || entryLastLba >= headerLba)
return NTLSTATUS_INVALID_PARAMETER;
}
IOP_GPT_VALIDATED_HEADER validated = {0};
validated.Header = header;
validated.EntryBytes = entryBytes;
validated.EntrySectors = entrySectors;
status = IopValidateGptEntryArrayCrc(diskIndex, &validated);
if (!KSUCCESS(status))
return status;
*validatedOut = validated;
return NTLSTATUS_SUCCESS;
}
// Returns whether independently valid primary and backup GPT headers describe the same table.
static bool IopGptHeadersAgree(const IOP_GPT_VALIDATED_HEADER* primary,
const IOP_GPT_VALIDATED_HEADER* backup) {
if (!primary || !backup)
return false;
return IopGuidEquals(&primary->Header.DiskGuid, &backup->Header.DiskGuid) &&
primary->Header.FirstUsableLba == backup->Header.FirstUsableLba &&
primary->Header.LastUsableLba == backup->Header.LastUsableLba &&
primary->Header.NumberOfPartitionEntries == backup->Header.NumberOfPartitionEntries &&
primary->Header.SizeOfPartitionEntry == backup->Header.SizeOfPartitionEntry &&
primary->Header.PartitionEntryArrayCrc32 == backup->Header.PartitionEntryArrayCrc32;
}
// Parses all populated GPT entries from one already validated table.
static KSTATUS IopParseGptEntries(uint8_t diskIndex,
uint64_t diskSectors,
const IOP_GPT_VALIDATED_HEADER* validated,
IOP_PARTITION_RECORD** partitionsOut) {
if (!validated || !partitionsOut)
return NTLSTATUS_INVALID_PARAMETER;
IOP_PARTITION_RECORD* partitions = NULL;
if (validated->Header.PartitionEntryLba > UINT64_MAX / IOP_SECTOR_SIZE)
return NTLSTATUS_INVALID_PARAMETER;
uint64_t arrayByteBase = validated->Header.PartitionEntryLba * IOP_SECTOR_SIZE;
for (uint32_t index = 0; index < validated->Header.NumberOfPartitionEntries; ++index) {
uint64_t entryOffset = (uint64_t)index * validated->Header.SizeOfPartitionEntry;
if (arrayByteBase > UINT64_MAX - entryOffset) {
IopFreePartitionList(partitions);
return NTLSTATUS_INVALID_PARAMETER;
}
IOP_GPT_ENTRY_PREFIX entry;
KSTATUS status = IopReadDiskBytes(diskIndex,
diskSectors,
arrayByteBase + entryOffset,
&entry,
sizeof(entry));
if (!KSUCCESS(status)) {
IopFreePartitionList(partitions);
return status;
}
if (IopGuidIsZero(&entry.PartitionTypeGuid))
continue;
if (IopGuidIsZero(&entry.UniquePartitionGuid) ||
entry.EndingLba < entry.StartingLba ||
entry.StartingLba < validated->Header.FirstUsableLba ||
entry.EndingLba > validated->Header.LastUsableLba) {
IopFreePartitionList(partitions);
return NTLSTATUS_INVALID_PARAMETER;
}
uint64_t sectorCount = entry.EndingLba - entry.StartingLba + 1u;
BOOT_VOLUME_SELECTOR identity = {0};
identity.Kind = BootVolumeSelectorGpt;
identity.Identity.Gpt.PartitionGuid = entry.UniquePartitionGuid;
IOP_PARTITION_RECORD* record = IopAllocatePartition(diskIndex,
entry.StartingLba,
sectorCount,
&identity);
if (!record) {
IopFreePartitionList(partitions);
return NTLSTATUS_NO_MEMORY;
}
status = IopInsertDiscoveredPartition(&partitions, record);
if (!KSUCCESS(status)) {
MmFreePool(record);
IopFreePartitionList(partitions);
return status;
}
}
*partitionsOut = partitions;
return NTLSTATUS_SUCCESS;
}
// Discovers one GPT disk from primary/backup metadata and rejects inconsistent valid copies.
static KSTATUS IopDiscoverGptDisk(uint8_t diskIndex,
uint64_t diskSectors,
IOP_PARTITION_RECORD** partitionsOut) {
IOP_GPT_VALIDATED_HEADER primary = {0};
IOP_GPT_VALIDATED_HEADER backup = {0};
KSTATUS primaryStatus = IopValidateGptHeader(diskIndex, diskSectors, 1u,
diskSectors - 1u, &primary);
KSTATUS backupStatus = IopValidateGptHeader(diskIndex, diskSectors, diskSectors - 1u,
1u, &backup);
bool primaryValid = KSUCCESS(primaryStatus);
bool backupValid = KSUCCESS(backupStatus);
if (!primaryValid && !backupValid) {
DbgPrint("partition: disk %u GPT primary=0x%x backup=0x%x\n",
diskIndex, primaryStatus, backupStatus);
return primaryStatus == NTLSTATUS_CHECKSUM_MISMATCH || backupStatus == NTLSTATUS_CHECKSUM_MISMATCH
? NTLSTATUS_CHECKSUM_MISMATCH
: NTLSTATUS_INVALID_PARAMETER;
}
if (primaryValid && backupValid && !IopGptHeadersAgree(&primary, &backup)) {
DbgPrint("partition: disk %u GPT primary/backup metadata disagree\n", diskIndex);
return NTLSTATUS_INVALID_PARAMETER;
}
const IOP_GPT_VALIDATED_HEADER* selected = primaryValid ? &primary : &backup;
if (!primaryValid)
DbgPrint("partition: disk %u using valid backup GPT\n", diskIndex);
return IopParseGptEntries(diskIndex, diskSectors, selected, partitionsOut);
}
// Discovers either an MBR primary-partition map or a protective-MBR GPT map.
static KSTATUS IopDiscoverDiskPartitions(uint8_t diskIndex,
DEVICE_OBJECT* device,
IOP_PARTITION_RECORD** partitionsOut) {
if (!device || !partitionsOut)
return NTLSTATUS_INVALID_PARAMETER;
*partitionsOut = NULL;
if (device->u.Disk.SectorSize != IOP_SECTOR_SIZE || device->u.Disk.SectorCount == 0) {
DbgPrint("partition: disk %u unsupported sector geometry size=%u sectors=%llu\n",
diskIndex, device->u.Disk.SectorSize,
(unsigned long long)device->u.Disk.SectorCount);
return NTLSTATUS_NOT_SUPPORTED;
}
uint64_t diskSectors = device->u.Disk.SectorCount;
uint8_t sector[IOP_SECTOR_SIZE];
KSTATUS status = IoReadDiskSectors(diskIndex, 0, 1, sector);
if (!KSUCCESS(status))
return status;
if (sector[510] != 0x55u || sector[511] != 0xaau)
return NTLSTATUS_NOT_FOUND;
uint32_t diskSignature = 0;
memcpy(&diskSignature, sector + 440u, sizeof(diskSignature));
IOP_MBR_PARTITION_ENTRY entries[4];
memcpy(entries, sector + 446u, sizeof(entries));
bool protective = false;
for (uint32_t i = 0; i < 4; ++i) {
if (entries[i].Type == 0xeeu && entries[i].SectorCount != 0)
protective = true;
}
if (protective)
return IopDiscoverGptDisk(diskIndex, diskSectors, partitionsOut);
// Validate the complete primary table before emitting supported partitions. Extended containers remain
// unsupported, but malformed container extents must not make a partially trusted MBR map acceptable.
for (uint32_t i = 0; i < 4; ++i) {
const IOP_MBR_PARTITION_ENTRY* entry = &entries[i];
if (entry->BootIndicator != 0x00u && entry->BootIndicator != 0x80u)
return NTLSTATUS_INVALID_PARAMETER;
if (entry->Type == 0 && entry->SectorCount == 0)
continue;
if (entry->Type == 0 || entry->SectorCount == 0 ||
entry->StartingLba == 0 || entry->StartingLba >= diskSectors ||
(uint64_t)entry->SectorCount > diskSectors - entry->StartingLba) {
return NTLSTATUS_INVALID_PARAMETER;
}
uint64_t entryEnd = (uint64_t)entry->StartingLba + entry->SectorCount;
for (uint32_t previous = 0; previous < i; ++previous) {
const IOP_MBR_PARTITION_ENTRY* prior = &entries[previous];
if (prior->Type == 0 && prior->SectorCount == 0)
continue;
uint64_t priorEnd = (uint64_t)prior->StartingLba + prior->SectorCount;
if ((uint64_t)entry->StartingLba < priorEnd &&
(uint64_t)prior->StartingLba < entryEnd) {
return NTLSTATUS_INVALID_PARAMETER;
}
}
}
IOP_PARTITION_RECORD* partitions = NULL;
for (uint32_t i = 0; i < 4; ++i) {
const IOP_MBR_PARTITION_ENTRY* entry = &entries[i];
if (entry->Type == 0 || IopMbrTypeIsExtended(entry->Type))
continue;
BOOT_VOLUME_SELECTOR identity = {0};
identity.Kind = BootVolumeSelectorMbr;
identity.Identity.Mbr.DiskSignature = diskSignature;
identity.Identity.Mbr.PartitionNumber = i + 1u;
IOP_PARTITION_RECORD* record = IopAllocatePartition(diskIndex,
entry->StartingLba,
entry->SectorCount,
&identity);
if (!record) {
IopFreePartitionList(partitions);
return NTLSTATUS_NO_MEMORY;
}
status = IopInsertDiscoveredPartition(&partitions, record);
if (!KSUCCESS(status)) {
MmFreePool(record);
IopFreePartitionList(partitions);
return status;
}
}
*partitionsOut = partitions;
return NTLSTATUS_SUCCESS;
}
// Returns whether the registry already contains at least one partition for this disk index.
static bool IopDiskAlreadyDiscovered(uint8_t diskIndex) {
bool found = false;
if (!IopAcquirePartitionMutex())
return false;
for (IOP_PARTITION_RECORD* record = gPartitions; record; record = record->Next) {
if (record->Info.DiskIndex == diskIndex) {
found = true;
break;
}
}
KeReleaseMutex(gPartitionMutex);
return found;
}
// Appends one discovered list to the live registry and synchronously notifies every subscriber.
static KSTATUS IopPublishPartitions(uint8_t diskIndex, IOP_PARTITION_RECORD* discovered) {
if (!IopAcquirePartitionMutex()) {
IopFreePartitionList(discovered);
return NTLSTATUS_FAILURE;
}
for (IOP_PARTITION_RECORD* record = gPartitions; record; record = record->Next) {
if (record->Info.DiskIndex == diskIndex) {
KeReleaseMutex(gPartitionMutex);
IopFreePartitionList(discovered);
return NTLSTATUS_SUCCESS;
}
}
IOP_PARTITION_RECORD** tail = &gPartitions;
while (*tail)
tail = &(*tail)->Next;
*tail = discovered;
for (IOP_PARTITION_RECORD* record = discovered; record; record = record->Next) {
if (record->Identity.Kind == BootVolumeSelectorMbr) {
DbgPrint("partition: disk=%u mbr/%08X/%u start=%llu sectors=%llu\n",
record->Info.DiskIndex,
record->Identity.Identity.Mbr.DiskSignature,
record->Identity.Identity.Mbr.PartitionNumber,
(unsigned long long)record->Info.StartLba,
(unsigned long long)record->Info.SectorCount);
} else {
const BOOT_VOLUME_GUID* guid = &record->Identity.Identity.Gpt.PartitionGuid;
DbgPrint("partition: disk=%u gpt/%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X start=%llu sectors=%llu\n",
record->Info.DiskIndex,
guid->Data1, guid->Data2, guid->Data3,
guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7],
(unsigned long long)record->Info.StartLba,
(unsigned long long)record->Info.SectorCount);
}
if (IopIdentityEquals(&record->Identity, &gSystemVolumeSelector)) {
DbgPrint("partition: configured system_volume matches disk=%u start=%llu sectors=%llu\n",
record->Info.DiskIndex,
(unsigned long long)record->Info.StartLba,
(unsigned long long)record->Info.SectorCount);
}
for (IOP_PARTITION_CALLBACK_ENTRY* entry = gPartitionCallbacks; entry; entry = entry->Next)
entry->Callback(&record->Info, IoPartitionArrived, entry->Context);
}
KeReleaseMutex(gPartitionMutex);
return NTLSTATUS_SUCCESS;
}
// Discovers and publishes all partitions for one newly registered disk.
static void IopDiskArrived(uint8_t diskIndex, DEVICE_OBJECT* device) {
if (!device || IopDiskAlreadyDiscovered(diskIndex))
return;
IOP_PARTITION_RECORD* discovered = NULL;
KSTATUS status = IopDiscoverDiskPartitions(diskIndex, device, &discovered);
if (!KSUCCESS(status)) {
if (status != NTLSTATUS_NOT_FOUND) {
DbgPrint("partition: disk %u discovery failed status=0x%x\n", diskIndex, status);
} else {
DbgPrint("partition: disk %u has no partition table\n", diskIndex);
}
return;
}
if (!discovered) {
DbgPrint("partition: disk %u contains no supported partitions\n", diskIndex);
return;
}
status = IopPublishPartitions(diskIndex, discovered);
if (!KSUCCESS(status))
DbgPrint("partition: disk %u publish failed status=0x%x\n", diskIndex, status);
}
// Removes all partitions belonging to one disk and notifies consumers before freeing their snapshots.
static void IopDiskRemoved(uint8_t diskIndex, IO_PARTITION_CHANGE change) {
if (!IopAcquirePartitionMutex())
return;
IOP_PARTITION_RECORD** link = &gPartitions;
while (*link) {
IOP_PARTITION_RECORD* record = *link;
if (record->Info.DiskIndex != diskIndex) {
link = &record->Next;
continue;
}
*link = record->Next;
for (IOP_PARTITION_CALLBACK_ENTRY* entry = gPartitionCallbacks; entry; entry = entry->Next)
entry->Callback(&record->Info, change, entry->Context);
MmFreePool(record);
}
KeReleaseMutex(gPartitionMutex);
}
// Receives the underlying disk namespace and translates it into partition changes.
static void IopDiskChange(uint8_t diskIndex,
DEVICE_OBJECT* device,
IO_DEVICE_CHANGE change,
void* context) {
(void)context;
switch (change) {
case IoDeviceArrived:
IopDiskArrived(diskIndex, device);
break;
case IoDeviceSurpriseRemoved:
case IoDeviceRemoved:
IopDiskRemoved(diskIndex,
change == IoDeviceSurpriseRemoved
? IoPartitionSurpriseRemoved
: IoPartitionRemoved);
break;
case IoDeviceQueryRemove:
default:
break;
}
}
// Replays one disk that predates partition-manager initialization.
static void IopReplayDisk(uint8_t diskIndex, DEVICE_OBJECT* device, void* context) {
(void)context;
IopDiskArrived(diskIndex, device);
}
// Initializes dynamic partition discovery and the mandatory structural system-volume selector.
KSTATUS IoInitializePartitionManager(const char* systemVolumeSelector) {
if (!systemVolumeSelector || !*systemVolumeSelector)
return NTLSTATUS_INVALID_PARAMETER;
if (gPartitionManagerInitialized)
return NTLSTATUS_ALREADY_EXISTS;
BOOT_VOLUME_SELECTOR selector = {0};
if (!BootVolumeSelectorParse(systemVolumeSelector, strlen(systemVolumeSelector), &selector))
return NTLSTATUS_INVALID_PARAMETER;
MUTEX* mutex = NULL;
KSTATUS status = KeMutexCreate(&mutex);
if (!KSUCCESS(status))
return status;
gPartitionMutex = mutex;
gSystemVolumeSelector = selector;
status = IoRegisterDiskChangeCallback(IopDiskChange, NULL);
if (!KSUCCESS(status)) {
gPartitionMutex = NULL;
memset(&gSystemVolumeSelector, 0, sizeof(gSystemVolumeSelector));
ObDereferenceObject(&mutex->Header);
return status;
}
// The disk-change subscription is permanent, so the manager becomes live before replaying existing disks.
gPartitionManagerInitialized = true;
status = IoEnumerateDiskDevices(IopReplayDisk, NULL);
if (!KSUCCESS(status))
return status;
DbgPrint("partition: dynamic discovery initialized for system_volume=%s\n", systemVolumeSelector);
return NTLSTATUS_SUCCESS;
}
// Registers one partition-change consumer and replays every currently discovered partition before returning.
__declspec(dllexport) KSTATUS IoRegisterPartitionChangeCallback(IO_PARTITION_CHANGE_CALLBACK callback,
void* context) {
if (!callback || !gPartitionManagerInitialized)
return NTLSTATUS_INVALID_PARAMETER;
IOP_PARTITION_CALLBACK_ENTRY* entry = MmAllocatePool(sizeof(*entry));
if (!entry)
return NTLSTATUS_NO_MEMORY;
entry->Next = NULL;
entry->Callback = callback;
entry->Context = context;
if (!IopAcquirePartitionMutex()) {
MmFreePool(entry);
return NTLSTATUS_FAILURE;
}
IOP_PARTITION_CALLBACK_ENTRY** tail = &gPartitionCallbacks;
while (*tail)
tail = &(*tail)->Next;
*tail = entry;
for (IOP_PARTITION_RECORD* record = gPartitions; record; record = record->Next)
callback(&record->Info, IoPartitionArrived, context);
KeReleaseMutex(gPartitionMutex);
return NTLSTATUS_SUCCESS;
}
// Returns the uniquely selected system partition, or NOT_FOUND/COLLISION while discovery is incomplete or ambiguous.
KSTATUS IoQuerySystemPartition(IO_PARTITION_INFO* partitionOut) {
if (!partitionOut || !gPartitionManagerInitialized)
return NTLSTATUS_INVALID_PARAMETER;
if (!IopAcquirePartitionMutex())
return NTLSTATUS_FAILURE;
const IOP_PARTITION_RECORD* match = NULL;
uint32_t matchCount = 0;
for (IOP_PARTITION_RECORD* record = gPartitions; record; record = record->Next) {
if (!IopIdentityEquals(&record->Identity, &gSystemVolumeSelector))
continue;
match = record;
matchCount++;
}
KSTATUS status;
if (matchCount == 0) {
status = NTLSTATUS_NOT_FOUND;
} else if (matchCount != 1) {
status = NTLSTATUS_OBJECT_NAME_COLLISION;
} else {
*partitionOut = match->Info;
status = NTLSTATUS_SUCCESS;
}
KeReleaseMutex(gPartitionMutex);
return status;
}
+634
View File
@@ -0,0 +1,634 @@
// Mounted-volume core. This file owns volume identity, provider lifetime, and dead-volume dispatch semantics.
#include <string.h>
#include <kernel/io_volume.h>
#include <kernel/ke_id.h>
#include <kernel/ke_debug.h>
#include <kernel/ob_object.h>
#include <kernel/mm_file_cache.h>
#include <kernel/mm_virt.h>
#include <kernel/ke_event.h>
#include <kernel/ke_sched.h>
static KE_ID_ALLOCATOR gVolumeIds = KE_ID_ALLOCATOR_INIT(1);
static SPINLOCK gVolumeRegistryLock = SPINLOCK_INIT;
static LIST_ENTRY gVolumeRegistry;
static uint8_t gVolumeRegistryInitialized;
static SPINLOCK gVolumeStatsLock = SPINLOCK_INIT;
static IO_VOLUME_STATS gVolumeStats;
static IO_VOLUME_MANAGER_STATS gVolumeManagerStats;
static EVENT* gVolumeChangeEvent;
static SPINLOCK gVolumeChangeLock = SPINLOCK_INIT;
static uint64_t gVolumeGeneration;
static IO_VOLUME_MANAGER gVolumeManagerObject;
static uint8_t gVolumeManagerObjectInitialized;
static void KiIoVolumeAddStat(uint64_t* field, int64_t delta);
// Initializes the dynamic mounted-volume registry on first use.
static void KiIoEnsureVolumeRegistry(void) {
uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock);
if (!gVolumeRegistryInitialized) {
LIST_INIT(&gVolumeRegistry);
gVolumeRegistryInitialized = 1;
}
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
}
// Publishes one mounted-volume registry change to discovery waiters.
static void KiIoNotifyVolumeChange(void) {
uintptr_t flags = KeAcquireSpinlock(&gVolumeChangeLock);
gVolumeGeneration++;
KeReleaseSpinlock(&gVolumeChangeLock, flags);
if (gVolumeChangeEvent)
KeSignalEvent(gVolumeChangeEvent);
}
// Initializes the waitable mounted-volume registry notification before providers start.
KSTATUS IoInitializeVolumeManager(void) {
KiIoEnsureVolumeRegistry();
if (!gVolumeManagerObjectInitialized) {
ObInitializeObjectHeader(&gVolumeManagerObject.Header, ObjectTypeVolumeManager);
gVolumeManagerObjectInitialized = 1;
}
if (gVolumeChangeEvent)
return NTLSTATUS_SUCCESS;
return KeEventCreate(EventAutoReset, 0, &gVolumeChangeEvent);
}
// References the persistent manager object used to delegate mounted-volume control-plane authority.
KSTATUS IoReferenceVolumeManager(IO_VOLUME_MANAGER** managerOut) {
if (!managerOut)
return NTLSTATUS_INVALID_PARAMETER;
*managerOut = (IO_VOLUME_MANAGER*)0;
if (!gVolumeManagerObjectInitialized || !ObTryReferenceObject(&gVolumeManagerObject.Header))
return NTLSTATUS_OBJECT_DEAD;
*managerOut = &gVolumeManagerObject;
return NTLSTATUS_SUCCESS;
}
// Captures one stable value snapshot. A generation change during capture causes a retry instead of returning stale metadata as current.
KSTATUS IoSnapshotVolumes(IO_VOLUME_SNAPSHOT_ENTRY** entriesOut, uint32_t* countOut, uint64_t* generationOut) {
if (!entriesOut || !countOut || !generationOut)
return NTLSTATUS_INVALID_PARAMETER;
*entriesOut = (IO_VOLUME_SNAPSHOT_ENTRY*)0;
*countOut = 0;
*generationOut = 0;
KiIoEnsureVolumeRegistry();
for (;;) {
uint64_t generationBefore = IoGetVolumeGeneration();
uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock);
size_t count = 0;
LIST_ENTRY* head = &gVolumeRegistry;
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd)
count++;
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
if (count > UINT32_MAX)
return NTLSTATUS_LIMIT_REACHED;
IO_VOLUME_SNAPSHOT_ENTRY* entries = (IO_VOLUME_SNAPSHOT_ENTRY*)0;
if (count) {
entries = (IO_VOLUME_SNAPSHOT_ENTRY*)MmAllocatePool(count * sizeof(*entries));
if (!entries)
return NTLSTATUS_NO_MEMORY;
memset(entries, 0, count * sizeof(*entries));
}
size_t used = 0;
bool overflowed = false;
flags = KeAcquireSpinlock(&gVolumeRegistryLock);
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) {
if (used >= count) {
overflowed = true;
break;
}
IO_VOLUME* volume = CONTAINING_RECORD(node, IO_VOLUME, RegistryLink);
entries[used].InstanceId = volume->InstanceId;
used++;
}
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
uint64_t generationAfter = IoGetVolumeGeneration();
if (!overflowed && generationBefore == generationAfter) {
*entriesOut = entries;
*countOut = (uint32_t)used;
*generationOut = generationAfter;
KiIoVolumeAddStat(&gVolumeManagerStats.Snapshots, 1);
return NTLSTATUS_SUCCESS;
}
if (entries)
MmFreePool(entries);
}
}
// References one currently mounted volume by its runtime instance identity.
KSTATUS IoReferenceVolumeByInstanceId(uint32_t instanceId, IO_VOLUME** volumeOut) {
if (!instanceId || !volumeOut)
return NTLSTATUS_INVALID_PARAMETER;
*volumeOut = (IO_VOLUME*)0;
KiIoEnsureVolumeRegistry();
uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock);
LIST_ENTRY* head = &gVolumeRegistry;
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) {
IO_VOLUME* volume = CONTAINING_RECORD(node, IO_VOLUME, RegistryLink);
if (volume->InstanceId != instanceId)
continue;
if (!ObTryReferenceObject(&volume->Header)) {
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
return NTLSTATUS_OBJECT_DEAD;
}
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
if (!IoVolumeIsMounted(volume)) {
ObDereferenceObject(&volume->Header);
return NTLSTATUS_OBJECT_DEAD;
}
*volumeOut = volume;
KiIoVolumeAddStat(&gVolumeManagerStats.VolumeOpens, 1);
return NTLSTATUS_SUCCESS;
}
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
return NTLSTATUS_NOT_FOUND;
}
// Returns the current dynamic mounted-volume registry generation.
uint64_t IoGetVolumeGeneration(void) {
uintptr_t flags = KeAcquireSpinlock(&gVolumeChangeLock);
uint64_t generation = gVolumeGeneration;
KeReleaseSpinlock(&gVolumeChangeLock, flags);
return generation;
}
// Waits until the mounted-volume registry changes or the timeout expires.
KSTATUS IoWaitForVolumeChange(uint64_t observedGeneration, uint64_t timeoutMilliseconds) {
KiIoVolumeAddStat(&gVolumeManagerStats.Waits, 1);
if (IoGetVolumeGeneration() != observedGeneration) {
KiIoVolumeAddStat(&gVolumeManagerStats.Wakeups, 1);
return NTLSTATUS_SUCCESS;
}
if (!gVolumeChangeEvent)
return NTLSTATUS_OBJECT_DEAD;
EVENT* events[1] = { gVolumeChangeEvent };
uint32_t selected = 0;
KSTATUS status = KeWaitForMultipleEvents(events, 1, 0, timeoutMilliseconds, &selected);
if (KSUCCESS(status))
KiIoVolumeAddStat(&gVolumeManagerStats.Wakeups, 1);
return status;
}
// Adds one value to a mounted-volume statistic.
static void KiIoVolumeAddStat(uint64_t* field, int64_t delta) {
uintptr_t flags = KeAcquireSpinlock(&gVolumeStatsLock);
if (delta >= 0)
*field += (uint64_t)delta;
else {
uint64_t amount = (uint64_t)(-delta);
*field = *field > amount ? *field - amount : 0;
}
KeReleaseSpinlock(&gVolumeStatsLock, flags);
}
void IoQueryVolumeStats(IO_VOLUME_STATS* out) {
if (!out) return;
uintptr_t flags = KeAcquireSpinlock(&gVolumeStatsLock);
*out = gVolumeStats;
KeReleaseSpinlock(&gVolumeStatsLock, flags);
}
void IoQueryVolumeManagerStats(IO_VOLUME_MANAGER_STATS* out) {
if (!out) return;
uintptr_t flags = KeAcquireSpinlock(&gVolumeStatsLock);
*out = gVolumeManagerStats;
KeReleaseSpinlock(&gVolumeStatsLock, flags);
}
// References the unique mounted volume backed by one exact disk extent start.
KSTATUS IoReferenceVolumeByBacking(uint8_t diskIndex, uint64_t startLba, IO_VOLUME** volumeOut) {
if (!volumeOut)
return NTLSTATUS_INVALID_PARAMETER;
*volumeOut = (IO_VOLUME*)0;
KiIoEnsureVolumeRegistry();
IO_VOLUME* match = (IO_VOLUME*)0;
uint32_t matches = 0;
uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock);
LIST_ENTRY* head = &gVolumeRegistry;
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) {
IO_VOLUME* volume = CONTAINING_RECORD(node, IO_VOLUME, RegistryLink);
if (volume->Source.DiskIndex != diskIndex || volume->Source.StartLba != startLba)
continue;
matches++;
if (!match && ObTryReferenceObject(&volume->Header))
match = volume;
}
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
if (matches == 0)
return NTLSTATUS_NOT_FOUND;
if (matches != 1) {
if (match)
ObDereferenceObject(&match->Header);
return NTLSTATUS_OBJECT_NAME_COLLISION;
}
if (!match)
return NTLSTATUS_OBJECT_DEAD;
if (!IoVolumeIsMounted(match)) {
ObDereferenceObject(&match->Header);
return NTLSTATUS_OBJECT_DEAD;
}
*volumeOut = match;
return NTLSTATUS_SUCCESS;
}
// Returns whether the volume still accepts provider operations.
bool IoVolumeIsMounted(const IO_VOLUME* volume) {
if (!volume)
return false;
IO_VOLUME* mutableVolume = (IO_VOLUME*)volume;
uintptr_t flags = KeAcquireSpinlock(&mutableVolume->Lock);
bool mounted = mutableVolume->State == IoVolumeMounted;
KeReleaseSpinlock(&mutableVolume->Lock, flags);
return mounted;
}
// Acquires one object reference only while the mounted-volume state remains live.
bool IoVolumeTryReferenceMounted(IO_VOLUME* volume) {
if (!volume || !ObTryReferenceObject(&volume->Header))
return false;
if (IoVolumeIsMounted(volume))
return true;
ObDereferenceObject(&volume->Header);
return false;
}
// Destroys provider state only after namespace, file, directory, and transient references drain.
static void KiIoVolumeDestroy(OBJECT* object) {
IO_VOLUME* volume = CONTAINING_RECORD(object, IO_VOLUME, Header);
if (volume->Provider.Destroy)
volume->Provider.Destroy(&volume->Provider);
if (volume->BackingDevice) {
IoDereferenceDeviceObject(volume->BackingDevice);
volume->BackingDevice = (DEVICE_OBJECT*)0;
}
if (volume->InstanceId)
KiIoVolumeAddStat(&gVolumeStats.ProviderDestroys, 1);
}
// Creates a dynamic mounted volume and inserts it into the mounted-volume registry.
__declspec(dllexport) KSTATUS IoCreateVolume(const IO_VOLUME_PROVIDER* provider,
const IO_VOLUME_SOURCE* source,
IO_VOLUME** volumeOut) {
if (!provider || !source || !volumeOut || !provider->Open || !provider->Read ||
!provider->GetSize || !provider->Close || source->SectorCount == 0 ||
(source->Type != IoVolumeSourcePartition && source->Type != IoVolumeSourceBlockDevice)) {
return NTLSTATUS_INVALID_PARAMETER;
}
if (source->Type == IoVolumeSourceBlockDevice && source->StartLba != 0)
return NTLSTATUS_INVALID_PARAMETER;
*volumeOut = (IO_VOLUME*)0;
DEVICE_OBJECT* backingDevice = IoReferenceDiskDevice(source->DiskIndex);
if (!backingDevice)
return NTLSTATUS_NOT_FOUND;
uint64_t diskSectors = backingDevice->u.Disk.SectorCount;
if (source->StartLba >= diskSectors || source->SectorCount > diskSectors - source->StartLba ||
(source->Type == IoVolumeSourceBlockDevice && source->SectorCount != diskSectors)) {
IoDereferenceDeviceObject(backingDevice);
return NTLSTATUS_INVALID_PARAMETER;
}
IO_VOLUME* volume = (IO_VOLUME*)ObAllocateObject(ObjectTypeVolume, sizeof(IO_VOLUME));
if (!volume) {
IoDereferenceDeviceObject(backingDevice);
return NTLSTATUS_NO_MEMORY;
}
uint32_t instanceId = 0;
KSTATUS status = KeAllocateId32(&gVolumeIds, &instanceId);
if (!KSUCCESS(status)) {
IoDereferenceDeviceObject(backingDevice);
ObDereferenceObject(&volume->Header);
return status;
}
volume->Header.Destroy = KiIoVolumeDestroy;
volume->Lock = (SPINLOCK)SPINLOCK_INIT;
volume->InstanceId = instanceId;
volume->State = IoVolumeMounted;
volume->RemovalReason = 0;
volume->Source = *source;
volume->BackingDevice = backingDevice;
volume->Provider = *provider;
LIST_INIT(&volume->RegistryLink);
KiIoEnsureVolumeRegistry();
uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock);
ListInsertTail(&gVolumeRegistry, &volume->RegistryLink);
volume->RegistryLinked = 1;
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
// The registry owns the allocation reference; the creator receives an independent reference.
ObReferenceObject(&volume->Header);
*volumeOut = volume;
KiIoVolumeAddStat(&gVolumeStats.CreatedVolumes, 1);
KiIoVolumeAddStat(&gVolumeStats.MountedVolumes, 1);
KiIoNotifyVolumeChange();
DbgPrint("io: volume %u mounted disk=%u start=%llu sectors=%llu\n",
instanceId,
source->DiskIndex,
(unsigned long long)source->StartLba,
(unsigned long long)source->SectorCount);
return NTLSTATUS_SUCCESS;
}
// Marks a mounted volume terminally dead while retaining provider state until references drain.
__declspec(dllexport) KSTATUS IoRemoveVolume(IO_VOLUME* volume, IO_VOLUME_REMOVAL_REASON reason) {
if (!volume || reason < IoVolumeUnmounted || reason > IoVolumeProviderDead)
return NTLSTATUS_INVALID_PARAMETER;
uintptr_t flags = KeAcquireSpinlock(&volume->Lock);
if (volume->State != IoVolumeMounted) {
KeReleaseSpinlock(&volume->Lock, flags);
return NTLSTATUS_OBJECT_DEAD;
}
volume->State = IoVolumeDead;
volume->RemovalReason = (uint32_t)reason;
KeReleaseSpinlock(&volume->Lock, flags);
// No future provider operation can start after this point. The callback may cancel
// provider-specific work, but Context remains valid until KiIoVolumeDestroy.
if (volume->Provider.VolumeDead)
volume->Provider.VolumeDead(&volume->Provider, reason);
MmFileCacheDiscardVolume(volume);
bool dropRegistryReference = false;
flags = KeAcquireSpinlock(&gVolumeRegistryLock);
if (volume->RegistryLinked) {
ListRemove(&volume->RegistryLink);
volume->RegistryLinked = 0;
dropRegistryReference = true;
}
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
KiIoVolumeAddStat(&gVolumeStats.MountedVolumes, -1);
KiIoVolumeAddStat(&gVolumeStats.RemovedVolumes, 1);
KiIoNotifyVolumeChange();
DbgPrint("io: volume %u removed reason=%u\n", volume->InstanceId, (unsigned)reason);
if (dropRegistryReference)
ObDereferenceObject(&volume->Header);
return NTLSTATUS_SUCCESS;
}
// Releases a creator-owned mounted-volume reference.
__declspec(dllexport) void IoReleaseVolume(IO_VOLUME* volume) {
if (volume)
ObDereferenceObject(&volume->Header);
}
// Returns the monotonic identity of a mounted-volume instance.
__declspec(dllexport) uint32_t IoGetVolumeInstanceId(const IO_VOLUME* volume) {
return volume ? volume->InstanceId : 0;
}
// Returns the immutable block extent backing this mounted-volume instance.
__declspec(dllexport) KSTATUS IoGetVolumeSource(const IO_VOLUME* volume, IO_VOLUME_SOURCE* sourceOut) {
if (!volume || !sourceOut)
return NTLSTATUS_INVALID_PARAMETER;
*sourceOut = volume->Source;
return NTLSTATUS_SUCCESS;
}
// Returns whether native provider async read is available while the volume remains mounted.
bool IoVolumeSupportsReadAsync(const IO_VOLUME* volume) {
return volume && IoVolumeIsMounted(volume) && volume->Provider.ReadAsync != (void*)0;
}
// Returns whether native provider async write is available while the volume remains mounted.
bool IoVolumeSupportsWriteAsync(const IO_VOLUME* volume) {
return volume && IoVolumeIsMounted(volume) && volume->Provider.WriteAsync != (void*)0;
}
// Validates mounted state before entering provider code.
static KSTATUS KiIoVolumeCheckMounted(IO_VOLUME* volume) {
if (IoVolumeIsMounted(volume))
return NTLSTATUS_SUCCESS;
KiIoVolumeAddStat(&gVolumeStats.DeadIoRejects, 1);
return NTLSTATUS_OBJECT_DEAD;
}
KSTATUS IoVolumeOpen(IO_VOLUME* volume, SYS_STRING_VIEW path, void** fileCtx) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
return volume->Provider.Open(&volume->Provider, path, fileCtx);
}
KSTATUS IoVolumeRead(IO_VOLUME* volume, void* fileCtx, uint64_t offset,
void* buffer, size_t size, size_t* bytesRead) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Read) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.Read(&volume->Provider, fileCtx, offset, buffer, size, bytesRead);
}
KSTATUS IoVolumeWrite(IO_VOLUME* volume, void* fileCtx, uint64_t offset,
const void* buffer, size_t size, size_t* bytesWritten) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Write) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.Write(&volume->Provider, fileCtx, offset, buffer, size, bytesWritten);
}
KSTATUS IoVolumeReadAsync(IO_VOLUME* volume, void* fileCtx, uint64_t offset,
void* buffer, size_t size, IO_FS_ASYNC_COMPLETE complete,
void* completeContext) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.ReadAsync) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.ReadAsync(&volume->Provider, fileCtx, offset, buffer, size, complete, completeContext);
}
KSTATUS IoVolumeWriteAsync(IO_VOLUME* volume, void* fileCtx, uint64_t offset,
const void* buffer, size_t size, IO_FS_ASYNC_COMPLETE complete,
void* completeContext) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.WriteAsync) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.WriteAsync(&volume->Provider, fileCtx, offset, buffer, size, complete, completeContext);
}
KSTATUS IoVolumeCreate(IO_VOLUME* volume, SYS_STRING_VIEW path, void** fileCtx) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Create) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.Create(&volume->Provider, path, fileCtx);
}
KSTATUS IoVolumeDelete(IO_VOLUME* volume, SYS_STRING_VIEW path) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Delete) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.Delete(&volume->Provider, path);
}
KSTATUS IoVolumeRename(IO_VOLUME* volume, SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Rename) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.Rename(&volume->Provider, oldPath, newPath);
}
KSTATUS IoVolumeCreateDir(IO_VOLUME* volume, SYS_STRING_VIEW path) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.CreateDir) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.CreateDir(&volume->Provider, path);
}
KSTATUS IoVolumeRemoveDir(IO_VOLUME* volume, SYS_STRING_VIEW path) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.RemoveDir) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.RemoveDir(&volume->Provider, path);
}
KSTATUS IoVolumeTruncate(IO_VOLUME* volume, void* fileCtx, uint64_t newSize, uint32_t fileFlags) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Truncate) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.Truncate(&volume->Provider, fileCtx, newSize, fileFlags);
}
KSTATUS IoVolumeGetSize(IO_VOLUME* volume, void* fileCtx, uint64_t* sizeOut) {
if (!sizeOut) return NTLSTATUS_INVALID_PARAMETER;
*sizeOut = 0;
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.GetSize) return NTLSTATUS_NOT_SUPPORTED;
*sizeOut = volume->Provider.GetSize(&volume->Provider, fileCtx);
return NTLSTATUS_SUCCESS;
}
KSTATUS IoVolumeQueryInfo(IO_VOLUME* volume, void* fileCtx, SYS_FILE_STAT* out) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.QueryInfo) return NTLSTATUS_NOT_SUPPORTED;
s = volume->Provider.QueryInfo(&volume->Provider, fileCtx, out);
if (KSUCCESS(s) && out)
out->VolumeId = volume->InstanceId;
return s;
}
KSTATUS IoVolumeStat(IO_VOLUME* volume, SYS_STRING_VIEW path, SYS_FILE_STAT* out) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Stat) return NTLSTATUS_NOT_SUPPORTED;
s = volume->Provider.Stat(&volume->Provider, path, out);
if (KSUCCESS(s) && out)
out->VolumeId = volume->InstanceId;
return s;
}
KSTATUS IoVolumeQueryIdentity(IO_VOLUME* volume, void* fileCtx, IO_FILE_IDENTITY* out) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!out || !volume->Provider.QueryIdentity) return NTLSTATUS_NOT_SUPPORTED;
s = volume->Provider.QueryIdentity(&volume->Provider, fileCtx, out);
if (KSUCCESS(s))
out->VolumeId = volume->InstanceId;
return s;
}
KSTATUS IoVolumeFlush(IO_VOLUME* volume, void* fileCtx) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Flush) return NTLSTATUS_SUCCESS;
return volume->Provider.Flush(&volume->Provider, fileCtx);
}
void IoVolumeClose(IO_VOLUME* volume, void* fileCtx) {
if (volume && volume->Provider.Close && fileCtx)
volume->Provider.Close(&volume->Provider, fileCtx);
}
void IoVolumeQueryStats(IO_VOLUME* volume, IO_FILESYSTEM_STATS* out) {
if (!out) return;
memset(out, 0, sizeof(*out));
if (!volume || !IoVolumeIsMounted(volume) || !volume->Provider.QueryStats)
return;
volume->Provider.QueryStats(&volume->Provider, out);
}
// Aggregates filesystem traversal statistics across every dynamic mounted volume.
__declspec(dllexport) void IoQueryFilesystemStats(IO_FILESYSTEM_STATS* out) {
if (!out) return;
memset(out, 0, sizeof(*out));
KiIoEnsureVolumeRegistry();
uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock);
size_t count = 0;
LIST_ENTRY* head = &gVolumeRegistry;
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd)
count++;
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
if (!count) return;
IO_VOLUME** snapshot = (IO_VOLUME**)MmAllocatePool(count * sizeof(IO_VOLUME*));
if (!snapshot) return;
memset(snapshot, 0, count * sizeof(IO_VOLUME*));
size_t used = 0;
flags = KeAcquireSpinlock(&gVolumeRegistryLock);
for (LIST_ENTRY* node = head->Fwd; node != head && used < count; node = node->Fwd) {
IO_VOLUME* volume = CONTAINING_RECORD(node, IO_VOLUME, RegistryLink);
if (ObTryReferenceObject(&volume->Header))
snapshot[used++] = volume;
}
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
for (size_t i = 0; i < used; ++i) {
IO_FILESYSTEM_STATS one;
IoVolumeQueryStats(snapshot[i], &one);
ObDereferenceObject(&snapshot[i]->Header);
out->PathLookups += one.PathLookups;
out->PathCacheHits += one.PathCacheHits;
out->PathCacheMisses += one.PathCacheMisses;
out->PathCacheStores += one.PathCacheStores;
out->PathCacheInvalidations += one.PathCacheInvalidations;
if (one.PathCacheGeneration > out->PathCacheGeneration)
out->PathCacheGeneration = one.PathCacheGeneration;
out->DirectorySearches += one.DirectorySearches;
out->DirectoryBlocksRead += one.DirectoryBlocksRead;
out->DirectoryEntriesScanned += one.DirectoryEntriesScanned;
out->DataReadRuns += one.DataReadRuns;
out->DataReadSectors += one.DataReadSectors;
out->DataReadRunMerges += one.DataReadRunMerges;
if (one.MaxDataReadSpanSectors > out->MaxDataReadSpanSectors)
out->MaxDataReadSpanSectors = one.MaxDataReadSpanSectors;
}
MmFreePool(snapshot);
}
KSTATUS IoVolumeOpenDir(IO_VOLUME* volume, SYS_STRING_VIEW path, void** dirCtx) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.OpenDir) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.OpenDir(&volume->Provider, path, dirCtx);
}
KSTATUS IoVolumeReadDir(IO_VOLUME* volume, void* dirCtx, KDIR_ENTRY* out) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.ReadDir) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.ReadDir(&volume->Provider, dirCtx, out);
}
void IoVolumeCloseDir(IO_VOLUME* volume, void* dirCtx) {
if (volume && volume->Provider.CloseDir && dirCtx)
volume->Provider.CloseDir(&volume->Provider, dirCtx);
}
+23 -391
View File
@@ -8,6 +8,7 @@
#include <ntlite/ddi/io_driver.h>
#include <ntlite/ddk/audio.h>
#include <kernel/ke_audio.h>
#include <kernel/ke_id.h>
#include <kernel/ke_debug.h>
#include <kernel/ke_event.h>
#include <kernel/ke_list.h>
@@ -22,7 +23,6 @@
#define KE_AUDIO_ENDPOINT_TAG MM_POOL_TAG('a', 'e', 'p', 't')
#define KE_AUDIO_RING_TAG MM_POOL_TAG('a', 'r', 'n', 'g')
#define KE_AUDIO_SELFTEST_TAG MM_POOL_TAG('a', 't', 's', 't')
#define KE_AUDIO_PERIOD_STATE_FREE 0u
#define KE_AUDIO_PERIOD_STATE_FILLING 1u
@@ -31,7 +31,6 @@
#define KE_AUDIO_PERIOD_STATE_REAPING 4u
#define KE_AUDIO_TICKS_PER_MILLISECOND 1000ull
#define KE_AUDIO_SELFTEST_WAIT_MILLISECONDS 5000ull
typedef struct KE_AUDIO_PERIOD_CONTRIBUTION {
KE_AUDIO_STREAM* Stream;
@@ -103,7 +102,7 @@ struct IO_AUDIO_ENDPOINT {
EVENT* MixerExitEvent;
THREAD* MixerThread;
SPINLOCK NotifyLock;
uint64_t EndpointId;
uint32_t EndpointId;
IO_AUDIO_ENDPOINT_KIND Kind;
KE_AUDIO_ENDPOINT_STATE State;
uint32_t Capabilities;
@@ -174,8 +173,8 @@ typedef struct KE_AUDIO_LIFECYCLE_SNAPSHOT {
static SPINLOCK gAudioRegistryLock = SPINLOCK_INIT;
static LIST_ENTRY gAudioEndpointList;
static uint32_t gAudioEndpointCount;
static uint64_t gAudioNextEndpointId = 1;
static uint64_t gAudioDefaultEndpointId;
static KE_ID_ALLOCATOR gAudioEndpointIds = KE_ID_ALLOCATOR_INIT(1u);
static uint32_t gAudioDefaultEndpointId;
static uint8_t gAudioInitialized;
static KE_AUDIO_CORE_STATS gAudioCoreStats;
@@ -433,7 +432,7 @@ static uint32_t KiAudioEndpointRank(IO_AUDIO_ENDPOINT_KIND kind) {
// Recomputes the default endpoint while the registry lock is held.
static void KiAudioSelectDefaultEndpointLocked(void) {
uint64_t selectedId = 0;
uint32_t selectedId = 0;
uint32_t selectedRank = UINT32_MAX;
for (LIST_ENTRY* link = gAudioEndpointList.Fwd;
link != &gAudioEndpointList;
@@ -452,7 +451,7 @@ static void KiAudioSelectDefaultEndpointLocked(void) {
}
// References one ready endpoint by stable identifier.
static IO_AUDIO_ENDPOINT* KiAudioReferenceEndpointById(uint64_t endpointId) {
static IO_AUDIO_ENDPOINT* KiAudioReferenceEndpointById(uint32_t endpointId) {
IO_AUDIO_ENDPOINT* result = (IO_AUDIO_ENDPOINT*)0;
uintptr_t flags = KeAcquireSpinlock(&gAudioRegistryLock);
for (LIST_ENTRY* link = gAudioEndpointList.Fwd;
@@ -1421,7 +1420,18 @@ static KSTATUS KiAudioRegisterEndpoint(const IO_AUDIO_ENDPOINT_REGISTRATION* reg
ObDereferenceObject(&endpoint->Header);
return NTLSTATUS_NO_MEMORY;
}
endpoint->EndpointId = gAudioNextEndpointId++;
KSTATUS idStatus = KeAllocateId32(&gAudioEndpointIds, &endpoint->EndpointId);
if (!KSUCCESS(idStatus)) {
KiAudioSetEndpointStateLocked(endpoint, KeAudioEndpointStopping);
endpoint->FailureStatus = idStatus;
KeReleaseSpinlock(&gAudioRegistryLock, flags);
KeSignalEvent(endpoint->WorkEvent);
(void)KeWaitForEvent(endpoint->MixerExitEvent, 0);
ObDereferenceObject(&endpoint->MixerThread->Header);
endpoint->MixerThread = (THREAD*)0;
ObDereferenceObject(&endpoint->Header);
return idStatus;
}
KiAudioSetEndpointStateLocked(endpoint, KeAudioEndpointReady);
endpoint->Registered = 1;
ObReferenceObject(&endpoint->Header);
@@ -1590,9 +1600,9 @@ const NTL_DDK_AUDIO* KeAudioGetDdk(void) {
}
// Returns the current default endpoint identifier.
uint64_t KeAudioGetDefaultEndpointId(void) {
uint32_t KeAudioGetDefaultEndpointId(void) {
uintptr_t flags = KeAcquireSpinlock(&gAudioRegistryLock);
uint64_t endpointId = gAudioDefaultEndpointId;
uint32_t endpointId = gAudioDefaultEndpointId;
KeReleaseSpinlock(&gAudioRegistryLock, flags);
return endpointId;
}
@@ -1606,7 +1616,7 @@ KSTATUS KeAudioQueryEndpoints(KE_AUDIO_ENDPOINT_INFO* endpoints,
IO_AUDIO_ENDPOINT* references[KE_AUDIO_MAX_ENDPOINTS];
uint32_t count = 0;
uint64_t defaultId = 0;
uint32_t defaultId = 0;
uintptr_t flags = KeAcquireSpinlock(&gAudioRegistryLock);
defaultId = gAudioDefaultEndpointId;
for (LIST_ENTRY* link = gAudioEndpointList.Fwd;
@@ -1668,7 +1678,7 @@ KSTATUS KeAudioQueryEndpoints(KE_AUDIO_ENDPOINT_INFO* endpoints,
}
// Creates one logical render stream with a fixed-capacity source ring.
KSTATUS KeAudioCreateStream(uint64_t endpointId,
KSTATUS KeAudioCreateStream(uint32_t endpointId,
const IO_AUDIO_FORMAT* format,
KE_AUDIO_STREAM** outStream) {
if (!outStream || !KiAudioValidateSourceFormat(format))
@@ -2010,7 +2020,7 @@ KSTATUS KeAudioSetStreamVolume(KE_AUDIO_STREAM* stream,
}
// Updates endpoint master software gain and separate logical mute state.
KSTATUS KeAudioSetMasterVolume(uint64_t endpointId, float volume, bool muted) {
KSTATUS KeAudioSetMasterVolume(uint32_t endpointId, float volume, bool muted) {
if (!KiAudioFloatIsFinite(volume) || volume < 0.0f || volume > 1.0f)
return NTLSTATUS_INVALID_PARAMETER;
IO_AUDIO_ENDPOINT* endpoint = KiAudioReferenceEndpointById(endpointId);
@@ -2080,381 +2090,3 @@ KSTATUS KeAudioQueryStream(KE_AUDIO_STREAM* stream, KE_AUDIO_STREAM_INFO* infoOu
KiAudioReleaseMutex(stream->Lock);
return NTLSTATUS_SUCCESS;
}
// Copies one endpoint snapshot by stable identifier.
static KSTATUS KiAudioSelfTestQueryEndpoint(uint64_t endpointId,
KE_AUDIO_ENDPOINT_INFO* infoOut) {
KE_AUDIO_ENDPOINT_INFO infos[KE_AUDIO_MAX_ENDPOINTS];
uint32_t count = 0;
KSTATUS status = KeAudioQueryEndpoints(infos, KE_AUDIO_MAX_ENDPOINTS, &count);
if (KFAILED(status))
return status;
for (uint32_t index = 0; index < count; index++) {
if (infos[index].EndpointId == endpointId) {
*infoOut = infos[index];
return NTLSTATUS_SUCCESS;
}
}
return NTLSTATUS_NOT_FOUND;
}
// Generates one bounded run of a ramped 440 Hz stereo sine wave.
static void KiAudioSelfTestGenerateTone(int16_t* frames,
uint32_t frameCount,
uint64_t firstFrame,
double* sineInOut,
double* cosineInOut) {
const uint64_t totalFrames = 3ull * KE_AUDIO_OUTPUT_SAMPLE_RATE;
const uint64_t rampFrames = 384ull;
const double amplitude = 8231.0;
const double sineStep = 0.057564026959567284;
const double cosineStep = 0.9983418166140283;
double sine = *sineInOut;
double cosine = *cosineInOut;
for (uint32_t index = 0; index < frameCount; index++) {
uint64_t frame = firstFrame + index;
uint64_t framesFromEnd = totalFrames - 1ull - frame;
double envelope = 1.0;
if (frame < rampFrames)
envelope = (double)frame / (double)(rampFrames - 1ull);
if (framesFromEnd < rampFrames) {
double fadeOut = (double)framesFromEnd / (double)(rampFrames - 1ull);
if (fadeOut < envelope)
envelope = fadeOut;
}
double scaled = sine * amplitude * envelope;
int32_t sample = scaled >= 0.0 ? (int32_t)(scaled + 0.5)
: (int32_t)(scaled - 0.5);
frames[index * 2u] = (int16_t)sample;
frames[index * 2u + 1u] = (int16_t)sample;
double nextSine = sine * cosineStep + cosine * sineStep;
double nextCosine = cosine * cosineStep - sine * sineStep;
sine = nextSine;
cosine = nextCosine;
}
*sineInOut = sine;
*cosineInOut = cosine;
}
// Waits for one selftest stream condition with a finite boot-time deadline.
static KSTATUS KiAudioSelfTestWait(KE_AUDIO_STREAM* stream) {
EVENT* event = KeAudioStreamWaitEvent(stream);
if (!event)
return NTLSTATUS_OBJECT_DEAD;
uint32_t selected = 0;
return KeWaitForMultipleEvents(&event,
1,
0,
KE_AUDIO_SELFTEST_WAIT_MILLISECONDS,
&selected);
}
// Writes one generated run while using the stream event for bounded backpressure.
static KSTATUS KiAudioSelfTestWriteFrames(KE_AUDIO_STREAM* stream,
const int16_t* frames,
uint32_t frameCount,
uint64_t expectedDiscontinuity) {
uint32_t offset = 0;
while (offset < frameCount) {
uint32_t written = 0;
KSTATUS status = KeAudioWriteStream(stream,
frames + offset * 2u,
frameCount - offset,
&written);
if (KSUCCESS(status) && written) {
offset += written;
continue;
}
if (status != NTLSTATUS_WOULD_BLOCK)
return KFAILED(status) ? status : NTLSTATUS_FAILURE;
KE_AUDIO_STREAM_INFO info;
status = KeAudioQueryStream(stream, &info);
if (KFAILED(status))
return status;
if (info.State == KeAudioStreamFailed)
return KFAILED(info.FailureStatus) ? info.FailureStatus : NTLSTATUS_DEVICE_ERROR;
if (info.DiscontinuityGeneration != expectedDiscontinuity)
return NTLSTATUS_DEVICE_ERROR;
KE_AUDIO_STREAM_WAIT_CONDITION condition = {
.Mask = KE_AUDIO_WAIT_WRITABLE | KE_AUDIO_WAIT_FAILED | KE_AUDIO_WAIT_DISCONTINUITY,
.WritableFrames = 1,
.DiscontinuityGeneration = expectedDiscontinuity,
};
status = KeAudioArmStreamWait(stream, &condition);
if (KFAILED(status))
return status;
status = KiAudioSelfTestWait(stream);
if (KFAILED(status) || status == NTLSTATUS_TIMEOUT)
return status;
}
return NTLSTATUS_SUCCESS;
}
// Prints the common endpoint and stream counters for one selftest outcome.
static void KiAudioSelfTestPrintCounters(uint64_t endpointId,
const KE_AUDIO_ENDPOINT_INFO* endpointBefore,
const KE_AUDIO_ENDPOINT_INFO* endpointAfter,
const KE_AUDIO_STREAM_INFO* streamInfo,
uint64_t expectedDiscontinuity) {
uint64_t mixedPeriods = endpointAfter->PeriodsMixed - endpointBefore->PeriodsMixed;
uint64_t completedPeriods = endpointAfter->PeriodsCompleted - endpointBefore->PeriodsCompleted;
uint64_t interruptCount = endpointAfter->InterruptCount - endpointBefore->InterruptCount;
uint64_t endpointUnderruns = endpointAfter->Underruns - endpointBefore->Underruns;
uint64_t endpointDiscontinuities = endpointAfter->DiscontinuityGeneration -
endpointBefore->DiscontinuityGeneration;
uint64_t positionFrames = endpointAfter->HardwarePositionFrames -
endpointBefore->HardwarePositionFrames;
uint64_t streamDiscontinuities = streamInfo->DiscontinuityGeneration >= expectedDiscontinuity
? streamInfo->DiscontinuityGeneration - expectedDiscontinuity
: 0;
DbgPrint("AUDIO final selftest: endpoint=%llu submitted=%llu consumed=%llu presented=%llu\n",
(unsigned long long)endpointId,
(unsigned long long)streamInfo->FramesSubmitted,
(unsigned long long)streamInfo->FramesConsumed,
(unsigned long long)streamInfo->FramesPresented);
DbgPrint("AUDIO final selftest: periods mixed=%llu completed=%llu interrupts=%llu\n",
(unsigned long long)mixedPeriods,
(unsigned long long)completedPeriods,
(unsigned long long)interruptCount);
DbgPrint("AUDIO final selftest: underruns endpoint=%llu stream=%llu discontinuities endpoint=%llu stream=%llu\n",
(unsigned long long)endpointUnderruns,
(unsigned long long)streamInfo->Underruns,
(unsigned long long)endpointDiscontinuities,
(unsigned long long)streamDiscontinuities);
DbgPrint("AUDIO final selftest: position start=%llu end=%llu delta=%llu\n",
(unsigned long long)endpointBefore->HardwarePositionFrames,
(unsigned long long)endpointAfter->HardwarePositionFrames,
(unsigned long long)positionFrames);
}
// Plays the final-path sine test through the selected hardware endpoint and mixer.
void KiRunAudioFinalPathSelfTest(void) {
const uint64_t totalFrames = 3ull * KE_AUDIO_OUTPUT_SAMPLE_RATE;
const uint32_t chunkFrames = 1024u;
const char* failurePhase = (const char*)0;
KSTATUS failureStatus = NTLSTATUS_SUCCESS;
uint64_t failureValue = 0;
uint64_t endpointId = KeAudioGetDefaultEndpointId();
KE_AUDIO_ENDPOINT_INFO endpointBefore;
KE_AUDIO_ENDPOINT_INFO endpointAfter;
KE_AUDIO_STREAM_INFO startInfo;
KE_AUDIO_STREAM_INFO streamAfter;
KE_AUDIO_STREAM* stream = (KE_AUDIO_STREAM*)0;
int16_t* chunk = (int16_t*)0;
uint64_t expectedDiscontinuity = 0;
int haveEndpointBefore = 0;
int haveEndpointAfter = 0;
int haveStreamAfter = 0;
memset(&endpointBefore, 0, sizeof(endpointBefore));
memset(&endpointAfter, 0, sizeof(endpointAfter));
memset(&startInfo, 0, sizeof(startInfo));
memset(&streamAfter, 0, sizeof(streamAfter));
if (!endpointId) {
DbgPrint("AUDIO final selftest: SKIP no default endpoint\n");
return;
}
KSTATUS status = KiAudioSelfTestQueryEndpoint(endpointId, &endpointBefore);
if (KFAILED(status) || !endpointBefore.Available ||
endpointBefore.State != KeAudioEndpointReady) {
failurePhase = "endpoint-ready";
failureStatus = KFAILED(status) ? status : NTLSTATUS_DEVICE_ERROR;
goto cleanup;
}
haveEndpointBefore = 1;
if (endpointBefore.HardwareFormat.SampleRate != KE_AUDIO_OUTPUT_SAMPLE_RATE ||
endpointBefore.HardwareFormat.ChannelCount != KE_AUDIO_OUTPUT_CHANNELS ||
endpointBefore.HardwareFormat.SampleFormat != IoAudioSampleS16) {
failurePhase = "endpoint-format";
failureStatus = NTLSTATUS_NOT_SUPPORTED;
failureValue = endpointBefore.HardwareFormat.SampleFormat;
goto cleanup;
}
DbgPrint("AUDIO final selftest: begin endpoint=%llu kind=%u format=48000/S16/stereo\n",
(unsigned long long)endpointId,
(unsigned)endpointBefore.Kind);
IO_AUDIO_FORMAT format = {
.SampleRate = KE_AUDIO_OUTPUT_SAMPLE_RATE,
.ChannelCount = KE_AUDIO_OUTPUT_CHANNELS,
.SampleFormat = IoAudioSampleS16,
};
status = KeAudioCreateStream(endpointId, &format, &stream);
if (KFAILED(status)) {
failurePhase = "stream-create";
failureStatus = status;
goto cleanup;
}
chunk = (int16_t*)MmAllocatePoolTag(
(size_t)chunkFrames * KE_AUDIO_OUTPUT_BYTES_PER_FRAME,
KE_AUDIO_SELFTEST_TAG);
if (!chunk) {
failurePhase = "tone-allocation";
failureStatus = NTLSTATUS_NO_MEMORY;
goto cleanup;
}
double sine = 0.0;
double cosine = 1.0;
uint64_t submitted = 0;
while (submitted < KE_AUDIO_STREAM_RING_FRAMES) {
uint32_t count = (uint32_t)(KE_AUDIO_STREAM_RING_FRAMES - submitted);
if (count > chunkFrames)
count = chunkFrames;
KiAudioSelfTestGenerateTone(chunk, count, submitted, &sine, &cosine);
uint32_t written = 0;
status = KeAudioWriteStream(stream, chunk, count, &written);
if (KFAILED(status) || written != count) {
failurePhase = "initial-prefill";
failureStatus = KFAILED(status) ? status : NTLSTATUS_FAILURE;
failureValue = written;
goto cleanup;
}
submitted += written;
}
status = KeAudioStartStream(stream);
if (KFAILED(status)) {
failurePhase = "stream-start";
failureStatus = status;
goto cleanup;
}
status = KeAudioQueryStream(stream, &startInfo);
if (KFAILED(status)) {
failurePhase = "start-query";
failureStatus = status;
goto cleanup;
}
expectedDiscontinuity = startInfo.DiscontinuityGeneration;
while (submitted < totalFrames) {
uint64_t remaining = totalFrames - submitted;
uint32_t count = remaining < chunkFrames ? (uint32_t)remaining : chunkFrames;
KiAudioSelfTestGenerateTone(chunk, count, submitted, &sine, &cosine);
status = KiAudioSelfTestWriteFrames(stream,
chunk,
count,
expectedDiscontinuity);
if (KFAILED(status) || status == NTLSTATUS_TIMEOUT) {
failurePhase = "stream-write";
failureStatus = status;
failureValue = submitted;
goto cleanup;
}
submitted += count;
}
status = KeAudioDrainStream(stream);
if (KFAILED(status)) {
failurePhase = "stream-drain";
failureStatus = status;
goto cleanup;
}
KE_AUDIO_STREAM_WAIT_CONDITION drainCondition = {
.Mask = KE_AUDIO_WAIT_DRAINED | KE_AUDIO_WAIT_FAILED | KE_AUDIO_WAIT_DISCONTINUITY,
.DrainedGeneration = startInfo.DrainedGeneration,
.DiscontinuityGeneration = expectedDiscontinuity,
};
status = KeAudioArmStreamWait(stream, &drainCondition);
if (KFAILED(status)) {
failurePhase = "drain-arm";
failureStatus = status;
goto cleanup;
}
status = KiAudioSelfTestWait(stream);
if (KFAILED(status) || status == NTLSTATUS_TIMEOUT) {
failurePhase = "drain-wait";
failureStatus = status;
goto cleanup;
}
status = KeAudioQueryStream(stream, &streamAfter);
if (KFAILED(status)) {
failurePhase = "final-stream-query";
failureStatus = status;
goto cleanup;
}
haveStreamAfter = 1;
if (streamAfter.State != KeAudioStreamStopped ||
streamAfter.DrainedGeneration != startInfo.DrainedGeneration + 1u ||
streamAfter.BufferedFrames != 0 || streamAfter.FramesSubmitted != totalFrames ||
streamAfter.FramesConsumed != totalFrames || streamAfter.FramesPresented != totalFrames ||
streamAfter.FramesDiscarded != 0 || streamAfter.Underruns != 0 ||
streamAfter.DiscontinuityGeneration != expectedDiscontinuity) {
failurePhase = "final-stream-counters";
failureStatus = NTLSTATUS_DEVICE_ERROR;
failureValue = streamAfter.FramesPresented;
goto cleanup;
}
status = KiAudioSelfTestQueryEndpoint(endpointId, &endpointAfter);
if (KFAILED(status)) {
failurePhase = "final-endpoint-query";
failureStatus = status;
goto cleanup;
}
haveEndpointAfter = 1;
uint64_t mixedPeriods = endpointAfter.PeriodsMixed - endpointBefore.PeriodsMixed;
uint64_t completedPeriods = endpointAfter.PeriodsCompleted - endpointBefore.PeriodsCompleted;
uint64_t interruptCount = endpointAfter.InterruptCount - endpointBefore.InterruptCount;
uint64_t endpointUnderruns = endpointAfter.Underruns - endpointBefore.Underruns;
uint64_t endpointDiscontinuities = endpointAfter.DiscontinuityGeneration -
endpointBefore.DiscontinuityGeneration;
uint64_t positionFrames = endpointAfter.HardwarePositionFrames -
endpointBefore.HardwarePositionFrames;
if (!mixedPeriods || !completedPeriods || !interruptCount ||
endpointUnderruns || endpointDiscontinuities ||
positionFrames + endpointAfter.FramesPerPeriod < totalFrames) {
failurePhase = "final-endpoint-counters";
failureStatus = NTLSTATUS_DEVICE_ERROR;
failureValue = positionFrames;
goto cleanup;
}
cleanup:
if (stream && !haveStreamAfter && KSUCCESS(KeAudioQueryStream(stream, &streamAfter)))
haveStreamAfter = 1;
if (haveEndpointBefore && !haveEndpointAfter &&
KSUCCESS(KiAudioSelfTestQueryEndpoint(endpointId, &endpointAfter)))
haveEndpointAfter = 1;
if (haveEndpointBefore && haveEndpointAfter && haveStreamAfter)
KiAudioSelfTestPrintCounters(endpointId,
&endpointBefore,
&endpointAfter,
&streamAfter,
expectedDiscontinuity);
if (stream) {
KE_AUDIO_STREAM_INFO cleanupInfo;
if (KSUCCESS(KeAudioQueryStream(stream, &cleanupInfo)) &&
cleanupInfo.State != KeAudioStreamStopped &&
cleanupInfo.State != KeAudioStreamFailed &&
cleanupInfo.State != KeAudioStreamClosed)
(void)KeAudioStopStream(stream);
}
if (chunk)
MmFreePool(chunk);
if (stream)
ObDereferenceObject(&stream->Header);
if (failurePhase) {
DbgPrint("AUDIO final selftest: FAIL phase=%s status=0x%x value=%llu\n",
failurePhase,
failureStatus,
(unsigned long long)failureValue);
return;
}
DbgPrint("AUDIO final selftest: PASS\n");
}
+154
View File
@@ -0,0 +1,154 @@
// Kernel boot configuration parser. This file intentionally implements only the boot.ini settings the kernel owns.
#include <kernel/ke_boot_config.h>
#include <ntlite/ddk/debug.h>
#include <stdbool.h>
#include <string.h>
// Returns whether one character is horizontal configuration whitespace.
static bool KiBootConfigSpace(char c) {
return c == ' ' || c == '\t' || c == '\r';
}
// Trims horizontal whitespace from one immutable line span.
static void KiBootConfigTrim(const char** begin, const char** end) {
while (*begin < *end && KiBootConfigSpace(**begin))
++*begin;
while (*end > *begin && KiBootConfigSpace((*end)[-1]))
--*end;
}
// Returns whether one immutable span exactly matches a NUL-terminated string.
static bool KiBootConfigSpanEquals(const char* begin, const char* end, const char* text) {
while (begin < end && *text) {
if (*begin++ != *text++)
return false;
}
return begin == end && *text == '\0';
}
// Parses one unsigned decimal or 0x-prefixed hexadecimal value without accepting trailing junk.
static KSTATUS KiBootConfigParseUInt32(const char* begin, const char* end, uint32_t* valueOut) {
KiBootConfigTrim(&begin, &end);
if (begin == end || !valueOut)
return NTLSTATUS_INVALID_PARAMETER;
uint32_t base = 10;
if (end - begin >= 2 && begin[0] == '0' && (begin[1] == 'x' || begin[1] == 'X')) {
base = 16;
begin += 2;
if (begin == end)
return NTLSTATUS_INVALID_PARAMETER;
}
uint32_t value = 0;
for (const char* p = begin; p < end; ++p) {
uint32_t digit;
if (*p >= '0' && *p <= '9') digit = (uint32_t)(*p - '0');
else if (*p >= 'a' && *p <= 'f') digit = (uint32_t)(10 + *p - 'a');
else if (*p >= 'A' && *p <= 'F') digit = (uint32_t)(10 + *p - 'A');
else return NTLSTATUS_INVALID_PARAMETER;
if (digit >= base || value > (UINT32_MAX - digit) / base)
return NTLSTATUS_INVALID_PARAMETER;
value = value * base + digit;
}
*valueOut = value;
return NTLSTATUS_SUCCESS;
}
// Copies one trimmed configuration value into a fixed kernel-owned string.
static KSTATUS KiBootConfigCopyString(const char* begin, const char* end, char* buffer, size_t bufferSize) {
KiBootConfigTrim(&begin, &end);
size_t length = (size_t)(end - begin);
if (!buffer || bufferSize == 0 || length >= bufferSize)
return NTLSTATUS_BUFFER_TOO_SMALL;
memcpy(buffer, begin, length);
buffer[length] = '\0';
return NTLSTATUS_SUCCESS;
}
// Parses the kernel-owned boot.ini settings from the raw bootloader string.
KSTATUS KeParseBootConfiguration(const char* text, size_t length, KE_BOOT_CONFIG* configOut) {
if (!text || !configOut)
return NTLSTATUS_INVALID_PARAMETER;
KE_BOOT_CONFIG config = {0};
bool systemVolumeSeen = false;
char section[32] = {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;
KiBootConfigTrim(&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;
KiBootConfigTrim(&sectionBegin, &sectionEnd);
size_t sectionLength = (size_t)(sectionEnd - sectionBegin);
if (sectionLength >= sizeof(section))
return NTLSTATUS_BUFFER_TOO_SMALL;
memcpy(section, sectionBegin, sectionLength);
section[sectionLength] = '\0';
continue;
}
const char* equals = NULL;
for (const char* p = begin; p < end; ++p) {
if (*p == '=') {
equals = p;
break;
}
}
if (!equals)
return NTLSTATUS_INVALID_PARAMETER;
const char* keyBegin = begin;
const char* keyEnd = equals;
const char* valueBegin = equals + 1;
const char* valueEnd = end;
KiBootConfigTrim(&keyBegin, &keyEnd);
if (strcmp(section, "boot") == 0 && KiBootConfigSpanEquals(keyBegin, keyEnd, "system_volume")) {
if (systemVolumeSeen)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = KiBootConfigCopyString(valueBegin, valueEnd,
config.SystemVolume, sizeof(config.SystemVolume));
if (!KSUCCESS(status) || config.SystemVolume[0] == '\0')
return KSUCCESS(status) ? NTLSTATUS_INVALID_PARAMETER : status;
systemVolumeSeen = true;
} else if (strcmp(section, "kernel") == 0 && KiBootConfigSpanEquals(keyBegin, keyEnd, "debug")) {
KSTATUS status = KiBootConfigParseUInt32(valueBegin, valueEnd, &config.Debug);
if (!KSUCCESS(status))
return status;
if (config.Debug & ~KE_DEBUG_VALID_MASK)
return NTLSTATUS_INVALID_PARAMETER;
} else if (strcmp(section, "init") == 0 && KiBootConfigSpanEquals(keyBegin, keyEnd, "commandline")) {
KSTATUS status = KiBootConfigCopyString(valueBegin, valueEnd,
config.CommandLine, sizeof(config.CommandLine));
if (!KSUCCESS(status))
return status;
}
}
if (!systemVolumeSeen)
return NTLSTATUS_NOT_FOUND;
*configOut = config;
return NTLSTATUS_SUCCESS;
}
+10 -9
View File
@@ -3,9 +3,10 @@
#include <stdint.h>
#include <string.h>
#include <intrin.h>
#include <kernel/ke_hal.h>
#include <kernel/ke_cpu.h>
#include <kernel/ke_debug.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
#define BOOTSTRAP_CPU_STACK_SIZE 0x4000u
#define IA32_GS_BASE 0xC0000101u
@@ -64,7 +65,7 @@ static uintptr_t KiDisableGlobalTranslations(void) {
// nt-lite does not enable PCID. Refuse inherited PCID state rather than
// pretending that a CR3 reload flushed every tagged translation.
if (cr4 & CR4_PCID_ENABLE)
HalStopProcessor();
KeStopProcessor();
if (cr4 & CR4_PAGE_GLOBAL_ENABLE)
__writecr4(cr4 & ~CR4_PAGE_GLOBAL_ENABLE);
@@ -98,7 +99,7 @@ static bool KiMemoryTypePolicyInstalled(uint32_t variableCount) {
// Programs the kernel-owned PAT policy over a uniform write-back MTRR baseline.
void KeInitializeMemoryTypes(void) {
if (!KiMemoryTypeFeaturesAvailable())
HalStopProcessor();
KeStopProcessor();
uint32_t variableCount = (uint32_t)(__readmsr(IA32_MTRRCAP) & MTRR_CAP_VARIABLE_COUNT);
if (KiMemoryTypePolicyInstalled(variableCount)) {
@@ -106,10 +107,10 @@ void KeInitializeMemoryTypes(void) {
return;
}
uintptr_t interruptFlags = HalDisableInterrupts();
uintptr_t interruptFlags = KeDisableInterrupts();
uintptr_t cr0 = __readcr0();
// Every CPU runs this before entering normal kernel execution. The BSP
// Every CPU runs this before entering normal kernel execution. The boot processor
// installs the policy while it is alone, and each AP installs it before
// publishing its started state.
__writecr0((cr0 | CR0_CACHE_DISABLE) & ~CR0_NOT_WRITE_THROUGH);
@@ -134,9 +135,9 @@ void KeInitializeMemoryTypes(void) {
__writecr4(cr4);
bool installed = KiMemoryTypePolicyInstalled(variableCount);
HalRestoreInterrupts(interruptFlags);
KeRestoreInterrupts(interruptFlags);
if (!installed)
HalStopProcessor();
KeStopProcessor();
_InterlockedExchange(&gWriteCombiningAvailable, 1);
}
@@ -176,9 +177,9 @@ void KeInitializeBootstrapCpu(void) {
KCPU* cpu = &gCpus[0];
cpu->Self = cpu;
cpu->CpuIndex = 0;
cpu->LapicId = 0;
cpu->HardwareId = 0;
cpu->State = KeCpuStarted;
cpu->IsBsp = 1;
cpu->IsBootProcessor = 1;
cpu->PackageId = 0;
cpu->CoreId = 0;
cpu->ThreadId = 0;
+20 -43
View File
@@ -4,8 +4,8 @@
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_apic.h>
#include <kernel/hal/hal_api.h>
#include <kernel/ke_hal.h>
#include <intrin.h>
#include <kernel/ke_debug.h>
#include <kernel/ke_exception.h>
@@ -104,7 +104,7 @@ static volatile long gPanicStoppedCpuCount;
// Permanently stops a secondary processor that entered an active panic.
static _Noreturn void KiPanicSecondaryHalt(void) {
_InterlockedIncrement(&gPanicStoppedCpuCount);
HalStopProcessor();
KeStopProcessor();
}
// Returns a stable token for the current execution context.
@@ -131,7 +131,7 @@ static int KiPanicCurrentCpuOwnsReport(void) {
// Claims fatal-report ownership and masks interrupts on this processor.
static void KiBeginFatalReport(void) {
(void)HalDisableInterrupts();
(void)KeDisableInterrupts();
uintptr_t token = KiPanicCurrentToken();
if (_InterlockedCompareExchange((volatile long*)&gPanicInProgress, 1, 0) == 0) {
gPanicOwnerToken = token;
@@ -243,8 +243,8 @@ static KSTATUS KiDebugLogWriteAll(FILE_OBJECT* file,
}
// Persists the in-memory debug ring and flushes it through the storage stack.
__declspec(dllexport) KSTATUS KeFlushDebugLogToFile(SYS_STRING_VIEW path) {
if (!gDebugLogBuffer || gDebugLogSize == 0 || !SysSvIsValid(path) || path.Length == 0)
KSTATUS KeFlushDebugLogToSystemRoot(const char* relativePath) {
if (!gDebugLogBuffer || gDebugLogSize == 0 || !relativePath || !relativePath[0])
return NTLSTATUS_INVALID_PARAMETER;
if (!KeCanWait())
return NTLSTATUS_INVALID_IRQL;
@@ -259,10 +259,10 @@ __declspec(dllexport) KSTATUS KeFlushDebugLogToFile(SYS_STRING_VIEW path) {
start = (gDebugLogUsed == gDebugLogSize) ? gDebugLogWrite : 0;
KeReleaseSpinlock(&gDebugPrintLock, flags);
(void)IoDeleteFile(path);
(void)KeDeleteSystemRootFile(relativePath);
FILE_OBJECT* file = (FILE_OBJECT*)0;
KSTATUS st = IoCreateFile(path, &file);
KSTATUS st = KeCreateSystemRootFile(relativePath, &file);
if (!KSUCCESS(st)) {
_InterlockedExchange((volatile long*)&gDebugLogFlushing, 0);
return st;
@@ -303,13 +303,9 @@ static void KiTryFlushPanicDebugLog(void) {
if (!KeCanWait() || !KeSystemRootReady())
return;
char path[1024];
if (!KeBuildSystemRootPath("kernel-panic.log", path, sizeof(path)))
return;
KSTATUS st = KeFlushDebugLogToFile(SysSvFromCstr(path));
KSTATUS st = KeFlushDebugLogToSystemRoot("kernel-panic.log");
if (!KSUCCESS(st))
DbgPrint("debuglog: panic flush to %s failed st=0x%x\n", path, st);
DbgPrint("debuglog: panic flush failed st=0x%x\n", st);
}
// Writes one formatted panic record through panic-safe endpoints.
@@ -327,7 +323,7 @@ __declspec(dllexport) _Noreturn void KePanic(int code, const char* fmt, ...) {
// Stop every other CPU before writing the final report. Panic-safe endpoints
// do not depend on scheduler locks held by processors stopped by this IPI.
HalApicSendPanicIpi();
KeBroadcastProcessorNmi();
static const char banner[] = "\n*** KERNEL PANIC ***\n";
KiOutputWrite(HAL_OUTPUT_STREAM_PANIC, HAL_OUTPUT_WRITE_EMERGENCY,
@@ -337,9 +333,9 @@ __declspec(dllexport) _Noreturn void KePanic(int code, const char* fmt, ...) {
KCPU* cpu = KeGetCurrentCpu();
THREAD* thread = KeGetCurrentThread();
if (cpu) {
KiPanicPrintf("Panic CPU: %u LAPIC=%u",
KiPanicPrintf("Panic CPU: %u hardware=%u",
(unsigned)cpu->CpuIndex,
(unsigned)cpu->LapicId);
(unsigned)cpu->HardwareId);
if (thread) {
KiPanicPrintf(" thread=0x%016llX tid=%llu",
thread,
@@ -355,7 +351,7 @@ __declspec(dllexport) _Noreturn void KePanic(int code, const char* fmt, ...) {
va_end(args);
KiTryFlushPanicDebugLog();
HalStopProcessor();
KeStopProcessor();
}
// Writes user-visible console text through subscribed output endpoints.
@@ -377,9 +373,9 @@ static void KiFaultPrintf(const char* format, ...) {
va_end(args);
}
// Walk the process module list to find which image owns an address.
// Returns the module name string, or "unknown" if the address is outside all
// known image ranges. Base/size are optional and are useful for offset dumps.
// Resolves the main user image or a registered kernel image that owns an address.
// User-mode DLL bookkeeping belongs to the user-mode loader and is intentionally
// not duplicated in kernel diagnostics.
static const char* KiResolveModuleEx(PROCESS* proc, uintptr_t addr,
uintptr_t* baseOut, size_t* sizeOut) {
if (baseOut) *baseOut = 0;
@@ -394,16 +390,6 @@ static const char* KiResolveModuleEx(PROCESS* proc, uintptr_t addr,
return proc->Name;
}
for (uint32_t i = 0; i < proc->ModuleCount; i++) {
uintptr_t base = proc->Modules[i].Base;
size_t size = proc->Modules[i].Size;
if (base && size && addr >= base && addr < base + size) {
if (baseOut) *baseOut = base;
if (sizeOut) *sizeOut = size;
return proc->Modules[i].Name;
}
}
uintptr_t kernelBase = 0;
size_t kernelSize = 0;
const char* kernelModule = LdrFindKernelModuleByAddress(addr, &kernelBase, &kernelSize);
@@ -595,15 +581,6 @@ static void KiDumpModules(PROCESS* proc) {
(unsigned long long)proc->ImageSize);
}
for (uint32_t i = 0; i < proc->ModuleCount; i++) {
uintptr_t base = proc->Modules[i].Base;
size_t size = proc->Modules[i].Size;
KiFaultPrintf(" %-31s %016llx-%016llx size=%llx\n",
proc->Modules[i].Name,
(unsigned long long)base,
(unsigned long long)(base + size),
(unsigned long long)size);
}
}
#if 0
@@ -783,9 +760,9 @@ static _Noreturn void KiFinishFaultedProcess(PROCESS* proc, THREAD* thread, KSTA
// dead thread to the scheduler with interrupts enabled so waiters such as
// the foreground shell can observe the process exit event.
while (true) {
HalEnableInterrupts();
KeEnableInterrupts();
KeYield();
HalWaitForInterrupt();
KeWaitForInterrupt();
}
}
@@ -931,5 +908,5 @@ __declspec(dllexport) _Noreturn void KeFaultDispatch(int vector, uintptr_t rip,
frame.Rip = rip;
frame.Cs = cs;
(void)KeFaultDispatchFrame(&frame);
HalStopProcessor();
KeStopProcessor();
}
+2 -2
View File
@@ -96,9 +96,9 @@ static void KiRundownMultiWait(THREAD* thread, void* context, KSTATUS status) {
}
// Creates event.
__declspec(dllexport) KSTATUS KeEventCreate(EVENT_TYPE type, uint32_t initialState, EVENT **out) {
__declspec(dllexport) KSTATUS KeEventCreate(EVENT_TYPE type, uint32_t initialState, EVENT** out) {
if (!out) return NTLSTATUS_INVALID_PARAMETER;
EVENT* e = (EVENT *)ObAllocateObject(ObjectTypeEvent, sizeof(EVENT));
EVENT* e = (EVENT*)ObAllocateObject(ObjectTypeEvent, sizeof(EVENT));
if (!e) return NTLSTATUS_NO_MEMORY;
e->Header.Destroy = KiEventDestroy;
e->Lock = (SPINLOCK)SPINLOCK_INIT;
+125
View File
@@ -0,0 +1,125 @@
// Kernel HAL binding and generic platform-operation wrappers.
#include <kernel/ke_hal.h>
static const HAL_API* gHal;
// Binds the bootstrap HAL table before kernel code begins using interrupt-state primitives.
KSTATUS KeBindBootstrapHal(void) {
const HAL_API* hal = HalGetApi();
if (!hal || hal->Size != sizeof(HAL_API) || hal->Version != HAL_API_VERSION ||
!hal->Processor.DisableInterrupts || !hal->Processor.RestoreInterrupts ||
!hal->Processor.EnableInterrupts || !hal->Processor.WaitForInterrupt ||
!hal->Processor.Halt || !hal->Processor.Stop) {
return NTLSTATUS_INVALID_PARAMETER;
}
gHal = hal;
return NTLSTATUS_SUCCESS;
}
// Returns whether the initialized table contains every mandatory kernel platform service.
static int KiHalRuntimeTableValid(const HAL_API* hal) {
return hal && hal->Size == sizeof(HAL_API) && hal->Version == HAL_API_VERSION &&
hal->Processor.DisableInterrupts && hal->Processor.RestoreInterrupts &&
hal->Processor.EnableInterrupts && hal->Processor.WaitForInterrupt &&
hal->Processor.Halt && hal->Processor.Stop && hal->Processor.QueryTopology &&
hal->Processor.Start &&
hal->Screen.GetFramebuffer &&
hal->Output.RegisterEndpoint && hal->Output.Write && hal->Output.QueryStats &&
hal->Timer.QueryTicks && hal->Timer.QueryTickFrequency && hal->Timer.Stall &&
hal->Timer.HandleInterrupt && hal->Timer.ConfigureCurrentProcessor &&
hal->Rtc.QueryTime && hal->Pci.Read32 && hal->Pci.Write32 &&
hal->InterruptController.RouteIrq && hal->InterruptController.MaskIrq &&
hal->InterruptController.UnmaskIrq && hal->InterruptController.SendEoi &&
hal->InterruptController.InitializeCurrentProcessor &&
hal->InterruptController.SendProcessorInterrupt &&
hal->InterruptController.SendProcessorNmi && hal->InterruptController.BroadcastNmi &&
hal->InterruptController.ComposeMsi && hal->Power.Shutdown;
}
// Initializes the HAL and validates every mandatory runtime service consumed by the kernel.
KSTATUS KeInitializeHal(const HAL_INIT_PARAMS* params) {
if (!gHal || !params)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = HalInitialize(params);
if (!KSUCCESS(status))
return status;
const HAL_API* hal = HalGetApi();
if (hal != gHal || !KiHalRuntimeTableValid(hal))
return NTLSTATUS_INVALID_PARAMETER;
return NTLSTATUS_SUCCESS;
}
// Returns the bound HAL table.
const HAL_API* KeGetHal(void) {
return gHal;
}
// Masks interrupts and returns the previous processor flags.
__declspec(dllexport) uintptr_t KeDisableInterrupts(void) {
return gHal->Processor.DisableInterrupts();
}
// Restores a previously saved processor interrupt state.
__declspec(dllexport) void KeRestoreInterrupts(uintptr_t flags) {
gHal->Processor.RestoreInterrupts(flags);
}
// Enables maskable interrupts on the current processor.
void KeEnableInterrupts(void) {
gHal->Processor.EnableInterrupts();
}
// Enables interrupts and waits for the next interrupt on the current processor.
void KeWaitForInterrupt(void) {
gHal->Processor.WaitForInterrupt();
}
// Halts the current processor once without changing interrupt state.
void KeHaltProcessor(void) {
gHal->Processor.Halt();
}
// Permanently stops the current processor.
_Noreturn void KeStopProcessor(void) {
gHal->Processor.Stop();
for (;;) {
}
}
// Queries platform processor topology without exposing interrupt-controller details.
KSTATUS KeQueryProcessorTopology(HAL_PROCESSOR_TOPOLOGY* topologyOut) {
return gHal->Processor.QueryTopology(topologyOut);
}
// Starts one processor at a physical bootstrap entry address.
KSTATUS KeStartProcessor(uint32_t hardwareId, uintptr_t startPhysical) {
return gHal->Processor.Start(hardwareId, startPhysical);
}
// Initializes the current processor's local interrupt-controller state.
void KeInitializeCurrentProcessorInterruptController(void) {
gHal->InterruptController.InitializeCurrentProcessor();
}
// Sends one fixed-vector interrupt to a processor hardware ID.
void KeSendProcessorInterrupt(uint32_t hardwareId, uint8_t vector) {
gHal->InterruptController.SendProcessorInterrupt(hardwareId, vector);
}
// Sends one non-maskable interrupt to a processor hardware ID.
void KeSendProcessorNmi(uint32_t hardwareId) {
gHal->InterruptController.SendProcessorNmi(hardwareId);
}
// Sends a non-maskable interrupt to every other processor.
void KeBroadcastProcessorNmi(void) {
gHal->InterruptController.BroadcastNmi();
}
// Arms the calibrated periodic timer on the current processor.
void KeConfigureCurrentProcessorTimer(void) {
gHal->Timer.ConfigureCurrentProcessor();
}
+753
View File
@@ -0,0 +1,753 @@
// Kernel capability handles. This file owns process-local handle identity, grant delegation, and revocation.
#include <string.h>
#include <intrin.h>
#include <kernel/ke_handle.h>
#include <kernel/ke_process.h>
#include <kernel/ke_named_queue.h>
#include <kernel/ke_debug.h>
#include <kernel/io_file.h>
#include <kernel/ke_audio.h>
#include <kernel/mm_pool.h>
#include <ntlite/syscall.h>
#include <ntlite/security.h>
#include <ntlite/port.h>
typedef struct CAPABILITY_GRANT {
volatile long RefCount;
volatile long Revoked;
uint32_t Depth;
SYS_RIGHTS MaximumAccess;
OBJECT* Target;
struct CAPABILITY_GRANT* Parent;
} CAPABILITY_GRANT;
typedef struct KE_HANDLE_ENTRY {
struct KE_HANDLE_ENTRY* Next;
SYS_HANDLE Handle;
SYS_RIGHTS Access;
CAPABILITY_GRANT* Grant;
} KE_HANDLE_ENTRY;
typedef struct KE_CAPTURED_HANDLE {
CAPABILITY_GRANT* Grant;
SYS_RIGHTS Access;
} KE_CAPTURED_HANDLE;
_Static_assert((KE_HANDLE_BUCKET_COUNT & (KE_HANDLE_BUCKET_COUNT - 1u)) == 0,
"handle bucket count must be a power of two");
// Returns the process-local hash bucket for one opaque handle value.
static uint32_t KiHandleBucket(SYS_HANDLE handle) {
return (handle ^ (handle >> 16) ^ (handle >> 8)) & (KE_HANDLE_BUCKET_COUNT - 1u);
}
// Returns one entry while the handle-table lock is held.
static KE_HANDLE_ENTRY* KiFindHandleLocked(KE_HANDLE_TABLE* table, SYS_HANDLE handle, KE_HANDLE_ENTRY*** linkOut) {
if (linkOut) *linkOut = (KE_HANDLE_ENTRY**)0;
if (!table || handle == SYS_HANDLE_INVALID) return (KE_HANDLE_ENTRY*)0;
uint32_t bucket = KiHandleBucket(handle);
KE_HANDLE_ENTRY** link = &table->Buckets[bucket];
while (*link) {
if ((*link)->Handle == handle) {
if (linkOut) *linkOut = link;
return *link;
}
link = &(*link)->Next;
}
return (KE_HANDLE_ENTRY*)0;
}
// Acquires one grant reference while its owner still stabilizes the pointer.
static void KiReferenceGrant(CAPABILITY_GRANT* grant) {
if (!grant) return;
long refs = _InterlockedIncrement(&grant->RefCount);
if (refs <= 1) {
KePanic(KE_PANIC_CODE_GENERIC,
"capability: invalid grant reference grant=0x%016llX refs=%ld\n",
(uint64_t)(uintptr_t)grant,
refs);
}
}
// Releases one grant and recursively drops its immutable parent chain.
static void KiDereferenceGrant(CAPABILITY_GRANT* grant) {
while (grant) {
long refs = _InterlockedDecrement(&grant->RefCount);
if (refs > 0) return;
if (refs < 0) {
KePanic(KE_PANIC_CODE_GENERIC,
"capability: grant reference underflow grant=0x%016llX refs=%ld\n",
(uint64_t)(uintptr_t)grant,
refs);
}
CAPABILITY_GRANT* parent = grant->Parent;
if (!parent && grant->Target)
ObDereferenceObject(grant->Target);
MmFreePool(grant);
grant = parent;
}
}
// Returns whether a grant and every ancestor remain unrevoked.
static int KiGrantIsActive(const CAPABILITY_GRANT* grant) {
const CAPABILITY_GRANT* current = grant;
uint32_t depth = 0;
while (current) {
if (_InterlockedCompareExchange((volatile long*)&current->Revoked, 0, 0) != 0)
return 0;
current = current->Parent;
if (++depth > KE_CAPABILITY_MAX_DEPTH)
return 0;
}
return 1;
}
// Creates a root grant and takes the target object's lifetime reference.
static KSTATUS KiCreateRootGrant(OBJECT* object, SYS_RIGHTS maximumAccess, CAPABILITY_GRANT** grantOut) {
if (!object || !grantOut || maximumAccess == 0) return NTLSTATUS_INVALID_PARAMETER;
*grantOut = (CAPABILITY_GRANT*)0;
CAPABILITY_GRANT* grant = (CAPABILITY_GRANT*)MmAllocatePoolTag(sizeof(CAPABILITY_GRANT),
MM_POOL_TAG('c', 'a', 'p', 'g'));
if (!grant) return NTLSTATUS_NO_MEMORY;
memset(grant, 0, sizeof(*grant));
if (!ObTryReferenceObject(object)) {
MmFreePool(grant);
return NTLSTATUS_OBJECT_DEAD;
}
grant->RefCount = 1;
grant->Depth = 0;
grant->MaximumAccess = maximumAccess;
grant->Target = object;
grant->Parent = (CAPABILITY_GRANT*)0;
*grantOut = grant;
return NTLSTATUS_SUCCESS;
}
// Creates one attenuated child grant and retains its parent.
static KSTATUS KiCreateChildGrant(CAPABILITY_GRANT* parent,
SYS_RIGHTS maximumAccess,
CAPABILITY_GRANT** grantOut) {
if (!parent || !grantOut || maximumAccess == 0) return NTLSTATUS_INVALID_PARAMETER;
*grantOut = (CAPABILITY_GRANT*)0;
if (parent->Depth + 1u >= KE_CAPABILITY_MAX_DEPTH)
return NTLSTATUS_LIMIT_REACHED;
if ((maximumAccess & ~parent->MaximumAccess) != 0)
return NTLSTATUS_ACCESS_DENIED;
if (!KiGrantIsActive(parent))
return NTLSTATUS_CAPABILITY_REVOKED;
CAPABILITY_GRANT* grant = (CAPABILITY_GRANT*)MmAllocatePoolTag(sizeof(CAPABILITY_GRANT),
MM_POOL_TAG('c', 'a', 'p', 'g'));
if (!grant) return NTLSTATUS_NO_MEMORY;
memset(grant, 0, sizeof(*grant));
KiReferenceGrant(parent);
grant->RefCount = 1;
grant->Depth = parent->Depth + 1u;
grant->MaximumAccess = maximumAccess;
grant->Target = parent->Target;
grant->Parent = parent;
if (!KiGrantIsActive(parent)) {
KiDereferenceGrant(grant);
return NTLSTATUS_CAPABILITY_REVOKED;
}
*grantOut = grant;
return NTLSTATUS_SUCCESS;
}
// Records type-specific ownership after a process handle becomes visible.
static void KiHandleOpened(OBJECT* object) {
if (object && object->Type == ObjectTypeAudioStream)
KeAudioStreamHandleOpened((KE_AUDIO_STREAM*)object);
}
// Releases type-specific ownership before a process handle disappears.
static void KiHandleClosed(OBJECT* object) {
if (object && object->Type == ObjectTypeAudioStream)
KeAudioStreamHandleClosed((KE_AUDIO_STREAM*)object);
}
// Inserts one retained grant into a process and consumes that grant reference on success.
static KSTATUS KiInsertGrantHandle(PROCESS* process,
CAPABILITY_GRANT* grant,
SYS_RIGHTS access,
SYS_HANDLE* handleOut) {
if (!process || !grant || !handleOut || access == 0) return NTLSTATUS_INVALID_PARAMETER;
if ((access & ~grant->MaximumAccess) != 0) return NTLSTATUS_ACCESS_DENIED;
if (!KiGrantIsActive(grant)) return NTLSTATUS_CAPABILITY_REVOKED;
KE_HANDLE_ENTRY* entry = (KE_HANDLE_ENTRY*)MmAllocatePoolTag(sizeof(KE_HANDLE_ENTRY),
MM_POOL_TAG('h', 'n', 'd', 'l'));
if (!entry) return NTLSTATUS_NO_MEMORY;
memset(entry, 0, sizeof(*entry));
KE_HANDLE_TABLE* table = &process->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
if (table->Rundown) {
KeReleaseSpinlock(&table->Lock, flags);
MmFreePool(entry);
return NTLSTATUS_OBJECT_DEAD;
}
SYS_HANDLE handle = table->NextHandle;
if (handle == SYS_HANDLE_INVALID) {
KeReleaseSpinlock(&table->Lock, flags);
MmFreePool(entry);
return NTLSTATUS_ID_EXHAUSTED;
}
table->NextHandle = handle + 1u;
entry->Handle = handle;
entry->Access = access;
entry->Grant = grant;
uint32_t bucket = KiHandleBucket(handle);
entry->Next = table->Buckets[bucket];
table->Buckets[bucket] = entry;
table->Count++;
KiHandleOpened(grant->Target);
KeReleaseSpinlock(&table->Lock, flags);
*handleOut = handle;
return NTLSTATUS_SUCCESS;
}
// Initializes an empty process-local handle namespace.
void KeInitializeHandleTable(KE_HANDLE_TABLE* table) {
if (!table) return;
memset(table, 0, sizeof(*table));
table->NextHandle = 1u;
table->Lock = (SPINLOCK)SPINLOCK_INIT;
}
// Returns the object-type-specific rights mask accepted by the handle core.
SYS_RIGHTS KeGetValidHandleRights(OBJECT_TYPE type) {
const SYS_RIGHTS management = SYS_RIGHT_MANAGE;
switch (type) {
case ObjectTypeProcess:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT |
SYS_PROCESS_RIGHT_TERMINATE | SYS_PROCESS_RIGHT_INSERT_HANDLE |
SYS_PROCESS_RIGHT_BUILD;
case ObjectTypeThread:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_THREAD_RIGHT_RESUME;
case ObjectTypeMutex:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_MUTEX_RIGHT_RELEASE;
case ObjectTypeSemaphore:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_SEMAPHORE_RIGHT_SIGNAL;
case ObjectTypeEvent:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_EVENT_RIGHT_SIGNAL;
case ObjectTypeQueue:
return management | SYS_RIGHT_QUERY | SYS_QUEUE_RIGHT_SEND |
SYS_QUEUE_RIGHT_RECEIVE | SYS_QUEUE_RIGHT_PUBLISH;
case ObjectTypeFile:
return management | SYS_RIGHT_QUERY | SYS_FILE_RIGHT_READ_DATA |
SYS_FILE_RIGHT_WRITE_DATA | SYS_FILE_RIGHT_LOCK |
SYS_FILE_RIGHT_MAP_READ | SYS_FILE_RIGHT_MAP_WRITE;
case ObjectTypeDir:
return management | SYS_RIGHT_QUERY | SYS_DIR_RIGHT_ENUMERATE | SYS_DIR_RIGHT_OPEN |
SYS_DIR_RIGHT_MODIFY;
case ObjectTypeAsyncIo:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_ASYNC_IO_RIGHT_CANCEL;
case ObjectTypeSocket:
return management | SYS_RIGHT_QUERY | SYS_SOCKET_RIGHT_BIND |
SYS_SOCKET_RIGHT_CONNECT | SYS_SOCKET_RIGHT_LISTEN |
SYS_SOCKET_RIGHT_ACCEPT | SYS_SOCKET_RIGHT_SEND |
SYS_SOCKET_RIGHT_RECEIVE | SYS_SOCKET_RIGHT_SHUTDOWN |
SYS_SOCKET_RIGHT_POLL | SYS_SOCKET_RIGHT_SET_OPTION;
case ObjectTypeFileMapping:
return management | SYS_RIGHT_QUERY | SYS_FILE_MAPPING_RIGHT_MAP;
case ObjectTypeSurface:
return management | SYS_RIGHT_QUERY | SYS_SURFACE_RIGHT_MAP | SYS_SURFACE_RIGHT_PRESENT;
case ObjectTypeInputQueue:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_INPUT_QUEUE_RIGHT_READ;
case ObjectTypeTimer:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_TIMER_RIGHT_SET;
case ObjectTypeDisplayClock:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT;
case ObjectTypeAudioStream:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT |
SYS_AUDIO_STREAM_RIGHT_WRITE | SYS_AUDIO_STREAM_RIGHT_CONTROL;
case ObjectTypeSecurityContext:
return management | SYS_RIGHT_QUERY |
SYS_SECURITY_CONTEXT_RIGHT_DERIVE | SYS_SECURITY_CONTEXT_RIGHT_ASSIGN;
case ObjectTypePort:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT |
SYS_PORT_RIGHT_CONNECT | SYS_PORT_RIGHT_ACCEPT |
SYS_PORT_RIGHT_SEND | SYS_PORT_RIGHT_RECEIVE |
SYS_PORT_RIGHT_CALL | SYS_PORT_RIGHT_REPLY |
SYS_PORT_RIGHT_DISCONNECT;
case ObjectTypeVolume:
return management | SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY;
case ObjectTypeVolumeManager:
return management | SYS_RIGHT_QUERY |
SYS_VOLUME_MANAGER_RIGHT_ENUMERATE | SYS_VOLUME_MANAGER_RIGHT_OPEN |
SYS_VOLUME_MANAGER_RIGHT_WAIT;
case ObjectTypeDevice:
case ObjectTypeDriver:
case ObjectTypeUsbDevice:
case ObjectTypeUsbPipe:
case ObjectTypeImageSection:
case ObjectTypeAudioEndpoint:
return management | SYS_RIGHT_QUERY;
default:
return 0;
}
}
// Creates one root capability handle for an object.
KSTATUS KeCreateHandle(PROCESS* process, OBJECT* object, SYS_RIGHTS access, SYS_HANDLE* handleOut) {
if (!process || !object || !handleOut || access == 0) return NTLSTATUS_INVALID_PARAMETER;
SYS_RIGHTS valid = KeGetValidHandleRights(object->Type);
if (!valid || (access & ~valid) != 0) return NTLSTATUS_INVALID_PARAMETER;
CAPABILITY_GRANT* grant = (CAPABILITY_GRANT*)0;
KSTATUS status = KiCreateRootGrant(object, access, &grant);
if (!KSUCCESS(status)) return status;
status = KiInsertGrantHandle(process, grant, access, handleOut);
if (!KSUCCESS(status)) KiDereferenceGrant(grant);
return status;
}
// References the target object after validating type, revocation, and rights.
KSTATUS KeReferenceHandle(PROCESS* process,
SYS_HANDLE handle,
OBJECT_TYPE expectedType,
SYS_RIGHTS requiredRights,
OBJECT** objectOut) {
if (!process || !objectOut) return NTLSTATUS_INVALID_PARAMETER;
*objectOut = (OBJECT*)0;
if (handle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE;
KE_HANDLE_TABLE* table = &process->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, handle, (KE_HANDLE_ENTRY***)0);
if (!entry) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_HANDLE;
}
if (requiredRights && (entry->Access & requiredRights) != requiredRights) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_ACCESS_DENIED;
}
if (!KiGrantIsActive(entry->Grant)) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_CAPABILITY_REVOKED;
}
OBJECT* object = entry->Grant->Target;
if (!object || (expectedType != 0 && object->Type != expectedType)) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_HANDLE;
}
if (!ObTryReferenceObject(object)) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_OBJECT_DEAD;
}
KeReleaseSpinlock(&table->Lock, flags);
*objectOut = object;
return NTLSTATUS_SUCCESS;
}
// Closes one process-local handle without reusing its numeric identity.
KSTATUS KeCloseHandle(PROCESS* process, SYS_HANDLE handle) {
if (!process || handle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE;
KE_HANDLE_TABLE* table = &process->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
KE_HANDLE_ENTRY** link = (KE_HANDLE_ENTRY**)0;
KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, handle, &link);
if (!entry || !link) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_HANDLE;
}
*link = entry->Next;
if (table->Count == 0) {
KeReleaseSpinlock(&table->Lock, flags);
KePanic(KE_PANIC_CODE_GENERIC, "capability: handle count underflow pid=%u\n", process->ProcessId);
}
table->Count--;
KeReleaseSpinlock(&table->Lock, flags);
KSTATUS closeStatus = NTLSTATUS_SUCCESS;
OBJECT* object = entry->Grant->Target;
if (object && object->Type == ObjectTypeFile)
closeStatus = IoFlushFile((FILE_OBJECT*)object);
KeNamedQueueUnpublishHandle(process, handle, object);
KiHandleClosed(object);
KiDereferenceGrant(entry->Grant);
MmFreePool(entry);
return closeStatus;
}
// Duplicates one alias of the same capability grant into another process.
KSTATUS KeDuplicateHandle(PROCESS* sourceProcess,
SYS_HANDLE sourceHandle,
PROCESS* targetProcess,
SYS_RIGHTS desiredRights,
uint32_t duplicateFlags,
SYS_HANDLE* targetHandleOut) {
if (!sourceProcess || !targetProcess || !targetHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
if (sourceHandle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE;
const uint32_t validFlags = SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS;
if ((duplicateFlags & ~validFlags) != 0) return NTLSTATUS_INVALID_PARAMETER;
if ((duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) && desiredRights != 0)
return NTLSTATUS_INVALID_PARAMETER;
if (!(duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) && desiredRights == 0)
return NTLSTATUS_INVALID_PARAMETER;
CAPABILITY_GRANT* grant = (CAPABILITY_GRANT*)0;
SYS_RIGHTS sourceAccess = 0;
KE_HANDLE_TABLE* table = &sourceProcess->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, sourceHandle, (KE_HANDLE_ENTRY***)0);
if (!entry) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_HANDLE;
}
if (!KiGrantIsActive(entry->Grant)) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_CAPABILITY_REVOKED;
}
SYS_RIGHTS requiredManagement = SYS_RIGHT_DUPLICATE;
if (sourceProcess != targetProcess)
requiredManagement |= SYS_RIGHT_TRANSFER;
if ((entry->Access & requiredManagement) != requiredManagement) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_ACCESS_DENIED;
}
sourceAccess = entry->Access;
SYS_RIGHTS validRights = KeGetValidHandleRights(entry->Grant->Target->Type);
if (!(duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) &&
(desiredRights & ~validRights) != 0) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_PARAMETER;
}
if (!(duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) &&
(desiredRights & ~sourceAccess) != 0) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_ACCESS_DENIED;
}
grant = entry->Grant;
KiReferenceGrant(grant);
KeReleaseSpinlock(&table->Lock, flags);
SYS_RIGHTS targetAccess = (duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS)
? sourceAccess : desiredRights;
KSTATUS status = KiInsertGrantHandle(targetProcess, grant, targetAccess, targetHandleOut);
if (!KSUCCESS(status)) KiDereferenceGrant(grant);
return status;
}
// Derives one child grant whose maximum rights are an attenuated subset.
KSTATUS KeDeriveHandle(PROCESS* process,
SYS_HANDLE sourceHandle,
SYS_RIGHTS desiredRights,
SYS_HANDLE* derivedHandleOut) {
if (!process || !derivedHandleOut || desiredRights == 0)
return NTLSTATUS_INVALID_PARAMETER;
if (sourceHandle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE;
CAPABILITY_GRANT* parent = (CAPABILITY_GRANT*)0;
KE_HANDLE_TABLE* table = &process->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, sourceHandle, (KE_HANDLE_ENTRY***)0);
if (!entry) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_HANDLE;
}
if (!KiGrantIsActive(entry->Grant)) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_CAPABILITY_REVOKED;
}
if ((entry->Access & SYS_RIGHT_DERIVE) == 0) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_ACCESS_DENIED;
}
SYS_RIGHTS validRights = KeGetValidHandleRights(entry->Grant->Target->Type);
if ((desiredRights & ~validRights) != 0) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_PARAMETER;
}
if ((desiredRights & ~entry->Access) != 0) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_ACCESS_DENIED;
}
parent = entry->Grant;
KiReferenceGrant(parent);
KeReleaseSpinlock(&table->Lock, flags);
CAPABILITY_GRANT* child = (CAPABILITY_GRANT*)0;
KSTATUS status = KiCreateChildGrant(parent, desiredRights, &child);
KiDereferenceGrant(parent);
if (!KSUCCESS(status)) return status;
status = KiInsertGrantHandle(process, child, desiredRights, derivedHandleOut);
if (!KSUCCESS(status)) KiDereferenceGrant(child);
return status;
}
// Revokes one grant and all grants derived from it.
KSTATUS KeRevokeHandle(PROCESS* process, SYS_HANDLE handle) {
if (!process) return NTLSTATUS_INVALID_PARAMETER;
if (handle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE;
KE_HANDLE_TABLE* table = &process->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, handle, (KE_HANDLE_ENTRY***)0);
if (!entry) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_HANDLE;
}
if ((entry->Access & SYS_RIGHT_REVOKE) == 0) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_ACCESS_DENIED;
}
if (!KiGrantIsActive(entry->Grant)) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_CAPABILITY_REVOKED;
}
_InterlockedExchange(&entry->Grant->Revoked, 1);
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_SUCCESS;
}
// Captures one capability so a later Port delivery does not depend on the source handle staying open.
KSTATUS KeCaptureHandleForTransfer(PROCESS* sourceProcess,
SYS_HANDLE sourceHandle,
SYS_RIGHTS desiredRights,
uint32_t flags,
KE_CAPTURED_HANDLE** capturedOut) {
if (!sourceProcess || !capturedOut)
return NTLSTATUS_INVALID_PARAMETER;
*capturedOut = (KE_CAPTURED_HANDLE*)0;
if (sourceHandle == SYS_HANDLE_INVALID)
return NTLSTATUS_INVALID_HANDLE;
const uint32_t validFlags = KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS |
KE_HANDLE_TRANSFER_FLAG_DERIVE;
if ((flags & ~validFlags) != 0)
return NTLSTATUS_INVALID_PARAMETER;
if ((flags & KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS) &&
(flags & KE_HANDLE_TRANSFER_FLAG_DERIVE))
return NTLSTATUS_INVALID_PARAMETER;
if ((flags & KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS) && desiredRights != 0)
return NTLSTATUS_INVALID_PARAMETER;
if (!(flags & KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS) && desiredRights == 0)
return NTLSTATUS_INVALID_PARAMETER;
CAPABILITY_GRANT* sourceGrant = (CAPABILITY_GRANT*)0;
SYS_RIGHTS sourceAccess = 0;
KE_HANDLE_TABLE* table = &sourceProcess->HandleTable;
uintptr_t lockFlags = KeAcquireSpinlock(&table->Lock);
KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, sourceHandle, (KE_HANDLE_ENTRY***)0);
if (!entry) {
KeReleaseSpinlock(&table->Lock, lockFlags);
return NTLSTATUS_INVALID_HANDLE;
}
if (!KiGrantIsActive(entry->Grant)) {
KeReleaseSpinlock(&table->Lock, lockFlags);
return NTLSTATUS_CAPABILITY_REVOKED;
}
SYS_RIGHTS required = SYS_RIGHT_TRANSFER;
if (flags & KE_HANDLE_TRANSFER_FLAG_DERIVE)
required |= SYS_RIGHT_DERIVE;
if ((entry->Access & required) != required) {
KeReleaseSpinlock(&table->Lock, lockFlags);
return NTLSTATUS_ACCESS_DENIED;
}
sourceAccess = entry->Access;
SYS_RIGHTS targetAccess = (flags & KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS)
? sourceAccess : desiredRights;
SYS_RIGHTS validRights = KeGetValidHandleRights(entry->Grant->Target->Type);
if ((targetAccess & ~validRights) != 0) {
KeReleaseSpinlock(&table->Lock, lockFlags);
return NTLSTATUS_INVALID_PARAMETER;
}
if ((targetAccess & ~sourceAccess) != 0) {
KeReleaseSpinlock(&table->Lock, lockFlags);
return NTLSTATUS_ACCESS_DENIED;
}
sourceGrant = entry->Grant;
KiReferenceGrant(sourceGrant);
KeReleaseSpinlock(&table->Lock, lockFlags);
CAPABILITY_GRANT* capturedGrant = sourceGrant;
if (flags & KE_HANDLE_TRANSFER_FLAG_DERIVE) {
KSTATUS status = KiCreateChildGrant(sourceGrant, targetAccess, &capturedGrant);
KiDereferenceGrant(sourceGrant);
if (!KSUCCESS(status))
return status;
}
KE_CAPTURED_HANDLE* captured = (KE_CAPTURED_HANDLE*)MmAllocatePoolTag(sizeof(*captured),
MM_POOL_TAG('c', 'a', 'p', 't'));
if (!captured) {
KiDereferenceGrant(capturedGrant);
return NTLSTATUS_NO_MEMORY;
}
captured->Grant = capturedGrant;
captured->Access = targetAccess;
*capturedOut = captured;
return NTLSTATUS_SUCCESS;
}
// Releases one captured capability that did not become a target-process handle.
void KeReleaseCapturedHandle(KE_CAPTURED_HANDLE* captured) {
if (!captured)
return;
if (captured->Grant)
KiDereferenceGrant(captured->Grant);
MmFreePool(captured);
}
// Materializes a complete transfer set atomically with respect to target-process handle visibility.
KSTATUS KeMaterializeCapturedHandles(PROCESS* targetProcess,
KE_CAPTURED_HANDLE** captured,
uint32_t count,
SYS_HANDLE* handlesOut) {
if (!targetProcess || (count && (!captured || !handlesOut)))
return NTLSTATUS_INVALID_PARAMETER;
if (count == 0)
return NTLSTATUS_SUCCESS;
KE_HANDLE_ENTRY** entries = (KE_HANDLE_ENTRY**)MmAllocatePoolTag(sizeof(KE_HANDLE_ENTRY*) * count,
MM_POOL_TAG('h', 'b', 'a', 't'));
if (!entries)
return NTLSTATUS_NO_MEMORY;
memset(entries, 0, sizeof(KE_HANDLE_ENTRY*) * count);
KSTATUS status = NTLSTATUS_SUCCESS;
for (uint32_t i = 0; i < count; ++i) {
if (!captured[i] || !captured[i]->Grant || captured[i]->Access == 0) {
status = NTLSTATUS_INVALID_PARAMETER;
break;
}
if (!KiGrantIsActive(captured[i]->Grant)) {
status = NTLSTATUS_CAPABILITY_REVOKED;
break;
}
entries[i] = (KE_HANDLE_ENTRY*)MmAllocatePoolTag(sizeof(KE_HANDLE_ENTRY),
MM_POOL_TAG('h', 'n', 'd', 'l'));
if (!entries[i]) {
status = NTLSTATUS_NO_MEMORY;
break;
}
memset(entries[i], 0, sizeof(*entries[i]));
}
if (!KSUCCESS(status)) {
for (uint32_t i = 0; i < count; ++i)
if (entries[i]) MmFreePool(entries[i]);
MmFreePool(entries);
return status;
}
KE_HANDLE_TABLE* table = &targetProcess->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
if (table->Rundown) {
status = NTLSTATUS_OBJECT_DEAD;
} else if (table->NextHandle == SYS_HANDLE_INVALID ||
(uint64_t)table->NextHandle + (uint64_t)count - 1ull > UINT32_MAX) {
status = NTLSTATUS_ID_EXHAUSTED;
} else {
for (uint32_t i = 0; i < count; ++i) {
if (!KiGrantIsActive(captured[i]->Grant)) {
status = NTLSTATUS_CAPABILITY_REVOKED;
break;
}
}
}
if (KSUCCESS(status)) {
for (uint32_t i = 0; i < count; ++i) {
SYS_HANDLE handle = table->NextHandle++;
entries[i]->Handle = handle;
entries[i]->Access = captured[i]->Access;
entries[i]->Grant = captured[i]->Grant;
uint32_t bucket = KiHandleBucket(handle);
entries[i]->Next = table->Buckets[bucket];
table->Buckets[bucket] = entries[i];
table->Count++;
KiHandleOpened(captured[i]->Grant->Target);
handlesOut[i] = handle;
captured[i]->Grant = (CAPABILITY_GRANT*)0;
}
}
KeReleaseSpinlock(&table->Lock, flags);
if (!KSUCCESS(status)) {
for (uint32_t i = 0; i < count; ++i)
MmFreePool(entries[i]);
MmFreePool(entries);
return status;
}
for (uint32_t i = 0; i < count; ++i) {
MmFreePool(captured[i]);
captured[i] = (KE_CAPTURED_HANDLE*)0;
}
MmFreePool(entries);
return NTLSTATUS_SUCCESS;
}
// Returns the number of currently live handles in one process.
uint32_t KeGetHandleCount(PROCESS* process) {
if (!process) return 0;
KE_HANDLE_TABLE* table = &process->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
uint32_t count = table->Count;
KeReleaseSpinlock(&table->Lock, flags);
return count;
}
// Releases every handle and capability grant owned by one process.
void KeRundownHandleTable(PROCESS* process) {
if (!process) return;
KE_HANDLE_TABLE* table = &process->HandleTable;
uintptr_t rundownFlags = KeAcquireSpinlock(&table->Lock);
table->Rundown = 1u;
KeReleaseSpinlock(&table->Lock, rundownFlags);
for (uint32_t bucket = 0; bucket < KE_HANDLE_BUCKET_COUNT; ++bucket) {
while (true) {
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
KE_HANDLE_ENTRY* entry = table->Buckets[bucket];
if (entry) {
table->Buckets[bucket] = entry->Next;
if (table->Count == 0) {
KeReleaseSpinlock(&table->Lock, flags);
KePanic(KE_PANIC_CODE_GENERIC,
"capability: rundown count underflow pid=%u\n",
process->ProcessId);
}
table->Count--;
}
KeReleaseSpinlock(&table->Lock, flags);
if (!entry) break;
OBJECT* object = entry->Grant->Target;
KeNamedQueueUnpublishHandle(process, entry->Handle, object);
KiHandleClosed(object);
KiDereferenceGrant(entry->Grant);
MmFreePool(entry);
}
}
}
+22
View File
@@ -0,0 +1,22 @@
// Kernel monotonic identifier allocator. Public identities stay 32-bit while the internal cursor detects exhaustion without wrapping.
#include <intrin.h>
#include <kernel/ke_id.h>
// Allocates one 32-bit identifier and permanently advances the allocator.
KSTATUS KeAllocateId32(KE_ID_ALLOCATOR* allocator, uint32_t* idOut) {
if (!allocator || !idOut)
return NTLSTATUS_INVALID_PARAMETER;
while (1) {
long long next = _InterlockedCompareExchange64(&allocator->Next, 0, 0);
if ((unsigned long long)next > 0xFFFFFFFFULL)
return NTLSTATUS_ID_EXHAUSTED;
long long desired = next + 1;
if (_InterlockedCompareExchange64(&allocator->Next, desired, next) == next) {
*idOut = (uint32_t)next;
return NTLSTATUS_SUCCESS;
}
}
}
+1 -1
View File
@@ -221,7 +221,7 @@ void KeInitializeIdt(void) {
KiIdtSetGate(0x40, (uintptr_t)KeSyscallEntry, KI_IDT_TYPE_USER_INTERRUPT_GATE, 0);
static void *irqStubs[NUM_IRQS] = {
static void* irqStubs[NUM_IRQS] = {
(void*)KeIrqStub0, (void*)KeIrqStub1, (void*)KeIrqStub2,
(void*)KeIrqStub3, (void*)KeIrqStub4, (void*)KeIrqStub5,
(void*)KeIrqStub6, (void*)KeIrqStub7, (void*)KeIrqStub8,
+5 -4
View File
@@ -7,6 +7,7 @@
#include <intrin.h>
#include <kernel/ke_input.h>
#include <kernel/ke_id.h>
#include <kernel/ke_debug.h>
#include <kernel/ke_display.h>
#include <kernel/ke_event.h>
@@ -19,7 +20,6 @@
#define MOUSE_QUEUE_SIZE 128u
#define MOUSE_QUEUE_MASK (MOUSE_QUEUE_SIZE - 1u)
#define MAX_INPUT_KEYS 256u
#define MAX_INPUT_DEVICES 16u
#define KEY_REPEAT_DELAY_US 500000ull
#define KEY_REPEAT_PERIOD_US 33000ull
@@ -30,7 +30,7 @@ typedef struct KEY_CHAR_PAIR {
} KEY_CHAR_PAIR;
static KE_INPUT_CONTEXT gDefaultInputContext;
static volatile uint32_t gNextInputDeviceId = 1;
static KE_ID_ALLOCATOR gInputDeviceIds = KE_ID_ALLOCATOR_INIT(1u);
static char KiInputKeyToChar(KE_INPUT_CONTEXT* context, INPUT_KEY key);
static void KiInputServiceRepeat(KE_INPUT_CONTEXT* context);
@@ -246,8 +246,9 @@ void KeInputInitialize(const HAL_API* hal) {
// Implements the input Register Device operation.
__declspec(dllexport) KSTATUS KeInputRegisterDevice(const char* name, uint32_t flags, uint32_t* outDeviceId) {
(void)flags;
uint32_t id = gNextInputDeviceId++;
if (id >= MAX_INPUT_DEVICES) return NTLSTATUS_NO_MEMORY;
uint32_t id = 0;
KSTATUS status = KeAllocateId32(&gInputDeviceIds, &id);
if (!KSUCCESS(status)) return status;
if (outDeviceId) *outDeviceId = id;
DbgPrint("Input: registered device %u (%s)\n", id, name ? name : "unnamed");
return NTLSTATUS_SUCCESS;
+12 -5
View File
@@ -8,7 +8,7 @@
#include <kernel/ke_dpc.h>
#include <kernel/ke_thread.h>
#include <kernel/ke_process.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
#include <kernel/ke_debug.h>
#define MAX_IRQS 24
@@ -23,7 +23,7 @@ typedef struct IRQ_HANDLER_LIST {
static IRQ_HANDLER_LIST gIrqHandlers[MAX_IRQS];
static bool gIrqSoftwareVector[MAX_IRQS];
static const HAL_API *gHal;
static const HAL_API* gHal;
// Implements the irq To Irql operation.
static KIRQL KiIrqToIrql(uint8_t irq) {
@@ -34,7 +34,9 @@ static KIRQL KiIrqToIrql(uint8_t irq) {
}
// Implements the irq Initialize operation.
void KeIrqInitialize(const HAL_API *hal) {
void KeIrqInitialize(const HAL_API* hal) {
if (!hal || !hal->InterruptController.RouteIrq || !hal->InterruptController.SendEoi)
KePanic(KE_PANIC_CODE_INIT, "IRQ: invalid interrupt-controller API during initialization\n");
gHal = hal;
}
@@ -59,6 +61,8 @@ __declspec(dllexport) KSTATUS KeIrqAllocateMsiVector(uint8_t* outIrq) {
// Implements the irq Register operation.
__declspec(dllexport) void KeIrqRegister(uint8_t irq, void (*handler)(void)) {
if (irq >= MAX_IRQS || !handler) return;
if (!gHal)
KePanic(KE_PANIC_CODE_INIT, "IRQ: registration before interrupt-manager initialization\n");
IRQ_HANDLER_LIST* list = &gIrqHandlers[irq];
for (uint8_t i = 0; i < list->Count; i++) {
@@ -73,11 +77,14 @@ __declspec(dllexport) void KeIrqRegister(uint8_t irq, void (*handler)(void)) {
list->Handlers[list->Count++] = handler;
if (!gIrqSoftwareVector[irq])
gHal->Intc.RouteIrq(irq);
gHal->InterruptController.RouteIrq(irq);
}
// Implements the irq Dispatch operation.
void KeIrqDispatch(uint8_t irq) {
if (!gHal)
KePanic(KE_PANIC_CODE_INIT, "IRQ: dispatch before interrupt-manager initialization, irq=%u\n", irq);
KIRQL targetIrql = KiIrqToIrql(irq);
KIRQL currentIrql = KeGetCurrentIrql();
@@ -104,7 +111,7 @@ void KeIrqDispatch(uint8_t irq) {
// only request rescheduling; KiExitInterrupt performs the switch after this
// function returns, so the EOI is guaranteed to be sent first. Keep the
// software level at DIRQL until after the controller has been acknowledged.
gHal->Intc.SendEoi();
gHal->InterruptController.SendEoi();
KeLowerIrql(oldIrql);
}
File diff suppressed because it is too large. Load diff
+138 -332
View File
@@ -1,6 +1,8 @@
// Kernel process model. This file owns address-space metadata, handle tables, and process lifetime transitions.
// Kernel process model. This file owns process lifetime, thread membership, and address-space metadata.
#include <string.h>
#include <kernel/ke_idt.h>
#include <kernel/ke_hal.h>
#include <kernel/ke_list.h>
#include <intrin.h>
#include <kernel/ke_process.h>
@@ -8,21 +10,19 @@
#include <kernel/ke_sched.h>
#include <kernel/ke_event.h>
#include <kernel/ke_debug.h>
#include <kernel/ke_system_root.h>
#include <kernel/ke_userenv.h>
#include <kernel/io_file.h>
#include <kernel/mm_virt.h>
#include <kernel/ob_object.h>
#include <kernel/ke_named_queue.h>
#include <kernel/ke_cpu.h>
#include <kernel/ke_audio.h>
#include <kernel/hal/hal_apic.h>
#include <kernel/ke_id.h>
#include <kernel/se_security.h>
#include <ntlite/syscall.h>
// Global process list.
static LIST_ENTRY gProcessList;
static SPINLOCK gProcessListLock;
static uint32_t gNextProcessId = 0;
static KE_ID_ALLOCATOR gProcessIds = KE_ID_ALLOCATOR_INIT(1u);
static PROCESS* gSystemProcess = (PROCESS*)0;
// Processes init.
@@ -64,7 +64,7 @@ KSTATUS KeCreateSystemProcess(PROCESS** out) {
}
PROCESS* proc = (PROCESS*)0;
KSTATUS s = KeCreateProcess("system", &proc);
KSTATUS s = KeCreateProcess("system", SeGetRootSecurityContext(), &proc);
if (!KSUCCESS(s)) {
*out = (PROCESS*)0;
return s;
@@ -76,17 +76,6 @@ KSTATUS KeCreateSystemProcess(PROCESS** out) {
return NTLSTATUS_SUCCESS;
}
// Implements the count Process Handles Locked operation.
static uint32_t KiCountProcessHandlesLocked(PROCESS* process) {
uint32_t count = 0;
if (!process) return 0;
for (uint32_t i = 1; i < HANDLE_TABLE_SIZE; i++) {
if (process->Handles[i].Object) count++;
}
return count;
}
// Copies truncated String.
static void KiCopyTruncatedString(char* dst, size_t dstSize, const char* src, size_t srcLen) {
if (!dst || dstSize == 0) return;
@@ -147,20 +136,17 @@ size_t KeCopyProcessInfo(struct SYS_PROCESS_INFO* infoOut, size_t maxCount) {
memset(info, 0, sizeof(*info));
info->Size = sizeof(*info);
info->ProcessId = p->ProcessId;
info->SecurityId = p->SecurityContext ? p->SecurityContext->SecurityId : 0u;
info->ImageBase = p->ImageBase;
info->ImageSize = p->ImageSize;
info->PebBase = p->UserPebBase;
uintptr_t modFlags = KeAcquireSpinlock(&p->ModuleLock);
info->ModuleCount = p->ModuleCount;
KeReleaseSpinlock(&p->ModuleLock, modFlags);
uintptr_t handleFlags = KeAcquireSpinlock(&p->HandleLock);
uintptr_t threadFlags = KeAcquireSpinlock(&p->ThreadLock);
uint32_t threadCount = p->ThreadCount;
uint32_t handleCount = KiCountProcessHandlesLocked(p);
KeReleaseSpinlock(&p->ThreadLock, threadFlags);
uint32_t handleCount = KeGetHandleCount(p);
int32_t exitCode = p->ExitCode;
long terminating = p->Terminating;
long exited = p->Exited;
KeReleaseSpinlock(&p->HandleLock, handleFlags);
info->ThreadCount = threadCount;
info->HandleCount = handleCount;
@@ -184,6 +170,9 @@ size_t KeCopyProcessInfo(struct SYS_PROCESS_INFO* infoOut, size_t maxCount) {
} else if (terminating) {
info->State = SYS_PROCESS_STATE_TERMINATING;
info->Flags |= SYS_PROCESS_FLAG_TERMINATING;
} else if (_InterlockedCompareExchange(&p->BuildState, 0, 0) != KE_PROCESS_BUILD_STARTED) {
info->State = SYS_PROCESS_STATE_CONSTRUCTING;
info->Flags |= SYS_PROCESS_FLAG_CONSTRUCTING;
} else {
info->State = SYS_PROCESS_STATE_RUNNING;
}
@@ -200,11 +189,6 @@ size_t KeCopyProcessInfo(struct SYS_PROCESS_INFO* infoOut, size_t maxCount) {
KiCopyTruncatedString(info->Name, sizeof(info->Name), p->Name, sizeof(p->Name));
uintptr_t cwdFlags = KeAcquireSpinlock(&p->CwdLock);
info->CurrentDrive = p->CurrentDrive;
KiCopyTruncatedString(info->CurrentDirectory, sizeof(info->CurrentDirectory),
p->CurrentDirectory, p->CurrentDirectoryLength);
KeReleaseSpinlock(&p->CwdLock, cwdFlags);
}
count++;
}
@@ -260,7 +244,7 @@ KSTATUS KeCopyProcessThreadInfo(uint32_t pid, struct SYS_THREAD_INFO* infoOut, s
THREAD* current = KeGetCurrentThread();
size_t count = 0;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
uintptr_t flags = KeAcquireSpinlock(&process->ThreadLock);
LIST_ENTRY* head = &process->ThreadList;
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) {
THREAD* thread = CONTAINING_RECORD(node, THREAD, ProcessLink);
@@ -300,55 +284,13 @@ KSTATUS KeCopyProcessThreadInfo(uint32_t pid, struct SYS_THREAD_INFO* infoOut, s
}
count++;
}
KeReleaseSpinlock(&process->HandleLock, flags);
KeReleaseSpinlock(&process->ThreadLock, flags);
*countOut = count;
ObDereferenceObject(&process->Header);
return NTLSTATUS_SUCCESS;
}
// Records type-specific ownership after a handle slot becomes visible.
static void KiProcessHandleOpened(OBJECT* object) {
if (!object)
return;
if (object->Type == ObjectTypeAudioStream)
KeAudioStreamHandleOpened((KE_AUDIO_STREAM*)object);
}
// Releases type-specific ownership before the handle-table reference drops.
static void KiProcessReleaseHandleObject(OBJECT* object) {
if (!object)
return;
if (object->Type == ObjectTypeAudioStream)
KeAudioStreamHandleClosed((KE_AUDIO_STREAM*)object);
ObDereferenceObject(object);
}
// Releases every handle-table reference owned by one process.
static void KiProcessReleaseAllHandles(PROCESS* proc) {
if (!proc) return;
for (uint32_t i = 1; i < HANDLE_TABLE_SIZE; ++i) {
OBJECT* obj = (OBJECT*)0;
uintptr_t flags = KeAcquireSpinlock(&proc->HandleLock);
obj = proc->Handles[i].Object;
if (obj) {
proc->Handles[i].Object = (OBJECT*)0;
proc->Handles[i].Rights = 0;
proc->Handles[i].Flags = 0;
}
KeReleaseSpinlock(&proc->HandleLock, flags);
if (obj) {
KeNamedQueueUnpublishHandle(proc, (uint32_t)i, obj);
KiProcessReleaseHandleObject(obj);
}
}
}
// Releases the process-owned address space and per-process kernel resources.
// Idempotent because the normal exit path and the final OBJECT destructor can
// both observe a process whose threads are already gone.
@@ -358,7 +300,7 @@ static void KiProcessRundownResources(PROCESS* proc) {
return;
IoReleaseFileLocksForProcess(proc);
KiProcessReleaseAllHandles(proc);
KeRundownHandleTable(proc);
// Release every user VA region owned by this process. This includes the
// main image, DLLs, TEBs/stacks that survived thread teardown, process
@@ -366,17 +308,14 @@ static void KiProcessRundownResources(PROCESS* proc) {
MmVadDestroyAllForProcess(proc);
proc->ImageBase = 0;
proc->ImageSize = 0;
uintptr_t modFlags = KeAcquireSpinlock(&proc->ModuleLock);
proc->ModuleCount = 0;
KeReleaseSpinlock(&proc->ModuleLock, modFlags);
proc->UserPebBase = 0;
proc->UserPebSize = 0;
proc->UserImagePathBase = 0;
proc->UserImagePathSize = 0;
proc->UserCmdlineBase = 0;
proc->UserCmdlineSize = 0;
proc->UserCwdBase = 0;
proc->UserCwdSize = 0;
proc->UserStartupCapabilitiesBase = 0;
proc->UserStartupCapabilitiesSize = 0;
uintptr_t exceptionFlags = KeAcquireSpinlock(&proc->ExceptionLock);
proc->ExceptionDispatcher = 0;
proc->ExceptionDispatcherContext = 0;
@@ -397,11 +336,29 @@ static void KiProcessDestroy(OBJECT* obj) {
// ran, make sure its own handles and VADs are still reclaimed.
KiProcessRundownResources(proc);
if (proc->PortOutstandingMessages != 0 ||
proc->PortOutstandingBytes != 0 ||
proc->PortOutstandingHandles != 0 ||
proc->PortOutstandingCalls != 0) {
KePanic(KE_PANIC_CODE_GENERIC,
"process: destroying with live Port quota pid=%u messages=%u bytes=%u handles=%u calls=%u\n",
proc->ProcessId,
proc->PortOutstandingMessages,
proc->PortOutstandingBytes,
proc->PortOutstandingHandles,
proc->PortOutstandingCalls);
}
// Release the exit event only when the process object itself dies. External
// process handles may still wait/query an exited process after rundown.
if (proc->ExitEvent) {
ObDereferenceObject(&proc->ExitEvent->Header);
proc->ExitEvent = (EVENT *)0;
proc->ExitEvent = (EVENT*)0;
}
if (proc->SecurityContext) {
ObDereferenceObject(&proc->SecurityContext->Header);
proc->SecurityContext = (SECURITY_CONTEXT*)0;
}
// Remove from global list.
@@ -414,28 +371,36 @@ static void KiProcessDestroy(OBJECT* obj) {
}
// KeCreateProcess.
KSTATUS KeCreateProcess(const char* name, PROCESS **out) {
KSTATUS KeCreateProcess(const char* name, SECURITY_CONTEXT* securityContext, PROCESS** out) {
KiProcessInit();
if (!name || !out) return NTLSTATUS_INVALID_PARAMETER;
if (!name || !securityContext || !out) return NTLSTATUS_INVALID_PARAMETER;
PROCESS* proc = (PROCESS *)ObAllocateObject(ObjectTypeProcess, sizeof(PROCESS));
uint32_t processId = 0;
KSTATUS idStatus = KeAllocateId32(&gProcessIds, &processId);
if (!KSUCCESS(idStatus))
return idStatus;
PROCESS* proc = (PROCESS*)ObAllocateObject(ObjectTypeProcess, sizeof(PROCESS));
if (!proc) return NTLSTATUS_NO_MEMORY;
proc->Header.Destroy = KiProcessDestroy;
proc->ProcessId = _InterlockedExchangeAdd((volatile long *)&gNextProcessId, 1);
proc->Flags = 0;
proc->ProcessId = processId;
proc->Flags = 0;
proc->SecurityContext = securityContext;
ObReferenceObject(&securityContext->Header);
strncpy(proc->Name, name, PROCESS_NAME_MAX - 1);
proc->Name[PROCESS_NAME_MAX - 1] = '\0';
LIST_INIT(&proc->ThreadList);
proc->ThreadCount = 0;
proc->HandleLock = (SPINLOCK)SPINLOCK_INIT;
proc->ThreadLock = (SPINLOCK)SPINLOCK_INIT;
proc->ExitCode = 0;
proc->Terminating = 0;
proc->Exited = 0;
proc->ResourcesReleased = 0;
proc->BuildState = KE_PROCESS_BUILD_STARTED;
proc->BuildOperations = 0;
proc->ImageBase = 0;
proc->ImageSize = 0;
proc->UserPebBase = 0;
@@ -444,10 +409,8 @@ KSTATUS KeCreateProcess(const char* name, PROCESS **out) {
proc->UserImagePathSize = 0;
proc->UserCmdlineBase = 0;
proc->UserCmdlineSize = 0;
proc->UserCwdBase = 0;
proc->UserCwdSize = 0;
proc->ModuleCount = 0;
proc->ModuleLock = (SPINLOCK)SPINLOCK_INIT;
proc->UserStartupCapabilitiesBase = 0;
proc->UserStartupCapabilitiesSize = 0;
proc->ExceptionDispatcher = 0;
proc->ExceptionDispatcherContext = 0;
proc->ExceptionStackBase = 0;
@@ -463,15 +426,15 @@ KSTATUS KeCreateProcess(const char* name, PROCESS **out) {
proc->LastExceptionAddress = 0;
proc->LastExceptionFaultAddress = 0;
proc->ExceptionLock = (SPINLOCK)SPINLOCK_INIT;
proc->CurrentDirectory[0] = '\0';
proc->CurrentDirectoryLength = 0;
proc->CurrentDrive = KeGetSystemRootDrive();
proc->CwdLock = (SPINLOCK)SPINLOCK_INIT;
LIST_INIT(&proc->VadList);
proc->VadLock = (SPINLOCK)SPINLOCK_INIT;
// Slot 0 is the invalid handle - leave it NULL.
memset(proc->Handles, 0, sizeof(proc->Handles));
KeInitializeHandleTable(&proc->HandleTable);
proc->PortQuotaLock = (SPINLOCK)SPINLOCK_INIT;
proc->PortOutstandingMessages = 0;
proc->PortOutstandingBytes = 0;
proc->PortOutstandingHandles = 0;
proc->PortOutstandingCalls = 0;
LIST_INIT(&proc->GlobalLink);
@@ -514,32 +477,7 @@ static KSTATUS KiCopyEnvironmentStringView(PROCESS* proc,
return NTLSTATUS_SUCCESS;
}
static KSTATUS KiCopyEnvironmentStringViewFixed(PROCESS* proc,
SYS_STRING_VIEW src,
size_t capacity,
uintptr_t* baseOut,
size_t* sizeOut) {
if (!baseOut || !sizeOut || capacity == 0) return NTLSTATUS_INVALID_PARAMETER;
*baseOut = 0;
*sizeOut = 0;
if (!SysSvIsValid(src)) return NTLSTATUS_INVALID_PARAMETER;
if (SysSvContainsByte(src, '\0')) return NTLSTATUS_INVALID_PARAMETER;
if (src.Length + 1 > capacity) return NTLSTATUS_BUFFER_TOO_SMALL;
char* dst = (char*)MmAllocateUserMemoryForProcess(proc, capacity);
if (!dst) return NTLSTATUS_NO_MEMORY;
memset(dst, 0, capacity);
if (src.Length) memcpy(dst, src.Data, src.Length);
dst[src.Length] = '\0';
*baseOut = (uintptr_t)dst;
*sizeOut = capacity;
return NTLSTATUS_SUCCESS;
}
// Releases partial Process Environment.
// Releases a partially constructed process environment.
static void KiFreePartialProcessEnvironment(PROCESS* proc) {
if (!proc) return;
@@ -558,78 +496,31 @@ static void KiFreePartialProcessEnvironment(PROCESS* proc) {
proc->UserCmdlineBase = 0;
proc->UserCmdlineSize = 0;
}
if (proc->UserCwdBase && proc->UserCwdSize) {
MmFreeUserMemoryForProcess(proc, (void*)proc->UserCwdBase);
proc->UserCwdBase = 0;
proc->UserCwdSize = 0;
if (proc->UserStartupCapabilitiesBase && proc->UserStartupCapabilitiesSize) {
MmFreeUserMemoryForProcess(proc, (void*)proc->UserStartupCapabilitiesBase);
proc->UserStartupCapabilitiesBase = 0;
proc->UserStartupCapabilitiesSize = 0;
}
}
static KSTATUS KiFormatNativeCwdV(const PROCESS* proc,
SYS_STRING_VIEW currentDirectory,
char* out,
size_t outSize,
size_t* lengthOut) {
if (lengthOut) *lengthOut = 0;
if (!out || outSize == 0) return NTLSTATUS_INVALID_PARAMETER;
out[0] = '\0';
if (!SysSvIsValid(currentDirectory)) return NTLSTATUS_INVALID_PARAMETER;
if (SysSvContainsByte(currentDirectory, '\0')) return NTLSTATUS_INVALID_PARAMETER;
uint8_t drive = proc ? proc->CurrentDrive : 0;
if (drive > 7) drive = 0;
if (currentDirectory.Length + 4 > outSize) return NTLSTATUS_BUFFER_TOO_SMALL;
out[0] = (char)('0' + drive);
out[1] = ':';
out[2] = '/';
if (currentDirectory.Length)
memcpy(out + 3, currentDirectory.Data, currentDirectory.Length);
out[3 + currentDirectory.Length] = '\0';
if (lengthOut) *lengthOut = 3 + currentDirectory.Length;
return NTLSTATUS_SUCCESS;
}
KSTATUS KeSetupUserProcessEnvironment(PROCESS* proc,
SYS_STRING_VIEW imagePath,
SYS_STRING_VIEW commandLine,
SYS_STRING_VIEW currentDirectory) {
SYS_STRING_VIEW commandLine) {
if (!proc || imagePath.Length == 0) return NTLSTATUS_INVALID_PARAMETER;
if (!SysSvIsValid(imagePath) || !SysSvIsValid(commandLine) || !SysSvIsValid(currentDirectory))
if (!SysSvIsValid(imagePath) || !SysSvIsValid(commandLine))
return NTLSTATUS_INVALID_PARAMETER;
if (SysSvContainsByte(imagePath, '\0') ||
SysSvContainsByte(commandLine, '\0') ||
SysSvContainsByte(currentDirectory, '\0'))
if (SysSvContainsByte(imagePath, '\0') || SysSvContainsByte(commandLine, '\0'))
return NTLSTATUS_INVALID_PARAMETER;
SYS_STRING_VIEW finalCommandLine = commandLine.Length ? commandLine : imagePath;
SYS_STRING_VIEW finalCwdRel = currentDirectory;
if (finalCwdRel.Length == 0 && proc->CurrentDirectoryLength)
finalCwdRel = SysSvFromParts(proc->CurrentDirectory, proc->CurrentDirectoryLength);
char finalCwd[SYS_CWD_MAX];
size_t finalCwdLength = 0;
KSTATUS s = KiFormatNativeCwdV(proc, finalCwdRel, finalCwd, sizeof(finalCwd), &finalCwdLength);
if (!KSUCCESS(s)) return s;
KiFreePartialProcessEnvironment(proc);
s = KiCopyEnvironmentStringView(proc, imagePath, &proc->UserImagePathBase, &proc->UserImagePathSize);
KSTATUS s = KiCopyEnvironmentStringView(proc, imagePath, &proc->UserImagePathBase, &proc->UserImagePathSize);
if (!KSUCCESS(s)) goto fail;
s = KiCopyEnvironmentStringView(proc, finalCommandLine, &proc->UserCmdlineBase, &proc->UserCmdlineSize);
if (!KSUCCESS(s)) goto fail;
s = KiCopyEnvironmentStringViewFixed(proc,
SysSvFromParts(finalCwd, finalCwdLength),
SYS_CWD_MAX,
&proc->UserCwdBase,
&proc->UserCwdSize);
if (!KSUCCESS(s)) goto fail;
SYS_PEB* peb = (SYS_PEB*)MmAllocateUserMemoryForProcess(proc, sizeof(SYS_PEB));
if (!peb) { s = NTLSTATUS_NO_MEMORY; goto fail; }
memset(peb, 0, sizeof(SYS_PEB));
@@ -639,7 +530,10 @@ KSTATUS KeSetupUserProcessEnvironment(PROCESS* proc,
peb->ProcessId = proc->ProcessId;
peb->ImagePath = SysSvFromParts((const char*)proc->UserImagePathBase, imagePath.Length);
peb->CommandLine = SysSvFromParts((const char*)proc->UserCmdlineBase, finalCommandLine.Length);
peb->CurrentDirectory = SysSvFromParts((const char*)proc->UserCwdBase, finalCwdLength);
peb->StartupCapabilities = (const SYS_STARTUP_CAPABILITY*)0;
peb->StartupCapabilityCount = 0;
peb->Reserved = 0;
peb->LoaderData = (const void*)0;
proc->UserPebBase = (uintptr_t)peb;
proc->UserPebSize = sizeof(SYS_PEB);
@@ -650,13 +544,65 @@ fail:
return s;
}
// Copies the immutable startup-capability descriptor table into child-owned user memory.
KSTATUS KeSetProcessStartupCapabilities(PROCESS* proc,
const SYS_STARTUP_CAPABILITY* capabilities,
uint32_t count) {
if (!proc || !proc->UserPebBase) return NTLSTATUS_INVALID_PARAMETER;
if (count > SYS_STARTUP_CAPABILITY_MAX) return NTLSTATUS_LIMIT_REACHED;
if (count && !capabilities) return NTLSTATUS_INVALID_PARAMETER;
if (proc->UserStartupCapabilitiesBase && proc->UserStartupCapabilitiesSize) {
MmFreeUserMemoryForProcess(proc, (void*)proc->UserStartupCapabilitiesBase);
proc->UserStartupCapabilitiesBase = 0;
proc->UserStartupCapabilitiesSize = 0;
}
SYS_PEB* peb = (SYS_PEB*)proc->UserPebBase;
peb->StartupCapabilities = (const SYS_STARTUP_CAPABILITY*)0;
peb->StartupCapabilityCount = 0;
if (!count) return NTLSTATUS_SUCCESS;
size_t bytes = (size_t)count * sizeof(SYS_STARTUP_CAPABILITY);
SYS_STARTUP_CAPABILITY* dst = (SYS_STARTUP_CAPABILITY*)MmAllocateUserMemoryForProcess(proc, bytes);
if (!dst) return NTLSTATUS_NO_MEMORY;
memcpy(dst, capabilities, bytes);
KSTATUS protectStatus = MmVadProtect(proc, dst, bytes, PAGE_PRESENT | PAGE_USER | PAGE_NX);
if (!KSUCCESS(protectStatus)) {
MmFreeUserMemoryForProcess(proc, dst);
return protectStatus;
}
proc->UserStartupCapabilitiesBase = (uintptr_t)dst;
proc->UserStartupCapabilitiesSize = bytes;
peb->StartupCapabilities = dst;
peb->StartupCapabilityCount = count;
return NTLSTATUS_SUCCESS;
}
// Installs the opaque userspace loader-data pointer in the child PEB.
KSTATUS KeSetProcessLoaderData(PROCESS* proc, const void* loaderData) {
if (!proc || !proc->UserPebBase) return NTLSTATUS_INVALID_PARAMETER;
SYS_PEB* peb = (SYS_PEB*)proc->UserPebBase;
peb->LoaderData = loaderData;
return NTLSTATUS_SUCCESS;
}
// Stops new process-construction operations. Active builders defer final teardown until they leave.
void KeStopProcessConstruction(PROCESS* process) {
if (!process) return;
_InterlockedExchange(&process->BuildState, KE_PROCESS_BUILD_DEAD);
}
// Process exit / termination.
void KeCompleteProcessExitIfLastThread(PROCESS* process) {
if (!process) return;
if (_InterlockedCompareExchange(&process->BuildOperations, 0, 0) != 0) return;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
uintptr_t flags = KeAcquireSpinlock(&process->ThreadLock);
uint32_t threadCount = process->ThreadCount;
KeReleaseSpinlock(&process->HandleLock, flags);
KeReleaseSpinlock(&process->ThreadLock, flags);
if (threadCount == 0 && _InterlockedExchange(&process->Exited, 1) == 0) {
KiProcessRundownResources(process);
@@ -664,30 +610,24 @@ void KeCompleteProcessExitIfLastThread(PROCESS* process) {
}
}
// Implements the exit Process operation.
// Exits one process by terminating every thread in it.
// SysExitThread remains the operation for ending only the calling thread.
KSTATUS KeExitProcess(PROCESS* process, int32_t exitCode) {
if (!process) return NTLSTATUS_INVALID_PARAMETER;
if (KeIsProcessTerminationProtected(process)) return NTLSTATUS_ACCESS_DENIED;
process->ExitCode = exitCode;
_InterlockedExchange(&process->Terminating, 1);
// The calling thread exits immediately after this in SysExitProcess. Do
// not drop the process object here: each thread owns one process reference,
// and KiThreadDestroy releases that reference after unlinking the thread.
KeCompleteProcessExitIfLastThread(process);
return NTLSTATUS_SUCCESS;
return KeTerminateProcess(process, exitCode);
}
// Implements the terminate Process operation.
// 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;
process->ExitCode = exitCode;
_InterlockedExchange(&process->Terminating, 1);
KeStopProcessConstruction(process);
if (_InterlockedCompareExchange(&process->BuildOperations, 0, 0) != 0)
return NTLSTATUS_SUCCESS;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
uintptr_t flags = KeAcquireSpinlock(&process->ThreadLock);
LIST_ENTRY* head = &process->ThreadList;
THREAD* self = KeGetCurrentThread();
KCPU* selfCpu = KeGetCurrentCpu();
@@ -714,11 +654,11 @@ KSTATUS KeTerminateProcess(PROCESS* process, int32_t exitCode) {
KCPU* cpu = &gCpus[t->RunningCpu];
if (cpu->State == KeCpuOnline) {
_InterlockedExchange(&cpu->ReschedulePending, 1);
HalApicSendRescheduleIpi(cpu->LapicId);
KeSendProcessorInterrupt(cpu->HardwareId, KE_VECTOR_RESCHEDULE_IPI);
}
}
}
KeReleaseSpinlock(&process->HandleLock, flags);
KeReleaseSpinlock(&process->ThreadLock, flags);
KeCompleteProcessExitIfLastThread(process);
return NTLSTATUS_SUCCESS;
@@ -741,140 +681,6 @@ KSTATUS KeGetProcessExitCode(PROCESS* process, int32_t* exitCodeOut) {
return NTLSTATUS_SUCCESS;
}
// Handle table.
KSTATUS KeAllocateHandleWithRights(PROCESS* process, OBJECT* object, uint32_t rights, uint32_t* handleOut) {
if (!process || !object || !handleOut || rights == 0) return NTLSTATUS_INVALID_PARAMETER;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
for (int i = 1; i < HANDLE_TABLE_SIZE; i++) {
if (!process->Handles[i].Object) {
if (!ObTryReferenceObject(object)) {
KeReleaseSpinlock(&process->HandleLock, flags);
return NTLSTATUS_OBJECT_DEAD;
}
process->Handles[i].Object = object;
process->Handles[i].Rights = rights;
process->Handles[i].Flags = 0;
KiProcessHandleOpened(object);
*handleOut = (uint32_t)i;
KeReleaseSpinlock(&process->HandleLock, flags);
return NTLSTATUS_SUCCESS;
}
}
KeReleaseSpinlock(&process->HandleLock, flags);
return NTLSTATUS_NO_MEMORY;
}
// Creates handle.
KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut) {
return KeAllocateHandleWithRights(process, object, HANDLE_RIGHT_ALL, handleOut);
}
// Releases handle.
void KeCloseHandle(PROCESS* process, uint32_t handle) {
if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) return;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
OBJECT* obj = process->Handles[handle].Object;
if (obj) {
process->Handles[handle].Object = (OBJECT*)0;
process->Handles[handle].Rights = 0;
process->Handles[handle].Flags = 0;
KeReleaseSpinlock(&process->HandleLock, flags);
if (obj->Type == ObjectTypeFile)
(void)IoFlushFile((FILE_OBJECT*)obj);
KeNamedQueueUnpublishHandle(process, handle, obj);
KiProcessReleaseHandleObject(obj);
return;
}
KeReleaseSpinlock(&process->HandleLock, flags);
}
// Returns handle With Rights.
OBJECT* KeLookupHandleWithRights(PROCESS* process, uint32_t handle, uint32_t requiredRights) {
if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE)
return (OBJECT*)0;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
HANDLE_ENTRY* entry = &process->Handles[handle];
OBJECT* obj = entry->Object;
if (obj && requiredRights && ((entry->Rights & requiredRights) != requiredRights))
obj = (OBJECT*)0;
if (obj && !ObTryReferenceObject(obj))
obj = (OBJECT*)0;
KeReleaseSpinlock(&process->HandleLock, flags);
return obj;
}
// Returns handle.
OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle) {
return KeLookupHandleWithRights(process, handle, 0);
}
// Returns handle Rights.
uint32_t KeGetHandleRights(PROCESS* process, uint32_t handle) {
if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) return 0;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
uint32_t rights = process->Handles[handle].Object ? process->Handles[handle].Rights : 0;
KeReleaseSpinlock(&process->HandleLock, flags);
return rights;
}
// Duplicates a handle from one process table into another.
KSTATUS KeDuplicateHandleToProcess(PROCESS* sourceProcess, uint32_t sourceHandle, PROCESS* targetProcess, uint32_t desiredRights, uint32_t duplicateFlags, uint32_t* targetHandleOut) {
if (!sourceProcess || !targetProcess || !targetHandleOut) return NTLSTATUS_INVALID_PARAMETER;
if (sourceHandle == 0 || sourceHandle >= HANDLE_TABLE_SIZE) return NTLSTATUS_INVALID_HANDLE;
const uint32_t validDuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS;
const uint32_t publicHandleRights = SYS_HANDLE_RIGHT_ALL;
if ((duplicateFlags & ~validDuplicateFlags) != 0) return NTLSTATUS_INVALID_PARAMETER;
if ((duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) != 0 && desiredRights != 0)
return NTLSTATUS_INVALID_PARAMETER;
if ((duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) == 0) {
if (desiredRights == 0) return NTLSTATUS_INVALID_PARAMETER;
if ((desiredRights & ~publicHandleRights) != 0) return NTLSTATUS_INVALID_PARAMETER;
}
OBJECT* obj = NULL;
uint32_t sourceRights = 0;
uintptr_t savedFlags = KeAcquireSpinlock(&sourceProcess->HandleLock);
HANDLE_ENTRY* entry = &sourceProcess->Handles[sourceHandle];
if (!entry->Object) {
KeReleaseSpinlock(&sourceProcess->HandleLock, savedFlags);
return NTLSTATUS_INVALID_HANDLE;
}
if ((entry->Rights & HANDLE_RIGHT_DUPLICATE) == 0) {
KeReleaseSpinlock(&sourceProcess->HandleLock, savedFlags);
return NTLSTATUS_ACCESS_DENIED;
}
obj = entry->Object;
sourceRights = entry->Rights;
if (!ObTryReferenceObject(obj)) {
KeReleaseSpinlock(&sourceProcess->HandleLock, savedFlags);
return NTLSTATUS_OBJECT_DEAD;
}
KeReleaseSpinlock(&sourceProcess->HandleLock, savedFlags);
uint32_t targetRights = sourceRights;
if ((duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) == 0) {
if ((desiredRights & ~sourceRights) != 0) {
ObDereferenceObject(obj);
return NTLSTATUS_ACCESS_DENIED;
}
targetRights = desiredRights;
}
KSTATUS s = KeAllocateHandleWithRights(targetProcess, obj, targetRights, targetHandleOut);
ObDereferenceObject(obj);
return s;
}
// Returns process By Id.
PROCESS* KeGetProcessById(uint32_t pid) {
KiProcessInit();
+15 -39
View File
@@ -4,6 +4,7 @@
#include <string.h>
#include <kernel/ke_queue.h>
#include <kernel/ke_sched.h>
#include <kernel/ke_time.h>
#include <kernel/ke_irql.h>
#include <kernel/ob_object.h>
@@ -46,49 +47,24 @@ static void KiQueueNoteRemovedLocked(QUEUE* queue, uint32_t previousCount) {
KeClearEvent(queue->NotEmptyEvent);
}
// Returns the scheduler tick value used by queue finite-timeout accounting.
static uint64_t KiQueueReadSchedulerTick(void) {
KE_SCHED_STATS stats;
KeGetSchedulerStats(&stats);
return stats.Ticks;
}
// Converts a millisecond timeout to at least one scheduler tick.
static uint64_t KiQueueTimeoutTicks(uint64_t timeoutMilliseconds) {
uint64_t ticks = timeoutMilliseconds / SCHED_TICK_MS;
if ((timeoutMilliseconds % SCHED_TICK_MS) != 0)
ticks++;
return ticks ? ticks : 1;
}
// Computes an absolute scheduler-tick deadline for a finite timeout.
// Computes an absolute monotonic deadline for a finite timeout.
static uint64_t KiQueueTimeoutDeadline(uint64_t timeoutMilliseconds) {
uint64_t now = KiQueueReadSchedulerTick();
uint64_t ticks = KiQueueTimeoutTicks(timeoutMilliseconds);
uint64_t deadline = now + ticks;
return deadline < now ? UINT64_MAX : deadline;
return KeMonotonicDeadlineAfterMilliseconds(timeoutMilliseconds);
}
// Computes the remaining wait time for a finite timeout deadline.
static uint64_t KiQueueRemainingMilliseconds(uint64_t deadlineTick) {
uint64_t now = KiQueueReadSchedulerTick();
if (deadlineTick <= now)
return 0;
uint64_t ticks = deadlineTick - now;
if (ticks > UINT64_MAX / SCHED_TICK_MS)
return UINT64_MAX;
return ticks * SCHED_TICK_MS;
static uint64_t KiQueueRemainingMilliseconds(uint64_t deadlineMicroseconds) {
return KeMonotonicMillisecondsUntil(deadlineMicroseconds);
}
// Waits for a queue state event using either an infinite or finite timeout.
static KSTATUS KiQueueWaitForEvent(QUEUE* queue, EVENT* event, uint64_t timeoutMilliseconds, uint64_t deadlineTick) {
static KSTATUS KiQueueWaitForEvent(QUEUE* queue, EVENT* event, uint64_t timeoutMilliseconds, uint64_t deadlineMicroseconds) {
if (!queue || !event)
return NTLSTATUS_INVALID_PARAMETER;
uint64_t waitMilliseconds = timeoutMilliseconds;
if (timeoutMilliseconds != QUEUE_TIMEOUT_INFINITE) {
waitMilliseconds = KiQueueRemainingMilliseconds(deadlineTick);
waitMilliseconds = KiQueueRemainingMilliseconds(deadlineMicroseconds);
if (waitMilliseconds == 0)
return NTLSTATUS_TIMEOUT;
}
@@ -98,11 +74,11 @@ static KSTATUS KiQueueWaitForEvent(QUEUE* queue, EVENT* event, uint64_t timeoutM
}
// Creates a fixed-depth queue with waitable occupancy and capacity events.
KSTATUS KeQueueCreate(QUEUE **out) {
KSTATUS KeQueueCreate(QUEUE** out) {
if (!out) return NTLSTATUS_INVALID_PARAMETER;
*out = (QUEUE*)0;
QUEUE* q = (QUEUE *)ObAllocateObject(ObjectTypeQueue, sizeof(QUEUE));
QUEUE* q = (QUEUE*)ObAllocateObject(ObjectTypeQueue, sizeof(QUEUE));
if (!q) return NTLSTATUS_NO_MEMORY;
q->Header.Destroy = KiQueueDestroy;
q->Lock = (SPINLOCK)SPINLOCK_INIT;
@@ -129,9 +105,9 @@ KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, uint64_t timeoutMi
if (!queue || !msg) return NTLSTATUS_INVALID_PARAMETER;
if (timeoutMilliseconds != 0 && !KeCanWait()) return NTLSTATUS_INVALID_IRQL;
uint64_t deadlineTick = 0;
uint64_t deadlineMicroseconds = 0;
if (timeoutMilliseconds != 0 && timeoutMilliseconds != QUEUE_TIMEOUT_INFINITE)
deadlineTick = KiQueueTimeoutDeadline(timeoutMilliseconds);
deadlineMicroseconds = KiQueueTimeoutDeadline(timeoutMilliseconds);
while (true) {
uintptr_t flags = KeAcquireSpinlock(&queue->Lock);
@@ -150,7 +126,7 @@ KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, uint64_t timeoutMi
if (timeoutMilliseconds == 0)
return NTLSTATUS_WOULD_BLOCK;
KSTATUS s = KiQueueWaitForEvent(queue, queue->NotFullEvent, timeoutMilliseconds, deadlineTick);
KSTATUS s = KiQueueWaitForEvent(queue, queue->NotFullEvent, timeoutMilliseconds, deadlineMicroseconds);
if (s == NTLSTATUS_TIMEOUT || KFAILED(s))
return s;
}
@@ -161,9 +137,9 @@ KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, uint64_t timeoutMilli
if (!queue || !msg) return NTLSTATUS_INVALID_PARAMETER;
if (timeoutMilliseconds != 0 && !KeCanWait()) return NTLSTATUS_INVALID_IRQL;
uint64_t deadlineTick = 0;
uint64_t deadlineMicroseconds = 0;
if (timeoutMilliseconds != 0 && timeoutMilliseconds != QUEUE_TIMEOUT_INFINITE)
deadlineTick = KiQueueTimeoutDeadline(timeoutMilliseconds);
deadlineMicroseconds = KiQueueTimeoutDeadline(timeoutMilliseconds);
while (true) {
uintptr_t flags = KeAcquireSpinlock(&queue->Lock);
@@ -182,7 +158,7 @@ KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, uint64_t timeoutMilli
if (timeoutMilliseconds == 0)
return NTLSTATUS_WOULD_BLOCK;
KSTATUS s = KiQueueWaitForEvent(queue, queue->NotEmptyEvent, timeoutMilliseconds, deadlineTick);
KSTATUS s = KiQueueWaitForEvent(queue, queue->NotEmptyEvent, timeoutMilliseconds, deadlineMicroseconds);
if (s == NTLSTATUS_TIMEOUT || KFAILED(s))
return s;
}
+49 -40
View File
@@ -3,10 +3,13 @@
#include <stddef.h>
#include <stdbool.h>
#include <intrin.h>
#include <kernel/ke_idt.h>
#include <kernel/ke_hal.h>
#include <kernel/ke_sched.h>
#include <kernel/ke_cpu.h>
#include <kernel/ke_process.h>
#include <kernel/ke_thread.h>
#include <kernel/se_security.h>
#include <kernel/ke_mutex.h>
#include <kernel/ke_spinlock.h>
#include <kernel/ke_irql.h>
@@ -18,7 +21,6 @@
#include <kernel/ke_list.h>
#include <kernel/ke_work.h>
#include <kernel/mm_virt.h>
#include <kernel/hal/hal_apic.h>
static LIST_ENTRY gNormalRunQueue[PriorityCount];
static LIST_ENTRY gRealtimeRunQueue[KE_REALTIME_PRIORITY_COUNT];
@@ -129,7 +131,7 @@ int KiSchedulerRefreshMutexDonation(MUTEX* mutex) {
// Implements the default Thread Affinity Mask operation.
static uint64_t KiDefaultThreadAffinityMask(void) {
// Normal kernel, user, and driver-created threads are BSP-affine by
// Normal kernel, user, and driver-created threads are boot-processor-affine by
// default. Code that is explicitly audited for AP execution can widen its
// affinity before the thread enters the ready queue.
return 1ULL;
@@ -200,7 +202,7 @@ static void KiRequestRescheduleCpuLocked(KCPU* target) {
KCPU* self = KeGetCurrentCpu();
if (self && target->CpuIndex != self->CpuIndex) {
HalApicSendRescheduleIpi(target->LapicId);
KeSendProcessorInterrupt(target->HardwareId, KE_VECTOR_RESCHEDULE_IPI);
}
}
@@ -707,8 +709,8 @@ static void KiIdleLoop(void) {
KCPU* cpu = KeGetCurrentCpu();
cpu->SchedulerTicks++;
// AP idle threads sleep until an interrupt or reschedule IPI wakes the
// CPU. Ready paths set ReschedulePending and target the CPU's LAPIC;
// Secondary-processor idle threads sleep until an interrupt or reschedule IPI wakes
// the CPU. Ready paths set ReschedulePending and target that processor;
// KiExitInterrupt consumes the request after EOI.
if (KiAnyRunnableForCurrentCpu()) {
KeYield();
@@ -719,7 +721,7 @@ static void KiIdleLoop(void) {
// the idle thread through an unusual switch path must not sleep with
// interrupts disabled, otherwise reschedule/TLB shootdown IPIs can
// be delayed forever.
HalWaitForInterrupt();
KeWaitForInterrupt();
}
}
@@ -731,7 +733,7 @@ void KiInitializeSchedulerGlobal(void) {
KeInitializeWorkItem(&gTimeoutWorkItem, KiTimeoutWorkRoutine, (void*)0);
gSchedLock = (SPINLOCK)SPINLOCK_INIT;
KSTATUS s = KeCreateProcess("idle", &gIdleProcess);
KSTATUS s = KeCreateProcess("idle", SeGetRootSecurityContext(), &gIdleProcess);
if (!KSUCCESS(s))
KePanic(KE_PANIC_CODE_INIT, "scheduler: failed to create idle process, status=%u\n", s);
gIdleProcess->Flags |= KE_PROCESS_FLAG_IDLE | KE_PROCESS_FLAG_TERMINATION_PROTECTED;
@@ -750,12 +752,16 @@ KSTATUS KiCreateIdleThreadForCpu(KCPU* cpu) {
if (cpu->IdleThread)
return NTLSTATUS_SUCCESS;
THREAD* idle = (THREAD *)ObAllocateObject(ObjectTypeThread, sizeof(THREAD));
THREAD* idle = (THREAD*)ObAllocateObject(ObjectTypeThread, sizeof(THREAD));
if (!idle)
return NTLSTATUS_NO_MEMORY;
idle->Header.Destroy = (void *)0;
idle->ThreadId = cpu->CpuIndex ? (0x80000000u | cpu->CpuIndex) : 0;
idle->Header.Destroy = (void*)0;
KSTATUS idStatus = KeAllocateThreadId(&idle->ThreadId);
if (!KSUCCESS(idStatus)) {
ObDereferenceObject(&idle->Header);
return idStatus;
}
idle->State = ThreadRunning;
idle->BaseSchedulingClass = KeSchedulingNormal;
idle->BasePriority = PriorityLow;
@@ -776,7 +782,7 @@ KSTATUS KiCreateIdleThreadForCpu(KCPU* cpu) {
idle->LastCpu = cpu->CpuIndex;
idle->AffinityMask = (cpu->CpuIndex < 64) ? (1ULL << cpu->CpuIndex) : 0ULL;
idle->Owner = gIdleProcess;
idle->WaitObject = (OBJECT *)0;
idle->WaitObject = (OBJECT*)0;
idle->WaitStatus = NTLSTATUS_SUCCESS;
idle->WaitListHead = (LIST_ENTRY*)0;
idle->AlertableWait = 0;
@@ -822,13 +828,13 @@ KSTATUS KiCreateIdleThreadForCpu(KCPU* cpu) {
KeInitThreadFpuContext(idle);
// The hand-built CPU idle thread is not created through KeCreateThread(),
// so register it manually with the idle process. This keeps PID 0 visible
// as the real idle process while kernel worker threads live under PID 1.
// so register it manually with the dedicated idle process while kernel worker
// threads remain grouped under the system process.
ObReferenceObject(&gIdleProcess->Header);
uintptr_t idleFlags = KeAcquireSpinlock(&gIdleProcess->HandleLock);
uintptr_t idleFlags = KeAcquireSpinlock(&gIdleProcess->ThreadLock);
KeListInsertTailChecked(&gIdleProcess->ThreadList, &idle->ProcessLink);
gIdleProcess->ThreadCount++;
KeReleaseSpinlock(&gIdleProcess->HandleLock, idleFlags);
KeReleaseSpinlock(&gIdleProcess->ThreadLock, idleFlags);
cpu->IdleThread = idle;
cpu->CurrentThread = idle;
@@ -1321,11 +1327,11 @@ static void KiTimeoutWorkRoutine(void* context) {
// Checks whether this CPU owns global scheduler tick duties.
static int KiCpuOwnsGlobalSchedulerTick(const KCPU* cpu) {
// LAPIC timer interrupts are per-CPU, but sleep deadlines, waitable timers,
// Periodic timer interrupts are per-CPU, but sleep deadlines, waitable timers,
// input repeat, and priority aging are global scheduler services. Keep the
// global clock single-sourced on the BSP so enabling AP timers does not
// global clock single-sourced on the boot processor so enabling AP timers does not
// make those services run once per online CPU.
return cpu && cpu->IsBsp;
return cpu && cpu->IsBootProcessor;
}
// Ages ready normal-class threads after enough global ticks have elapsed.
@@ -1451,6 +1457,29 @@ void KeSchedulerTick(void) {
KiRequestReschedule();
}
// Converts one requested delay to scheduler ticks while reserving one phase guard tick.
static uint64_t KiTimeoutTicksForMilliseconds(uint64_t milliseconds) {
uint64_t ticks = milliseconds / SCHED_TICK_MS;
if ((milliseconds % SCHED_TICK_MS) != 0)
ticks++;
// The scheduler tick count identifies completed quanta, not the current
// position inside one quantum. One additional tick guarantees that a
// timeout armed immediately before an interrupt cannot expire early.
if (ticks < UINT64_MAX)
ticks++;
return ticks ? ticks : 1;
}
// Returns the saturated scheduler tick at which one finite timeout may expire.
static uint64_t KiTimeoutWakeTickLocked(uint64_t milliseconds) {
uint64_t ticks = KiTimeoutTicksForMilliseconds(milliseconds);
uint64_t now = gSchedStats.Ticks;
if (ticks == UINT64_MAX || ticks > UINT64_MAX - now)
return UINT64_MAX;
return now + ticks;
}
// Arms a timeout for the current thread after a wait primitive has prepared it to block.
KSTATUS KeArmCurrentThreadTimeout(uint64_t milliseconds, KSTATUS timeoutStatus) {
if (milliseconds == 0)
@@ -1462,12 +1491,6 @@ KSTATUS KeArmCurrentThreadTimeout(uint64_t milliseconds, KSTATUS timeoutStatus)
if (!self || self->IsIdleThread)
return NTLSTATUS_INVALID_PARAMETER;
uint64_t ticks = milliseconds / SCHED_TICK_MS;
if ((milliseconds % SCHED_TICK_MS) != 0)
ticks++;
if (ticks == 0)
ticks = 1;
uintptr_t flags = KeAcquireSpinlock(&gSchedLock);
if (self->State != ThreadWaiting ||
self->RunningCpu != KeGetCurrentCpu()->CpuIndex ||
@@ -1478,12 +1501,7 @@ KSTATUS KeArmCurrentThreadTimeout(uint64_t milliseconds, KSTATUS timeoutStatus)
return NTLSTATUS_INVALID_PARAMETER;
}
uint64_t now = gSchedStats.Ticks;
uint64_t wakeTick = now + ticks;
if (wakeTick < now)
wakeTick = UINT64_MAX;
self->TimeoutTick = wakeTick;
self->TimeoutTick = KiTimeoutWakeTickLocked(milliseconds);
self->TimeoutStatus = timeoutStatus;
self->TimeoutArmed = 1;
KeListInsertTailChecked(&gTimeoutList, &self->TimeoutLink);
@@ -1505,12 +1523,6 @@ __declspec(dllexport) KSTATUS KeDelayThread(uint64_t milliseconds) {
if (!self || self->IsIdleThread)
return NTLSTATUS_INVALID_PARAMETER;
uint64_t ticks = milliseconds / SCHED_TICK_MS;
if ((milliseconds % SCHED_TICK_MS) != 0)
ticks++;
if (ticks == 0)
ticks = 1;
uintptr_t flags = KeAcquireSpinlock(&gSchedLock);
if (self->Queued || self->State != ThreadRunning ||
@@ -1522,10 +1534,7 @@ __declspec(dllexport) KSTATUS KeDelayThread(uint64_t milliseconds) {
return NTLSTATUS_INVALID_PARAMETER;
}
uint64_t now = gSchedStats.Ticks;
uint64_t wakeTick = now + ticks;
if (wakeTick < now)
wakeTick = UINT64_MAX;
uint64_t wakeTick = KiTimeoutWakeTickLocked(milliseconds);
self->State = ThreadWaiting;
self->WaitObject = (OBJECT*)0;
+76 -16
View File
@@ -4,7 +4,7 @@
#include <stddef.h>
#include <string.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/hal/hal_api.h>
#include <kernel/ke_apc.h>
#include <kernel/ke_cpu.h>
#include <kernel/ke_debug.h>
@@ -18,6 +18,7 @@
#include <kernel/ke_thread.h>
#include <kernel/ke_process.h>
#include <kernel/io_file.h>
#include <kernel/ke_system_root.h>
#include <kernel/io_dma.h>
#include <ntlite/ddi/io_driver.h>
#include <kernel/ke_trap.h>
@@ -1152,10 +1153,15 @@ static void KiRunStringViewSelfTest(void) {
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 FAT/VFS path names do not
// 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;
KSTATUS s = IoOpenFile(SysSvFromParts("system/init.exe\0junk", 20), &f);
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);
@@ -1164,6 +1170,59 @@ static void KiRunStringViewSelfTest(void) {
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 {
@@ -1221,7 +1280,7 @@ static void KiRunAsyncFileIoSelfTest(void) {
memset(&st, 0, sizeof(st));
st.Iosb.Status = NTLSTATUS_FAILURE;
KSTATUS s = IoOpenFile(SYS_SV_LITERAL("system/init.exe"), &st.File);
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;
@@ -1303,8 +1362,8 @@ static void KiRunAsyncFileIoSelfTest(void) {
wiosb.Status = NTLSTATUS_FAILURE;
wiosb.Information = 0;
(void)IoDeleteFile(SYS_SV_LITERAL("asyncio.tmp"));
s = IoCreateFile(SYS_SV_LITERAL("asyncio.tmp"), &wf);
(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);
@@ -1343,7 +1402,7 @@ static void KiRunAsyncFileIoSelfTest(void) {
IoCloseFile(wf);
wf = (FILE_OBJECT*)0;
s = IoOpenFile(SYS_SV_LITERAL("asyncio.tmp"), &rf);
s = KeOpenSystemRootFile("asyncio.tmp", &rf);
if (!KSUCCESS(s))
KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: reopen write temp failed %u\n", s);
@@ -1357,7 +1416,7 @@ static void KiRunAsyncFileIoSelfTest(void) {
IoCloseFile(rf);
ObDereferenceObject(&wev->Header);
s = IoDeleteFile(SYS_SV_LITERAL("asyncio.tmp"));
s = KeDeleteSystemRootFile("asyncio.tmp");
if (!KSUCCESS(s))
KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: delete temp failed %u\n", s);
@@ -1774,16 +1833,16 @@ void KiRunCoreHardeningSelfTests(void) {
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 BSP/0\n");
if (cpu->State != KeCpuOnline || !cpu->IsBsp)
KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: BSP lifecycle invalid state=%ld bsp=%u\n",
cpu->State, cpu->IsBsp);
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 BSP TSS rsp0=0x%016llX iomap=%u\n",
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",
@@ -1793,10 +1852,10 @@ void KiRunCoreHardeningSelfTests(void) {
KCPU* ap = &gCpus[i];
if (ap->State == KeCpuAbsent)
continue;
if (ap->State != KeCpuOnline || ap->IsBsp)
if (ap->State != KeCpuOnline || ap->IsBootProcessor)
KePanic(KE_PANIC_CODE_SELFTEST,
"CORE selftest: AP%u lifecycle invalid state=%ld bsp=%u\n",
i, ap->State, ap->IsBsp);
"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))
@@ -1847,6 +1906,7 @@ void KiRunKernelSelfTests(const HAL_API* hal) {
KiRunVadCopyOnWriteSelfTest();
KiRunVadSwapSelfTest();
KiRunStringViewSelfTest();
KiRunVolumeRelativePathSelfTest();
KiRunIrpKernelApcSelfTest(hal);
KiRunAsyncFileIoSelfTest();
KiRunTrapFrameSelfTest();
+2 -2
View File
@@ -12,10 +12,10 @@
static void KiSemaphoreDestroy(OBJECT* obj) { (void)obj; }
// Implements the semaphore Create operation.
__declspec(dllexport) KSTATUS KeSemaphoreCreate(int32_t initial, int32_t max, SEMAPHORE **out) {
__declspec(dllexport) KSTATUS KeSemaphoreCreate(int32_t initial, int32_t max, SEMAPHORE** out) {
if (!out || initial < 0 || max < 1 || initial > max)
return NTLSTATUS_INVALID_PARAMETER;
SEMAPHORE* s = (SEMAPHORE *)ObAllocateObject(ObjectTypeSemaphore, sizeof(SEMAPHORE));
SEMAPHORE* s = (SEMAPHORE*)ObAllocateObject(ObjectTypeSemaphore, sizeof(SEMAPHORE));
if (!s) return NTLSTATUS_NO_MEMORY;
s->Header.Destroy = KiSemaphoreDestroy;
s->Lock = (SPINLOCK)SPINLOCK_INIT;
Loaded 100 of 232 files, more files were not shown because too many files have changed in this diff. Show more