/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

    NtTimer.c

Abstract:

    This module implements the nt version of the timer and worker thread management routines.
    These services are provided to all mini redirector writers. The timer service comes in two
    flavours - a periodic trigger and a one shot notification.

Author:

    Joe Linn     [JoeLinn]   2-mar-95

Revision History:

    Balan Sethu Raman [SethuR] 7-Mar-95
         Included one shot, periodic notification for work queue items.

--*/

#include "precomp.h"
#pragma hdrstop

#ifdef  ALLOC_PRAGMA
#pragma alloc_text(PAGE, RxInitializeRxTimer)
#pragma alloc_text(PAGE, RxTearDownRxTimer)
#pragma alloc_text(PAGE, RxPostRecurrentTimerRequest)
#pragma alloc_text(PAGE, RxRecurrentTimerWorkItemDispatcher)
#endif

typedef struct _RX_RECURRENT_WORK_ITEM_ {
   RX_WORK_ITEM               WorkItem;
   LIST_ENTRY                 RecurrentWorkItemsList;
   LARGE_INTEGER              TimeInterval;
   PRX_WORKERTHREAD_ROUTINE   Routine;
   PVOID                      pContext;
} RX_RECURRENT_WORK_ITEM, *PRX_RECURRENT_WORK_ITEM;

//
// Forward declarations of routines
//

extern VOID
RxTimerDispatch(
    IN PKDPC Dpc,
    IN PVOID DeferredContext,
    IN PVOID SystemArgument1,
    IN PVOID SystemArgument2
    );

extern VOID
RxRecurrentTimerWorkItemDispatcher (
    IN PVOID Context
    );


//  The Bug check file id for this module
#define BugCheckFileId  (RDBSS_BUG_CHECK_NTTIMER)


//  The local trace mask for this part of the module
#define Dbg                              (DEBUG_TRACE_NTTIMER)

LARGE_INTEGER s_RxTimerInterval;
KSPIN_LOCK    s_RxTimerLock;
KDPC          s_RxTimerDpc;
LIST_ENTRY    s_RxTimerQueueHead;  // queue of the list of timer calls
LIST_ENTRY    s_RxRecurrentWorkItemsList;
KTIMER        s_RxTimer;
ULONG         s_RxTimerTickCount;

#define NoOf100nsTicksIn1ms  (10 * 1000)
#define NoOf100nsTicksIn55ms (10 * 1000 * 55)

NTSTATUS
RxInitializeRxTimer()
/*++

Routine Description:

    The routine initializes everything having to do with the timer stuff.

Arguments:

    none

Return Value:

    none

--*/
{
    NTSTATUS Status = STATUS_SUCCESS;

    PAGED_CODE();

    s_RxTimerInterval.LowPart = (ULONG)(-((LONG)NoOf100nsTicksIn55ms));
    s_RxTimerInterval.HighPart = -1;

    KeInitializeSpinLock( &s_RxTimerLock );

    InitializeListHead( &s_RxTimerQueueHead );
    InitializeListHead( &s_RxRecurrentWorkItemsList );

    KeInitializeDpc( &s_RxTimerDpc, RxTimerDispatch, NULL );
    KeInitializeTimer( &s_RxTimer );

    s_RxTimerTickCount = 0;

    return Status;
}


VOID
RxTearDownRxTimer(
    void)
/*++

Routine Description:

    This routine is used by drivers to initialize a timer entry for a device
    object.

Arguments:

    TimerEntry - Pointer to a timer entry to be used.

    TimerRoutine - Driver routine to be executed when timer expires.

    Context - Context parameter that is passed to the driver routine.

Return Value:

    The function value indicates whether or not the timer was initialized.

--*/

{
    PRX_RECURRENT_WORK_ITEM pWorkItem;
    PLIST_ENTRY             pListEntry;

    PAGED_CODE();

    KeCancelTimer( &s_RxTimer );

    // Walk down the list freeing up the recurrent requests since the memory was
    // allocated by us.
    while (!IsListEmpty(&s_RxRecurrentWorkItemsList)) {
        pListEntry = RemoveHeadList(&s_RxRecurrentWorkItemsList);
        pWorkItem  = (PRX_RECURRENT_WORK_ITEM)
                     CONTAINING_RECORD(
                         pListEntry,
                         RX_RECURRENT_WORK_ITEM,
                         RecurrentWorkItemsList);
        RxFreePool(pWorkItem);
    }
}

VOID
RxTimerDispatch(
    IN PKDPC Dpc,
    IN PVOID DeferredContext,
    IN PVOID SystemArgument1,
    IN PVOID SystemArgument2
    )
/*++

Routine Description:

    This routine scans the  timer database and posts a work item for all those requests
    whose temporal constraints have been satisfied.

Arguments:

    Dpc - Supplies a pointer to a control object of type DPC.

    DeferredContext - Optional deferred context;  not used.

    SystemArgument1 - Optional argument 1;  not used.

    SystemArgument2 - Optional argument 2;  not used.

Return Value:

    None.

--*/
{
    PLIST_ENTRY      pListEntry;
    LIST_ENTRY       ExpiredList;

    //KIRQL            Irql;
    BOOLEAN          ContinueTimer = FALSE;

    PRX_WORK_QUEUE_ITEM pWorkQueueItem;
    PRX_WORK_ITEM       pWorkItem;

    UNREFERENCED_PARAMETER( Dpc );
    UNREFERENCED_PARAMETER( DeferredContext );
    UNREFERENCED_PARAMETER( SystemArgument1 );
    UNREFERENCED_PARAMETER( SystemArgument2 );

    InitializeListHead(&ExpiredList);

    KeAcquireSpinLockAtDpcLevel( &s_RxTimerLock );

    s_RxTimerTickCount++;

    pListEntry = s_RxTimerQueueHead.Flink;

    while (pListEntry != &s_RxTimerQueueHead) {
        pWorkQueueItem = CONTAINING_RECORD(
                             pListEntry,
                             RX_WORK_QUEUE_ITEM,
                             List );
        pWorkItem      = CONTAINING_RECORD(
                             pWorkQueueItem,
                             RX_WORK_ITEM,
                             WorkQueueItem);

        if (pWorkItem->LastTick == s_RxTimerTickCount) {
           PLIST_ENTRY pExpiredEntry = pListEntry;
           pListEntry = pListEntry->Flink;

           RemoveEntryList(pExpiredEntry);
           InsertTailList(&ExpiredList,pExpiredEntry);
        } else {
           pListEntry = pListEntry->Flink;
        }
    }

    ContinueTimer = !(IsListEmpty(&s_RxTimerQueueHead));
    KeReleaseSpinLockFromDpcLevel( &s_RxTimerLock );

    // Resubmit the timer queue dispatch routine so that it will be reinvoked.
    if (ContinueTimer)
        KeSetTimer( &s_RxTimer, s_RxTimerInterval, &s_RxTimerDpc );

    // Queue all the expired entries on the worker threads.
    while (!IsListEmpty(&ExpiredList)) {
        pListEntry = RemoveHeadList(&ExpiredList);
        pListEntry->Flink = pListEntry->Blink = NULL;

        pWorkQueueItem = CONTAINING_RECORD(
                             pListEntry,
                             RX_WORK_QUEUE_ITEM,
                             List );

        // Post the work item to a worker thread
        RxPostToWorkerThread(
            pWorkQueueItem->pDeviceObject,
            CriticalWorkQueue,
            pWorkQueueItem,
            pWorkQueueItem->WorkerRoutine,
            pWorkQueueItem->Parameter);
    }
}

NTSTATUS
RxPostOneShotTimerRequest(
    IN PRDBSS_DEVICE_OBJECT     pDeviceObject,
    IN PRX_WORK_ITEM            pWorkItem,
    IN PRX_WORKERTHREAD_ROUTINE Routine,
    IN PVOID                    pContext,
    IN LARGE_INTEGER            TimeInterval)
/*++

Routine Description:

    This routine is used by drivers to initialize a timer entry for a device
    object.

Arguments:

    pDeviceObject - the device object

    pWorkItem - the work item

    Routine        - the routine to be invoked on timeout

    pContext       - the Context parameter that is passed to the driver routine.

    TimeInterval   - the time interval in 100 ns ticks.

Return Value:

    The function value indicates whether or not the timer was initialized.

--*/

{
    BOOLEAN       StartTimer;
    //NTSTATUS      Status;
    ULONG         NumberOf55msIntervals;
    KIRQL         Irql;
    LARGE_INTEGER StrobeInterval;

    ASSERT(pWorkItem != NULL);

    // Initialize the work queue item.
    ExInitializeWorkItem(
        (PWORK_QUEUE_ITEM)&pWorkItem->WorkQueueItem,
        Routine,
        pContext );

    pWorkItem->WorkQueueItem.pDeviceObject = pDeviceObject;

    // Compute the time interval in number of ticks.
    StrobeInterval.QuadPart= NoOf100nsTicksIn55ms;
    NumberOf55msIntervals = (ULONG)(TimeInterval.QuadPart / StrobeInterval.QuadPart);
    NumberOf55msIntervals += 1; // Take the ceiling to be conservative
    RxDbgTraceLV( 0, Dbg, 1500, ("Timer will expire after %ld 55ms intervals\n",NumberOf55msIntervals));

    // Insert the entry in the timer queue.
    KeAcquireSpinLock( &s_RxTimerLock, &Irql );

    // Update the tick relative to the current tick.
    pWorkItem->LastTick = s_RxTimerTickCount + NumberOf55msIntervals;

    StartTimer = IsListEmpty(&s_RxTimerQueueHead);
    InsertTailList( &s_RxTimerQueueHead,&pWorkItem->WorkQueueItem.List);

    KeReleaseSpinLock( &s_RxTimerLock, Irql );

    if (StartTimer) {
        KeSetTimer( &s_RxTimer, s_RxTimerInterval, &s_RxTimerDpc );
    }

    return STATUS_SUCCESS;
}

NTSTATUS
RxPostRecurrentTimerRequest(
    IN PRDBSS_DEVICE_OBJECT     pDeviceObject,
    IN PRX_WORKERTHREAD_ROUTINE Routine,
    IN PVOID                    pContext,
    IN LARGE_INTEGER            TimeInterval)
/*++

Routine Description:

    This routine is used to post a recurrent timer request. The passed in routine once every
    (TimeInterval) milli seconds.

Arguments:

    pDeviceObject  - the device object

    Routine        - the routine to be invoked on timeout

    pContext       - the Context parameter that is passed to the driver routine.

    TimeInterval   - the time interval in 100ns ticks.

Return Value:

    The function value indicates whether or not the timer was initialized.

--*/
{
    PRX_RECURRENT_WORK_ITEM pRecurrentWorkItem;
    NTSTATUS      Status;

    PAGED_CODE();

    // Allocate a work item.
    pRecurrentWorkItem = (PRX_RECURRENT_WORK_ITEM)
                        RxAllocatePoolWithTag(
                            NonPagedPool,
                            sizeof(RX_RECURRENT_WORK_ITEM),
                            RX_TIMER_POOLTAG);

    if (pRecurrentWorkItem != NULL) {
        InsertTailList(
            &s_RxRecurrentWorkItemsList,
            &pRecurrentWorkItem->RecurrentWorkItemsList);

        pRecurrentWorkItem->Routine = Routine;
        pRecurrentWorkItem->pContext = pContext;
        pRecurrentWorkItem->TimeInterval = TimeInterval;
        pRecurrentWorkItem->WorkItem.WorkQueueItem.pDeviceObject = pDeviceObject;

        Status = RxPostOneShotTimerRequest(
                     pRecurrentWorkItem->WorkItem.WorkQueueItem.pDeviceObject,
                     &pRecurrentWorkItem->WorkItem,
                     RxRecurrentTimerWorkItemDispatcher,
                     pRecurrentWorkItem,
                     TimeInterval);
    } else {
        Status = STATUS_INSUFFICIENT_RESOURCES;
    }

    return Status;
}

NTSTATUS
RxCancelTimerRequest(
    IN PRDBSS_DEVICE_OBJECT       pDeviceObject,
    IN PRX_WORKERTHREAD_ROUTINE   Routine,
    IN PVOID                      pContext)
/*++

Routine Description:

    This routine cancels a  timer request. The request to be cancelled is identified
    by the routine and context.

Arguments:

    Routine        - the routine to be invoked on timeout

    pContext       - the Context parameter that is passed to the driver routine.

--*/
{
    NTSTATUS                Status = STATUS_NOT_FOUND;
    PLIST_ENTRY             pListEntry;
    PWORK_QUEUE_ITEM     pWorkQueueItem;
    PRX_WORK_ITEM           pWorkItem;
    PRX_RECURRENT_WORK_ITEM pRecurrentWorkItem = NULL;
    KIRQL Irql;

    KeAcquireSpinLock( &s_RxTimerLock, &Irql );

    // Walk through the list of entries
    for (pListEntry = s_RxTimerQueueHead.Flink;
         (pListEntry != &s_RxTimerQueueHead);
         pListEntry = pListEntry->Flink ) {
        pWorkQueueItem = CONTAINING_RECORD( pListEntry, WORK_QUEUE_ITEM, List );
        pWorkItem      = CONTAINING_RECORD( pWorkQueueItem, RX_WORK_ITEM, WorkQueueItem);

        if ((pWorkItem->WorkQueueItem.pDeviceObject == pDeviceObject) &&
            (pWorkItem->WorkQueueItem.WorkerRoutine == Routine) &&
            (pWorkItem->WorkQueueItem.Parameter == pContext)) {
            RemoveEntryList(pListEntry);
            Status = STATUS_SUCCESS;
            pRecurrentWorkItem = NULL;
            break;
        } else if (pWorkItem->WorkQueueItem.WorkerRoutine == RxRecurrentTimerWorkItemDispatcher) {
            pRecurrentWorkItem = (PRX_RECURRENT_WORK_ITEM)pWorkItem->WorkQueueItem.Parameter;

            if ((pRecurrentWorkItem->Routine == Routine) &&
                (pRecurrentWorkItem->pContext == pContext)) {
                RemoveEntryList(pListEntry);
                RemoveEntryList(&pRecurrentWorkItem->RecurrentWorkItemsList);
                Status = STATUS_SUCCESS;
            } else {
                pRecurrentWorkItem = NULL;
            }
        }
    }

    KeReleaseSpinLock( &s_RxTimerLock, Irql );

    if (pRecurrentWorkItem != NULL) {
        RxFreePool(pRecurrentWorkItem);
    }

    return Status;
}

VOID
RxRecurrentTimerWorkItemDispatcher (
    IN PVOID Context
    )
/*++

Routine Description:

    This routine dispatches a recurrent timer request. On completion of the invocation of the
    associated routine the request s requeued.

Arguments:

    Routine        - the routine to be invoked on timeout

    pContext       - the Context parameter that is passed to the driver routine.

--*/
{
    PRX_RECURRENT_WORK_ITEM  pPeriodicWorkItem = (PRX_RECURRENT_WORK_ITEM)Context;
    PRX_WORKERTHREAD_ROUTINE Routine  = pPeriodicWorkItem->Routine;
    PVOID                    pContext = pPeriodicWorkItem->pContext;

    PAGED_CODE();

    //KIRQL  Irql;

    // Invoke the routine.
    Routine(pContext);

    // enqueue the item if necessary.
    RxPostOneShotTimerRequest(
        pPeriodicWorkItem->WorkItem.WorkQueueItem.pDeviceObject,
        &pPeriodicWorkItem->WorkItem,
        RxRecurrentTimerWorkItemDispatcher,
        pPeriodicWorkItem,
        pPeriodicWorkItem->TimeInterval);
}