//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
/*++
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.

Module Name:  
    timer.c
    
Abstract:  
    Routines for retransmission timer thread used in Ethernet debug
    protocol.  When a frame is sent, a timer is inserted on the list of
    active timers.  This list is checked periodically, and when timers
    expire, the corresponding frame is retransmitted.

Functions:


Notes: 

--*/
#include <windows.h>
#include <types.h>
#include <nkintr.h>
#include <kitl.h>
#include <kitlprot.h>
#include "kernel.h"
#include "kitlp.h"

// How often the retransmit thread will wake up to check for retransmissions
#define RETRANSMIT_CHECK_INTERVAL_MS  500
#define DHCP_RENEW_SCALE    500 // 1000 ms/sec and renew at 1/2 of the lease time
#define DHCP_RETRY_TIMEOUT      10000
#define NUM_DHCP_RETRIES        4

typedef struct _timerent
{
    struct _timerent *pNext;
    union {
        KITL_CLIENT *pClient;
        PFN_KITLTIMERCB pfnCB;
    };
    DWORD  Index;
    DWORD  dwExpireTime;
    DWORD  dwExpireInt;
    
} TIMERENT;

// for DEBUGMSG and CESH, we don't want to call AllocMem to allocate timer
// entry, or they will stop working in low memory condition.
// KDBG always poll, so we don't need to pre-allocate for it.
 // Added one additional node to account for possible "transition" nodes where 
 // in timerthread a node is not in the free list or timerlist but is in transition for
 // a brief duration when a call could come into TimerStart leading to no free
 // slots avaialble.
TIMERENT preallocent[KITL_MAX_WINDOW_SIZE*2 + 1];
#define preallocentLen (sizeof(preallocent) / sizeof(preallocent[0]))

// We borrow kernel fixed size memory allocation routine
ERRFALSE(sizeof(TIMERENT) <= sizeof(PROXY));


static TIMERENT *TimerList;
static TIMERENT *pFreeList;
extern CRITICAL_SECTION TimerListCS;
extern KITLTRANSPORT Kitl;
static void TimerListInsert(TIMERENT *pTimerEnt);

//
// we'll setup one buffer slot for timer callback since the OEM might want to set timer 
// callback before timer thread is started
static TIMERENT entbuf;

void
DumpTimerList()
{
    TIMERENT *pTimerEnt;
    for (pTimerEnt=TimerList; pTimerEnt; pTimerEnt=pTimerEnt->pNext) {
        KITLOutputDebugString("TimerEnt: slot %u, expire: %u\n",pTimerEnt->Index,pTimerEnt->dwExpireTime);
    }
}

void InitTimerList (void)
{
    int i;
    pFreeList = preallocent;

    for (i = 0; i < preallocentLen - 1; i ++) {
        preallocent[i].pNext = &preallocent[i+1];
    }
    preallocent[i].pNext = NULL;
}

void FreeTimerEntry (TIMERENT *pTimerEnt)
{
    DEBUGCHK (OwnCS (&TimerListCS));

    if (!(pTimerEnt->dwExpireInt & 0x80000000)) {
        DEBUGCHK (pTimerEnt->pClient);
        switch (pTimerEnt->pClient->ServiceId) {
        case KITL_SVC_DBGMSG:
        case KITL_SVC_PPSH:
            pTimerEnt->pNext = pFreeList;
            pFreeList = pTimerEnt;
            return;
        default:
            break;
        }
    }
    FreeMem (pTimerEnt,HEAP_TIMERENTRY);
}

/* TimerCallback
 *
 *   Function to send any timer notifications based on desktop ack packet. 
 */
DWORD
TimerCallback(DWORD* pdwCurInterval)
{
    // Check to see if any timers expired
    DWORD dwTimerInt = 0;
    PFN_KITLTIMERCB pfn = entbuf.pfnCB;
    TIMERENT *pTimerEnt = TimerList;
    TIMERENT* pTimerPrev = NULL;
    
    if (NULL != (HANDLE)TimerListCS.OwnerThread || !*(g_kpriv.pcInterruptsOff)) {
        // timerlist is locked or we are not in a debug break state; ignore the request
        return 0;
    }

    while (pTimerEnt) {

        if (pTimerEnt->dwExpireInt & 0x80000000) { // timer callback
            dwTimerInt = (pTimerEnt->dwExpireInt & 0x7fffffff) / 1000; // expire time in seconds         

            if (dwTimerInt <= *pdwCurInterval) { // timer expired
                if (!pTimerPrev) {
                    TimerList = pTimerEnt->pNext;
                } else {
                    pTimerPrev->pNext = pTimerEnt->pNext;
                }
                
                // call the callback function
                pTimerEnt->pfnCB ((LPVOID) pTimerEnt->Index);
                FreeMem (pTimerEnt,HEAP_TIMERENTRY);

                *pdwCurInterval = 0; // reset the desktop timer count
                return 1;
            }
        }
        pTimerPrev = pTimerEnt;
        pTimerEnt = pTimerEnt->pNext;
    }

    // if we didn't find any timer notification expirations, see if there are any pending
    // timer callback notifications
    if (pfn && entbuf.dwExpireInt & 0x80000000) {
        dwTimerInt = (entbuf.dwExpireInt & 0x7fffffff) / 1000; // expire time in seconds         
        if (dwTimerInt <= *pdwCurInterval) { // timer expired
            // call the callback function
            entbuf.pfnCB = NULL;
            (*pfn) ((LPVOID) entbuf.Index);
            *pdwCurInterval = 0; // reset the desktop timer count            
            return 1;
        }
    }

    return 0;
}

/* TimerThread
 *
 *   Thread that handles retransmissions.  Periodically check the list
 *   to see if any timers have expired.  If so, retransmit the associated
 *   frame, and re-insert back in the list. 
 */
DWORD
TimerThread(LPVOID pUnused)
{
    BOOL fResched;
    UCHAR *pData = NULL;
    KITL_HDR *pHdr = NULL;
    
    KITL_DEBUGMSG(ZONE_INIT,("KITL Timer thread started, (hTh: 0x%X, pTh: 0x%X)\n",
                             dwCurThId, pCurThread));

    dwTimerThId = dwCurThId;
    KITLGlobalState |= KITL_ST_TIMER_INIT;
    pCurThread->bDbgCnt = 1;   // no entry messages
    while (1) {
        DWORD CurTime = OEMGetTickCount();

        if (entbuf.pfnCB) {
            TimerStart (entbuf.pClient, entbuf.Index, entbuf.dwExpireInt, TRUE);
            entbuf.pfnCB = NULL;
        }
        // Check to see if any timers expired
        //KITLOutputDebugString ("Timer Thread checking, TimerListCS Owner = %x\r\n", TimerListCS.OwnerThread);
        EnterCriticalSection(&TimerListCS);        
        while (TimerList && ((int) (TimerList->dwExpireTime - CurTime) <= 0)) {
            TIMERENT *pTimerEnt = TimerList;
            
            // Expired timer, remove from timer list and retransmit frame (first release 
            // CS to avoid potential deadlock, since RetransmitFrame gets client CS).
            TimerList = pTimerEnt->pNext;
            LeaveCriticalSection(&TimerListCS);
            if (!(pTimerEnt->dwExpireInt & 0x80000000)) {
                //KITLOutputDebugString ("Timer Thread retransmitting, %x\r\n", pTimerEnt->Index);
                fResched = RetransmitFrame(pTimerEnt->pClient, (UCHAR) pTimerEnt->Index, TRUE);
                //KITLOutputDebugString ("Timer Thread Done retransmitting, fResched = %x\r\n", fResched);
            } else {
                fResched = FALSE;
                pTimerEnt->pfnCB ((LPVOID) pTimerEnt->Index);
            }
            EnterCriticalSection(&TimerListCS);
            if (fResched) {
                // find out if this particular kitl packet is already acked
                pData = pTimerEnt->pClient->pTxBufferPool + (pTimerEnt->Index * KITL_MTU);                
                pHdr = (KITL_HDR *)(pData + Kitl.FrmHdrSize);
    
                if (SEQ_BETWEEN(pTimerEnt->pClient->AckExpected, pHdr->SeqNum, pTimerEnt->pClient->TxSeqNum)) {
                    // Put back in list, increase expiry time so that we get
                    // timeouts at 1s, 2s, 4s, and 8s (max)
                    if (pTimerEnt->dwExpireInt < KITL_RETRANSMIT_INTERVAL_MAX)
                        pTimerEnt->dwExpireInt <<= 1;
                    TimerListInsert (pTimerEnt);
                } else {
                    // this particular packet is acked; remove it from the list
                    FreeTimerEntry (pTimerEnt);
                }
            }
            else {
                FreeTimerEntry (pTimerEnt);
            }
        }
        LeaveCriticalSection(&TimerListCS);
        NKSleep (RETRANSMIT_CHECK_INTERVAL_MS);
    }
    return 0;
}

static void TimerListInsert (TIMERENT *pTimerEnt)
{
    TIMERENT *pTmp;
    if (!(KITLGlobalState & KITL_ST_TIMER_INIT))
        return;

    EnterCriticalSection(&TimerListCS);

    pTimerEnt->dwExpireTime = OEMGetTickCount() + (pTimerEnt->dwExpireInt & 0x7fffffff);
        
    // Insert into timer list, sorted by time remaining (if equal, put at end of equal
    // entries, to keep retransmission sequence the same)
    if (((pTmp = TimerList) == NULL) || ((int) (pTmp->dwExpireTime - pTimerEnt->dwExpireTime) > 0)) {
        pTimerEnt->pNext = TimerList;
        TimerList = pTimerEnt;
    }
    else {
        while (pTmp->pNext && ((int) (pTmp->pNext->dwExpireTime - pTimerEnt->dwExpireTime) <= 0))
            pTmp = pTmp->pNext;
        pTimerEnt->pNext = pTmp->pNext;
        pTmp->pNext = pTimerEnt;
    }
    KITL_DEBUGMSG(ZONE_TIMER,("Timer: Inserted entry 0x%X, slot: %u, expire: %u\n",pTimerEnt,pTimerEnt->Index,pTimerEnt->dwExpireTime));
    if (KITLDebugZone & ZONE_TIMER)
        DumpTimerList();

    LeaveCriticalSection(&TimerListCS);
}

// If we are in the no syscall state, we can't call AllocMem or FreeMem, 
// because they do KCalls, which will reenable interrupts, causing scheduling
// to resume if we are called in through the debugger.  So, just chain up blocks
// of memory to free and free the memory when we get called again in a normal state.
static TIMERENT *pTimerEntsToFree;

BOOL
TimerStart(KITL_CLIENT *pClient, DWORD Index, DWORD dwMSecs, BOOL fUseSysCalls)
{
    TIMERENT *pTimerEnt = NULL;
    if (!(KITLGlobalState & KITL_ST_TIMER_INIT))
        return FALSE;
    
    // See note below about not calling AllocMem in !fUseSysCalls state.  Potentially,
    // I could deal with this better - possibly pre allocate some buffers. But for
    // now, there isn't any reason we need to start timers when in this state (since
    // we poll for acks when in no syscall state).
    if (!fUseSysCalls)
        return FALSE;

    // dwMSecs < 0 iff for timer callback
    if (!(dwMSecs & 0x80000000)) {

        switch (pClient->ServiceId) {
        case KITL_SVC_DBGMSG:
        case KITL_SVC_PPSH:
            EnterCriticalSection (&TimerListCS);

            while (pTimerEntsToFree)
            {
                pTimerEnt = pTimerEntsToFree;
                pTimerEntsToFree = pTimerEnt->pNext;
                FreeTimerEntry (pTimerEnt);
            }

            DEBUGCHK (pFreeList);
            pTimerEnt = pFreeList;
            pFreeList = pFreeList->pNext;
            LeaveCriticalSection (&TimerListCS);
            break;
        default:
            break;
        }
    }

    if (!pTimerEnt && !(pTimerEnt = (TIMERENT *) AllocMem(HEAP_TIMERENTRY))) {
        KITLOutputDebugString("KITL Error allocating timer memory\n");
        return FALSE;
    }

    pTimerEnt->pClient      = pClient;
    pTimerEnt->Index        = Index;
    pTimerEnt->dwExpireInt  = dwMSecs;
    
    TimerListInsert (pTimerEnt);
    return TRUE;
}

BOOL
TimerStop(KITL_CLIENT *pClient, DWORD Index, BOOL fUseSysCalls)
{
    TIMERENT *pTmp,**ppTmp;

    if (!(KITLGlobalState & KITL_ST_TIMER_INIT))
        return FALSE;

    // If we can't use sys calls, and someone owns the CS, just bail. Since RetransmitFrame
    // checks the sequence number, we'll dequeue the timer the next time it expires anyway.
    if (fUseSysCalls)
        EnterCriticalSection(&TimerListCS);
    else if (TimerListCS.OwnerThread)
        return FALSE;
    
    // Remove timer from list
    for (ppTmp = &TimerList;
         *ppTmp && !(((*ppTmp)->pClient == pClient) && ((*ppTmp)->Index == Index));
         ppTmp = &(*ppTmp)->pNext)
        ;
    if (*ppTmp) {
        pTmp = *ppTmp;
        KITL_DEBUGMSG(ZONE_TIMER,("Timer: Removing entry, slot: %u, expire: %u\n",pTmp->Index,pTmp->dwExpireTime));
        *ppTmp = pTmp->pNext;
        // Put timer entry on list to be freed the next time we are called with
        // syscalls enabled.
        pTmp->pNext = pTimerEntsToFree;
        pTimerEntsToFree = pTmp;
    }
    else
        KITL_DEBUGMSG(ZONE_TIMER,("!TimerStop: Timer not started for slot %u\n",Index));
    if (KITLDebugZone & ZONE_TIMER)
        DumpTimerList();
    if (fUseSysCalls) {
        while (pTimerEntsToFree)
        {
            pTmp = pTimerEntsToFree;
            pTimerEntsToFree = pTmp->pNext;
            FreeTimerEntry (pTmp);
        }
        LeaveCriticalSection(&TimerListCS);
    }
    return TRUE;
}

/*
 * Stop all timers for a given client.
 */
void
CancelTimersForClient(KITL_CLIENT *pClient)
{
    UCHAR i;

    KITL_DEBUGMSG(ZONE_TIMER,("+CancelTimersForClient(%s)\n",pClient->ServiceName));    
    for (i = 0; i < pClient->WindowSize; i ++)
        TimerStop(pClient,i,(pClient->State & KITL_USE_SYSCALLS) != 0);
}

BOOL KitlSetTimerCallback (int nSecs, PFN_KITLTIMERCB pfnCB, LPVOID lpParam)
{
    if (!(KITLGlobalState & KITL_ST_TIMER_INIT) || InSysCall () || *(g_kpriv.pcInterruptsOff)) {
        // can't insert into timer when we're in system call. Just save the info
        // and Timer thread will pick it up once it starts running
        if (!entbuf.pfnCB) {
            entbuf.pfnCB = pfnCB;
            entbuf.dwExpireInt = (nSecs * 1000) | 0x80000000;   // set MSB to indicate callback
            entbuf.Index = (DWORD) lpParam;
            return TRUE;
        }
    }

    return TimerStart ((KITL_CLIENT *) pfnCB, (DWORD) lpParam, (DWORD) (nSecs * 1000) | 0x80000000, TRUE);
}

BOOL KitlStopTimerCallback (PFN_KITLTIMERCB pfnCB, LPVOID lpParam)
{
    if (pfnCB == entbuf.pfnCB) {
        entbuf.pfnCB = NULL;
        return TRUE;
    }
    return TimerStop ((KITL_CLIENT *) pfnCB, (DWORD) lpParam, !InSysCall ());
}
