|
|
/*++
Copyright (c) 1989-1995 Microsoft Corporation
Module Name:
callback.c
Abstract:
This module implements the executive callbaqck object. Functions are provided to open, register, unregister , and notify callback objects.
Author:
Ken Reneris (kenr) 7-March-1995
Neill Clift (NeillC) 17-Feb-2001
Added low overhead callbacks for critical components like thread/registry etc. These routines have a high probability of not requiring any locks for an individual call.
Environment:
Kernel mode only.
Revision History:
--*/
#include "exp.h"
//
// Callback Specific Access Rights.
//
#define CALLBACK_MODIFY_STATE 0x0001
#define CALLBACK_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE|\
CALLBACK_MODIFY_STATE )
//
// Event to wait for registration to become idle
//
KEVENT ExpCallbackEvent;
//
// Lock used when fast referencing fails.
//
EX_PUSH_LOCK ExpCallBackFlush;
//
// Debug flag to force certain code paths. Let it get optimized away on free builds.
//
#if DBG
BOOLEAN ExpCallBackReturnRefs = FALSE;
#else
const BOOLEAN ExpCallBackReturnRefs = FALSE;
#endif
//
// Address of callback object type descriptor.
//
POBJECT_TYPE ExCallbackObjectType;
//
// Structure that describes the mapping of generic access rights to object
// specific access rights for callback objects.
//
#ifdef ALLOC_DATA_PRAGMA
#pragma const_seg("INITCONST")
#endif
const GENERIC_MAPPING ExpCallbackMapping = { STANDARD_RIGHTS_READ , STANDARD_RIGHTS_WRITE | CALLBACK_MODIFY_STATE, STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE, CALLBACK_ALL_ACCESS }; #ifdef ALLOC_DATA_PRAGMA
#pragma const_seg()
#endif
//
// Executive callback object structure definition.
//
typedef struct _CALLBACK_OBJECT { ULONG Signature; KSPIN_LOCK Lock; LIST_ENTRY RegisteredCallbacks; BOOLEAN AllowMultipleCallbacks; UCHAR reserved[3]; } CALLBACK_OBJECT , *PCALLBACK_OBJECT;
//
// Executive callback registration structure definition.
//
typedef struct _CALLBACK_REGISTRATION { LIST_ENTRY Link; PCALLBACK_OBJECT CallbackObject; PCALLBACK_FUNCTION CallbackFunction; PVOID CallbackContext; ULONG Busy; BOOLEAN UnregisterWaiting; } CALLBACK_REGISTRATION , *PCALLBACK_REGISTRATION;
VOID ExpDeleteCallback ( IN PCALLBACK_OBJECT CallbackObject );
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, ExpInitializeCallbacks)
#pragma alloc_text(PAGE, ExCreateCallback)
#pragma alloc_text(PAGE, ExpDeleteCallback)
#pragma alloc_text(PAGE, ExInitializeCallBack)
#pragma alloc_text(PAGE, ExCompareExchangeCallBack)
#pragma alloc_text(PAGE, ExCallCallBack)
#pragma alloc_text(PAGE, ExFreeCallBack)
#pragma alloc_text(PAGE, ExAllocateCallBack)
#pragma alloc_text(PAGE, ExReferenceCallBackBlock)
#pragma alloc_text(PAGE, ExGetCallBackBlockRoutine)
#pragma alloc_text(PAGE, ExWaitForCallBacks)
#pragma alloc_text(PAGE, ExGetCallBackBlockContext)
#pragma alloc_text(PAGE, ExDereferenceCallBackBlock)
#endif
BOOLEAN ExpInitializeCallbacks ( )
/*++
Routine Description:
This function creates the callback object type descriptor at system initialization and stores the address of the object type descriptor in local static storage.
Arguments:
None.
Return Value:
A value of TRUE is returned if the timer object type descriptor is successfully initialized. Otherwise a value of FALSE is returned.
--*/
{ OBJECT_TYPE_INITIALIZER ObjectTypeInitializer; OBJECT_ATTRIBUTES ObjectAttributes; NTSTATUS Status; UNICODE_STRING unicodeString; ULONG i; HANDLE handle;
//
// Initialize the slow referencing lock
//
ExInitializePushLock (&ExpCallBackFlush);
//
// Initialize string descriptor.
//
RtlInitUnicodeString(&unicodeString, L"Callback");
//
// Create timer object type descriptor.
//
RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer)); ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer); ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK; ObjectTypeInitializer.GenericMapping = ExpCallbackMapping; ObjectTypeInitializer.DeleteProcedure = ExpDeleteCallback; ObjectTypeInitializer.PoolType = NonPagedPool; ObjectTypeInitializer.ValidAccessMask = CALLBACK_ALL_ACCESS; Status = ObCreateObjectType(&unicodeString, &ObjectTypeInitializer, (PSECURITY_DESCRIPTOR)NULL, &ExCallbackObjectType);
if (!NT_SUCCESS(Status)) { return FALSE; }
RtlInitUnicodeString( &unicodeString, ExpWstrCallback ); InitializeObjectAttributes( &ObjectAttributes, &unicodeString, OBJ_CASE_INSENSITIVE | OBJ_PERMANENT, NULL, SePublicDefaultSd );
Status = NtCreateDirectoryObject( &handle, DIRECTORY_ALL_ACCESS, &ObjectAttributes );
if (!NT_SUCCESS(Status)) { return FALSE; }
NtClose (handle);
//
// Initialize event to wait on for Unregisters which occur while
// notifications are in progress
//
KeInitializeEvent (&ExpCallbackEvent, NotificationEvent, 0);
//
// Initialize NT global callbacks
//
for (i=0; ExpInitializeCallback[i].CallBackObject; i++) {
//
// Create named calledback
//
RtlInitUnicodeString(&unicodeString, ExpInitializeCallback[i].CallbackName);
InitializeObjectAttributes( &ObjectAttributes, &unicodeString, OBJ_PERMANENT | OBJ_CASE_INSENSITIVE, NULL, NULL );
Status = ExCreateCallback ( ExpInitializeCallback[i].CallBackObject, &ObjectAttributes, TRUE, TRUE );
if (!NT_SUCCESS(Status)) { return FALSE; } }
return TRUE; }
NTSTATUS ExCreateCallback ( OUT PCALLBACK_OBJECT * CallbackObject, IN POBJECT_ATTRIBUTES ObjectAttributes, IN BOOLEAN Create, IN BOOLEAN AllowMultipleCallbacks )
/*++
Routine Description:
This function opens a callback object with the specified callback object. If the callback object does not exist or it is a NULL then a callback object will be created if create is TRUE. If a callbackobject is created it will only support multiple registered callbacks if AllowMulitipleCallbacks is TRUE.
Arguments:
CallbackObject - Supplies a pointer to a variable that will receive the Callback object.
CallbackName - Supplies a pointer to a object name that will receive the
Create - Supplies a flag which indicates whether a callback object will be created or not .
AllowMultipleCallbacks - Supplies a flag which indicates only support mulitiple registered callbacks.
Return Value:
NTSTATUS.
--*/
{ PCALLBACK_OBJECT cbObject; NTSTATUS Status; HANDLE Handle;
PAGED_CODE();
//
// Initializing cbObject & Handle is not needed for correctness but without
// it the compiler cannot compile this code W4 to check for use of
// uninitialized variables.
//
Handle = NULL; cbObject = NULL;
//
// If named callback, open handle to it
//
if (ObjectAttributes->ObjectName) { Status = ObOpenObjectByName(ObjectAttributes, ExCallbackObjectType, KernelMode, NULL, 0, // DesiredAccess,
NULL, &Handle); } else { Status = STATUS_UNSUCCESSFUL; }
//
// If not opened, check if callback should be created
//
if (!NT_SUCCESS(Status) && Create ) {
Status = ObCreateObject(KernelMode, ExCallbackObjectType, ObjectAttributes, KernelMode, NULL, sizeof(CALLBACK_OBJECT), 0, 0, (PVOID *)&cbObject );
if(NT_SUCCESS(Status)){
//
// Fill in structure signature
//
cbObject->Signature = 'llaC';
//
// It will support multiple registered callbacks if
// AllowMultipleCallbacks is TRUE.
//
cbObject->AllowMultipleCallbacks = AllowMultipleCallbacks;
//
// Initialize CallbackObject queue.
//
InitializeListHead( &cbObject->RegisteredCallbacks );
//
// Initialize spinlock
//
KeInitializeSpinLock (&cbObject->Lock);
//
// Put the object in the root directory
//
Status = ObInsertObject ( cbObject, NULL, FILE_READ_DATA, 0, NULL, &Handle );
}
}
if(NT_SUCCESS(Status)){
//
// Add one to callback object reference count.
//
Status = ObReferenceObjectByHandle ( Handle, 0, // DesiredAccess
ExCallbackObjectType, KernelMode, &cbObject, NULL );
ZwClose (Handle); }
//
// If success, returns a referenced pointer to the CallbackObject.
//
if (NT_SUCCESS(Status)) { *CallbackObject = cbObject; }
return Status; }
VOID ExpDeleteCallback ( IN PCALLBACK_OBJECT CallbackObject ) { #if !DBG
UNREFERENCED_PARAMETER (CallbackObject); #endif
ASSERT (IsListEmpty(&CallbackObject->RegisteredCallbacks)); }
PVOID ExRegisterCallback ( IN PCALLBACK_OBJECT CallbackObject, IN PCALLBACK_FUNCTION CallbackFunction, IN PVOID CallbackContext )
/*++
Routine Description:
This routine allows a caller to register that it would like to have its callback Function invoked when the callback notification call occurs.
Arguments:
CallbackObject - Supplies a pointer to a CallbackObject.
CallbackFunction - Supplies a pointer to a function which is to be executed when the Callback notification occures.
CallbackContext - Supplies a pointer to an arbitrary data structure that will be passed to the function specified by the CallbackFunction parameter.
Return Value:
Returns handle to callback registration.
--*/ { PCALLBACK_REGISTRATION CallbackRegistration; BOOLEAN Inserted; KIRQL OldIrql;
ASSERT (CallbackFunction); ASSERT (KeGetCurrentIrql() < DISPATCH_LEVEL);
//
// Add reference to object
//
ObReferenceObject (CallbackObject);
//
// Begin by attempting to allocate storage for the CallbackRegistration.
// one cannot be allocated, return the error status.
//
CallbackRegistration = ExAllocatePoolWithTag( NonPagedPool, sizeof( CALLBACK_REGISTRATION ), 'eRBC' );
if( !CallbackRegistration ) { ObDereferenceObject (CallbackObject); return NULL; }
//
// Initialize the callback packet
//
CallbackRegistration->CallbackObject = CallbackObject; CallbackRegistration->CallbackFunction = CallbackFunction; CallbackRegistration->CallbackContext = CallbackContext; CallbackRegistration->Busy = 0; CallbackRegistration->UnregisterWaiting = FALSE;
Inserted = FALSE; KeAcquireSpinLock (&CallbackObject->Lock, &OldIrql);
if( CallbackObject->AllowMultipleCallbacks || IsListEmpty( &CallbackObject->RegisteredCallbacks ) ) {
//
// add CallbackRegistration to tail
//
Inserted = TRUE; InsertTailList( &CallbackObject->RegisteredCallbacks, &CallbackRegistration->Link ); }
KeReleaseSpinLock (&CallbackObject->Lock, OldIrql);
if (!Inserted) { ExFreePool (CallbackRegistration); CallbackRegistration = NULL; }
return (PVOID) CallbackRegistration; }
VOID ExUnregisterCallback ( IN PVOID CbRegistration )
/*++
Routine Description:
This function removes the callback registration for the callbacks from the list of callback object .
Arguments:
CallbackRegistration - Pointer to device object for the file system.
Return Value:
None.
--*/
{ PCALLBACK_REGISTRATION CallbackRegistration; PCALLBACK_OBJECT CallbackObject; KIRQL OldIrql;
ASSERT (KeGetCurrentIrql() < DISPATCH_LEVEL);
CallbackRegistration = (PCALLBACK_REGISTRATION) CbRegistration; CallbackObject = CallbackRegistration->CallbackObject;
KeAcquireSpinLock (&CallbackObject->Lock, &OldIrql);
//
// Wait for registration
//
while (CallbackRegistration->Busy) {
//
// Set waiting flag, then wait. (not performance critical - use
// single global event to wait for any and all unregister waits)
//
CallbackRegistration->UnregisterWaiting = TRUE; KeClearEvent (&ExpCallbackEvent); KeReleaseSpinLock (&CallbackObject->Lock, OldIrql);
KeWaitForSingleObject ( &ExpCallbackEvent, Executive, KernelMode, FALSE, NULL );
//
// Synchronize with callback object and recheck registration busy
//
KeAcquireSpinLock (&CallbackObject->Lock, &OldIrql); }
//
// Registration not busy, remove it from the callback object
//
RemoveEntryList (&CallbackRegistration->Link); KeReleaseSpinLock (&CallbackObject->Lock, OldIrql);
//
// Free memory used for CallbackRegistration
//
ExFreePool (CallbackRegistration);
//
// Remove reference count on CallbackObject
//
ObDereferenceObject (CallbackObject); }
VOID ExNotifyCallback ( IN PCALLBACK_OBJECT CallbackObject, IN PVOID Argument1, IN PVOID Argument2 )
/*++
Routine Description:
This function notifies all registered callbacks .
Arguments:
CallbackObject - supplies a pointer to the callback object should be notified.
SystemArgument1 - supplies a pointer will be passed to callback function.
SystemArgument2 - supplies a pointer will be passed to callback function.
Return Value:
None.
--*/
{ PLIST_ENTRY Link; PCALLBACK_REGISTRATION CallbackRegistration; KIRQL OldIrql;
if (CallbackObject == NULL) { return ; }
//
// Synchronize with callback object
//
KeAcquireSpinLock (&CallbackObject->Lock, &OldIrql);
//
// call registered callbacks at callers IRQL level
// ( done if FIFO order of registration )
//
if (OldIrql == DISPATCH_LEVEL) {
//
// OldIrql is DISPATCH_LEVEL, just invoke all callbacks without
// releasing the lock
//
for (Link = CallbackObject->RegisteredCallbacks.Flink; Link != &CallbackObject->RegisteredCallbacks; Link = Link->Flink) {
//
// Get current registration to notify
//
CallbackRegistration = CONTAINING_RECORD (Link, CALLBACK_REGISTRATION, Link);
//
// Notify reigstration
//
CallbackRegistration->CallbackFunction( CallbackRegistration->CallbackContext, Argument1, Argument2 );
} // next registration
} else {
//
// OldIrql is < DISPATCH_LEVEL, the code being called may be pagable
// and the callback object spinlock needs to be released around
// each registration callback.
//
for (Link = CallbackObject->RegisteredCallbacks.Flink; Link != &CallbackObject->RegisteredCallbacks; Link = Link->Flink ) {
//
// Get current registration to notify
//
CallbackRegistration = CONTAINING_RECORD (Link, CALLBACK_REGISTRATION, Link);
//
// If registration is being removed, don't bothing calling it
//
if (!CallbackRegistration->UnregisterWaiting) {
//
// Set registration busy
//
CallbackRegistration->Busy += 1;
//
// Release SpinLock and notify this callback
//
KeReleaseSpinLock (&CallbackObject->Lock, OldIrql);
CallbackRegistration->CallbackFunction( CallbackRegistration->CallbackContext, Argument1, Argument2 );
//
// Synchronize with CallbackObject
//
KeAcquireSpinLock (&CallbackObject->Lock, &OldIrql);
//
// Remove our busy count
//
CallbackRegistration->Busy -= 1;
//
// If the registriation removal is pending, kick global
// event let unregister conitnue
//
if (CallbackRegistration->UnregisterWaiting && CallbackRegistration->Busy == 0) { KeSetEvent (&ExpCallbackEvent, 0, FALSE); } } } }
//
// Release callback
//
KeReleaseSpinLock (&CallbackObject->Lock, OldIrql); }
VOID ExInitializeCallBack ( IN OUT PEX_CALLBACK CallBack ) /*++
Routine Description:
This function initializes a low overhead callback.
Arguments:
CallBack - Pointer to the callback structure
Return Value:
None.
--*/ { ExFastRefInitialize (&CallBack->RoutineBlock, NULL); }
PEX_CALLBACK_ROUTINE_BLOCK ExAllocateCallBack ( IN PEX_CALLBACK_FUNCTION Function, IN PVOID Context ) /*++
Routine Description:
This function allocates a low overhead callback.
Arguments:
Function - Routine to issue callbacks to Context - A context value to issue
Return Value:
PEX_CALLBACK_ROUTINE_BLOCK - Allocated block or NULL if allocation fails.
--*/ { PEX_CALLBACK_ROUTINE_BLOCK NewBlock;
NewBlock = ExAllocatePoolWithTag (PagedPool, sizeof (EX_CALLBACK_ROUTINE_BLOCK), 'brbC'); if (NewBlock != NULL) { NewBlock->Function = Function; NewBlock->Context = Context; ExInitializeRundownProtection (&NewBlock->RundownProtect); } return NewBlock; }
VOID ExFreeCallBack ( IN PEX_CALLBACK_ROUTINE_BLOCK CallBackBlock ) /*++
Routine Description:
This function destroys a low overhead callback block.
Arguments:
CallBackBlock - Call back block to destroy
Return Value:
None.
--*/ {
ExFreePool (CallBackBlock); }
VOID ExWaitForCallBacks ( IN PEX_CALLBACK_ROUTINE_BLOCK CallBackBlock ) /*++
Routine Description:
This function waits for all outcalls on the specified callback block to complete
Arguments:
CallBackBlock - Call back block to wait for
Return Value:
None.
--*/ { //
// Wait for all active callbacks to be finished.
//
ExWaitForRundownProtectionRelease (&CallBackBlock->RundownProtect); }
BOOLEAN ExCompareExchangeCallBack ( IN OUT PEX_CALLBACK CallBack, IN PEX_CALLBACK_ROUTINE_BLOCK NewBlock, IN PEX_CALLBACK_ROUTINE_BLOCK OldBlock ) /*++
Routine Description:
This function assigns, removes or swaps a low overhead callback function.
Arguments:
CallBack - Callback structure to be modified
NewBlock - New block to be installed in the callback
OldBlock - The old block that must be there now to be replaced
Return Value:
BOOLEAN - TRUE: The swap occured, FALSE: The swap failed
--*/ { EX_FAST_REF OldRef; PEX_CALLBACK_ROUTINE_BLOCK ReplacedBlock;
if (NewBlock != NULL) { //
// Add the additional references to the routine block
//
if (!ExAcquireRundownProtectionEx (&NewBlock->RundownProtect, ExFastRefGetAdditionalReferenceCount () + 1)) { ASSERTMSG ("Callback block is already undergoing rundown", FALSE); return FALSE; } }
//
// Attempt to replace the existing object and balance all the reference counts
//
OldRef = ExFastRefCompareSwapObject (&CallBack->RoutineBlock, NewBlock, OldBlock);
ReplacedBlock = ExFastRefGetObject (OldRef);
//
// See if the swap occured. If it didn't undo the original references we added.
// If it did then release remaining references on the original
//
if (ReplacedBlock == OldBlock) { PKTHREAD CurrentThread; //
// We need to flush out any slow referencers at this point. We do this by
// acquiring and releasing a lock.
//
if (ReplacedBlock != NULL) { CurrentThread = KeGetCurrentThread ();
KeEnterCriticalRegionThread (CurrentThread);
ExAcquireReleasePushLockExclusive (&ExpCallBackFlush);
KeLeaveCriticalRegionThread (CurrentThread);
ExReleaseRundownProtectionEx (&ReplacedBlock->RundownProtect, ExFastRefGetUnusedReferences (OldRef) + 1);
} return TRUE; } else { //
// The swap failed. Remove the addition references if we had added any.
//
if (NewBlock != NULL) { ExReleaseRundownProtectionEx (&NewBlock->RundownProtect, ExFastRefGetAdditionalReferenceCount () + 1); } return FALSE; } }
PEX_CALLBACK_ROUTINE_BLOCK ExReferenceCallBackBlock ( IN OUT PEX_CALLBACK CallBack ) /*++
Routine Description:
This function takes a reference on the call back block inside the callback structure.
Arguments:
CallBack - Call back to obtain the call back block from
Return Value:
PEX_CALLBACK_ROUTINE_BLOCK - Referenced structure or NULL if these wasn't one
--*/ { EX_FAST_REF OldRef; PEX_CALLBACK_ROUTINE_BLOCK CallBackBlock;
//
// Get a reference to the callback block if we can.
//
OldRef = ExFastReference (&CallBack->RoutineBlock);
//
// If there is no callback then return
//
if (ExFastRefObjectNull (OldRef)) { return NULL; } //
// If we didn't get a reference then use a lock to get one.
//
if (!ExFastRefCanBeReferenced (OldRef)) { PKTHREAD CurrentThread; CurrentThread = KeGetCurrentThread ();
KeEnterCriticalRegionThread (CurrentThread);
ExAcquirePushLockExclusive (&ExpCallBackFlush);
CallBackBlock = ExFastRefGetObject (CallBack->RoutineBlock); if (CallBackBlock && !ExAcquireRundownProtection (&CallBackBlock->RundownProtect)) { CallBackBlock = NULL; }
ExReleasePushLockExclusive (&ExpCallBackFlush);
KeLeaveCriticalRegionThread (CurrentThread);
if (CallBackBlock == NULL) { return NULL; }
} else { CallBackBlock = ExFastRefGetObject (OldRef);
//
// If we just removed the last reference then attempt fix it up.
//
if (ExFastRefIsLastReference (OldRef) && !ExpCallBackReturnRefs) { ULONG RefsToAdd;
RefsToAdd = ExFastRefGetAdditionalReferenceCount ();
//
// If we can't add the references then just give up
//
if (ExAcquireRundownProtectionEx (&CallBackBlock->RundownProtect, RefsToAdd)) { //
// Repopulate the cached refs. If this fails we just give them back.
//
if (!ExFastRefAddAdditionalReferenceCounts (&CallBack->RoutineBlock, CallBackBlock, RefsToAdd)) { ExReleaseRundownProtectionEx (&CallBackBlock->RundownProtect, RefsToAdd); } } } }
return CallBackBlock; }
PEX_CALLBACK_FUNCTION ExGetCallBackBlockRoutine ( IN PEX_CALLBACK_ROUTINE_BLOCK CallBackBlock ) /*++
Routine Description:
This function gets the routine associated with a call back block
Arguments:
CallBackBlock - Call back block to obtain routine for
Return Value:
PEX_CALLBACK_FUNCTION - The function pointer associated with this block
--*/ { return CallBackBlock->Function; }
PVOID ExGetCallBackBlockContext ( IN PEX_CALLBACK_ROUTINE_BLOCK CallBackBlock ) /*++
Routine Description:
This function gets the context associated with a call back block
Arguments:
CallBackBlock - Call back block to obtain context for
Return Value:
PVOID - The context associated with this block
--*/ { return CallBackBlock->Context; }
VOID ExDereferenceCallBackBlock ( IN OUT PEX_CALLBACK CallBack, IN PEX_CALLBACK_ROUTINE_BLOCK CallBackBlock ) /*++
Routine Description:
This returns a reference previous obtained on a call back block
Arguments:
CallBackBlock - Call back block to return reference to
Return Value:
None
--*/ { if (ExpCallBackReturnRefs || !ExFastRefDereference (&CallBack->RoutineBlock, CallBackBlock)) { ExReleaseRundownProtection (&CallBackBlock->RundownProtect); } }
NTSTATUS ExCallCallBack ( IN OUT PEX_CALLBACK CallBack, IN PVOID Argument1, IN PVOID Argument2 ) /*++
Routine Description:
This function calls the callback thats inside a callback structure
Arguments:
CallBack - Call back that needs to be called through
Argument1 - Caller provided argument to pass on
Argument2 - Caller provided argument to pass on
Return Value:
NTSTATUS - Status returned by callback or STATUS_SUCCESS if theres wasn't one
--*/ { PEX_CALLBACK_ROUTINE_BLOCK CallBackBlock; NTSTATUS Status;
CallBackBlock = ExReferenceCallBackBlock (CallBack); if (CallBackBlock) { //
// Call the function
//
Status = CallBackBlock->Function (CallBackBlock->Context, Argument1, Argument2);
ExDereferenceCallBackBlock (CallBack, CallBackBlock); } else { Status = STATUS_SUCCESS; } return Status; }
|