#include #include #include #include #include #include #include #include #include #include #include #include #include #define PNP_MAX_DESCRIPTORS 64u #define PNP_MAX_DEVICES 128u #define PNP_WORKERS 2u typedef enum PNP_BIND_STATE { PnpUnbound, PnpReserved, PnpStarting, PnpPending, PnpCancelling, PnpStarted, PnpFailed, PnpCleaning, PnpStopping, } PNP_BIND_STATE; typedef struct PNP_DRIVER { const NTL_DEVICE_DESC* Descriptor; DRIVER_OBJECT* IoDriver; struct NTL_MODULE* Module; } PNP_DRIVER; typedef struct PNP_NODE { DEVICE_OBJECT* Pdo; DEVICE_OBJECT* ParentPdo; NTL_DEVICE* Binding; const PNP_DRIVER* Driver; PNP_BIND_STATE State; bool Removed; bool Published; bool CancelIssued; } PNP_NODE; struct NTL_DEVICE_RESOURCE { NTL_DEVICE_RESOURCE* Next; NTL_DEVICE* Owner; NTL_DEVICE_RESOURCE_RELEASE Release; void* Context; }; struct NTL_DEVICE { SPINLOCK Lock; PNP_NODE* Node; void* Context; NTL_DEVICE_RESOURCE* Resources; KSTATUS CompletionStatus; bool CompletionSet; bool Closing; }; static SPINLOCK gPnpLock = SPINLOCK_INIT; static PNP_DRIVER gPnpDrivers[PNP_MAX_DESCRIPTORS]; static uint32_t gPnpDriverCount; static PNP_NODE* gPnpNodes[PNP_MAX_DEVICES]; static uint32_t gPnpNodeCount; static EVENT* gPnpReady; static EVENT* gPnpChanged; static bool gPnpStarted; static bool gPnpShutdown; static KWORK_ITEM gPnpWakeItem; static void IoPnpSignal(void* unused) { (void)unused; for (uint32_t i = 0; i < PNP_WORKERS; ++i) KeSignalEvent(gPnpReady); KeSignalEvent(gPnpChanged); } static void IoPnpWake(void) { if (gPnpStarted) (void)KeQueueWorkItem(&gPnpWakeItem); } static bool IoPnpMatches(const NTL_DEVICE_DESC* descriptor, const DEVICE_OBJECT* pdo) { for (uint32_t i = 0; i < descriptor->MatchCount; ++i) { const NTL_DEVICE_MATCH* match = &descriptor->Matches[i]; if ((uint32_t)match->Bus != (uint32_t)pdo->u.Pdo.BusType) continue; size_t length = strlen(match->Id); for (uint8_t j = 0; j < pdo->u.Pdo.IdCount; ++j) { SYS_STRING_VIEW id = pdo->u.Pdo.Ids[j].Id; if (id.Length == length && !memcmp(id.Data, match->Id, length)) return true; } } return false; } static bool IoPnpMatchesIoDriver(const DRIVER_OBJECT* driver, const DEVICE_OBJECT* pdo) { if (!driver->IdTable || !driver->DriverAddDevice) return false; for (size_t i = 0; i < driver->IdTable->Count; ++i) { const IO_DEVICE_ID* wanted = &driver->IdTable->Ids[i]; if (wanted->BusType != IoBusUnknown && wanted->BusType != pdo->u.Pdo.BusType) continue; for (uint8_t j = 0; j < pdo->u.Pdo.IdCount; ++j) if (SysSvEqual(wanted->Id, pdo->u.Pdo.Ids[j].Id)) return true; } return false; } static const PNP_DRIVER* IoPnpFindDriver(const DEVICE_OBJECT* pdo) { for (uint32_t i = 0; i < gPnpDriverCount; ++i) { if ((gPnpDrivers[i].Descriptor && IoPnpMatches(gPnpDrivers[i].Descriptor, pdo)) || (gPnpDrivers[i].IoDriver && IoPnpMatchesIoDriver(gPnpDrivers[i].IoDriver, pdo))) return &gPnpDrivers[i]; } return (const PNP_DRIVER*)0; } static PNP_NODE* IoPnpFindNode(const DEVICE_OBJECT* pdo) { for (uint32_t i = 0; i < gPnpNodeCount; ++i) if (gPnpNodes[i]->Pdo == pdo) return gPnpNodes[i]; return (PNP_NODE*)0; } static DEVICE_OBJECT* IoPnpParentPdo(const DEVICE_OBJECT* pdo) { const DEVICE_OBJECT* parent = pdo->Parent; for (uint32_t i = 0; parent && i < 64u; ++i) { if (parent->Type == DeviceObjectPhysical) return (DEVICE_OBJECT*)parent; parent = parent->LowerDevice ? parent->LowerDevice : parent->Parent; } return (DEVICE_OBJECT*)0; } static bool IoPnpHasChildren(const DEVICE_OBJECT* parent) { for (uint32_t i = 0; i < gPnpNodeCount; ++i) { const DEVICE_OBJECT* ancestor = gPnpNodes[i]->ParentPdo; for (uint32_t depth = 0; ancestor && depth < PNP_MAX_DEVICES; ++depth) { if (ancestor == parent) return true; const PNP_NODE* node = IoPnpFindNode(ancestor); ancestor = node ? node->ParentPdo : (const DEVICE_OBJECT*)0; } } return false; } static void IoPnpReleaseResources(NTL_DEVICE* device) { uintptr_t flags = KeAcquireSpinlock(&device->Lock); device->Closing = true; NTL_DEVICE_RESOURCE* resource = device->Resources; device->Resources = (NTL_DEVICE_RESOURCE*)0; KeReleaseSpinlock(&device->Lock, flags); while (resource) { NTL_DEVICE_RESOURCE* next = resource->Next; resource->Owner = (NTL_DEVICE*)0; resource->Release(resource->Context); MmFreePool(resource); resource = next; } } static void IoPnpDispose(PNP_NODE* node) { NTL_DEVICE* device = node->Binding; if (device) { if (node->State == PnpStopping) { if (node->Driver->Descriptor && node->Driver->Descriptor->Stop) node->Driver->Descriptor->Stop(device); else if (node->Driver->IoDriver && !node->Pdo->DeletePending) (void)IoSendPnpIrpSync(node->Pdo, IRP_MN_REMOVE_DEVICE); } IoPnpReleaseResources(device); MmFreePool(device); } uintptr_t flags = KeAcquireSpinlock(&gPnpLock); for (uint32_t i = 0; i < gPnpNodeCount; ++i) { if (gPnpNodes[i] == node) { gPnpNodes[i] = gPnpNodes[--gPnpNodeCount]; gPnpNodes[gPnpNodeCount] = (PNP_NODE*)0; break; } } KeReleaseSpinlock(&gPnpLock, flags); IoDereferenceDeviceObject(node->Pdo); MmFreePool(node); IoPnpWake(); } static void IoPnpStart(PNP_NODE* node) { const NTL_DEVICE_DESC* desc = node->Driver->Descriptor; size_t contextSize = desc ? desc->ContextSize : 0; NTL_DEVICE* device = (NTL_DEVICE*)MmAllocatePool(sizeof(*device) + contextSize); if (!device) { uintptr_t flags = KeAcquireSpinlock(&gPnpLock); node->State = PnpFailed; KeReleaseSpinlock(&gPnpLock, flags); IoPnpWake(); return; } memset(device, 0, sizeof(*device) + contextSize); device->Lock = (SPINLOCK)SPINLOCK_INIT; device->Node = node; device->Context = (void*)(device + 1); uintptr_t flags = KeAcquireSpinlock(&gPnpLock); node->Binding = device; node->State = PnpStarting; KeReleaseSpinlock(&gPnpLock, flags); KSTATUS result; if (desc) { result = desc->Start(device); } else { DRIVER_OBJECT* io = node->Driver->IoDriver; result = io->DriverAddDevice(io, node->Pdo); if (KSUCCESS(result)) { result = IoSendPnpIrpSync(node->Pdo, IRP_MN_START_DEVICE); if (result == NTLSTATUS_NOT_SUPPORTED) result = NTLSTATUS_SUCCESS; } } flags = KeAcquireSpinlock(&gPnpLock); if (device->CompletionSet) result = device->CompletionStatus; node->State = result == NTLSTATUS_PENDING ? PnpPending : KSUCCESS(result) ? PnpStarted : PnpFailed; KeReleaseSpinlock(&gPnpLock, flags); if (KFAILED(result)) DbgPrint("pnp: start failed (0x%x)\n", result); IoPnpWake(); } static void IoPnpWorker(void* ignored) { (void)ignored; for (;;) { (void)KeWaitForEvent(gPnpReady, false); for (;;) { PNP_NODE* node = (PNP_NODE*)0; bool stopping = false; bool cleanup = false; bool cancel = false; uintptr_t flags = KeAcquireSpinlock(&gPnpLock); for (uint32_t i = 0; i < gPnpNodeCount; ++i) { PNP_NODE* candidate = gPnpNodes[i]; if (candidate->State == PnpPending && candidate->Removed && !candidate->CancelIssued && (candidate->Driver->Descriptor && candidate->Driver->Descriptor->CancelStart)) { candidate->CancelIssued = true; candidate->State = PnpCancelling; node = candidate; cancel = true; break; } if (candidate->State == PnpFailed && candidate->Binding && !candidate->Removed) { candidate->State = PnpCleaning; node = candidate; cleanup = true; break; } if (gPnpShutdown && candidate->Removed && IoPnpHasChildren(candidate->Pdo)) continue; if (candidate->Removed && candidate->State != PnpReserved && candidate->State != PnpStarting && candidate->State != PnpPending && candidate->State != PnpCancelling && candidate->State != PnpStopping && candidate->State != PnpCleaning) { candidate->State = PnpStopping; node = candidate; stopping = true; break; } if (candidate->State != PnpUnbound || candidate->Removed || gPnpShutdown || !candidate->Published) continue; const PNP_DRIVER* driver = IoPnpFindDriver(candidate->Pdo); if (driver) { candidate->Driver = driver; candidate->State = PnpReserved; node = candidate; break; } } KeReleaseSpinlock(&gPnpLock, flags); if (!node) break; KeSignalEvent(gPnpReady); if (stopping) { IoPnpDispose(node); } else if (cancel) { node->Driver->Descriptor->CancelStart(node->Binding); uintptr_t update = KeAcquireSpinlock(&gPnpLock); node->State = node->Binding->CompletionSet ? (KSUCCESS(node->Binding->CompletionStatus) ? PnpStarted : PnpFailed) : PnpPending; KeReleaseSpinlock(&gPnpLock, update); IoPnpWake(); } else if (cleanup) { NTL_DEVICE* device = node->Binding; if (node->Driver->Descriptor && node->Driver->Descriptor->Stop) node->Driver->Descriptor->Stop(device); else if (node->Driver->IoDriver && node->Pdo->AttachedDevice) (void)IoSendPnpIrpSync(node->Pdo, IRP_MN_REMOVE_DEVICE); IoPnpReleaseResources(device); uintptr_t update = KeAcquireSpinlock(&gPnpLock); node->Binding = (NTL_DEVICE*)0; node->State = PnpFailed; KeReleaseSpinlock(&gPnpLock, update); MmFreePool(device); IoPnpWake(); } else { IoPnpStart(node); } } } } KSTATUS IoPnpInitialize(void) { if (gPnpStarted) return NTLSTATUS_ALREADY_EXISTS; KSTATUS st = KeEventCreate(EventAutoReset, false, &gPnpReady); if (!KSUCCESS(st)) return st; st = KeEventCreate(EventAutoReset, false, &gPnpChanged); if (!KSUCCESS(st)) return st; for (uint32_t i = 0; i < PNP_WORKERS; ++i) { const KE_THREAD_CREATE_PARAMETERS params = KE_THREAD_CREATE_NAMED_NORMAL(PriorityNormal, "pnp.device"); st = KeCreateKernelThread((uintptr_t)IoPnpWorker, 0, ¶ms, (THREAD**)0); if (!KSUCCESS(st)) return st; } KeInitializeWorkItem(&gPnpWakeItem, IoPnpSignal, (void*)0); gPnpStarted = true; IoPnpWake(); return NTLSTATUS_SUCCESS; } KSTATUS IoPnpPublishModule(struct NTL_MODULE* module, const NTL_DEVICE_DESC* descriptors, uint32_t count, DRIVER_OBJECT* const* ioDrivers, uint32_t ioCount) { if (!gPnpStarted || !module || (count && !descriptors) || (ioCount && !ioDrivers)) return NTLSTATUS_INVALID_PARAMETER; uintptr_t flags = KeAcquireSpinlock(&gPnpLock); if (gPnpShutdown) { KeReleaseSpinlock(&gPnpLock, flags); return NTLSTATUS_DEVICE_REMOVED; } if (count > PNP_MAX_DESCRIPTORS - gPnpDriverCount || ioCount > PNP_MAX_DESCRIPTORS - gPnpDriverCount - count) { KeReleaseSpinlock(&gPnpLock, flags); return NTLSTATUS_NO_MEMORY; } for (uint32_t i = 0; i < count; ++i) { for (uint32_t j = 0; j < gPnpDriverCount; ++j) { SYS_STRING_VIEW existing = gPnpDrivers[j].IoDriver ? gPnpDrivers[j].IoDriver->Name : SysSvFromParts(gPnpDrivers[j].Descriptor->Name, strlen(gPnpDrivers[j].Descriptor->Name)); SYS_STRING_VIEW incoming = SysSvFromParts(descriptors[i].Name, strlen(descriptors[i].Name)); if (SysSvEqual(existing, incoming)) { KeReleaseSpinlock(&gPnpLock, flags); return NTLSTATUS_ALREADY_EXISTS; } } for (uint32_t j = i + 1; j < count; ++j) { if (!strcmp(descriptors[i].Name, descriptors[j].Name)) { KeReleaseSpinlock(&gPnpLock, flags); return NTLSTATUS_ALREADY_EXISTS; } } } for (uint32_t i = 0; i < ioCount; ++i) { DRIVER_OBJECT* driver = ioDrivers[i]; for (uint32_t j = 0; j < gPnpDriverCount; ++j) { SYS_STRING_VIEW existing = gPnpDrivers[j].IoDriver ? gPnpDrivers[j].IoDriver->Name : SysSvFromParts(gPnpDrivers[j].Descriptor->Name, strlen(gPnpDrivers[j].Descriptor->Name)); if (SysSvEqual(driver->Name, existing)) { KeReleaseSpinlock(&gPnpLock, flags); return NTLSTATUS_ALREADY_EXISTS; } } for (uint32_t j = i + 1; j < ioCount; ++j) { if (SysSvEqual(driver->Name, ioDrivers[j]->Name)) { KeReleaseSpinlock(&gPnpLock, flags); return NTLSTATUS_ALREADY_EXISTS; } } for (uint32_t j = 0; j < count; ++j) { if (SysSvEqual(driver->Name, SysSvFromParts(descriptors[j].Name, strlen(descriptors[j].Name)))) { KeReleaseSpinlock(&gPnpLock, flags); return NTLSTATUS_ALREADY_EXISTS; } } } for (uint32_t i = 0; i < count; ++i) { gPnpDrivers[gPnpDriverCount].Descriptor = &descriptors[i]; gPnpDrivers[gPnpDriverCount].IoDriver = (DRIVER_OBJECT*)0; gPnpDrivers[gPnpDriverCount].Module = module; ++gPnpDriverCount; } for (uint32_t i = 0; i < ioCount; ++i) { gPnpDrivers[gPnpDriverCount].Descriptor = (const NTL_DEVICE_DESC*)0; gPnpDrivers[gPnpDriverCount].IoDriver = ioDrivers[i]; gPnpDrivers[gPnpDriverCount].Module = module; ++gPnpDriverCount; } KeReleaseSpinlock(&gPnpLock, flags); IoPnpWake(); return NTLSTATUS_SUCCESS; } KSTATUS IoPnpPhysicalDeviceArrived(DEVICE_OBJECT* pdo) { if (!pdo || pdo->Type != DeviceObjectPhysical) return NTLSTATUS_INVALID_PARAMETER; if (!gPnpStarted) return NTLSTATUS_SUCCESS; PNP_NODE* node = (PNP_NODE*)MmAllocatePool(sizeof(*node)); if (!node) return NTLSTATUS_NO_MEMORY; memset(node, 0, sizeof(*node)); node->Pdo = pdo; node->ParentPdo = IoPnpParentPdo(pdo); IoReferenceDeviceObject(pdo); uintptr_t flags = KeAcquireSpinlock(&gPnpLock); if (gPnpShutdown) { KeReleaseSpinlock(&gPnpLock, flags); IoDereferenceDeviceObject(pdo); MmFreePool(node); return NTLSTATUS_DEVICE_REMOVED; } for (uint32_t i = 0; i < gPnpNodeCount; ++i) { if (gPnpNodes[i]->Pdo == pdo) { KeReleaseSpinlock(&gPnpLock, flags); IoDereferenceDeviceObject(pdo); MmFreePool(node); return NTLSTATUS_ALREADY_EXISTS; } } if (gPnpNodeCount == PNP_MAX_DEVICES) { KeReleaseSpinlock(&gPnpLock, flags); IoDereferenceDeviceObject(pdo); MmFreePool(node); return NTLSTATUS_NO_MEMORY; } gPnpNodes[gPnpNodeCount++] = node; KeReleaseSpinlock(&gPnpLock, flags); return NTLSTATUS_SUCCESS; } void IoPnpPhysicalDevicePublished(DEVICE_OBJECT* pdo) { if (!pdo || !gPnpStarted) return; uintptr_t flags = KeAcquireSpinlock(&gPnpLock); for (uint32_t i = 0; i < gPnpNodeCount; ++i) { if (gPnpNodes[i]->Pdo == pdo) { gPnpNodes[i]->Published = true; break; } } KeReleaseSpinlock(&gPnpLock, flags); IoPnpWake(); } void IoPnpPhysicalDeviceRemoved(DEVICE_OBJECT* pdo) { if (!pdo || !gPnpStarted) return; uintptr_t flags = KeAcquireSpinlock(&gPnpLock); for (uint32_t i = 0; i < gPnpNodeCount; ++i) { if (gPnpNodes[i]->Pdo == pdo) { gPnpNodes[i]->Removed = true; break; } } KeReleaseSpinlock(&gPnpLock, flags); IoPnpWake(); } KSTATUS IoPnpWaitForIdle(uint64_t timeoutMs) { if (!gPnpStarted) return NTLSTATUS_INVALID_PARAMETER; uint64_t deadline = KeMonotonicDeadlineAfterMilliseconds(timeoutMs); for (;;) { bool idle = true; uintptr_t flags = KeAcquireSpinlock(&gPnpLock); for (uint32_t i = 0; i < gPnpNodeCount; ++i) { PNP_NODE* node = gPnpNodes[i]; if (node->State == PnpReserved || node->State == PnpStarting || node->State == PnpPending || node->State == PnpCancelling || node->State == PnpCleaning || node->State == PnpStopping || (node->State == PnpFailed && node->Binding) || (node->Removed && node->State != PnpPending) || (!gPnpShutdown && node->State == PnpUnbound && node->Published && IoPnpFindDriver(node->Pdo))) { idle = false; break; } } KeReleaseSpinlock(&gPnpLock, flags); if (idle) return NTLSTATUS_SUCCESS; uint64_t remaining = KeMonotonicMillisecondsUntil(deadline); if (!remaining) return NTLSTATUS_TIMEOUT; uint32_t index = 0; KSTATUS st = KeWaitForMultipleEvents(&gPnpChanged, 1, false, remaining, &index); if (st == NTLSTATUS_TIMEOUT) return NTLSTATUS_TIMEOUT; if (!KSUCCESS(st)) return st; } } KSTATUS IoPnpShutdown(uint64_t timeoutMs) { if (!gPnpStarted) return NTLSTATUS_INVALID_PARAMETER; uintptr_t flags = KeAcquireSpinlock(&gPnpLock); gPnpShutdown = true; for (uint32_t i = 0; i < gPnpNodeCount; ++i) gPnpNodes[i]->Removed = true; KeReleaseSpinlock(&gPnpLock, flags); IoPnpWake(); return IoPnpWaitForIdle(timeoutMs); } __declspec(dllexport) void* NtlDeviceContext(NTL_DEVICE* device) { return device ? device->Context : (void*)0; } __declspec(dllexport) const char* NtlDeviceId(NTL_DEVICE* device, uint32_t index) { if (!device || index >= device->Node->Pdo->u.Pdo.IdCount) return (const char*)0; return device->Node->Pdo->u.Pdo.Ids[index].Id.Data; } __declspec(dllexport) NTL_BUS_TYPE NtlDeviceBus(NTL_DEVICE* device) { return device ? (NTL_BUS_TYPE)device->Node->Pdo->u.Pdo.BusType : (NTL_BUS_TYPE)0; } __declspec(dllexport) KSTATUS NtlDeviceCompleteStart(NTL_DEVICE* device, KSTATUS result) { if (!device || result == NTLSTATUS_PENDING || !device->Node) return NTLSTATUS_INVALID_PARAMETER; uintptr_t flags = KeAcquireSpinlock(&gPnpLock); PNP_NODE* node = device->Node; if (node->Binding != device || device->CompletionSet || (node->State != PnpStarting && node->State != PnpPending && node->State != PnpCancelling)) { KeReleaseSpinlock(&gPnpLock, flags); return NTLSTATUS_OBJECT_DEAD; } device->CompletionSet = true; device->CompletionStatus = result; if (node->State == PnpPending) { node->State = KSUCCESS(result) ? PnpStarted : PnpFailed; } KeReleaseSpinlock(&gPnpLock, flags); IoPnpWake(); return NTLSTATUS_SUCCESS; } __declspec(dllexport) KSTATUS NtlDeviceOwnResource(NTL_DEVICE* device, void* context, NTL_DEVICE_RESOURCE_RELEASE release, NTL_DEVICE_RESOURCE** out) { if (!device || !release || !out) return NTLSTATUS_INVALID_PARAMETER; *out = (NTL_DEVICE_RESOURCE*)0; NTL_DEVICE_RESOURCE* resource = (NTL_DEVICE_RESOURCE*)MmAllocatePool(sizeof(*resource)); if (!resource) return NTLSTATUS_NO_MEMORY; resource->Owner = device; resource->Context = context; resource->Release = release; uintptr_t flags = KeAcquireSpinlock(&device->Lock); if (device->Closing) { KeReleaseSpinlock(&device->Lock, flags); MmFreePool(resource); return NTLSTATUS_OBJECT_DEAD; } resource->Next = device->Resources; device->Resources = resource; KeReleaseSpinlock(&device->Lock, flags); *out = resource; return NTLSTATUS_SUCCESS; } __declspec(dllexport) KSTATUS NtlDeviceReleaseResource(NTL_DEVICE_RESOURCE* resource) { if (!resource || !resource->Owner) return NTLSTATUS_INVALID_PARAMETER; NTL_DEVICE* device = resource->Owner; uintptr_t flags = KeAcquireSpinlock(&device->Lock); NTL_DEVICE_RESOURCE** link = &device->Resources; while (*link && *link != resource) link = &(*link)->Next; if (*link != resource) { KeReleaseSpinlock(&device->Lock, flags); return NTLSTATUS_OBJECT_DEAD; } *link = resource->Next; resource->Owner = (NTL_DEVICE*)0; KeReleaseSpinlock(&device->Lock, flags); resource->Release(resource->Context); MmFreePool(resource); return NTLSTATUS_SUCCESS; }