windows-nt/Source/XPSP1/NT/base/ntos/verifier/vfirpdb.c
2020-09-26 16:20:57 +08:00

814 lines
20 KiB
C

/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
vfirpdb.c
Abstract:
This module contains functions used to manage the database of IRP tracking
data.
Author:
Adrian J. Oney (adriao) 20-Apr-1998
Environment:
Kernel mode
Revision History:
AdriaO 05/02/2000 - Seperated out from ntos\io\hashirp.c
--*/
#include "vfdef.h"
#include "viirpdb.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, VfIrpDatabaseInit)
#pragma alloc_text(PAGEVRFY, ViIrpDatabaseFindPointer)
#pragma alloc_text(PAGEVRFY, VfIrpDatabaseEntryInsertAndLock)
#pragma alloc_text(PAGEVRFY, VfIrpDatabaseEntryFindAndLock)
#pragma alloc_text(PAGEVRFY, VfIrpDatabaseEntryAcquireLock)
#pragma alloc_text(PAGEVRFY, VfIrpDatabaseEntryReleaseLock)
#pragma alloc_text(PAGEVRFY, VfIrpDatabaseEntryReference)
#pragma alloc_text(PAGEVRFY, VfIrpDatabaseEntryDereference)
#pragma alloc_text(PAGEVRFY, VfIrpDatabaseEntryAppendToChain)
#pragma alloc_text(PAGEVRFY, VfIrpDatabaseEntryRemoveFromChain)
#pragma alloc_text(PAGEVRFY, VfIrpDatabaseEntryGetChainPrevious)
#pragma alloc_text(PAGEVRFY, VfIrpDatabaseEntryGetChainNext)
#pragma alloc_text(PAGEVRFY, ViIrpDatabaseEntryDestroy)
#endif
#define POOL_TAG_IRP_DATABASE 'tToI'
//
// This is our IRP tracking table, a hash table that points to a block of
// data associated with each IRP.
//
PLIST_ENTRY ViIrpDatabase;
KSPIN_LOCK ViIrpDatabaseLock;
/*
* The routines listed below -
* VfIrpDatabaseInit
* VfIrpDatabaseEntryInsertAndLock
* VfIrpDatabaseEntryFindAndLock
* VfIrpDatabaseAcquireLock
* VfIrpDatabaseReleaseLock
* VfIrpDatabaseReference
* VfIrpDatabaseDereference
* VfIrpDatabaseEntryAppendToChain
* VfIrpDatabaseEntryRemoveFromChain
* VfIrpDatabaseEntryGetChainPrevious
* VfIrpDatabaseEntryGetChainNext
* ViIrpDatabaseFindPointer - (internal)
* ViIrpDatabaseEntryDestroy - (internal)
*
* - store and retrieve IRP tracking information from the IRP database. Users
* of the database pass around IOV_DATABASE_HEADER's which are usually part of
* a larger structure. We use a hash table setup to quickly find the IRPs in
* our table.
*
* Each entry in the table has a pointer count and a reference count. The
* pointer count expresses the number of reasons the IRP should be located by
* address. For instance, when an IRP is freed or recycled the pointer count
* would go to zero. The reference count is greater or equal to the pointer
* count, and expresses the number of reasons to keep the data structure around.
* It is fairly common for a database entry to lose it's "pointer" but have a
* non-zero reference count during which time thread stacks may be unwinding.
*
* Another aspect of the IRP database is it supports the "chaining" of
* entries together. Locking an entry automatically locks all entries back to
* the head of the chain. Entries can only be added or removed from the end of
* the chain. This feature is used to support "surrogate" IRPs, where a new
* IRP is sent in place of the IRP originally delivered to a new stack.
*
* Locking semantics:
* There are two locks involved when dealing with IRP database entries, the
* global database lock and the per-entry header lock. No IRP may be removed
* from or inserted into the table without the DatabaseLock being taken. The
* database lock must also be held when the IRP pointer is zeroed due to a newly
* zeroed pointer count. The reference count must be manipulated using
* interlocked operators, as it is may be modified when either lock is held.
* The pointer count on the other hand is only modified with the header lock
* held, and as such does not require interlocked ops.
*
* Perf - The database lock should be replaced with an array of
* VI_DATABASE_HASH_SIZE database locks with little cost.
*/
VOID
FASTCALL
VfIrpDatabaseInit(
VOID
)
/*++
Description:
This routine initializes all the important structures we use to track
IRPs through the hash tables.
Arguments:
None
Return Value:
None
--*/
{
ULONG i;
PAGED_CODE();
KeInitializeSpinLock(&ViIrpDatabaseLock);
//
// As this is system startup code, it is one of the very few places where
// it's ok to use MustSucceed.
//
ViIrpDatabase = (PLIST_ENTRY) ExAllocatePoolWithTag(
NonPagedPoolMustSucceed,
VI_DATABASE_HASH_SIZE * sizeof(LIST_ENTRY),
POOL_TAG_IRP_DATABASE
);
for(i=0; i < VI_DATABASE_HASH_SIZE; i++) {
InitializeListHead(ViIrpDatabase+i);
}
}
PIOV_DATABASE_HEADER
FASTCALL
ViIrpDatabaseFindPointer(
IN PIRP Irp,
OUT PLIST_ENTRY *HashHead
)
/*++
Description:
This routine returns a pointer to a pointer to the Irp tracking data.
This function is meant to be called by other routines in this file.
N.B. The tracking lock is assumed to be held by the caller.
Arguments:
Irp - Irp to locate in the tracking table.
HashHead - If return is non-null, points to the
list head that should be used to insert
the IRP.
Return Value:
IovHeader iff found, NULL otherwise.
--*/
{
PIOV_DATABASE_HEADER iovHeader;
PLIST_ENTRY listEntry, listHead;
UINT_PTR hashIndex;
hashIndex = VI_DATABASE_CALCULATE_HASH(Irp);
ASSERT_SPINLOCK_HELD(&ViIrpDatabaseLock);
*HashHead = listHead = ViIrpDatabase + hashIndex;
for(listEntry = listHead;
listEntry->Flink != listHead;
listEntry = listEntry->Flink) {
iovHeader = CONTAINING_RECORD(listEntry->Flink, IOV_DATABASE_HEADER, HashLink);
if (iovHeader->TrackedIrp == Irp) {
return iovHeader;
}
}
return NULL;
}
BOOLEAN
FASTCALL
VfIrpDatabaseEntryInsertAndLock(
IN PIRP Irp,
IN PFN_IRPDBEVENT_CALLBACK NotificationCallback,
IN OUT PIOV_DATABASE_HEADER IovHeader
)
/*++
Description:
This routine inserts an IovHeader that is associated with the Irp into the
IRP database table. The IRP does not get an initial reference count however.
VfIrpDatabaseEntryReleaseLock must be called to drop the lock taken out.
Arguments:
Irp - Irp to begin tracking.
NotificationCallback - Callback function to invoke for various database
events.
IovHeader - Points to an IovHeader to insert. The IovHeader
fields will be properly initialized by this function.
Return Value:
TRUE if successful, FALSE if driver error detected. On error the passed in
header will have been freed.
--*/
{
KIRQL oldIrql;
PIOV_DATABASE_HEADER iovHeaderPointer;
PLIST_ENTRY hashHead;
ExAcquireSpinLock(&ViIrpDatabaseLock, &oldIrql);
iovHeaderPointer = ViIrpDatabaseFindPointer(Irp, &hashHead);
ASSERT(iovHeaderPointer == NULL);
if (iovHeaderPointer) {
ExReleaseSpinLock(&ViIrpDatabaseLock, oldIrql);
return FALSE;
}
//
// From top to bottom, initialize the fields. Note that there is not a
// "surrogateHead". If any code needs to find out the first entry in the
// circularly linked list of IRPs (the first is the only non-surrogate IRP),
// then HeadPacket should be used. Note that the link to the session is
// stored by the headPacket, more on this later.
//
IovHeader->TrackedIrp = Irp;
KeInitializeSpinLock(&IovHeader->HeaderLock);
IovHeader->ReferenceCount = 1;
IovHeader->PointerCount = 1;
IovHeader->HeaderFlags = 0;
InitializeListHead(&IovHeader->HashLink);
InitializeListHead(&IovHeader->ChainLink);
IovHeader->ChainHead = IovHeader;
IovHeader->NotificationCallback = NotificationCallback;
//
// Place into hash table under lock (with the initial reference count)
//
InsertHeadList(hashHead, &IovHeader->HashLink);
VERIFIER_DBGPRINT((
" VRP CREATE(%x)->%x\n",
Irp,
IovHeader
), 3);
ExReleaseSpinLock(&ViIrpDatabaseLock, oldIrql);
iovHeaderPointer = VfIrpDatabaseEntryFindAndLock(Irp);
ASSERT(iovHeaderPointer == IovHeader);
if (iovHeaderPointer == NULL) {
return FALSE;
} else if (iovHeaderPointer != IovHeader) {
VfIrpDatabaseEntryReleaseLock(iovHeaderPointer);
return FALSE;
}
InterlockedDecrement(&IovHeader->ReferenceCount);
IovHeader->PointerCount--;
ASSERT(IovHeader->PointerCount == 0);
return TRUE;
}
PIOV_DATABASE_HEADER
FASTCALL
VfIrpDatabaseEntryFindAndLock(
IN PIRP Irp
)
/*++
Description:
This routine will return the tracking data for an IRP that is
being tracked without a surrogate or the tracking data for with
a surrogate if the surrogate IRP is what was passed in.
Arguments:
Irp - Irp to find.
Return Value:
IovHeader block, iff above conditions are satified.
--*/
{
KIRQL oldIrql;
PIOV_DATABASE_HEADER iovHeader;
PLIST_ENTRY listHead;
ASSERT(Irp);
ExAcquireSpinLock(&ViIrpDatabaseLock, &oldIrql);
iovHeader = ViIrpDatabaseFindPointer(Irp, &listHead);
if (!iovHeader) {
ExReleaseSpinLock(&ViIrpDatabaseLock, oldIrql);
return NULL;
}
InterlockedIncrement(&iovHeader->ReferenceCount);
ExReleaseSpinLock(&ViIrpDatabaseLock, oldIrql);
VfIrpDatabaseEntryAcquireLock(iovHeader);
iovHeader->LockIrql = oldIrql;
InterlockedDecrement(&iovHeader->ReferenceCount);
//
// Here we check the PointerCount field under the header lock. This might
// be zero if the another thread just unlocked the entry after decrementing
// the pointer count all the way to zero.
//
if (iovHeader->PointerCount == 0) {
//
// This might happen in the following manner:
// 1) IoInitializeIrp is called on an allocated block of pool
// 2) The IRP is first seen by the verifier in IoCallDriver
// 3) The IRP completes, disappearing from the verifier's view
// 4) At that exact moment, the driver calls IoCancelIrp
// The above sequence can occur in a safetly coded driver if the memory
// backing the IRP isn't freed until some event fired. Ie...
// ExAllocatePool
// IoInitializeIrp
// IoCallDriver
// IoCompleteRequest
// IoCancelIrp*
// KeWaitForSingleObject
// ExFreePool
//
//ASSERT(0);
VfIrpDatabaseEntryReleaseLock(iovHeader);
return NULL;
}
VERIFIER_DBGPRINT((
" VRP FIND(%x)->%x\n",
Irp,
iovHeader
), 3);
return iovHeader;
}
VOID
FASTCALL
VfIrpDatabaseEntryAcquireLock(
IN PIOV_DATABASE_HEADER IovHeader OPTIONAL
)
/*++
Description:
This routine is called by to acquire the IRPs tracking data lock.
This function returns at DISPATCH_LEVEL. Callers *must* follow up with
VfIrpDatabaseEntryReleaseLock.
Arguments:
IovHeader - Pointer to the IRP tracking data (or NULL, in which
case this routine does nothing).
Return Value:
None.
--*/
{
KIRQL oldIrql;
PIOV_DATABASE_HEADER iovCurHeader;
if (!IovHeader) {
return;
}
iovCurHeader = IovHeader;
ASSERT(iovCurHeader->ReferenceCount != 0);
while(1) {
ExAcquireSpinLock(&iovCurHeader->HeaderLock, &oldIrql);
iovCurHeader->LockIrql = oldIrql;
if (iovCurHeader == iovCurHeader->ChainHead) {
break;
}
iovCurHeader = CONTAINING_RECORD(
iovCurHeader->ChainLink.Blink,
IOV_DATABASE_HEADER,
ChainLink
);
}
}
VOID
FASTCALL
VfIrpDatabaseEntryReleaseLock(
IN PIOV_DATABASE_HEADER IovHeader
)
/*++
Description:
This routine releases the IRPs tracking data lock and adjusts the ref count
as appropriate. If the reference count drops to zero, the tracking data is
freed.
Arguments:
IovHeader - Pointer to the IRP tracking data.
Return Value:
Nothing.
--*/
{
BOOLEAN freeTrackingData;
PIOV_DATABASE_HEADER iovCurHeader, iovChainHead, iovNextHeader;
KIRQL oldIrql;
//
// Pass one, delink anyone from the tree who's leaving, and assert that
// no surrogates are left after a freed one.
//
iovCurHeader = iovChainHead = IovHeader->ChainHead;
while(1) {
ASSERT_SPINLOCK_HELD(&iovCurHeader->HeaderLock);
iovNextHeader = CONTAINING_RECORD(
iovCurHeader->ChainLink.Flink,
IOV_DATABASE_HEADER,
ChainLink
);
//
// PointerCount is always referenced under the header lock.
//
if (iovCurHeader->PointerCount == 0) {
ExAcquireSpinLock(&ViIrpDatabaseLock, &oldIrql);
//
// This field may be examined only under the database lock.
//
if (iovCurHeader->TrackedIrp) {
iovCurHeader->NotificationCallback(
iovCurHeader,
iovCurHeader->TrackedIrp,
IRPDBEVENT_POINTER_COUNT_ZERO
);
iovCurHeader->TrackedIrp = NULL;
}
ExReleaseSpinLock(&ViIrpDatabaseLock, oldIrql);
}
//
// We now remove any entries that will be leaving from the hash table.
// Note that the ReferenceCount may be incremented outside the header
// lock (but under the database lock) but ReferenceCount can never be
// dropped outside of the IRP lock. Therefore for performance we check
// once and then take the lock to prevent anyone finding it and
// incrementing it.
//
if (iovCurHeader->ReferenceCount == 0) {
ExAcquireSpinLock(&ViIrpDatabaseLock, &oldIrql);
if (iovCurHeader->ReferenceCount == 0) {
ASSERT(iovCurHeader->PointerCount == 0);
/*
ASSERT((iovCurHeader->pIovSessionData == NULL) ||
(iovCurHeader != iovChainHead));
*/
ASSERT((iovNextHeader->ReferenceCount == 0) ||
(iovNextHeader == iovChainHead));
RemoveEntryList(&iovCurHeader->HashLink);
InitializeListHead(&iovCurHeader->HashLink);
}
ExReleaseSpinLock(&ViIrpDatabaseLock, oldIrql);
}
if (iovCurHeader == IovHeader) {
break;
}
iovCurHeader = iovNextHeader;
}
//
// Pass two, drop locks and free neccessary data.
//
iovCurHeader = iovChainHead;
while(1) {
freeTrackingData = (BOOLEAN)IsListEmpty(&iovCurHeader->HashLink);
iovNextHeader = CONTAINING_RECORD(
iovCurHeader->ChainLink.Flink,
IOV_DATABASE_HEADER,
ChainLink
);
ExReleaseSpinLock(&iovCurHeader->HeaderLock, iovCurHeader->LockIrql);
if (freeTrackingData) {
ASSERT(IsListEmpty(&iovCurHeader->ChainLink));
ViIrpDatabaseEntryDestroy(iovCurHeader);
iovCurHeader->NotificationCallback(
iovCurHeader,
iovCurHeader->TrackedIrp,
IRPDBEVENT_REFERENCE_COUNT_ZERO
);
}
if (iovCurHeader == IovHeader) {
break;
}
iovCurHeader = iovNextHeader;
}
}
VOID
FASTCALL
VfIrpDatabaseEntryReference(
IN PIOV_DATABASE_HEADER IovHeader,
IN IOV_REFERENCE_TYPE IovRefType
)
{
ASSERT_SPINLOCK_HELD(&IovHeader->HeaderLock);
VERIFIER_DBGPRINT((
" VRP REF(%x) %x++\n",
IovHeader,
IovHeader->ReferenceCount
), 3);
InterlockedIncrement(&IovHeader->ReferenceCount);
if (IovRefType == IOVREFTYPE_POINTER) {
VERIFIER_DBGPRINT((
" VRP REF2(%x) %x++\n",
IovHeader,
IovHeader->PointerCount
), 3);
IovHeader->PointerCount++;
}
}
VOID
FASTCALL
VfIrpDatabaseEntryDereference(
IN PIOV_DATABASE_HEADER IovHeader,
IN IOV_REFERENCE_TYPE IovRefType
)
{
KIRQL oldIrql;
ASSERT_SPINLOCK_HELD(&IovHeader->HeaderLock);
ASSERT(IovHeader->ReferenceCount > 0);
VERIFIER_DBGPRINT((
" VRP DEREF(%x) %x--\n",
IovHeader,
IovHeader->ReferenceCount
), 3);
if (IovRefType == IOVREFTYPE_POINTER) {
ASSERT(IovHeader->PointerCount > 0);
VERIFIER_DBGPRINT((
" VRP DEREF2(%x) %x--\n",
IovHeader,
IovHeader->PointerCount
), 3);
IovHeader->PointerCount--;
if (IovHeader->PointerCount == 0) {
ExAcquireSpinLock(&ViIrpDatabaseLock, &oldIrql);
IovHeader->NotificationCallback(
IovHeader,
IovHeader->TrackedIrp,
IRPDBEVENT_POINTER_COUNT_ZERO
);
IovHeader->TrackedIrp = NULL;
ExReleaseSpinLock(&ViIrpDatabaseLock, oldIrql);
}
}
InterlockedDecrement(&IovHeader->ReferenceCount);
ASSERT(IovHeader->ReferenceCount >= IovHeader->PointerCount);
}
VOID
FASTCALL
VfIrpDatabaseEntryAppendToChain(
IN OUT PIOV_DATABASE_HEADER IovExistingHeader,
IN OUT PIOV_DATABASE_HEADER IovNewHeader
)
{
ASSERT_SPINLOCK_HELD(&IovExistingHeader->HeaderLock);
ASSERT_SPINLOCK_HELD(&IovNewHeader->HeaderLock);
IovNewHeader->ChainHead = IovExistingHeader->ChainHead;
//
// Fix up IRQL's so spinlocks are released in the right order. Link'm.
//
IovNewHeader->LockIrql = IovExistingHeader->LockIrql;
IovExistingHeader->LockIrql = DISPATCH_LEVEL;
//
// Insert this entry into the chain list
//
InsertTailList(
&IovExistingHeader->ChainHead->ChainLink,
&IovNewHeader->ChainLink
);
}
VOID
FASTCALL
VfIrpDatabaseEntryRemoveFromChain(
IN OUT PIOV_DATABASE_HEADER IovHeader
)
{
PIOV_DATABASE_HEADER iovNextHeader;
ASSERT_SPINLOCK_HELD(&IovHeader->HeaderLock);
//
// It is not legal to remove an entry unless it is at the end of the chain.
// This is illegal because the following entries might not be locked down,
// and the ChainLink must be protected.
//
iovNextHeader = CONTAINING_RECORD(
IovHeader->ChainLink.Flink,
IOV_DATABASE_HEADER,
ChainLink
);
ASSERT(iovNextHeader == IovHeader->ChainHead);
RemoveEntryList(&IovHeader->ChainLink);
InitializeListHead(&IovHeader->ChainLink);
IovHeader->ChainHead = IovHeader;
}
PIOV_DATABASE_HEADER
FASTCALL
VfIrpDatabaseEntryGetChainPrevious(
IN PIOV_DATABASE_HEADER IovHeader
)
{
PIOV_DATABASE_HEADER iovPrevHeader;
ASSERT_SPINLOCK_HELD(&IovHeader->HeaderLock);
if (IovHeader == IovHeader->ChainHead) {
return NULL;
}
iovPrevHeader = CONTAINING_RECORD(
IovHeader->ChainLink.Blink,
IOV_DATABASE_HEADER,
ChainLink
);
return iovPrevHeader;
}
PIOV_DATABASE_HEADER
FASTCALL
VfIrpDatabaseEntryGetChainNext(
IN PIOV_DATABASE_HEADER IovHeader
)
{
PIOV_DATABASE_HEADER iovNextHeader;
ASSERT_SPINLOCK_HELD(&IovHeader->HeaderLock);
iovNextHeader = CONTAINING_RECORD(
IovHeader->ChainLink.Flink,
IOV_DATABASE_HEADER,
ChainLink
);
return (iovNextHeader == IovHeader->ChainHead) ? NULL : iovNextHeader;
}
VOID
FASTCALL
ViIrpDatabaseEntryDestroy(
IN OUT PIOV_DATABASE_HEADER IovHeader
)
/*++
Description:
This routine marks an IovHeader as dead. The header should already have been
removed from the table by a call to VfIrpDatabaseEntryReleaseLock with the
ReferenceCount at 0. This routine is solely here for debugging purposes.
Arguments:
IovHeader - Header to mark dead.
Return Value:
Nope.
--*/
{
//
// The list entry is inited to point back to itself when removed. The
// pointer count should of course still be zero.
//
IovHeader->HeaderFlags |= IOVHEADERFLAG_REMOVED_FROM_TABLE;
ASSERT(IsListEmpty(&IovHeader->HashLink));
//
// with no reference counts...
//
ASSERT(!IovHeader->ReferenceCount);
ASSERT(!IovHeader->PointerCount);
VERIFIER_DBGPRINT((
" VRP FREE(%x)x\n",
IovHeader
), 3);
}