|
|
/*************************************************************************
* * timer.c * * This module contains the ICA timer routines. * * Copyright 1998, Microsoft. * *************************************************************************/
/*
* Includes */ #include <precomp.h>
#pragma hdrstop
#include <ntddkbd.h>
#include <ntddmou.h>
/*
* Local structures */ typedef VOID (*PICATIMERFUNC)( PVOID, PVOID );
typedef struct _ICA_WORK_ITEM { LIST_ENTRY Links; WORK_QUEUE_ITEM WorkItem; PICATIMERFUNC pFunc; PVOID pParam; PSDLINK pSdLink; ULONG LockFlags; ULONG fCanceled: 1; } ICA_WORK_ITEM, *PICA_WORK_ITEM;
/*
* Timer structure */ typedef struct _ICA_TIMER { LONG RefCount; KTIMER kTimer; KDPC TimerDpc; PSDLINK pSdLink; LIST_ENTRY WorkItemListHead; } ICA_TIMER, * PICA_TIMER;
/*
* Local procedure prototypes */ VOID _IcaTimerDpc( IN struct _KDPC *Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 );
VOID _IcaDelayedWorker( IN PVOID WorkerContext );
BOOLEAN _IcaCancelTimer( PICA_TIMER pTimer, PICA_WORK_ITEM *ppWorkItem );
VOID _IcaReferenceTimer( PICA_TIMER pTimer );
VOID _IcaDereferenceTimer( PICA_TIMER pTimer );
NTSTATUS IcaExceptionFilter( IN PWSTR OutputString, IN PEXCEPTION_POINTERS pexi );
/*******************************************************************************
* * IcaTimerCreate * * Create a timer * * * ENTRY: * pContext (input) * Pointer to SDCONTEXT of caller * phTimer (output) * address to return timer handle * * EXIT: * STATUS_SUCCESS - no error * ******************************************************************************/
NTSTATUS IcaTimerCreate( IN PSDCONTEXT pContext, OUT PVOID * phTimer ) { PICA_TIMER pTimer; NTSTATUS Status;
/*
* Allocate timer object and initialize it */ pTimer = ICA_ALLOCATE_POOL( NonPagedPool, sizeof(ICA_TIMER) ); if ( pTimer == NULL ) return( STATUS_NO_MEMORY ); RtlZeroMemory( pTimer, sizeof(ICA_TIMER) ); pTimer->RefCount = 1; KeInitializeTimer( &pTimer->kTimer ); KeInitializeDpc( &pTimer->TimerDpc, _IcaTimerDpc, pTimer ); pTimer->pSdLink = CONTAINING_RECORD( pContext, SDLINK, SdContext ); InitializeListHead( &pTimer->WorkItemListHead );
TRACESTACK(( pTimer->pSdLink->pStack, TC_ICADD, TT_API3, "ICADD: TimerCreate: %08x\n", pTimer ));
*phTimer = (PVOID) pTimer; return( STATUS_SUCCESS ); }
/*******************************************************************************
* * IcaTimerStart * * Start a timer * * * ENTRY: * TimerHandle (input) * timer handle * pFunc (input) * address of procedure to call when timer expires * pParam (input) * parameter to pass to procedure * TimeLeft (input) * relative time until timer expires (1/1000 seconds) * LockFlags (input) * Bit flags to specify which (if any) stack locks to obtain * * EXIT: * TRUE : timer was already armed and had to be canceled * FALSE : timer was not armed * ******************************************************************************/
BOOLEAN IcaTimerStart( IN PVOID TimerHandle, IN PVOID pFunc, IN PVOID pParam, IN ULONG TimeLeft, IN ULONG LockFlags ) { PICA_TIMER pTimer = (PICA_TIMER)TimerHandle; KIRQL oldIrql; PICA_WORK_ITEM pWorkItem; LARGE_INTEGER DueTime; BOOLEAN bCanceled, bSet; TRACESTACK(( pTimer->pSdLink->pStack, TC_ICADD, TT_API3, "ICADD: TimerStart: %08x, Time %08x, pFunc %08x (%08x)\n", TimerHandle, TimeLeft, pFunc, pParam ));
ASSERT( ExIsResourceAcquiredExclusiveLite( &pTimer->pSdLink->pStack->Resource ) );
/*
* Cancel the timer if it currently armed, * and get the current workitem and reuse it if there was one. */ bCanceled = _IcaCancelTimer( pTimer, &pWorkItem );
/*
* Initialize the ICA work item (allocate one first if there isn't one). */ if ( pWorkItem == NULL ) { pWorkItem = ICA_ALLOCATE_POOL( NonPagedPool, sizeof(ICA_WORK_ITEM) ); if ( pWorkItem == NULL ) { return( FALSE ); } }
pWorkItem->pFunc = pFunc; pWorkItem->pParam = pParam; pWorkItem->pSdLink = pTimer->pSdLink; pWorkItem->LockFlags = LockFlags; pWorkItem->fCanceled = FALSE; ExInitializeWorkItem( &pWorkItem->WorkItem, _IcaDelayedWorker, pWorkItem );
/*
* If the timer was NOT canceled above (we are setting it for * the first time), then reference the SDLINK object on behalf * of the timer thread. */ if ( !bCanceled ) IcaReferenceSdLink( pTimer->pSdLink );
/*
* If timer should run immediately, then just queue the * workitem to an ExWorker thread now. */ if ( TimeLeft == 0 ) {
ExQueueWorkItem( &pWorkItem->WorkItem, CriticalWorkQueue );
} else { /*
* Convert timer time from milliseconds to system relative time */ DueTime = RtlEnlargedIntegerMultiply( TimeLeft, -10000 );
/*
* Increment the timer reference count, * insert the workitem onto the workitem list, * and arm the timer. */ _IcaReferenceTimer( pTimer ); IcaAcquireSpinLock( &IcaSpinLock, &oldIrql ); InsertTailList( &pTimer->WorkItemListHead, &pWorkItem->Links ); IcaReleaseSpinLock( &IcaSpinLock, oldIrql ); bSet = KeSetTimer( &pTimer->kTimer, DueTime, &pTimer->TimerDpc ); ASSERT( !bSet ); }
return( bCanceled ); }
/*******************************************************************************
* * IcaTimerCancel * * cancel the specified timer * * * ENTRY: * TimerHandle (input) * timer handle * * EXIT: * TRUE : timer was actually canceled * FALSE : timer was not armed * ******************************************************************************/
BOOLEAN IcaTimerCancel( IN PVOID TimerHandle ) { PICA_TIMER pTimer = (PICA_TIMER)TimerHandle; BOOLEAN bCanceled;
TRACESTACK(( pTimer->pSdLink->pStack, TC_ICADD, TT_API3, "ICADD: TimerCancel: %08x\n", pTimer ));
ASSERT( ExIsResourceAcquiredExclusiveLite( &pTimer->pSdLink->pStack->Resource ) );
/*
* Cancel timer if it is enabled */ bCanceled = _IcaCancelTimer( pTimer, NULL ); if ( bCanceled ) IcaDereferenceSdLink( pTimer->pSdLink );
return( bCanceled ); }
/*******************************************************************************
* * IcaTimerClose * * cancel the specified timer * * * ENTRY: * TimerHandle (input) * timer handle * * EXIT: * TRUE : timer was actually canceled * FALSE : timer was not armed * ******************************************************************************/
BOOLEAN IcaTimerClose( IN PVOID TimerHandle ) { PICA_TIMER pTimer = (PICA_TIMER)TimerHandle; BOOLEAN bCanceled;
TRACESTACK(( pTimer->pSdLink->pStack, TC_ICADD, TT_API3, "ICADD: TimerClose: %08x\n", pTimer ));
ASSERT( ExIsResourceAcquiredExclusiveLite( &pTimer->pSdLink->pStack->Resource ) );
/*
* Cancel timer if it is enabled */ bCanceled = IcaTimerCancel( TimerHandle );
/*
* Decrement timer reference * (the last reference will free the object) */ //ASSERT( pTimer->RefCount == 1 );
//ASSERT( IsListEmpty( &pTimer->WorkItemListHead ) );
_IcaDereferenceTimer( pTimer ); return( bCanceled ); }
/*******************************************************************************
* * IcaQueueWorkItemEx, IcaQueueWorkItem. * * Queue a work item for async execution * * REM: IcaQueueWorkItemEx is the new API. It allows the caller to preallocate * the ICA_WORK_ITEM. IcaQueueWorkItem is left there for lecacy drivers that have not * been compiled with the new library not to crash the system. * * ENTRY: * pContext (input) * Pointer to SDCONTEXT of caller * pFunc (input) * address of procedure to call when timer expires * pParam (input) * parameter to pass to procedure * LockFlags (input) * Bit flags to specify which (if any) stack locks to obtain * * EXIT: * STATUS_SUCCESS - no error * ******************************************************************************/
NTSTATUS IcaQueueWorkItem( IN PSDCONTEXT pContext, IN PVOID pFunc, IN PVOID pParam, IN ULONG LockFlags ) { PSDLINK pSdLink; PICA_WORK_ITEM pWorkItem;
NTSTATUS Status;
Status = IcaQueueWorkItemEx( pContext, pFunc, pParam, LockFlags, NULL ); return Status; }
NTSTATUS IcaQueueWorkItemEx( IN PSDCONTEXT pContext, IN PVOID pFunc, IN PVOID pParam, IN ULONG LockFlags, IN PVOID pIcaWorkItem ) { PSDLINK pSdLink; PICA_WORK_ITEM pWorkItem = (PICA_WORK_ITEM) pIcaWorkItem; pSdLink = CONTAINING_RECORD( pContext, SDLINK, SdContext );
/*
* Allocate the ICA work item if not yet allocated and initialize it. */ if (pWorkItem == NULL) { pWorkItem = ICA_ALLOCATE_POOL( NonPagedPool, sizeof(ICA_WORK_ITEM) ); if ( pWorkItem == NULL ) return( STATUS_NO_MEMORY ); }
pWorkItem->pFunc = pFunc; pWorkItem->pParam = pParam; pWorkItem->pSdLink = pSdLink; pWorkItem->LockFlags = LockFlags; ExInitializeWorkItem( &pWorkItem->WorkItem, _IcaDelayedWorker, pWorkItem );
/*
* Reference the SDLINK object on behalf of the delayed worker routine. */ IcaReferenceSdLink( pSdLink );
/*
* Queue work item to an ExWorker thread. */ ExQueueWorkItem( &pWorkItem->WorkItem, CriticalWorkQueue );
return( STATUS_SUCCESS ); }
/*******************************************************************************
* * IcaAllocateWorkItem. * * Allocate ICA_WORK_ITEM structure to queue a workitem. * * REM: The main reason to allocate this in termdd (instead of doing it * in the caller is to keep ICA_WORK_ITEM an internal termdd structure that is * opaque for protocol drivers. There is no need for an IcaFreeWorkItem() API in * termdd since the deallocation is transparently done in termdd once the workitem * has been delivered. * * ENTRY: * pParam (output) : pointer to return allocated workitem * * EXIT: * STATUS_SUCCESS - no error * ******************************************************************************/
NTSTATUS IcaAllocateWorkItem( OUT PVOID *pParam ) { PICA_WORK_ITEM pWorkItem;
*pParam = ICA_ALLOCATE_POOL( NonPagedPool, sizeof(ICA_WORK_ITEM) ); if ( *pParam == NULL ){ return( STATUS_NO_MEMORY ); } return STATUS_SUCCESS; }
/*******************************************************************************
* * _IcaTimerDpc * * Ica timer DPC routine. * * * ENTRY: * Dpc (input) * Unused * * DeferredContext (input) * Pointer to ICA_TIMER object. * * SystemArgument1 (input) * Unused * * SystemArgument2 (input) * Unused * * EXIT: * nothing * ******************************************************************************/
VOID _IcaTimerDpc( IN struct _KDPC *Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) { PICA_TIMER pTimer = (PICA_TIMER)DeferredContext; KIRQL oldIrql; PLIST_ENTRY Head; PICA_WORK_ITEM pWorkItem;
/*
* Acquire spinlock and remove the first workitem from the list */ IcaAcquireSpinLock( &IcaSpinLock, &oldIrql );
Head = RemoveHeadList( &pTimer->WorkItemListHead ); pWorkItem = CONTAINING_RECORD( Head, ICA_WORK_ITEM, Links );
IcaReleaseSpinLock( &IcaSpinLock, oldIrql );
/*
* If workitem has been canceled, just free the memory now. */ if ( pWorkItem->fCanceled ) {
ICA_FREE_POOL( pWorkItem );
/*
* Otherwise, queue workitem to an ExWorker thread. */ } else {
ExQueueWorkItem( &pWorkItem->WorkItem, CriticalWorkQueue ); }
_IcaDereferenceTimer( pTimer ); }
/*******************************************************************************
* * _IcaDelayedWorker * * Ica delayed worker routine. * * * ENTRY: * WorkerContext (input) * Pointer to ICA_WORK_ITEM object. * * EXIT: * nothing * ******************************************************************************/
VOID _IcaDelayedWorker( IN PVOID WorkerContext ) { PICA_CONNECTION pConnect; PICA_WORK_ITEM pWorkItem = (PICA_WORK_ITEM)WorkerContext; PICA_STACK pStack = pWorkItem->pSdLink->pStack; NTSTATUS Status;
/*
* Obtain any required locks before calling the worker routine. */ if ( pWorkItem->LockFlags & ICALOCK_IO ) { pConnect = IcaLockConnectionForStack( pStack ); } if ( pWorkItem->LockFlags & ICALOCK_DRIVER ) { IcaLockStack( pStack ); }
/*
* Call the worker routine. */ try { (*pWorkItem->pFunc)( pWorkItem->pSdLink->SdContext.pContext, pWorkItem->pParam ); } except( IcaExceptionFilter( L"_IcaDelayedWorker TRAPPED!!", GetExceptionInformation() ) ) { Status = GetExceptionCode(); }
/*
* Release any locks acquired above. */ if ( pWorkItem->LockFlags & ICALOCK_DRIVER ) { IcaUnlockStack( pStack ); } if ( pWorkItem->LockFlags & ICALOCK_IO ) { IcaUnlockConnection( pConnect ); }
/*
* Dereference the SDLINK object now. * This undoes the reference that was made on our behalf in the * IcaTimerStart or IcaQueueWorkItem routine. */ IcaDereferenceSdLink( pWorkItem->pSdLink );
/*
* Free the ICA_WORK_ITEM memory block. */ ICA_FREE_POOL( pWorkItem ); }
BOOLEAN _IcaCancelTimer( PICA_TIMER pTimer, PICA_WORK_ITEM *ppWorkItem ) { KIRQL oldIrql; PLIST_ENTRY Tail; PICA_WORK_ITEM pWorkItem; BOOLEAN bCanceled;
/*
* Get IcaSpinLock to in order to cancel any previous timer */ IcaAcquireSpinLock( &IcaSpinLock, &oldIrql );
/*
* See if the timer is currently armed. * The timer is armed if the workitem list is non-empty and * the tail entry is not marked canceled. */ if ( !IsListEmpty( &pTimer->WorkItemListHead ) && (Tail = pTimer->WorkItemListHead.Blink) && (pWorkItem = CONTAINING_RECORD( Tail, ICA_WORK_ITEM, Links )) && !pWorkItem->fCanceled ) {
/*
* If the timer can be canceled, remove the workitem from the list * and decrement the reference count for the timer. */ if ( KeCancelTimer( &pTimer->kTimer ) ) { RemoveEntryList( &pWorkItem->Links ); pTimer->RefCount--; ASSERT( pTimer->RefCount > 0 );
/*
* The timer was armed but could not be canceled. * On a MP system, its possible for this to happen and the timer * DPC can be executing on another CPU in parallel with this code. * * Mark the workitem as canceled, * but leave it for the timer DPC routine to cleanup. */ } else { pWorkItem->fCanceled = TRUE; pWorkItem = NULL; }
/*
* Indicate we (effectively) canceled the timer */ bCanceled = TRUE;
/*
* No timer is armed */ } else { pWorkItem = NULL; bCanceled = FALSE; }
/*
* Release IcaSpinLock now */ IcaReleaseSpinLock( &IcaSpinLock, oldIrql );
if ( ppWorkItem ) { *ppWorkItem = pWorkItem; } else if ( pWorkItem ) { ICA_FREE_POOL( pWorkItem ); }
return( bCanceled ); }
VOID _IcaReferenceTimer( PICA_TIMER pTimer ) {
ASSERT( pTimer->RefCount >= 0 );
/*
* Increment the reference count */ if ( InterlockedIncrement( &pTimer->RefCount) <= 0 ) { ASSERT( FALSE ); } }
VOID _IcaDereferenceTimer( PICA_TIMER pTimer ) {
ASSERT( pTimer->RefCount > 0 );
/*
* Decrement the reference count * If it is 0 then free the timer now. */ if ( InterlockedDecrement( &pTimer->RefCount) == 0 ) { ASSERT( IsListEmpty( &pTimer->WorkItemListHead ) ); ICA_FREE_POOL( pTimer ); } }
|