Files

595 lines
22 KiB
C

#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <ntlite/ddi/io_driver.h>
#include <ntlite/ddk/device.h>
#include <kernel/io_pnp.h>
#include <kernel/ke_event.h>
#include <kernel/ke_thread.h>
#include <kernel/ke_spinlock.h>
#include <kernel/ke_work.h>
#include <kernel/ke_time.h>
#include <kernel/mm_virt.h>
#include <kernel/ke_debug.h>
#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, &params, (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;
}