/*++

Copyright (c) 1992-1996  Microsoft Corporation

Module Name:

    timer.c

Abstract:

    This file contains the code to manipulate timers.

Author:

    Jameel Hyder (jameelh@microsoft.com)	July 1996

Environment:

    Kernel mode

Revision History:

--*/

#include <precomp.h>
#define	_FILENUM_		FILENUM_TIMER

VOID
ArpSTimerEnqueue(
	IN	PINTF					pIntF,
	IN	PTIMER					pTimer
	)
/*++

Routine Description:

	The timer events are maintained as a list which the timer thread wakes up,
	it looks at every timer tick. The list is maintained in such a way that
	only the head of the list needs to be updated every tick i.e. the entire
	list is never scanned. The way this is achieved is by keeping delta times
	relative to the previous entry.

	Every timer tick, the relative time at the head of the list is decremented.
	When that goes to ZERO, the head of the list is unlinked and dispatched.

	To give an example, we have the following events queued at time slots
	X			Schedule A after 10 ticks.
	X+3			Schedule B after 5  ticks.
	X+5			Schedule C after 4  ticks.
	X+8			Schedule D after 6  ticks.

	So A will schedule at X+10, B at X+8 (X+3+5), C at X+9 (X+5+4) and
	D at X+14 (X+8+6).

	The above example covers all the situations.

	- NULL List.
	- Inserting at head of list.
	- Inserting in the middle of the list.
	- Appending to the list tail.

	The list will look as follows.

		    BEFORE                          AFTER
		    ------                          -----

    X   Head -->|                  Head -> A(10) ->|
    A(10)

    X+3 Head -> A(7) ->|           Head -> B(5) -> A(2) ->|
    B(5)

    X+5 Head -> B(3) -> A(2) ->|   Head -> B(3) -> C(1) -> A(1) ->|
    C(4)

    X+8 Head -> C(1) -> A(1) ->|   Head -> C(1) -> A(1) -> D(4) ->|
    D(6)

	The granularity is one tick. THIS MUST BE CALLED WITH THE TIMER LOCK HELD.

Arguments:


Return Value:

--*/
{
	PTIMER		pList, *ppList;
	USHORT		DeltaTime = pTimer->AbsTime;

#if DBG
	if (pTimer->Routine == (TIMER_ROUTINE)NULL)
	{
		DBGPRINT(DBG_LEVEL_ERROR,
				("TimerEnqueue: pIntF %x, pTimer %x, NULL Routine!\n",
					pIntF, pTimer));
		DbgBreakPoint();
	}
#endif // DBG

	DBGPRINT(DBG_LEVEL_INFO,
			("ArpSTimerEnqueue: Entered for pTimer %lx\n", pTimer));

	// The DeltaTime is adjusted in every pass of the loop to reflect the
	// time after the previous entry that the new entry will schedule.
	for (ppList = &pIntF->ArpTimer;
		 (pList = *ppList) != NULL;
		 ppList = &pList->Next)
	{
		if (DeltaTime <= pList->RelDelta)
		{
			pList->RelDelta -= DeltaTime;
			break;
		}
		DeltaTime -= pList->RelDelta;
	}
	

	// Link this in the chain
	pTimer->RelDelta = DeltaTime;
	pTimer->Next = pList;
	pTimer->Prev = ppList;
	*ppList = pTimer;
	if (pList != NULL)
	{
		pList->Prev = &pTimer->Next;
	}
}


VOID
ArpSTimerCancel(
	IN	PTIMER					pTimer
	)
/*++

Routine Description:

	Cancel a previously queued timer. Called with the ArpCache mutex held.

Arguments:


Return Value:

--*/
{
	DBGPRINT(DBG_LEVEL_INFO,
			("ArpSTimerCancel: Entered for pTimer %lx\n", pTimer));

	//
	// Unlink it from the list adjusting relative deltas carefully
	//
	if (pTimer->Next != NULL)
	{
		pTimer->Next->RelDelta += pTimer->RelDelta;
		pTimer->Next->Prev = pTimer->Prev;
	}

	*(pTimer->Prev) = pTimer->Next;
}


VOID
ArpSTimerThread(
	IN	PVOID					Context
	)
/*++

Routine Description:

	Handle timer events here.

Arguments:

	None

Return Value:

	None
--*/
{
	PINTF			pIntF = (PINTF)Context;
	NTSTATUS		Status;
	LARGE_INTEGER	TimeOut;
	PTIMER			pTimer;
	BOOLEAN			ReQueue;

	ARPS_PAGED_CODE( );

	DBGPRINT(DBG_LEVEL_INFO,
			("ArpSTimerThread: Came to life\n"));

	TimeOut.QuadPart = TIMER_TICK;

	do
	{
		WAIT_FOR_OBJECT(Status, &pIntF->TimerThreadEvent, &TimeOut);
		if (Status == STATUS_SUCCESS)
		{
			//
			// Signalled to quit, do so.
			//
			break;
		}

		WAIT_FOR_OBJECT(Status, &pIntF->ArpCacheMutex, NULL);	

		if ((pTimer = pIntF->ArpTimer) != NULL)
		{
			//
			// Careful here. If two timers fire together - let them !!
			//
			if (pTimer->RelDelta != 0)
				pTimer->RelDelta --;

			if (pTimer->RelDelta == 0)
			{
				pIntF->ArpTimer = pTimer->Next;
				if (pIntF->ArpTimer != NULL)
				{
					pIntF->ArpTimer->Prev = &pIntF->ArpTimer;
				}

				ReQueue = (*pTimer->Routine)(pIntF, pTimer, FALSE);
				if (ReQueue)
				{
					ArpSTimerEnqueue(pIntF, pTimer);
				}
			}
		}

		RELEASE_MUTEX(&pIntF->ArpCacheMutex);	
	} while (TRUE);

	DBGPRINT(DBG_LEVEL_INFO,
			("ArpSTimerThread: terminating\n"));

	//
	// Now fire all queued timers
	//
	WAIT_FOR_OBJECT(Status, &pIntF->ArpCacheMutex, NULL);	

	for (pTimer = pIntF->ArpTimer;
		 pTimer != NULL;
		 pTimer = pIntF->ArpTimer)
	{
		pIntF->ArpTimer = pTimer->Next;
		ReQueue = (*pTimer->Routine)(pIntF, pTimer, TRUE);
		ASSERT(ReQueue == FALSE);
	}

	RELEASE_MUTEX(&pIntF->ArpCacheMutex);	

	DBGPRINT(DBG_LEVEL_INFO,
			("ArpSTimerThread: terminated\n"));
	//
	// Finally dereference the IntF
	//
	ArpSDereferenceIntF(pIntF);
}