/*static char *SCCSID = "@(#)tksleep.c  6.20 92/03/18";*/
#define SCCSID  "@(#)tksleep.c  6.20 92/03/18"
/*
 *      Tasking - Sleep/Wakeup and Timeout Queue Routines
 *
 *      IBM/Microsoft Confidential
 *
 *      Copyright (c) IBM Corporation 1987, 1989, 1990
 *      Copyright (c) Microsoft Corporation 1987, 1989, 1990
 *
 *      All Rights Reserved
 *   3/10/92 SFB b734380 remove thd group tmsleep
 *   9/9/92  SFB 53160   remove pvwmeasurement(cpu,statechange,0) macro
 *  12/15/93 RAC 76699   reduce code paths
 *   2/20/94 RAC 79242   Add code for PM Semaphore Support. Look for PMSEM
 *   9/12/94 FxM 97886   Extend time slice per TCBSpareFlags.
 */

#define INCL_TYPES                      // for basedef.h typedefs
#define INCL_ERROR_H                    // for error.h defines
#define INCL_ERROR2_H                   // for error2.h defines
#define FNO_NUM FNO_TKSLEEP_C

#include <basedef.h>
#include <basemac.h>
#include <fnotask.h>
#include <osmac.h>
#include <osconst.h>
#include <ostypes.h>
#include <infoseg.h>
#include <mi.h>
#include <dossymc.h>
#include <proc.h>
#include <bseerr.h>
#include <smexport.h>
#include <sigdef.h>
#include <except.h>
#include <seldesc.h>
#include <pgexport.h>
#include <vmexport.h>
#include <tkexport.h>
#include <sched.h>
#include <sem.h>
#include <screen.h>
#include <curdir.h>
#include <dirent.h>
#include <find.h>
#include <tkptda.h>
#include <kmode.h>
#include <bsetib.h>
#include <tklocal.h>
#include <kutil.h>
#include <tom.h>

extern uchar_t  SMswapping;
extern ulong_t  pgListCount;

/*************************** PMSEM *******************************************/
/* This must match what is defined in SEMLOCAL.H */
#define  PM_EVENT_POSTED          3000                  /* PMSEM return code */
/*************************** PMSEM *******************************************/

ulong_t         pgfaultboost=0;

/***    Scheduling data structures, listed by state
 *
 *      TCBQState               Data Structure Head     Structure type
 *
 *      STATE_VOID              NONE
 *      STATE_READY             ptcbPriQReady;          PriQ
 *      STATE_BLOCKED           aptcbSleep[]            (hashed-chained-PriQ)
 *      STATE_SUSPENDED         NONE.  See TK__FF_ICE
 *      STATE_CRITSEC           ptda_pTCBPriQCritSec    PriQ (one per PTDA)
 *      STATE_RUNNING           ptcbPriQRunner;         PriQ (one element max)
 *      STATE_READYBOOST        NONE
 *      STATE_TSD               ptcbPriQTSD;            PriQ
 *      STATE_DELAYED           ptcbPriQDelayed;        PriQ
 *      STATE_FROZEN            NONE.  See TK_FF_ICE
 *      STATE_GETSTACK          ptcbPriQGetStack;       PriQ (one element max)
 *
 */

// Moved to sdata.asm for System Anchor Block - f122427
extern  PTCB    ptcbPriQTSD;                    // STATE_TSD
extern  PTCB    ptcbPriQDelayed;                // STATE_DELAYED
extern  PTCB    ptcbPriQGetStack;               // STATE_GETSTACK
extern  ulong_t tkTSDSwapRateMax;

PTCB    ptcbPriQReady;                  // STATE_READY
PTCB    ptcbPriQRunner;                 // STATE_RUNNING
PTCB    ptcbPriQBadStack;               // STATE_BADSTACK

extern  ulong_t tkMaxSlice = DefaultMaxSlice;
extern  ulong_t tkMinSlice = DefaultMinSlice;
extern  ulong_t tkMaxWait = DefaultMaxWait;

ULONG   cmsecStarveLength = TOM_WAIT_FOREVER;
ULONG   TKBitBucket;
PTCB    ptcbtkTSDDaemon;                // TSD Daemon TCB pointer


/***    tkcStateTrans - State transition counter.
 *          First index - old scheduler state  (TCBQState)
 *          Second Index - New scheduler state (TCBState)
 *          Data - number of state transitions since boot.
 *
 *      tkcPriUpdates - Priority Update counter (total updates)
 *
 *      tkcPriChanges - Priority Changes counter (modified priority only)
 *          Index - scheduler state
 *          Data - number of priority updates since boot, per state.
 */

#define TKPROFILE
#ifdef  TKPROFILE
ULONG   tkcStateTrans[STATE_MAX][STATE_MAX];
ULONG   tkcPriUpdates;
ULONG   tkcPriChanges[STATE_MAX];
#endif


/***    tkAssertPriQCircle - Assert Priority Q links, when inserted
 ***    tkAssertPriQNull - Assert Priority Q links, when not inserted
 ***    tkAssertStateTrans - Assert that the state change is allowed.
 *
 *      tkAllowedStateTrans[STATE_MAX] is a USHORT array of bits.
 *      The USHORT is indexed via the descending state (TCBQState)
 *      and the bit is selected via (1 << TCBState).  If the bit is
 *      ON, then the state transition is allowed.  If not, the kernel
 *      will panic.   The valid state transitions are outlined below.
 */

#define tkAssertPriQCircle(ptcb)                                \
    tkAssert ((ptcb == NULL) || (                               \
        (ptcb-> TCBpTCBPriPrevQ-> TCBpTCBPriNextQ == ptcb)  &&  \
        (ptcb-> TCBpTCBPriNextQ-> TCBpTCBPriPrevQ == ptcb)  &&  \
        (ptcb-> TCBpTCBPriLower-> TCBpTCBPriHigher == ptcb) &&  \
        (ptcb-> TCBpTCBPriHigher-> TCBpTCBPriLower == ptcb) &&  \
        (ptcb-> TCBpTCBPriPrev-> TCBpTCBPriNext == ptcb)    &&  \
        (ptcb-> TCBpTCBPriNext-> TCBpTCBPriPrev == ptcb)));

#define tkAssertPriQNull(ptcb)                                  \
    tkAssert ((ptcb == NULL) || (                               \
        (ptcb-> TCBpTCBPriNextQ == ptcb)  &&                    \
        (ptcb-> TCBpTCBPriPrevQ == ptcb)  &&                    \
        (ptcb-> TCBpTCBPriHigher == ptcb) &&                    \
        (ptcb-> TCBpTCBPriLower == ptcb)  &&                    \
        (ptcb-> TCBpTCBPriNext == ptcb)   &&                    \
        (ptcb-> TCBpTCBPriPrev == ptcb)));


#ifdef  MISCSTRICT
#define STAB_VOID       (1 << STATE_VOID)
#define STAB_READY      (1 << STATE_READY)
#define STAB_BLOCKED    (1 << STATE_BLOCKED)
#define STAB_SUSPENDED  (1 << STATE_SUSPENDED)
#define STAB_CRITSEC    (1 << STATE_CRITSEC)
#define STAB_RUNNING    (1 << STATE_RUNNING)
#define STAB_READYBOOST (1 << STATE_READYBOOST)
#define STAB_TSD        (1 << STATE_TSD)
#define STAB_DELAYED    (1 << STATE_DELAYED)
#define STAB_FROZEN     (1 << STATE_FROZEN)
#define STAB_GETSTACK   (1 << STATE_GETSTACK)
#define STAB_BADSTACK   (1 << STATE_BADSTACK)

#define tkAssertStateTrans(Descend,Ascend) \
        tkAssert( tkAllowedStateTrans [Descend] & (1 << Ascend));

USHORT  tkAllowedStateTrans[STATE_MAX] = {

// Descending States    Allowed Ascending States
// ------------------|--------------------------
        // STATE_VOID
                     // STAB_VOID       |
                        STAB_READY      |       // Create Thread (ready)
                     // STAB_BLOCKED    |
                     // STAB_SUSPENDED  |
                     // STAB_CRITSEC    |
                        STAB_RUNNING    |       // Sysinit
                     // STAB_READYBOOST |
                     // STAB_TSD        |
                     // STAB_DELAYED    |
                        STAB_FROZEN     |       // Create Thread (Frozen)
                     // STAB_GETSTACK   |
                     // STAB_BADSTACK   |
0,      // STATE_READY
                     // STAB_VOID       |
                     // STAB_READY      |
                     // STAB_BLOCKED    |
                     // STAB_SUSPENDED  |
                     // STAB_CRITSEC    |
                        STAB_RUNNING    |       // GetNextRunner: select runner
                     // STAB_READYBOOST |
                        STAB_TSD        |       // GetNextRunner: NP TSD
                     // STAB_DELAYED    |
                     // STAB_FROZEN     |
                     // STAB_GETSTACK   |
                     // STAB_BADSTACK   |
0,      // STATE_BLOCKED
                     // STAB_VOID       |
                        STAB_READY      |       // TKWakeup, TKWakeThread
                     // STAB_BLOCKED    |
                     // STAB_SUSPENDED  |
                     // STAB_CRITSEC    |
                     // STAB_RUNNING    |
                        STAB_READYBOOST |       // TKWakeup (Device Boost)
                     // STAB_TSD        |
                        STAB_DELAYED    |       // TKWakeup -> TKDelayedWakeup
                     // STAB_FROZEN     |
                     // STAB_GETSTACK   |
                     // STAB_BADSTACK   |
0,      // STATE_SUSPENDED
                     // STAB_VOID       |
                        STAB_READY      |       // ResumeThread
                     // STAB_BLOCKED    |
                     // STAB_SUSPENDED  |
                     // STAB_CRITSEC    |
                     // STAB_RUNNING    |
                     // STAB_READYBOOST |
                     // STAB_TSD        |
                     // STAB_DELAYED    |
                     // STAB_FROZEN     |
                     // STAB_GETSTACK   |
                     // STAB_BADSTACK   |
0,      // STATE_CRITSEC
                     // STAB_VOID       |
                        STAB_READY      |       // ExitCritSec
                     // STAB_BLOCKED    |
                     // STAB_SUSPENDED  |
                     // STAB_CRITSEC    |
                     // STAB_RUNNING    |
                     // STAB_READYBOOST |
                     // STAB_TSD        |
                     // STAB_DELAYED    |
                     // STAB_FROZEN     |
                     // STAB_GETSTACK   |
                     // STAB_BADSTACK   |
0,      // STATE_RUNNING
                        STAB_VOID       |       // TKExit
                        STAB_READY      |       // GetNextRunner: runner done
                        STAB_BLOCKED    |       // TKSleep
                        STAB_SUSPENDED  |       // SuspendThread (TK_FF_ICE)
                        STAB_CRITSEC    |       // EnterCritSec (TK_FF_CRITSEC)
                     // STAB_RUNNING    |
                     // STAB_READYBOOST |
                     // STAB_TSD        |
                     // STAB_DELAYED    |
                        STAB_FROZEN     |       // SystemService Freeze FF_ICE
                     // STAB_GETSTACK   |
                     // STAB_BADSTACK   |
0,      // STATE_READYBOOST                     // Invalid TCBQState value
                     // STAB_VOID       |
                     // STAB_READY      |       // READYBOOST becomes READY
                     // STAB_BLOCKED    |
                     // STAB_SUSPENDED  |
                     // STAB_CRITSEC    |
                     // STAB_RUNNING    |
                     // STAB_READYBOOST |
                     // STAB_TSD        |
                     // STAB_DELAYED    |
                     // STAB_FROZEN     |
                     // STAB_GETSTACK   |
                     // STAB_BADSTACK   |
0,      // STATE_TSD
                     // STAB_VOID       |
                     // STAB_READY      |
                     // STAB_BLOCKED    |
                     // STAB_SUSPENDED  |
                     // STAB_CRITSEC    |
                     // STAB_RUNNING    |
                     // STAB_READYBOOST |
                     // STAB_TSD        |
                     // STAB_DELAYED    |
                     // STAB_FROZEN     |
                        STAB_GETSTACK   |       // tkTSDDaemon about to swap in
                     // STAB_BADSTACK   |
0,      // STATE_DELAYED
                     // STAB_VOID       |
                        STAB_READY      |       // TKWakeup (Multi)
                     // STAB_BLOCKED    |
                     // STAB_SUSPENDED  |
                     // STAB_CRITSEC    |
                     // STAB_RUNNING    |
                        STAB_READYBOOST |       // TKWakeup (Multi, Boost)
                     // STAB_TSD        |
                     // STAB_DELAYED    |
                     // STAB_FROZEN     |
                     // STAB_GETSTACK   |
                     // STAB_BADSTACK   |
0,      // STATE_FROZEN
                     // STAB_VOID       |
                        STAB_READY      |       // SystemService Thaw
                     // STAB_BLOCKED    |
                     // STAB_SUSPENDED  |
                     // STAB_CRITSEC    |
                     // STAB_RUNNING    |
                     // STAB_READYBOOST |
                     // STAB_TSD        |
                     // STAB_DELAYED    |
                     // STAB_FROZEN     |
                     // STAB_GETSTACK   |
                     // STAB_BADSTACK   |
0,      // STATE_GETSTACK
                     // STAB_VOID       |
                     // STAB_READY      |
                     // STAB_BLOCKED    |
                     // STAB_SUSPENDED  |
                     // STAB_CRITSEC    |
                     // STAB_RUNNING    |
                        STAB_READYBOOST |       // tkTSDDaemon finished swap in
                     // STAB_TSD        |
                     // STAB_DELAYED    |
                     // STAB_FROZEN     |
                     // STAB_GETSTACK   |
                        STAB_BADSTACK   |       // tkTSDDaemon failed swap in
0,      // STATE_BADSTACK
                        STAB_VOID       |       // Finish off a dead thread
                     // STAB_READY      |
                     // STAB_BLOCKED    |
                     // STAB_SUSPENDED  |
                     // STAB_CRITSEC    |
                     // STAB_RUNNING    |
                     // STAB_READYBOOST |
                     // STAB_TSD        |
                     // STAB_DELAYED    |
                     // STAB_FROZEN     |
                     // STAB_GETSTACK   |
                     // STAB_BADSTACK   |
0
};
#else
#define tkAssertStateTrans(d,a)
#endif


/***LD+ schPriClassTbl - Priority Class Translation Table.
 *
 *      This table controls the high byte priority calculation in
 *      TKPriCalc.  It gives the high and low bytes of the priority
 *      word, based on  current boosts.   The low byte is further
 *      combined with TCBPriLevel.  For priorities with a low byte of
 *      0x1f, there is only a single level allowed in the class when
 *      the boost is given, and a minimum timeslice is given out in
 *      those cases.
 *
 *      The final calculated priority is always subject to TCBPriorityMin.
 *
 *      Table Index = (TCBPriClass | TCBPriClassMod)
 *
 *      Starved         08 --------------------------------------------------+
 *      Device I/O      10 -------------------------------------------------+|
 *      Foreground      20 ------------------------------------------------+||
 *      Window          40 -----------------------------------------------+|||
 *      VDM Interrupt   80 ----------------------------------------------+||||
 *                                                                       |||||
 *  +-- TCBPriClass                                                      |||||
 *  |                                                                    |||||
 *  |   ============================================================     |||||
 *  |   Not Keyboard                ||  Keyboard                         |||||
 *  |                               ||
 *  +-> Server  Idle    Regular TC  ||  Server  Idle    Regular TC       IWFDS
 */

USHORT  schPriClassTbl[128] = {
        0x300,  0x100,  0x200,  0x800,  0x300,  0x100,  0x200,  0x800,// -----
        0x62f,  0x100,  0x61f,  0x800,  0x62f,  0x100,  0x61f,  0x800,// ----S
        0x72f,  0x100,  0x71f,  0x800,  0x72f,  0x100,  0x71f,  0x800,// ---D-
        0x72f,  0x100,  0x71f,  0x800,  0x72f,  0x100,  0x71f,  0x800,// ---DS
        0x300,  0x100,  0x300,  0x800,  0x300,  0x100,  0x400,  0x800,// --F--
        0x62f,  0x100,  0x61f,  0x800,  0x62f,  0x100,  0x61f,  0x800,// --F-S
        0x74f,  0x100,  0x73f,  0x800,  0x74f,  0x100,  0x73f,  0x800,// --FD-
        0x74f,  0x100,  0x73f,  0x800,  0x74f,  0x100,  0x73f,  0x800,// --FDS
        0x500,  0x100,  0x500,  0x800,  0x500,  0x100,  0x500,  0x800,// -W---
        0x62f,  0x100,  0x61f,  0x800,  0x62f,  0x100,  0x61f,  0x800,// -W--S
        0x74f,  0x100,  0x73f,  0x800,  0x74f,  0x100,  0x73f,  0x800,// -W-D-
        0x74f,  0x100,  0x73f,  0x800,  0x74f,  0x100,  0x73f,  0x800,// -W-DS
        0x500,  0x100,  0x500,  0x800,  0x500,  0x100,  0x500,  0x800,// -WF--
        0x62f,  0x100,  0x61f,  0x800,  0x62f,  0x100,  0x61f,  0x800,// -WF-S
        0x74f,  0x100,  0x73f,  0x800,  0x74f,  0x100,  0x73f,  0x800,// -WFD-
        0x74f,  0x100,  0x73f,  0x800,  0x74f,  0x100,  0x73f,  0x800,// -WFDS


/*      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// I----
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// I---S
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// I--D-
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// I--DS
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// I-F--
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// I-F-S
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// I-FD-
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// I-FDS
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// IW---
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// IW--S
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// IW-D-
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// IW-DS
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// IWF--
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// IWF-S
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,// IWFD-
 *      0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800,  0x800 // IWFDS
 */
};


/***    TKPriCalc - Calculate the priority of a thread
 *
 *      TKPriCalc calculates the priority of a thread by using the
 *      Priority Class Translation Table (schPriTbl).
 *
 *      The resultant priority is stored in ptcb-> TCBPriority.
 *
 *      ENTRY   ptcb    - TCB pointer of thread to calculate priority for.
 *
 *      EXIT    void
 *
 *      EFFECTS Sets ptcb-> TCBPriority based on current information.
 *              Does not affect queue structures.
 */

void
TKPriCalc (PTCB ptcb)
{


    if (ptcb -> TCBSchFlags & SCH_PAGE_FAULT) {            //tcw
        ptcb-> TCBPriClassMod &= ~CLASSMOD_DEVICE;         //tcw
    }

    ptcb-> TCBPriority =
           schPriClassTbl [ptcb->TCBPriClass | ptcb->TCBPriClassMod] |
           ptcb-> TCBPriLevel;

    if (ptcb-> TCBPriority < ptcb-> TCBPriorityMin) {
        ptcb-> TCBPriority = ptcb-> TCBPriorityMin;
    }
}


/***    TKGetPriority - Get the base scheduling priority of a thread
 *
 *      TKGetPriority gets the base scheduling priority of a thread.
 *      TKGetPriority does NOT account for priority modifiers or boosts.
 *
 *      ENTRY   ptcb    - TCB pointer of thread to calculate priority for.
 *
 *      EXIT    ULONG   - TCB priority.
 *                      Low Byte : Base scheduling priority Level
 *                      2nd Byte : Base scheduling priority Class
 */

ULONG
TKGetPriority (PTCB ptcb)
{
    return (((ULONG) ptcb->TCBPriClass << 8) | (ULONG) ptcb-> TCBPriLevel);
}


/***    aptcbSleep - Sleep Hash Table Definition
 *
 *      The TKSleep data structure is a hash table to circular sorted
 *      chains of priority queues.
 *
 *      The hash table index is calculated based on the current value
 *      of TCBSleepID and of the TK_WF_SINGLEWAKEUP bit in TCBWakeFlags.
 *      The single and multi wake clients with the same SleepID cannot
 *      collide to the same hash bucket.
 *
 *      A hash chain with no threads attached is signified by a NULL
 *      pointer in aptcbSleep[i].  If there are threads attached,
 *      aptcbSleep[i] will point to some thread which is the head of
 *      a priority queue.  As the queus are circular, aptcbSleep[i]
 *      will point anywhere into the chain.  It will point at (or near)
 *      the most recently used location in the chain to optimize the
 *      short-term waits.
 *
 *      Each hash chain consists of a sorted circular list of priority
 *      queues, using TCBpTCBPriNextQ, and TCBpTCBPriPrevQ, with
 *      TCBSleepID as the sort index.  Only the heads of the priority
 *      queues are linked onto this chain.   TCBpTCBPriNextQ points to
 *      a thread with a larger TCBSleepID.   If a thread is not on any
 *      priority queue chain, PriNextQ and PriPrevQ are circularly
 *      linked to that thread.
 *
 *      Threads with the same TCBSleepID are stored on a Priority
 *      Queue headed by the thread that is on the PriNextQ/PriPrevQ list.
 *
 *      aptcbSleep
 *      +-------------------------------------------------------+
 *      |  Multi-Wake section   |       Single-Wake section     |
 *      +-------------------------------------------------------+
 *              |
 *        +-----+ (most recently used TCB)
 *        V
 *      [TCB] === [TCB] === [TCB] === [TCB] === [TCB] ->
 *                            |
 *                            V
 *                      +--------------+
 *                      |Priority Queue|
 *                      +--------------+
 *                      Same SleepID
 *
 */

#define TKSLEEPSIZE 257                 // The Prime Number nearest 256.
PTCB    aptcbSleep[TKSLEEPSIZE];        // Sleep Hash Table.


/***    TKSLEEPHASH(id,fSingle) - Compute Sleep hash function
 ***    TKSLEEPHASHTCB(ptcb) - Compute Sleep hash function given ptcb.
 *
 *      We make sure that the single and multi wakeup threads with the
 *      same SleepID cannot be put into the same hash bucket by adding
 *      one to the single wakeup threads.
 *
 *      The hash just uses a prime-number modulo to spread the values.
 *
 */

#define TKSLEEPHASH(id,fSingle) ((ULONG) \
        ((((ULONG) id) + (!!(fSingle))) % TKSLEEPSIZE))
#define TKSLEEPHASHTCB(ptcb)  (TKSLEEPHASH(ptcb-> TCBSleepID, \
            ptcb-> TCBWakeFlags & TK_WF_SINGLEWAKEUP))


/***    TKSLEEPSEARCH - Search for a SleepID (MACRO)
 *
 *      TKSLEEPSEARCH is used to search the TKSleep hash table for a
 *      particular SleepID, after the hash value has been calculated.
 *      TKSLEEPSEARCH is implemented as a macro for speed.
 *
 *      The hash function should be calculated first, and pptcbHash
 *      should point to the head of the hash bucket on entry.
 *
 *      *pptcbHash is not modified here.  Rules for modifying this
 *      are given below.  This gives an MRU ordering to the buckets.
 *
 *      TKSLEEPSEARCH(id,ptcbScan,pptcbHash)
 *
 *      ENTRY   id              - SleepID to search for
 *              ptcbScan        - TCB pointer to use for scan
 *              pptcbHash       - Hash bucket pointer
 *
 *      EXIT    id              - unchanged
 *              pptcbHash       - unchanged
 *              *pptcbHash      - unchanged  (see below for motion rules)
 *              ptcbScan        - resultant TCB pointer.
 *                  if (ptcbScan == NULL)
 *                      - Hash bucket is empty.
 *                      - No TCB exists with the given SleepID.
 *                  else
 *                      if (ptcbScan-> TCBSleepID != id)
 *                          - Hash bucket not empty.
 *                          - No TCB exists with the given SleepID.
 *                          - ptcbScan == *pptcbHash.
 *                          - Insert a new queue previous to ptcbScan, and
 *                            move *pptcbHash to ptcbScan.
 *                      else
 *                          - Hash bucket not empty.
 *                          - ptcbScan points to a TCB with the given id.
 *                          - ptcbScan has the highest priority of all blocked
 *                            threads at that id.
 *                          - When deleting the queue, move *pptcbHash to
 *                            ptcbScan-> TCBpTCBPriNextQ.
 *                          - On Query, move *pptcbHash to ptcbScan
 *
 */

#define TKSLEEPSEARCH(id,ptcbScan,pptcbHash)                            \
    {                                                                   \
        ptcbScan = *pptcbHash;                                          \
        if ((ptcbScan != NULL) && (ptcbScan-> TCBSleepID != id)) {      \
            ptcbScan = ptcbScan-> TCBpTCBPriNextQ;                      \
            while ((ptcbScan != *pptcbHash) &&                          \
                   (ptcbScan-> TCBSleepID != id)) {                     \
                ptcbScan = ptcbScan-> TCBpTCBPriNextQ;                  \
            }                                                           \
        }                                                               \
    }


/***EP  VRSleep - Worker for DosSleep
 *
 *      ENTRY   sef             System Entry Frame
 *              ulinterval      Sleep interval in milliseconds
 *
 *      EXIT    NO_ERROR        Timed out
 *              ERROR_TS_WAKEUP Error
 */

int cdecl
VRSleep(SEF sef, ULONG ulinterval)
{
    int rc;

    if (ulinterval == 0) {
        TKYield();
        return (NO_ERROR);
    }
    if (ulinterval == -1)
        ulinterval = -2;

    rc = NO_ERROR;
    if (TKSleep((PVOID) VRSleep, ulinterval, 0, WAKEUP_SINGLE) != ERROR_TIMEOUT)
        rc = ERROR_TS_WAKEUP;

    return (rc);
}


/***EP  TKSleep - Block the current thread from running.
 *
 *    TKSleep is called to put the calling thread to sleep until
 *    some other thread (or interrupt service routine) issues an
 *    appropriate TKWakeup call.  Both TKSleep and TKWakeup are
 *    supplied with a 32-bit "event identifier" (aka sleep-id).
 *    When TKWakeup is called with a particular sleep-id, the thread
 *    or threads woken up are controlled via a flag parameter.
 *    This parameter specifies whether to wakeup all threads that
 *    called TKSleep with the matching sleep-id, or only the highest
 *    priority thread.
 *
 *    When calling TKSleep it is important to use the following sequence:
 *
 *       If the TKWakeup that will wake this thread will NEVER occur
 *       at interrupt time:
 *
 *          while (need to wait)
 *             TKSleep(value);
 *
 *       If the TKWakeup that will wake this thread may occur at
 *       interrupt time:
 *
 *          Disable interrupts;
 *          while (need to wait) {
 *             TKSleep(value);
 *             Disable interrupts;
 *          }
 *          Enable interrupts;
 *
 *    Interrupts are disabled BEFORE checking the condition (I/O done,
 *    resource freed, etc) so that we don't deadlock by getting an
 *    interrupt time TKWakeup before we complete the call to TKSleep.
 *    Also note the while clause.  Although the convention of using
 *    an address as a sleep-id should prevent double use of an
 *    identifier, it is always good practice to re-check the awaited
 *    condition after the TKSleep call, and if necessary re-call TKSleep.
 *
 *    ENTRY    SleepID - event identifier
 *             cmsecTimeout - timeout interval (in MS).
 *               -1 if to never timeout
 *             fUnint - Indicates whether sleep is interruptible
 *               - 0 if sleep is interruptible
 *               - 1 if sleep is uninterruptible
 *             flWakeType - indicate whether single or multiple wakeup
 *                          TKWakeup call should wakeup this thread.
 *               - 0 if multiple TKWakeup call
 *               - WAKEUP_SINGLE if single TKWakeup call
 *
 *    EXIT    If successful, reply = NO_ERROR
 *            else reply = error code
 *              ERROR_INTERRUPT - awakened to handle a signal
 *              ERROR_TIMEOUT - timeout wakeup
 *            ** Interrupts enabled **
 */

int
TKSleep(void *SleepID, ULONG cmsecTimeout, ULONG funint, ULONG flWakeType)
{
    char flag = TK_WF_SLEEPING;
    int rc = NO_ERROR;


    tkAssert(cInDos != 0);
    tkAssert(SleepID != NULL);
    tkAssert(cTKNoBlock == 0);
    tkAssertTasktime();
    tkAssert(!(pTCBCur->TCBWakeFlags & TK_WF_SLEEPING));
    tkAssert(pTCBCur-> TCBState == STATE_RUNNING);

    _disable();

/**************************** PMSEM Start ***********************************/
    /*
     * We being requested to put a thread to sleep. Before we put it to sleep,
     * check to see if a PM Event has been posted for this thread. If it hase,
     * just reset the flag fields and return control to the caller.
     */
    if (pTCBCur->TCBWakeFlags & TK_WF_PMPOSTED) {    /* PM Event Posted?  */
                                                     /* Yes */
       pTCBCur->TCBWakeFlags = 0;                    /* Reset Wake Flags  */
       pTCBCur->TCBSpareFlags &= (~SPFLAGS_PM_EVENT); /* Reset PM Event    */
                                                     /* waiting flag.     */
       _enable();                                    /* enable interrupts */
       return(PM_EVENT_POSTED);                      /* Return to caller. */
    }
/**************************** PMSEM End **************************************/

    /*
     * If funint indicates sleep is interruptible,
     * check for any interrupts pending for this thread.
     */

    if (!funint) {
        if (pTCBCur->TCBWakeFlags & TK_WF_INTPENDING) {
            pTCBCur->TCBWakeFlags = 0;  /* Interrupt has been noticed */
            if (pTCBCur->TCBForcedActions & (TK_FF_EXIT | TK_FF_SIG)) {
                _enable();
                return(ERROR_INTERRUPT);/* Return if action pending */
            }
        }
        flag |= TK_WF_INTERRUPTIBLE;    /* Record interruptibility */
    }
    tkAssert(!(pTCBCur->TCBWakeFlags & ~TK_WF_INTPENDING) &&
      (!(pTCBCur->TCBWakeFlags & TK_WF_INTPENDING) || funint));
                                        /* INTPENDING okay if uninterruptible */

    TOMCancel (&pTCBCur-> TCBtoe);      /* Cancel Starvation, if any */

    /*
     * Handle timeout if one was specified
     */
    if (cmsecTimeout != -1) {

       /***********************************************************************/
       /* If timeout value is zero and another non idle class thread wants to */
       /* run, then perform a yield of this threads timeslice.                */
       if (cmsecTimeout == 0) {            /* P723638 */
           _enable();                       /* enable interrupts */
           if ((ptcbPriQReady != NULL) &&   // B733890
               (ptcbPriQReady->TCBPriClass != CLASS_IDLE_TIME)) { // B733890
               TKYield();
           }
           return(ERROR_TIMEOUT);           /* P723638 */
       }
       /***********************************************************************/

        tkAssert (!(pTCBCur->TCBWakeFlags & TK_WF_TIMEOUT));
        TOMSet(&(pTCBCur->TCBtoe), cmsecTimeout, TOM_W_WAKE);
        pTCBCur->TCBWakeFlags |= TK_WF_TIMEOUT;
    }

    /*
     * See if caller wants to be woken only by a single wakeup TKWakeup call
     */
    if (flWakeType & WAKEUP_SINGLE)
        flag |= TK_WF_SINGLEWAKEUP;

    /*
     * Set the thread's state to blocked, add it to the sleep queue
     * corresponding to the SleepID specified, and call SchedNext().
     */

    pTCBCur->TCBWakeFlags |= flag;      /* Set wakeup flags */
    pTCBCur->TCBSleepID = SleepID;      /* Set sleep id */
    pTCBCur->TCBState = STATE_BLOCKED;  /* Set new State */
    TKUpdateState(pTCBCur);             /* Change States */
    _enable();                          /* Okay to accept interrupts now */
    tkSchedNext();                      /* Go run others for a while */

    /*
     * We resume here after a TKWakeup, signal, or timeout occurs.
     * If we are still in the timeout queue, remove our entry now.
     * Then check reason for wakeup and set return code appropriately.
     */
    tkAssert(!(pTCBCur->TCBWakeFlags & TK_WF_TIMEOUT));
                                        /* Should be clear by now */

/**************************** PMSEM Start ************************************/
/* Add PM Event Posted flag to switch statement that is checked after a thread */
/* has been woken up. */

    switch(pTCBCur->TCBWakeFlags &
      (TK_WF_INTERRUPTED | TK_WF_TIMEEXP | TK_WF_INTPENDING | TK_WF_PMPOSTED)) {
                                        /* Switch on wakeup type */
/**************************** PMSEM End **************************************/

        case TK_WF_TIMEEXP:             /* Simple timeout */
        case TK_WF_TIMEEXP | TK_WF_INTPENDING: /* Interrupt AFTER timeout */
            tkAssert(cmsecTimeout != -1);      /* Must have had one */
            rc = ERROR_TIMEOUT;         /* Set error code */
            break;

        case TK_WF_INTPENDING:          /* Interrupt after made runnable */
            /*
             *  We used to return ERROR_INTERRUPT here if the thread
             *  were interruptible.  This worked fine for multiple wakeup
             *  where everybody is made runnable, but it breaks down
             *  for single wakeup, since the single thread awakened sees the
             *  error and not the wakeup.  Thus, the wakeup is, in essence,
             *  lost, leaving all the other waiting threads blocked
             *  indefinitely.
             */
            break;                      /* Ignore for now */

        case TK_WF_INTERRUPTED | TK_WF_TIMEEXP: /* Invalid combinations */
        case TK_WF_INTERRUPTED | TK_WF_INTPENDING | TK_WF_TIMEEXP:
            tkAssert(!(pTCBCur->TCBWakeFlags & TK_WF_TIMEEXP));
                                        /* Cause internal error */

        case TK_WF_INTERRUPTED:         /* Truly interrupted */
        case TK_WF_INTERRUPTED | TK_WF_INTPENDING: /* Interrupted twice */
            tkAssert(!funint);          /* Must have been interruptible */
            pTCBCur->TCBWakeFlags = 0;  /* Clear any pending interrupt */
            rc = ERROR_INTERRUPT;       /* Return error */
            break;

/**************************** PMSEM Start ************************************/
/* Add check to see if a PM Event was posted for this thread. The flag will be
 * set in SEMPMWKR.C, semPMPostEventSem, when the user posts the special PM
 * Event semaphore.
 */
        case TK_WF_PMPOSTED:                              /* PM Posted Event */

            pTCBCur->TCBWakeFlags = 0;               /* Clear all wait flags */
            rc = PM_EVENT_POSTED;                    /* Set Return code to  */
                                                     /* PM Event Posted.    */
            break;
/**************************** PMSEM End **************************************/
    }
    pTCBCur->TCBWakeFlags &= TK_WF_INTPENDING;
                                        /* SLEEPING off, INTPENDING saved */

/**************************** PMSEM Start ************************************/

/* p81459 */
    pTCBCur->TCBSpareFlags &= (~SPFLAGS_PM_EVENT);    /* Reset PM Event Flag */

/**************************** PMSEM End **************************************/

    return(rc);                         /* Return error code */
}


/***EP  TKWakeup - release blocked threads
 *
 *    TKWakeup is called to wake up blocked threads.
 *
 *    TKWakeup is interrupt time callable.
 *
 *    See TKSleep for a detailed discussion.
 *
 *    ENTRY    SleepID = event identifier
 *             WakeType - flag bits indicating what type of wakeup to perform
 *               - 0 if multiple wakeup call
 *               - WAKEUP_SINGLE if single wakeup call
 *                 (if this bit not set, then multiple wakeup call)
 *               - WAKEUP_SINGLE_ALL if single wakeup call, but all single
 *                 wakeup sleepers should be awoken (WAKEUP_SINGLE must also
 *                 be set)
 *               - WAKEUP_BOOST if thread(s) being woken are to get priority
 *                 boost
 *             pctcb = Pointer to where count of those awoken will be returned
 *    **************************************************************************
 *    NOTE:        In case its a SINGLE WAKEUP, pointer to the TCB of the thread
 *                 woken up will be returned.
 *    **************************************************************************
 *
 *    EXIT     If successful, reply = NO_ERROR
 *             else reply = error code
 */

void
TKWakeup(void *SleepID, ULONG flWakeType, ULONG *pctcb)
{
    PTCB       *pptcbHash;
    PTCB        ptcb;
    PTCB        ptcbLevel;
    PTCB        ptcbNext;
    USHORT      NewState = STATE_READY;
    ULONG       procflags;
    ULONG       ctcb = 0;               /* Count of threads woken */

    tkAssert(!(flWakeType & WAKEUP_SINGLE) || SleepID != (PVOID) VRSleep);
                                        /* Never wake up sleepers! */
//    _enable();    /*  B732143 */
    procflags = TKDisable();
    pptcbHash = &aptcbSleep[TKSLEEPHASH(SleepID, flWakeType & WAKEUP_SINGLE)];
    TKSLEEPSEARCH(SleepID, ptcb, pptcbHash);

    if ((ptcb != NULL) && (ptcb-> TCBSleepID == SleepID)) {
        tkAssertPriQCircle(ptcb);
        if (flWakeType & WAKEUP_BOOST) {
            NewState = STATE_READYBOOST;        // Decide to boost or not.
        }

        if ((flWakeType & (WAKEUP_SINGLE | WAKEUP_SINGLE_ALL)) ==
            WAKEUP_SINGLE) {
            /* Perform a Single Wakeup (not delayed)
             */
            ctcb = (ULONG) ptcb;        // For Single Wakeup, return the TCB
            ptcb-> TCBState = NewState; // of the thread woken up
            TKUpdateState(ptcb);
        } else {
            /* Perform Multi-Wakeup.
             * Remove entire PriQ from aptcbSleep hash chain.
             * Update hash chain head.
             * Move entire PriQ to the Delayed state.
             * Call TKUpdateState on the first thread in the PriQ.
             */

            ptcb-> TCBpTCBPriNextQ-> TCBpTCBPriPrevQ =
                ptcb-> TCBpTCBPriPrevQ;
            ptcb-> TCBpTCBPriPrevQ-> TCBpTCBPriNextQ =
                ptcb-> TCBpTCBPriNextQ;

            if (ptcb-> TCBpTCBPriNextQ == ptcb) {
                *pptcbHash = NULL;
            } else {
                *pptcbHash = ptcb-> TCBpTCBPriNextQ;
            }

            if (ptcbPriQDelayed == NULL) {
                ptcbPriQDelayed = ptcb;
                ptcb-> TCBpTCBPriNextQ = ptcb;
                ptcb-> TCBpTCBPriPrevQ = ptcb;
            } else {
                ptcb-> TCBpTCBPriNextQ = ptcbPriQDelayed;
                ptcb-> TCBpTCBPriPrevQ = ptcbPriQDelayed-> TCBpTCBPriPrevQ;
                ptcbPriQDelayed-> TCBpTCBPriPrevQ-> TCBpTCBPriNextQ = ptcb;
                ptcbPriQDelayed-> TCBpTCBPriPrevQ = ptcb;
            }
            tkAssertPriQCircle(ptcb);
            tkAssertPriQCircle((*pptcbHash));
            tkAssertPriQCircle(ptcbPriQDelayed);

            /* For each thread in the PriQ,
             * 1) Count threads.
             * 2) Set TCBQState in each thread to STATE_DELAYED.
             * 3) Set TCBState in each thread to NewState.
             */
            ptcbLevel = ptcb;
            do {
                ptcbNext = ptcbLevel;
                do {
                    ctcb++;
                    ptcbNext-> TCBQState = STATE_DELAYED;
                    ptcbNext-> TCBState = NewState;
                    ptcbNext = ptcbNext-> TCBpTCBPriNext;
                } while (ptcbNext != ptcbLevel);

                ptcbLevel = ptcbLevel-> TCBpTCBPriLower;
            } while (ptcbLevel != ptcb);

            // Do an immediate wakeup on the highest priority thread
            // in the PriQ to determine if the RESCHED_PREEMPT bit
            // should be set.   Delay all other remaining work to
            // SCHGetNextRunner so that we can get interrupt windows.

            TKUpdateState(ptcb);
        }
    }
    if (pctcb != NULL) {
        *pctcb = ctcb;
    }
    TKEnable(procflags);
}


/***EP  TKWakeThread - wake up a single (specific) thread
 *
 *    Called from TKForceThread to wakeup a thread that is sleeping
 *    and interruptible.
 *
 *    ENTRY   ptcb      - Address of TCB to wake
 *
 *    EXIT    If successful, reply = NO_ERROR
 *            else reply = error code
 */
void
TKWakeThread (PTCB ptcb)
{
    ULONG       procflags;

    procflags = TKDisable();
    tkAssert(ptcb->TCBWakeFlags & TK_WF_SLEEPING);
    ptcb->TCBState = STATE_READY;
    TKUpdateState(ptcb);
    TKEnable(procflags);
}


/***EP  TKQueryWakeup - query which thread(s) would be awakened
 *
 *      TKQueryWakeup is called to query which threads would be woken
 *      up if TKWakeup had been called instead.  This routine returns
 *      a TCB pointer to the first TCB that would have been woken up.
 *      If no thread would have been awoken, NULL will be returned.
 *
 *      The remaining TCBs that would be woken up will be chained
 *      together on a singly-linked list, via TCBpTCBWakeup.  The
 *      TCBSleepID, and the single/multi wakeup flag will be identical
 *      for all threads on this chain.  The chain will be linked in
 *      priority order, with the first thread on the chain being the
 *      first one to wake up.  The chain pointers will be valid until
 *      the next reschedule.  The chain is terminated with a NULL
 *      pointer.
 *
 *      Note that the priority and/or state of the threads may change
 *      at interrupt time.  This will occur without warning, but will
 *      never update the ordering of the returned chain.  To obtain
 *      trusted state information, the client of TKQueryWakeup sholud
 *      disable interrupts and see if TCBState is still STATE_BLOCKED.
 *      If not, then thread was woken in some interrupt window since
 *      the previous TKQueryWakeup call.  Trusted priority information
 *      is no longer available after the first interrupt window after
 *      TKQueryWakeup.  However, the priority ordering defined by the
 *      TCBpTCBWakeup chain is acceptable for use with interrupts enabled.
 *
 *      No cleanup or initialization will be performed on this chain.
 *      TCBpTCBWakeup must be considered garbage except in the timeslice
 *      just after a call to TKQueryWakeup.
 *
 *      If WAKEUP_SINGLE was specified without WAKEUP_SINGLE_ALL,
 *      the TCB returned would be the only thread awoken.  This is
 *      also indicated via TCBpTCBWakeup.
 *
 *      See TKSleep for a detailed discussion.
 *
 *      ENTRY    SleepID = event identifier
 *               WakeType - flag bits indicating type of wakeup to simulate
 *                 - 0 if multiple wakeup call
 *                 - WAKEUP_SINGLE if single wakeup call
 *                   (if this bit not set, then multiple wakeup call)
 *                 - WAKEUP_SINGLE_ALL if single wakeup call, but all
 *                   single wakeup sleepers should be awoken
 *                   (WAKEUP_SINGLE must also be set)
 *
 *      EXIT     If successful, reply = TCB pointer
 *               else reply = NULL
 */
PTCB
TKQueryWakeup (void *SleepID, ULONG flWakeType)
{
    PTCB        ptcb;
    PTCB        ptcbLevel;
    PTCB        ptcbNext;
    PTCB        ptcbPrev;
    PTCB       *pptcbHash;
    ULONG       procflags;

    procflags = TKDisable();
    pptcbHash = &aptcbSleep[TKSLEEPHASH(SleepID, flWakeType & WAKEUP_SINGLE)];
    TKSLEEPSEARCH(SleepID, ptcb, pptcbHash);

    if ((ptcb == NULL) || (ptcb-> TCBSleepID != SleepID)) {
        ptcb = NULL;            // No threads will be woken.
    } else {
        *pptcbHash = ptcb;      // Found the TCB.  Move hash chain head.
        if ((flWakeType & (WAKEUP_SINGLE | WAKEUP_SINGLE_ALL)) ==
            WAKEUP_SINGLE) {
            // Single wakeup requested.  TKQueryWakeup chain of length one.

            ptcb-> TCBpTCBWakeup = NULL;
        } else {
            // Multi-wakeup requested.  Create the TKQueryWakeup chain.
            // This is only good until the next reschedule.  State and
            // priority ordering is only good until the next interrupt.

            ptcbPrev = ptcb;
            ptcbLevel = ptcb;
            do {
                ptcbNext = ptcbLevel;
                do {
                    ptcbPrev = ptcbPrev-> TCBpTCBWakeup = ptcbNext;
                    ptcbNext = ptcbNext-> TCBpTCBPriNext;
                } while (ptcbNext != ptcbLevel);

                ptcbLevel = ptcbLevel-> TCBpTCBPriLower;
                ptcbPrev = ptcbPrev-> TCBpTCBWakeup = ptcbLevel;
            } while (ptcbLevel != ptcb);

            ptcb-> TCBpTCBPriHigher-> TCBpTCBPriPrev-> TCBpTCBWakeup = NULL;
        }
    }

    TKEnable(procflags);
    return (ptcb);
}


/***    TKDelayedWakeup - Handle delayed TKWakeup events
 *
 *      TKDelayedWakeup handles the delayed TKWakeup events.  These
 *      are thread which are moving into the ready state after an
 *      external event.
 *
 *      TKDelayedWakeup is used to provide interrupt windows
 *      between successive scheduler operations, to guarantee
 *      that minimal interrupt dispatch latency is maintained.
 *
 *      TKDelayedWakeup explicitly enables and disables interrupts,
 *      so TKDelayedWakeup MUST be called with interrupts enabled.
 *
 *      ENTRY   ptcb    - TCB to insert into priority queue
 *
 *      EXIT    ptcb added to proper sleep queue
 */

void
TKDelayedWakeup (void)
{
    /* The new state and priority information is all available
     * at this time.  Just update the state for each thread.
     */

    _disable();
    while (ptcbPriQDelayed != NULL) {
        TKUpdateState(ptcbPriQDelayed);         // Do delayed processing.
        _enable();
        TKBitBucket = 0;                        // Take an interrupt here.
        _disable();
    }
    _enable();
}


/***    tkSleepQInsert - Insert TCB into the Sleep priority queues
 *
 *      ENTRY   ptcb    - TCB to insert into priority queue
 *
 *      EXIT    ptcb added to proper sleep queue
 */

void
tkSleepQInsert (PTCB ptcb)
{
    PTCB       *pptcbHash;
    register    PTCB        ptcbScan;
    ULONG       procflags;

    procflags = TKDisable();
    tkAssertPriQNull(ptcb);                     // Linkages must be clean
    tkAssert(ptcb-> TCBState == STATE_BLOCKED);

    pptcbHash = &aptcbSleep[TKSLEEPHASHTCB(ptcb)];
    TKSLEEPSEARCH(ptcb-> TCBSleepID, ptcbScan, pptcbHash);

    if (ptcbScan == NULL) {
        /* The hash table entry is empty, so just set the head pointer.
         * All other linkages are already in place.   (See tkAssertPriQNull)
         */
        TKPriCalc(ptcb);

        *pptcbHash = ptcb;
    } else {
        tkAssertPriQCircle(ptcbScan);

        if (ptcbScan-> TCBSleepID == ptcb-> TCBSleepID) {
            /* Another thread exists with the same block ID.
             * Insert thread into priority queue at this location
             * in the chain.
             */
            *pptcbHash = ptcbScan;
            tkPriQInsert(pptcbHash, ptcb);

            /* If this thread defined a new highest priority level
             * for this TCBSleepID, AND there has already been a hash
             * collision, shift the NextQ/PrevQ chain to point to the
             * new thread's TCB.  Otherwise, everything is already OK.
             */
            if ((*pptcbHash != ptcbScan) &&
                (ptcbScan-> TCBpTCBPriNextQ != ptcbScan)) {
                ptcbScan-> TCBpTCBPriNextQ-> TCBpTCBPriPrevQ = *pptcbHash;
                ptcbScan-> TCBpTCBPriPrevQ-> TCBpTCBPriNextQ = *pptcbHash;
                (*pptcbHash)-> TCBpTCBPriNextQ = ptcbScan-> TCBpTCBPriNextQ;
                (*pptcbHash)-> TCBpTCBPriPrevQ = ptcbScan-> TCBpTCBPriPrevQ;
                ptcbScan-> TCBpTCBPriNextQ = ptcbScan;
                ptcbScan-> TCBpTCBPriPrevQ = ptcbScan;
            }
        } else {
            /* No other thread exists with the same block ID.
             * Just link the thread onto the PriQ chain PREVIOUS to
             * ptcbScan.   This gives an MRU ordering by SleepID.
             * Update the hash chain head.
             */
            TKPriCalc(ptcb);

            *pptcbHash = ptcb;
            ptcb-> TCBpTCBPriNextQ = ptcbScan;
            ptcb-> TCBpTCBPriPrevQ = ptcbScan-> TCBpTCBPriPrevQ;
            ptcbScan-> TCBpTCBPriPrevQ-> TCBpTCBPriNextQ = ptcb;
            ptcbScan-> TCBpTCBPriPrevQ = ptcb;
        }
        tkAssertPriQCircle(ptcb);
        tkAssertPriQCircle(ptcbScan);
    }
    TKEnable(procflags);
}


/***    tkSleepQRemove - Remove a TCB from the Sleep priority queues
 *
 *      ENTRY   ptcb    - TCB to be removed from sleep Q.
 *              INTERRUPTS DISABLED
 *
 *      EXIT    ptcb removed from proper sleep queue
 */

void
tkSleepQRemove (PTCB ptcb)
{
    tkPriQRemoveChain(&aptcbSleep[TKSLEEPHASHTCB(ptcb)], ptcb);
}


/***    tkPriQRemoveChain - Remove a TCB from a priority queue chain
 *
 *      tkPriQRemoveChain removes a TCB from a priority queue chain.
 *
 *      If the head priority queue exists after the deletion, the
 *      head pointer will continue to point at that same queue.
 *
 *      If the head priority queue does not exist after the deletion,
 *      the head pointer will be moved to the next Priority Queue in
 *      the chain, following TCBpTCBPriNextQ.
 *
 *      If the queue becomes empty, *pptcb will be set to NULL.
 *
 *      The caller is responsible for the state of the interrupt flag.
 *
 *      ENTRY   pptcb   - address of priority queue head pointer
 *              ptcb    - TCB to insert into priority queue
 *
 *      EXIT    *pptcb modified to point to new head of queue
 *              ptcb added to priority queue
 */

void
tkPriQRemoveChain (PTCB *pptcb, register PTCB ptcb)
{
    PTCB        ptcbPriQHead;
    ULONG       procflags;                     /* P724694 */

    procflags = TKDisable();                   /* P724694 */

    tkAssertPriQCircle(ptcb);
    ptcbPriQHead = ptcb;
    tkPriQRemove(SSToDS(&ptcbPriQHead), ptcb);

    /* If we removed the head of some PriQ, update PriQ chain links.
     */
    if (ptcbPriQHead != ptcb) {
        /* If this was the last element of the PriQ, move chain to
         * head of the next Queue in the chain.
         */
        if (ptcbPriQHead == NULL) {
            /* If this was not the last PriQ in the chain, move head
             * to next TCB in the chain, and remove the PriQ from
             * the chain.
             */
            if (ptcb-> TCBpTCBPriNextQ != ptcb) {
                ptcbPriQHead = ptcb-> TCBpTCBPriNextQ;
                ptcb-> TCBpTCBPriNextQ-> TCBpTCBPriPrevQ =
                    ptcb-> TCBpTCBPriPrevQ;
                ptcb-> TCBpTCBPriPrevQ-> TCBpTCBPriNextQ =
                    ptcb-> TCBpTCBPriNextQ;
                ptcb-> TCBpTCBPriNextQ = ptcb;
                ptcb-> TCBpTCBPriPrevQ = ptcb;
            }
        } else {
            /* This was not the last TCB in the PriQ.  If other Queues
             * exist in the chain, move the chain links to point at the
             * new head of the PriQ.  Otherwise, everything is already OK.
             */

            if (ptcb-> TCBpTCBPriNextQ != ptcb) {
                ptcb-> TCBpTCBPriNextQ-> TCBpTCBPriPrevQ = ptcbPriQHead;
                ptcb-> TCBpTCBPriPrevQ-> TCBpTCBPriNextQ = ptcbPriQHead;
                ptcbPriQHead-> TCBpTCBPriNextQ = ptcb-> TCBpTCBPriNextQ;
                ptcbPriQHead-> TCBpTCBPriPrevQ = ptcb-> TCBpTCBPriPrevQ;
                ptcb-> TCBpTCBPriNextQ = ptcb;
                ptcb-> TCBpTCBPriPrevQ = ptcb;
            }
        }

        /* Update *pptcb if this tcb was the head of the whole chain.
         */
        if (*pptcb == ptcb) {
            *pptcb = ptcbPriQHead;
        }
    }

    /* Make sure the current thread's chain linkages are clean as we leave.
     */
    tkAssertPriQNull(ptcb);
    tkAssertPriQCircle(ptcbPriQHead);
    TKEnable(procflags);                         /* P724694 */
}


/***    tkPriQInsert - Insert TCB into a priority queue
 *
 *      ENTRY   pptcb   - address of priority queue head pointer
 *              ptcb    - TCB to insert into priority queue
 *
 *      EXIT    *pptcb modified to point to new head of queue
 *              ptcb added to priority queue
 */

void
tkPriQInsert (PTCB *pptcb, register PTCB ptcb)
{
    register    PTCB ptcbScan;
    ULONG       procflags;

    procflags = TKDisable();

    /* Make sure the current thread's Q linkages are clean before
     * we continue.
     */
    tkAssertPriQCircle(ptcb);
    tkAssert ((ptcb-> TCBpTCBPriLower == ptcb) &&
              (ptcb-> TCBpTCBPriHigher == ptcb) &&
              (ptcb-> TCBpTCBPriPrev == ptcb)   &&
              (ptcb-> TCBpTCBPriNext == ptcb));

    TKPriCalc (ptcb);

    /* If no other thread is in this queue, just set *pptcb.  QED.
     */
    if ((ptcbScan = *pptcb) == NULL) {
        *pptcb = ptcb;
    } else {
        /* Scan the priority queue levels to find the insertion point.
         * Leave ptcbScan pointing at or just past the insertion point.
         */
        if (ptcbScan-> TCBPriority != ptcb-> TCBPriority) {
            if (ptcbScan-> TCBPriority < ptcb-> TCBPriority) {
                /* There is a new highest-priority thread.
                 * Set head pointer.  No scan required.
                 */
                *pptcb = ptcb;
                goto NewTopLevel;           // No need to recheck priority
            } else {
                /* Scan only if the new thread's priority is strictly
                 * between the current highest and lowest priorities.
                 * (Note: this check protects against an infinite loop)
                 */
                if (ptcbScan-> TCBpTCBPriHigher-> TCBPriority <
                    ptcb-> TCBPriority) {
                    /* Scan required.  We can never scan too far here.
                     */
                    while (ptcbScan-> TCBPriority > ptcb-> TCBPriority) {
                        ptcbScan = ptcbScan-> TCBpTCBPriLower;
                    }
                } else {
                    /* If the thread is at an existing lowest priority,
                     * insert it there.
                     */
                    if (ptcbScan-> TCBpTCBPriHigher-> TCBPriority ==
                        ptcb-> TCBPriority) {
                        ptcbScan = ptcbScan-> TCBpTCBPriHigher;
                    }
                }
            }
        } else {
            goto EndOfLevel;            // No need to recheck priority
        }

        if (ptcbScan-> TCBPriority == ptcb-> TCBPriority) {
EndOfLevel:
            /* The priority level already exists.  Insert on end of level.
             */
            ptcb-> TCBpTCBPriNext = ptcbScan;
            ptcb-> TCBpTCBPriPrev = ptcbScan-> TCBpTCBPriPrev;
            ptcbScan-> TCBpTCBPriPrev-> TCBpTCBPriNext = ptcb;
            ptcbScan-> TCBpTCBPriPrev = ptcb;
        } else {
NewTopLevel:
            /* Define new priority level, just above ptcbScan.
             */
            ptcb-> TCBpTCBPriLower  = ptcbScan;
            ptcb-> TCBpTCBPriHigher = ptcbScan-> TCBpTCBPriHigher;
            ptcbScan-> TCBpTCBPriHigher-> TCBpTCBPriLower = ptcb;
            ptcbScan-> TCBpTCBPriHigher = ptcb;
        }
        tkAssertPriQCircle(ptcb);
    }

    TKEnable(procflags);
}


/***    tkPriQRemove - Remove a specific TCB from a priority queue
 *
 *      ENTRY   pptcb   - address of priority queue head pointer
 *              ptcb    - TCB to remove from priority queue
 *
 *      EXIT    *pptcb modified to point to new head of queue
 *              ptcb removed from priority queue
 */

void
tkPriQRemove (PTCB *pptcb, register PTCB ptcb)
{
    ULONG       procflags;

    procflags = TKDisable();

    tkAssertPriQCircle(ptcb);
    /*
     * If this is the last TCB at this priority level,
     * remove the priority level.
     */
    if (ptcb-> TCBpTCBPriNext == ptcb) {
        /* If there is only one priority level, there must only be
         * one thread in the queue.  Just set the head to NULL.
         * All the other pointers are OK at this point.
         */
        if (ptcb-> TCBpTCBPriLower == ptcb) {
           *pptcb = NULL;
        } else {
            /* There is more than one level in the queue.
             * If this TCB is at the top of the queue, the next
             * thread is at the next lower priority level.
             */
            if (*pptcb == ptcb) {
                *pptcb = ptcb-> TCBpTCBPriLower;
            }

            /* There is more than one level.  Remove the current
             * level, and clean up TCB garbage.
             */
            ptcb-> TCBpTCBPriHigher-> TCBpTCBPriLower =
                ptcb-> TCBpTCBPriLower;
            ptcb-> TCBpTCBPriLower-> TCBpTCBPriHigher =
                ptcb-> TCBpTCBPriHigher;
            ptcb-> TCBpTCBPriLower = ptcb;
            ptcb-> TCBpTCBPriHigher = ptcb;
        }
    } else {
        /* There is more than one thread at the current priority
         * level.  If this thread is the top of the queue,
         * the next thread is at TCBpTCBPriNext.
         */
        if (*pptcb == ptcb) {
            *pptcb = ptcb-> TCBpTCBPriNext;
        }

        /* If this thread is the first thread in the given level,
         * Correct the adjacent Higher and Lower pointers to
         * point to the next thread in the current level.  Also,
         * point the next thread in the current level to the new
         * higher and lower positions.
         */
        if (ptcb-> TCBpTCBPriLower != ptcb) {
            ptcb-> TCBpTCBPriLower-> TCBpTCBPriHigher =
                ptcb-> TCBpTCBPriNext;
            ptcb-> TCBpTCBPriHigher-> TCBpTCBPriLower =
                ptcb-> TCBpTCBPriNext;
            ptcb-> TCBpTCBPriNext-> TCBpTCBPriLower =
                ptcb-> TCBpTCBPriLower;
            ptcb-> TCBpTCBPriNext-> TCBpTCBPriHigher =
                ptcb-> TCBpTCBPriHigher;
            ptcb-> TCBpTCBPriLower = ptcb;      // cleanup current
            ptcb-> TCBpTCBPriHigher = ptcb;
        }

        /* Remove the tcb from the current level.
         */
        ptcb-> TCBpTCBPriNext-> TCBpTCBPriPrev =
            ptcb-> TCBpTCBPriPrev;
        ptcb-> TCBpTCBPriPrev-> TCBpTCBPriNext =
            ptcb-> TCBpTCBPriNext;
        ptcb-> TCBpTCBPriNext = ptcb;
        ptcb-> TCBpTCBPriPrev = ptcb;
    }

    /* Make sure the current thread's linkages are clean.
     */
    tkAssert (ptcb-> TCBpTCBPriLower == ptcb);
    tkAssert (ptcb-> TCBpTCBPriHigher == ptcb);
    tkAssert (ptcb-> TCBpTCBPriPrev == ptcb);
    tkAssert (ptcb-> TCBpTCBPriNext == ptcb);
    tkAssertPriQCircle(ptcb);
    TKEnable(procflags);
}


/***LP  TKGetReadyThread - Get the highest priority ready thread
 *
 *      This procedure returns the highest priority ready thread in
 *      the scheduler priority queue.
 *
 *      The special -1 return value was added to provide an interrupt
 *      window in cases where large numbers of threads move from the
 *      ready state into the TSD state.  In this case, the interrupt
 *      window must be provided externally, and TKGetReadyThread must
 *      then be called again.  This is not an idle loop path.
 *
 *      The special NULL return value means indicates that there are
 *      no threads to switch to at the current time.  Enter kernel
 *      idle loop.
 *
 *      TKGetReadyThread()
 *
 *      ENTRY   NONE
 *
 *      EXIT    NULL    - No thread to switch to
 *              -1      - Interrupt window required
 *              ptcb    - ptcb is the thread to switch to
 *
 *      WARNING
 *
 *      EFFECTS
 */

PTCB
TKGetReadyThread(void)
{
    if (ptcbPriQReady != NULL) {
        // If the TSD is not currently mapped, place it into the
        // TSD state for reclaim by TSD daemon.  Because there can
        // be a large number of threads moving between these states
        // all at once, we must provide an interrupt window between
        // each thread.   We can't do that here, so we return -1.

        if (ptcbPriQReady-> TCBpteKStackTSD == 0) {
            ptcbPriQReady->TCBState = STATE_TSD;
            TKUpdateState(ptcbPriQReady);
            return((PTCB) -1);         // Need an interrupt window & restart
        }

        if (ptcbPriQReady != pTCBCur) {
            // We are switching to another thread.  Reset the timeslice.
            // If we don't switch, we just use the existing timeslice.

            if (DynTimeSlice != 0) {
              tkMaxSlice = 128;
            } else {
              tkMaxSlice = 32;
            }

            TOMCancel(&toeTimeslice);           // Ignore previous timeslice.
            TOMSet(&toeTimeslice, (ULONG)
                (ptcbPriQReady-> TCBSchFlags & SCH_MINSLICE) ?
                tkMinSlice : tkMaxSlice, TOM_W_TIMESLICE);
        }
    }

    return (ptcbPriQReady);
}


/***LP  TKIdleLoop - Perform Idle-Loop Processing
 *
 *      TKIdleLoop is called when the system has no threads to run.
 *
 *      ENTRY   ptcb    TCB pointer of thread that changed priority
 *
 *      RETURN  NONE
 *
 *      WARNING
 *
 *      EFFECTS
 */

void
TKIdleLoop (void)
{
    ULONG       ctcb;

    // There is nothing to do right now.
    // If there is a TSD on the TSD queue, and the TSDDaemon
    // is not swapping in a TSD, then kick the TSD daemon to keep
    // it moving.

    if ((ptcbPriQTSD != NULL) && (ptcbPriQGetStack == NULL)) {
        TKWakeup ((void *) tkTSDDaemon, 0, SSToDS(&ctcb));
    }

    return;
}


/***LP  TKUpdatePriority - Update thread priority after a possible change.
 *
 *      ENTRY   ptcb    TCB pointer of thread that changed priority
 *
 *      RETURN  NONE
 *
 *      WARNING
 *
 *      EFFECTS
 */

void
TKUpdatePriority (register PTCB ptcb)
{
    ULONG       PriOld;
    ULONG       procflags;

    procflags = TKDisable();

    /* An interrupt-time call may have already updated the state
     * before the tasktime code could call TKUpdateState.  In
     * this case, the states will be the same.  We need to update
     * the state in this case.
     */

    if ((ptcb-> TCBQState != ptcb-> TCBState) &&
        (ptcb-> TCBQState != STATE_DELAYED) &&
        (ptcb-> TCBQState != STATE_VOID)) {
        TKUpdateState (ptcb);        // no state change required
        TKEnable (procflags);
        return;
    }

    PriOld = ptcb-> TCBPriority;
    TKPriCalc(ptcb);

#ifdef  TKPROFILE
    ++tkcPriUpdates;                    // Count All Priority Updates
#endif

    /* If priority did not change, don't bother to change the queues.
     */
    if (PriOld != ptcb-> TCBPriority) {
#ifdef  TKPROFILE
        ++tkcPriChanges[ptcb-> TCBQState];      // Count Priority Changes
#endif
        switch (ptcb-> TCBQState) {
            case STATE_RUNNING:                 // PriQ of one element
                if ( (ptcbPriQReady != NULL) &&
                     (ptcb-> TCBPriority < ptcbPriQReady-> TCBPriority)) {
                    Resched |= RESCHED_PREEMPT;
                    if (ptcb-> TCBPriClass == CLASS_TIME_CRITICAL) {
                        TCResched |= RESCHED_PREEMPT;
                    }
                }
                break;
            case STATE_VOID:                    // Not on a priority queue
            case STATE_SUSPENDED:               // Not on a priority queue
            case STATE_FROZEN:                  // Not on a priority queue
            case STATE_GETSTACK:                // PriQ of one element
            case STATE_BADSTACK:                // No need to modify priority
                break;

            case STATE_READY:                   // Reorder, try preemption
                tkPriQRemove (&ptcbPriQReady, ptcb);
                tkPriQInsert (&ptcbPriQReady, ptcb);
                if ( (ptcbPriQRunner != NULL) &&
                     (ptcb-> TCBPriority > ptcbPriQRunner-> TCBPriority)) {
                    Resched |= RESCHED_PREEMPT;
                    if (ptcb-> TCBPriClass == CLASS_TIME_CRITICAL) {
                        TCResched |= RESCHED_PREEMPT;
                    }
                }
                break;

            case STATE_DELAYED:                 // No reorder, try preemption.
                /* If thread changed a priority to higher than current
                 * thread, preempt the current thread.  (See TKUpdateState)
                 */
                if ((ptcbPriQRunner == NULL) ||
                    (ptcb-> TCBPriority > ptcbPriQRunner-> TCBPriority)) {
                    Resched |= RESCHED_PREEMPT;
                    if (ptcb-> TCBPriClass == CLASS_TIME_CRITICAL) {
                        TCResched |= RESCHED_PREEMPT;
                    }
                }
                break;

            case STATE_BLOCKED:
                tkSleepQRemove(ptcb);
                tkSleepQInsert(ptcb);
                break;

            case STATE_CRITSEC:
                tkPriQRemove (&(ptcb-> TCBpPTDA-> ptda_pTCBPriQCritSec), ptcb);
                tkPriQInsert (&(ptcb-> TCBpPTDA-> ptda_pTCBPriQCritSec), ptcb);
                break;

            case STATE_TSD:
                tkPriQRemove (&ptcbPriQTSD, ptcb);
                tkPriQInsert (&ptcbPriQTSD, ptcb);
                break;

            case STATE_READYBOOST:
            default:
                tkAssert(0);            // Invalid state for a priority update.
        }
    }
    TKEnable(procflags);
}

/***LP  TKUpdateState - Update thread state after a possible change.
 *
 *      TKUpdateState corrects the priority queues to reflect
 *      the new state and priority of the specified thread.
 *
 *      The old state is determined from TCBQState.
 *      The new state is determined from TCBState.
 *
 *      TCBQState       Operation (Descending State)
 *      --------------------------------------------
 *      void            Initialize Priority Queue Structure
 *      ready           Remove from ptcbPriQReady
 *      blocked         tkSleepQRemove(ptcb)
 *      suspended       none - handled externally
 *      frozen          none - handled externally
 *      critsec         Remove from ptda_pTCBPriQCritSec
 *      running         Remove from ptcbPriQRunner (optimized)
 *      readyboost      panic - invalid descending state
 *      tsd             Remove from ptcbPriQTSD
 *      delayed         Remove from ptcbPriQDelayed chain
 *      getstack        Remove from ptcbPriQGetStack
 *
 *      TCBState        Operation (Ascending State)
 *      -------------------------------------------
 *      void            none
 *      ready           Add to ptcbPriQReady
 *      blocked         tkSleepQInsert(ptcb)
 *      suspended       none - handled externally
 *      frozen          none - handled externally
 *      critsec         Add to ptda_pTCBPriQCritSec
 *      running         Add to ptcbPriQRunner (optimized)
 *      readyboost      Boost Priority, make ready.
 *      tsd             Add to ptcbPriQTSD chain
 *      delayed         Not supported through this interface.  See TKWakeup.
 *      getstack        Add to ptcbPriQGetStack.
 *
 *      ENTRY   ptcb    TCB pointer of thread that changed state
 *
 *      RETURN  NONE
 *
 *      WARNING
 *
 *      EFFECTS
 */

void
TKUpdateState (register PTCB ptcb)
{
    ULONG       procflags;

    procflags = TKDisable();

    /* An interrupt-time call may have already updated the state
     * before the tasktime code could call TKUpdateState.  In
     * this case, the states will be the same.  We need only
     * check the priority in this case to verify the operation.
     */

    if (ptcb-> TCBQState == ptcb-> TCBState) {
        TKUpdatePriority (ptcb);        // no state change required
        TKEnable (procflags);
        return;
    }

#ifdef  TKPROFILE
    /* Count all state transitions if profiling.
     */
    ++tkcStateTrans[ptcb-> TCBQState][ptcb-> TCBState];
#endif

    /* Assert that a valid state transition is being attempted.
     * (This is just a bit-vector lookup - see above)
     */
    tkAssertStateTrans(ptcb-> TCBQState, ptcb-> TCBState);

     /***PV  Perfview - cpu state change measurement hook
      *                 thread number of times scheduled to run hook
      *                 thread time running hook
      *                 thread number of times passed over hook
      *                 thread time ready but not running hook
      *
      *      CPU hook 2 as described in DCR 614.
      *      Thread hooks 1-4 as described in DCR 614.
      */

//53160    PvwMeasurement(cpu,StateChange,0);

    /* Handle Descending state (QState)
     */

    switch (ptcb-> TCBQState) {
        case STATE_VOID:
            schInitTCB(ptcb);
            TOMSet (&ptcb-> TCBtoe, TOM_WAIT_FOREVER, TOM_W_STARVE);
            ptcb-> TCBpTCBPriHigher = ptcb;    // Initialize priority queue
            ptcb-> TCBpTCBPriLower = ptcb;
            ptcb-> TCBpTCBPriNext = ptcb;
            ptcb-> TCBpTCBPriPrev = ptcb;
            ptcb-> TCBpTCBPriNextQ = ptcb;
            ptcb-> TCBpTCBPriPrevQ = ptcb;
            break;

        case STATE_SUSPENDED:                   // State Handled externally
        case STATE_FROZEN:                      // State Handled externally
            break;

        case STATE_GETSTACK:                    // PriQ of one thread
            ptcbPriQGetStack = NULL;            // Optimized tkPriQRemove
            break;

        case STATE_BADSTACK:                    // Kill off a bad thread
            tkPriQRemove (&ptcbPriQBadStack, ptcb);
            break;

        case STATE_BLOCKED:                     // State Handled externally
            /* Remove from timeout queue (if necessary)
             */
            if (ptcb-> TCBWakeFlags & TK_WF_TIMEOUT) {
                TOMCancel(&(ptcb->TCBtoe));
                ptcb-> TCBWakeFlags &= ~TK_WF_TIMEOUT;
            }
            tkSleepQRemove(ptcb);
            break;

        case STATE_CRITSEC:
            tkPriQRemove (&(ptcb-> TCBpPTDA-> ptda_pTCBPriQCritSec), ptcb);
            break;

        case STATE_READY:
            tkPriQRemove (&ptcbPriQReady, ptcb);
            break;

        case STATE_RUNNING:
            /* If the thread has a starvation boost and has not yet
             * performed any useful work, give it a minimum timeslice.
             * If it is starved, and has performed useful work, then
             * clear the starvation boost.  Otherwise, allocate a
             * maximum timeslice, and destroy the IO boost.
             * (See also TKTomTimeslice for starvation clearing)
             */
            ptcb-> TCBSchFlags &= ~SCH_MINSLICE;
            if (ptcb-> TCBPriClassMod & (CLASSMOD_DEVICE|CLASSMOD_STARVED)) {
                ptcb-> TCBPriClassMod &= ~CLASSMOD_DEVICE;
                if (ptcb-> TCBPriClassMod & CLASSMOD_STARVED) {
                    if (!(ptcb-> TCBForcedActions & TK_FF_SCH)) {
                        ptcb-> TCBPriClassMod &= ~CLASSMOD_STARVED;
                    } else {
                        // Maintain the starvation boost.
                        ptcb-> TCBSchFlags |= SCH_MINSLICE;
                    }
                }
                TKPriCalc(ptcb);
            }

            if (ptcb-> TCBState == STATE_READY) {
                /* Running thread still wants to run.  Relinquish CPU
                 * only if it is no longer the highest priority thread,
                 * and only if there is another thread to run.
                 */
                if ((ptcbPriQReady != NULL) &&

/*************** RAC, make check for other threads at the same priority *****/
/****************************************************************************/
                    (ptcbPriQReady-> TCBPriority > ptcb-> TCBPriority)) {
                    Resched |= RESCHED_RELINQUISH;

                    if (ptcbPriQRunner-> TCBPriClass == CLASS_TIME_CRITICAL) {
                        TCResched |= RESCHED_RELINQUISH;
                    }
                }
            }
            ptcbPriQRunner = NULL;              // Optimized tkPriQRemove
            break;

        case STATE_TSD:
            tkPriQRemove (&ptcbPriQTSD, ptcb);
            break;

        case STATE_DELAYED:
            /* Remove from timeout queue (if necessary)
             */
            if (ptcb-> TCBWakeFlags & TK_WF_TIMEOUT) {
                TOMCancel(&(ptcb->TCBtoe));
                ptcb-> TCBWakeFlags &= ~TK_WF_TIMEOUT;
            }
            tkPriQRemoveChain(&ptcbPriQDelayed, ptcb);
            break;

        case STATE_READYBOOST:                  // Can't be a descending state
        default:
            tkAssert(0);                        // Invalid state
    }

    tkAssertPriQNull(ptcb);                     // Linkages must be clean here

    /* Handle Ascending state
     */

    switch (ptcb-> TCBState) {
        case STATE_VOID:                        // Void - just destroy it.
        case STATE_SUSPENDED:                   // Handled externally
        case STATE_FROZEN:                      // Handled externally
            break;

        case STATE_BLOCKED:
            tkSleepQInsert(ptcb);
            break;

        case STATE_CRITSEC:
            tkPriQInsert (&(ptcb-> TCBpPTDA-> ptda_pTCBPriQCritSec), ptcb);
            break;

        case STATE_READYBOOST:                  // Becomes STATE_READY
            if ((SISData.SIS_Dynamic != 0) &&
                (ptcb-> TCBPriClass != CLASS_TIME_CRITICAL) &&  /* B732143 */
                (ptcb-> TCBPriClass != CLASS_IDLE_TIME))        /* B732143 */
            {
                ptcb-> TCBPriClassMod |= CLASSMOD_DEVICE;
                ptcb-> TCBSchFlags |= SCH_MINSLICE;     // Minimum timeslice
            }
            ptcb-> TCBState = STATE_READY;

        case STATE_READY:
            tkPriQInsert (&ptcbPriQReady, ptcb);

            /* If the thread was not previously ready to run,
             * begin the starvation countdown.  Here, we count the
             * tsd state as a runnable state where starvtion applies.
             */
            if ((ptcb-> TCBQState != STATE_READY) &&
                (ptcb-> TCBPriClass != CLASS_TIME_CRITICAL) &&  /* B732143 */
                (ptcb-> TCBPriClass != CLASS_IDLE_TIME)     &&  /* B732143 */
                (ptcb-> TCBQState != STATE_GETSTACK)) {
                TOMCancel (&ptcb-> TCBtoe);
                TOMSet (&ptcb-> TCBtoe, cmsecStarveLength, TOM_W_STARVE);
            }

            /* Thread is becomming ready.  If there is no current thread,
             * or if the incomming thread has a higher priority than
             * the current runner, or if the incomming thread has a
             * priority equal to the current runner, AND the current
             * runner has recently exhausted it's timeslice, then preempt
             * the runner to run this thread instead.
             *
             * If Resched was set, and the incomming thread is time
             * critical, set TCResched also.
             */
            if ((ptcbPriQRunner == NULL) ||
                (ptcb-> TCBPriority > ptcbPriQRunner-> TCBPriority)) {
                Resched |= RESCHED_PREEMPT;
                if (ptcb-> TCBPriClass == CLASS_TIME_CRITICAL) {
                    TCResched |= RESCHED_PREEMPT;
                }
            }
            break;

        case STATE_RUNNING:
            ptcbPriQRunner = ptcb;              // Optimized tkPriQInsert
            break;

        case STATE_TSD:
            if (ptcbPriQTSD == NULL) {          // Kick TSD daemon
                SCHSetPriority(ptcbtkTSDDaemon,
                    (CLASS_TIME_CRITICAL << 8) + LEVEL_MAX);
                TKWakeup((void *) tkTSDDaemon, 0, &TKBitBucket);
            }
            tkPriQInsert (&ptcbPriQTSD, ptcb);
            break;

        case STATE_GETSTACK:                    // PriQ of one thread
            ptcbPriQGetStack = ptcb;            // Optimized tkPriQInsert
            break;

        case STATE_BADSTACK:                    // Thread failed TSD swap in!
            tkPriQInsert (&ptcbPriQBadStack, ptcb);
            break;

        case STATE_DELAYED:                     // Not Supported Individually
        default:
            tkAssert(0);                        // Invalid state
    }

    ptcb-> TCBQState = ptcb-> TCBState;
    tkAssertPriQCircle(ptcb);                   // Linkages must be OK here

    if ( ptcbPriQReady != NULL ) {
         if ( ptcbPriQRunner == NULL  ) {

              Resched |= RESCHED_PREEMPT;

              if (ptcbPriQReady-> TCBPriClass == CLASS_TIME_CRITICAL) {
                   TCResched |= RESCHED_PREEMPT;
              }
          }                                                           //  59378
    }

    TKEnable(procflags);
}


/***LP  TKYieldNonIdle - Yield to Non-Idle threads                  B733890
 *                                                                  B733890
 *      ENTRY   NONE                                                B733890
 *                                                                  B733890
 *      RETURN  NONE                                                B733890
 *                                                                  B733890
 *      WARNING                                                     B733890
 *                                                                  B733890
 *      EFFECTS                                                     B733890
 */                                                              // B733890

void                                                             // B733890
TKYieldNonIdle (void)                                            // B733890
{                                                                // B733890

  if ((ptcbPriQReady != NULL) && (ptcbPriQReady->TCBPriClass != CLASS_IDLE_TIME)) { // B733890

    TKYield();                          /* Let someone else run B733890 */
  }                                                              // B733890
                                                                 // B733890
}

/***LP  tkTSDDaemon - Make all TSDs in the TSD queue present
 *
 *      This procedure runs as a kernel internal thread.  It gets
 *      TSDs from the TSD queue until the queue is empty; then it
 *      sleeps until awakened.
 *
 *      Threads only move onto the TSD queue in TKGetReadyThread,
 *      and only come off of the TSD queue in tkTSDDaemon.  Threads
 *      may be repositioned on the TSD queue in response to priority
 *      changes.
 *
 *      tkTSDSwapRateMax sets an upper bound on TSD swap activity, so
 *      that the system does not spend all its time in the TSDDaemon
 *      on systems that have an ultra-fast swap device but don't have
 *      enough memory to hold all the TSDs at once.  tkTSDSwapRateMax
 *      is a tunable paramater, based on the MaxSlice parameter in
 *      CONFIG.SYS.   Because of preemption side effects, tkTSDSwapRateMax
 *      has the effect of setting the timeslice for lower priority
 *      threads while TSDs are required.
 *
 *      While swapping in a TSD, tkTSDDaemon changes the thread to
 *      STATE_GETSTACK.  The existence of a STATE_GETSTACK thread is
 *      used in the idle loop to detect whether an early wakeup of the
 *      TSD daemon (shorter than tkTSDSwapRateMax) is profitable.
 *
 *      tkTSDDaemon()
 *
 *      ENTRY   NONE
 *
 *      RETURN  NONE
 *
 *      WARNING The TSDDaemon is a time-critical thread.  Yield responsibly.
 *
 *      EFFECTS Changes ptcbPriQGetStack.
 */

void
tkTSDDaemon (void)
{
    register tcb_t      *ptcb;          /* TCB pointer */
    tcb_t               *ptcbCross;     /* Cross-link TCB pointer */
    int                 rc;             /* Error code */

    /* smSetSwapFlags and smClearSwapFlags depend on TSD daemon being pid 1 */

    tkAssert(pPTDACur->ptda_Pid == 1);

    /* Needed for hand-rolled priority inversion protection for compactor */
    SMCompactSlot = pTCBCur->TCBNumber;

    /* Set the TSD Daemon to Time-Critical priority. */

    SCHSetPriority(pTCBCur, (CLASS_TIME_CRITICAL << 8) + LEVEL_MAX);

    for(;;) {                           /* Loop forever */
        TKBeginNoBlock();
        while(ptcbPriQTSD != NULL) {    /* While the TSD queue is not empty */
            /* Get the TSD to fetch.  Mark it as incomming by
             * changing the thread's state to STATE_GETSTACK.
             */

            ptcb = ptcbPriQTSD;
            // SCHSetState(ptcb, STATE_GETSTACK);
            ptcb->TCBState = STATE_GETSTACK;
            TKUpdateState(ptcb);

            /*
             * Get the TSD.  Note that other threads will run while
             * tkTSDDaemon blocks to swap in the TSD.  Note that as
             * swap devices get faster, system throughput will go down on
             * highly overcommitted systems (!), so there is a momentary
             * sleep at the bottom of this loop to maintain throughput.
             */

            TKEndNoBlock();
            rc = PGMakePresent(((page_t) ptcb->TCBpTSD + TSDSTART) >> PAGESHIFT,
              NULL,1,0);                /* Make the TSD page present */

            if (rc != NO_ERROR) {
                /* We just failed to swap in a thread!  The system is
                 * in an unstable state.  We should shut down soon.
                 * Move this thread into the BADSTACK state, and
                 * suggest a shutdown.  Continue swapping in other
                 * TSDs so that the shutdown can make progress.  If
                 * we lose even more theads, just put them on the list.
                 */

                // SCHSetState(ptcb, STATE_BADSTACK);
                ptcb->TCBState = STATE_BADSTACK;
                TKUpdateState(ptcb);

                //b724288 Balance the TKEndNoBlock()/TKBeginNoBlock() pairs.
                TKBeginNoBlock();                                      //b724288

                // BUGBUG: Shut the system down here!
                continue;
            }

            /* Now set the accessed bit in the TSD's PTE.  This increases
             * the odds of keeping this TSD in memory if the ager decides
             * to run now.  We don't expect to block here, but the system
             * will survive if we do.  However, the new thread will not be
             * switched to until we call SCHSetState(ready).
             */

            ptcbCross = ptcb->TCBpTSD->TSDpTCB;
            TKBeginNoBlock();
            tkAssert(ptcbCross == ptcb);/* Check the cross-link */
            tkAssert(ptcb->TCBState == STATE_GETSTACK);
            PGKStackRemap(ptcb);        /* TSD may have moved */
            // SCHSetState(ptcb, STATE_READYBOOST);
            ptcb->TCBState = STATE_READYBOOST;
            TKUpdateState(ptcb);
                                        /* Make thread runnable */
            if (ptcbPriQTSD == NULL) {
                break;
            }

            /* We know that at least one other thread is runnable now,
             * and that there is another TSD on the queue.  We give the
             * runnable thread a moment to do what it needs, and then
             * the TSD daemon will wake up again and start getting
             * the next TSD.
             *
             * Note that this sleep normally puts an upper bound on TSD
             * response time so that system throughput is maintained.
             * However, if the system goes idle because the new thread
             * blocks immediately (for a reason other than swapping) the
             * idle loop will give the TSD daemon a little kick so that
             * we keep TSDs swapping in as fast as possible on a system
             * with a light load and TSD activity.
             *
             * tkTSDSwapRateMax is one maximum timeslice.  This allows
             * the other runnable threads to get full timeslices in the
             * normal case.
             */

            TKEndNoBlock();
            TKSleep((void *) tkTSDDaemon, tkTSDSwapRateMax, 1, 0);
            TKBeginNoBlock();
        }
        TKEndNoBlock();

        if (SMswapping) {
            /*
             * OK, there are no more TSDs to swap in, so let's see if
             * the swap file needs compacted.  This routine will return
             * when either: (a) the swap file is as small as it can get,
             * or (b) there's a TSD that needs brought in.
             */
            SCHSetPriority(pTCBCur, CLASS_REGULAR << 8);         // B733890
            SMCompact();
        }

        if (ptcbPriQTSD == NULL) {
            /*
             * There are no TSDs to swap in, so we'll just go to
             * sleep until we're needed.
             */
            TKSleep((void *) tkTSDDaemon, -1L, 1, 0);
        }

    }
}


/***EP  TKIdleTSD - Make a TSD not present
 *
 *      This procedure is called by the Ager when a TSD is made
 *      not present and moved to the idle list.
 *
 *      The TSD mapping is invalidated in the TCB by clearing
 *      TCBpteKStackTSD.  No state change is made at this time.
 *
 *      The TSD mapping is recomputed in TKMakeTSDPresent.  If
 *      TKMakeTSDPresent detects that the TSD is not reclaimable,
 *      it is moved onto the  TSD queue, and is then swapped in
 *      by the TSD daemon.
 *
 *      See also tkTSDDaemon and TKMakeTSDPresent.
 *
 *      TKIdleTSD (page)
 *
 *      ENTRY   page            - page number in TSD region
 *      RETURN  int             - NO_ERROR
 *
 */

int
TKIdleTSD (page_t page)         /* Page being idled */
{
    tkAssert(page >= pageTKTSDs && page < pageTKTSDsEnd);


    if (papTCBSlots[((page - pageTKTSDs)/CPGTSD)+2] == 0) return(NO_ERROR);

    /* Just invalidate the TSD mapping for this thread.
     * The +2 accounts for 1-based slots, and that slot 1 is special.
     */
    papTCBSlots[((page - pageTKTSDs)/CPGTSD)+2]-> TCBpteKStackTSD = 0; //B733901

    /* Must not be swapping.
     */
    tkAssert(!(papTCBSlots[((page - pageTKTSDs)/CPGTSD)+2]-> TCBfSwapping));

    return(NO_ERROR);
}


/***EP  TKNeedTSDDaemon
 *
 *      TKNeedTSDDaemon is only callable in the context of the
 *      TSD daemon by routines that need to know whether to start
 *      swapping TSDs again.
 *
 *      ENTRY   NONE
 *
 *      EXIT    TRUE    - Drop Everything!  Become Superman.
 *              FALSE   - Continue what you were doing
 *
 *      WARNING Only Callable within the TSD daemon thread, while
 *              performing idle time activity.
 */

ULONG
TKNeedTSDDaemon (void)
{
    return(ptcbPriQTSD != NULL);
}


/***EP  TKTomTimeslice - Handle a timeslice - expired TOM event
 *
 *      When the timeslice parameter is not configured in the CONFIG.SYS   DCR1203
 *      dynamic timeslicing is assumed. This will allocate a small         DCR1203
 *      time slice (32  milliseconds) and give additional timeslices       DCR1203
 *      if a page fault has occurred.                                      DCR1203
 *
 *      TKTomTimeslice sets RESCHED_TIMESLICE to force a context
 *      switch due to an elapsed timeslice.
 *
 *      RESCHED_TIMESLICE will only get set if it makes sense to
 *      switch away from this thread.   That is, if there is no
 *      waiting threads, or if the waiting threads have a lower
 *      priority than the running thread, we don't interrupt the
 *      running thread.
 *
 *      In any case, TKTomTimeslice will restart the timeslice wait.
 *      This prevents having to call TOMSet in TKGetReadyThread
 *      except when switching threads.
 *
 *      ENTRY   ptoe - pointer to timeslice timeout element
 *
 *      EXIT    NONE
 *
 *      EFFECTS Sets the RESCHED_TIMESLICE bit in Resched.
 *
 */

void
TKTomTimeslice (toe_t *ptoe)
{

    _disable();

    /* If there is no running thread, we're in the idle loop.
     * RESCHED_PREEMPT will takes care of this case.
     */


    tkMaxSlice = 32;                                  /* DCR1203 */

    if (ptcbPriQRunner != NULL) {
        /* Because the running thread has exhausted it's timeslice,
         * we have already given it sufficient time to handle the
         * IO or starvation boost.  We eliminate both boosts.
         * Note that starvation is cleared regardless of whether
         * the thread made it to ring 3 or not (TK_FF_SCH).
         *
         * We need to eliminate the boosts prior to checking if
         * RESCHED_TIMESLICE should be set.
         *
         * Note that this code must execute regardless of the
         * contents of ptcbPriQReady, so that the RESCHED_PREEMPT
         * logic is correct when another thread wakes up.
         *
         * See also TKUpdateState: Descending Running state.
         */

        if (ptcbPriQRunner-> TCBPriClassMod &
            (CLASSMOD_STARVED | CLASSMOD_DEVICE)) {
            ptcbPriQRunner-> TCBPriClassMod   &=
                ~(CLASSMOD_STARVED | CLASSMOD_DEVICE);
            ptcbPriQRunner-> TCBSchFlags      &= ~SCH_MINSLICE;
            ptcbPriQRunner-> TCBForcedActions &= ~TK_FF_SCH;
            TKPriCalc (ptcbPriQRunner);         // Optimized TKUpdatePriority.
        }

        /* Set RESCHED_TIMESLICE only if the priority of the running
         * thread is less than or equal to the priority of the highest
         * priority waiting thread.  Otherwise, we can just let the
         * same thread continue to run.
         */

        if ((ptcbPriQReady != NULL) &&
            (ptcbPriQRunner-> TCBPriority <= ptcbPriQReady-> TCBPriority)) {
            Resched |= RESCHED_TIMESLICE;

            if (ptcbPriQReady-> TCBPriClass == CLASS_TIME_CRITICAL) {
                 TCResched |= RESCHED_TIMESLICE;
             }


          /* NOTE : The following statement will cause a second scheduling of
           *        the current thread for n  time slices if it has
           *        just taken a page fault, and it has not received a
           *        an extra time slice. This is to assist in thrash control.
           */

/* This code was changed to just check if dynamic time slicing is being   */
/* used and a pagefault has occurred for this thread. We also set a       */
/* flag in the TCB to insure that the same threads does not keep getting  */
/* executed if he keeps taking page faults.                               */
            if ( (DynTimeSlice != 0) &&                                /* DCR1203 */
                 (( ptcbPriQRunner->TCBSchFlags & SCH_PAGE_FAULT ) ||
                 ( ptcbPriQRunner->TCBSpareFlags & SPFLAGS_XTIME_SLICE ))) { //#97886

               ptcbPriQRunner->TCBSpareFlags &= ~SPFLAGS_XTIME_SLICE;
               /* If we have already received an extra time slice, don't */
               /* do it 2 times in a row.                                */
               if ( ptcbPriQRunner->TCBSpareFlags & SPFLAGS_PGFAULT ) {
                 ptcbPriQRunner->TCBSpareFlags &= ~SPFLAGS_PGFAULT;
               } else {
                 ptcbPriQRunner->TCBSpareFlags |= SPFLAGS_PGFAULT;

//     Note : the code that counts the number of threads of equal priority
//            to calculate the timeslice is being removed. Now we just give
//            out 32 mills for everybody and give additional time slices
//            when a page fault occurs this is for PTR's 712651 712663 712666

                 TCResched &= ~RESCHED_TIMESLICE; // tcw  Reset Reschedule Flags
                 Resched &= ~RESCHED_TIMESLICE;

         /* Apply the page fault boost. Note that this value
          * is cleared at each dispatch in SUTIL.ASM
          */
                 ptcbPriQRunner->TCBSchFlags &= ~SCH_PAGE_FAULT;     //P725786

                 tkMaxSlice = 64;
              }                                                        /* DCR1203 */
            }
            else {
                 ptcbPriQRunner->TCBSpareFlags &= ~SPFLAGS_PGFAULT;
            }
       }
    }

    /* Set up another timeslice for the thread.  This prevents having
     * to call TOMSet in GetNextRunner when we switch to the same thread.
     * The next timeslice is always tkMaxSlice.
     */

    TOMSet(&toeTimeslice, (ULONG) tkMaxSlice, TOM_W_TIMESLICE);
}

/***EP  TKTomWake - Wake a thread when it times out.
 *
 *      Wakeup threads in the timeout queue whose time has expired.
 *      Called from the Timeout Manager, as a worker routine.  The
 *      semantics of TOM workers states that routine is called with
 *      interrupts enabled, and that interrupts will be enabled
 *      between each call.  Therefore, there is no corresponding
 *      _enable() call here.
 *
 *      ENTRY   ptoe - pointer to timeout element of TCB to wakeup
 *
 *      EXIT    NONE
 */

void
TKTomWake (toe_t *ptoe)
{
    register tcb_t *pTCB;                   // pointer to TCB to wakeup

    pTCB = (tcb_t *) ((char *) ptoe - FIELDOFFSET(tcb_t, TCBtoe));
    TKAssertPTCB (pTCB);
    _disable();
    if (pTCB-> TCBQState == STATE_BLOCKED) {            // P725773
    tkAssert (pTCB->TCBWakeFlags & TK_WF_TIMEOUT);      // Must have been set.
    pTCB->TCBWakeFlags &= ~TK_WF_TIMEOUT;
    pTCB->TCBWakeFlags |= TK_WF_TIMEEXP;
    TKWakeThread(pTCB);
    }                                                    // P725773
}


/***EP  TKTomStarve - Starvation TOM worker
 *
 *      TKTomStarve gets called by the TOM component to cause a
 *      starvation boost to be set on a thread.
 *
 *      The timeout element is assumed to be the "TCBtoe" field
 *      of the TCB that needs the boost.
 *
 *      TK_FF_SCH is set for this thread so that it's starvation
 *      boost will be cleared at Exit_Kmode.
 *
 *      ENTRY   ptoe - pointer to the TCBtoe field of the TCB to starve
 *
 *      EXIT    NONE
 *
 *      WARNING Must be called as a TOM worker (at interrupt time)
 *              Exits with interrupts disabled
 */

void
TKTomStarve (toe_t *ptoe)
{
    register PTCB ptcb;

    ptcb = (PTCB) ((char *) ptoe - FIELDOFFSET(tcb_t, TCBtoe));
    TKAssertPTCB (ptcb);
    if(ptcb-> TCBState == STATE_READY){

      ptcb-> TCBForcedActions |= TK_FF_SCH;
      ptcb-> TCBSchFlags |= SCH_MINSLICE;
      ptcb-> TCBPriClassMod |= CLASSMOD_STARVED;
      TKUpdatePriority(ptcb);
     }
}


/***LP  TKExitInversion - Exit Priority Inversion Protected Section
 *
 *      TKExitInversion is called as a thread clears a semaphore, to
 *      stop protecting against priority inversions.
 *
 *      This implementation is an approximation of a more complex
 *      solution.   The thread's priority is not lowered until it
 *      clears ALL inversion-protected semaphores it owns.  The
 *      correct solution is to reset the threads priority based on
 *      all the declared inversions after clearing each semaphore.
 *
 *      ENTRY   NONE    (Current thread assumed)
 *
 *      EXIT    NONE
 *
 *      WARNING The thread must call TKExitInversion once for each call
 *              to TKEnterInversion.
 */

void
TKExitInversion ()
{
    if (((--(pTCBCur->TCBcBoostLock)) == 0) &&
        (pTCBCur-> TCBPriorityMin != 0)) {
            pTCBCur-> TCBPriorityMin = 0;
            TKUpdatePriority(pTCBCur);
    }
}


/***LP  TKDeclareInversion - Check for a Priority Inversion
 *
 *      TKDeclareInversion is used to set the minimum priority
 *      of a thread that is to be waited on.  When a thread
 *      decides to wait for an inversion-protected semaphore,
 *      it should call TKDeclareInversion to declare a dependency
 *      between itself and the thread it is waiting on.
 *
 *      TKDeclareInversion should only be used on inversion-protected
 *      semaphores when the semaphore is actually owned.  The
 *      scheduler depends on the fact that the owning TCB will
 *      call TKExitInversion soon to zero TCBPriorityMin.
 *
 *      ENTRY   iSlot           - Thread being waited upon
 *
 *      EXIT    NONE
 */

void
TKDeclareInversion (USHORT iSlot)
{
    register PTCB ptcb;
    ptcb = papTCBSlots[iSlot];
    tkAssert (ptcb != NULL);
    tkAssert (ptcb->TCBcBoostLock != 0);
    if (ptcb-> TCBPriorityMin < pTCBCur-> TCBPriority) {
        ptcb-> TCBPriorityMin = pTCBCur-> TCBPriority;
        if (ptcb-> TCBState == STATE_READY) {                  //49324
           ptcb-> TCBPriorityMin = pTCBCur-> TCBPriority + 1;
           }
        TKUpdatePriority(ptcb);
    }
}


#ifdef  PMDEBUG
/***LD  uskdbSlot - slot number of thread whose TSD is wanted
 *
 *      This variable is used to communicate the number of the thread
 *      whose TSD the kernel debugger wishes to make present.  A value
 *      of zero indicates that no thread's TSD is currently desired.
 */

ushort_t                uskdbSlot;      /* slot number of thread */


/***LD  ulkdbAddr - address of page to be swapped in
 *
 *      This variable is used to communicate the linear address of a system
 *      arena page the kernel debugger wishes to make present.  A value of 0
 *      indicates that no page is currently desired.
 */

ulong_t                 ulkdbAddr;      /* linear address */


/***LD  ulkdbAddrLDT - address of LDT entry to be swapped in
 *
 *      This variable is used to communicate the linear address of the
 *      LDT page the kernel debugger wishes to make present.  A value of -1
 *      indicates that no LDT page is currently desired.
 */

ulong_t                 ulkdbAddrLDT;   /* linear address */


/***LP  tkKDBDaemon - make a TSD present for the kernel debugger
 *
 *      This procedure runs as a kernel internal thread and
 *      touches the TSD specified by the kernel debugger.
 *
 *      tkKDBDaemon()
 *
 *      ENTRY   NONE
 *      RETURN  NONE
 *
 *      CALLS   SCHSetPriority, TKSleep, PGMakePresent, tkStop
 *
 *      WARNING This thread is intended to be invoked from the debugger
 *              only.
 *
 *      EFFECTS Attempts to make the specified page or thread's TSD present.
 */

void
tkKDBDaemon (void)
{
    int rc;                                     /* error code */

    /* set self to highest priority */
    SCHSetPriority(pTCBCur, (CLASS_TIME_CRITICAL << 8) + LEVEL_MAX);
    for(;;)                                     /* loop forever */
      {
        while (uskdbSlot == 0) {
            TKSleep((void *) tkKDBDaemon, -1L, 1, 0); /* sleep until needed */
        }
        rc = TKMakePresentKDB(uskdbSlot, ulkdbAddr, ulkdbAddrLDT);
        rc |= uskdbSlot << 16;
        uskdbSlot = 0;
        tkStop(rc, ulkdbAddr);                  /* breakpoint */
    }
}


/***EP  TKMakePresentKDB - make a page or TSD present for the kernel debugger
 *
 *      ENTRY   (slot) =  slot number of address or TSD
 *              (laddr) = address to make present, -1 if TSD
 *
 *      RETURN  (error code) = 0 if success
 *
 *      EFFECTS Attempts to make the specified page or TSD present.
 */

int
TKMakePresentKDB(ulong_t slot, laddr_t laddr, laddr_t laddrLDT)
{
    ptda_t *pptda;                              /* PTDA pointer */
    tcb_t *ptcb;                                /* TCB pointer */
    int rc;                                     /* return code */

    pptda = NULL;
    if (slot > MaxThreads || (ptcb = papTCBSlots[slot]) == NULL) {
        return(ERROR_INVALID_THREADID);
    }
    if (laddr == -1) {                          /* if TSD to be swapped in */
        laddr = (laddr_t) ptcb->TCBpTSD + TSDSTART;
    }
    else {
        if (laddr <= MAXCRLADDR) {
            pptda = ptcb->TCBpPTDA;
            if (!pptda->ptda_ah.ah_parSen) {
                return(ERROR_INVALID_ADDRESS);
            }
        } else {
            if (!(laddr >= MINSYSLADDR && laddr < MAXSYSLADDR)) {
                return(ERROR_INVALID_ADDRESS);
            }
        }
    }
    rc = PGMakePresent(((page_t) laddr) >> PAGESHIFT, pptda, 1, 0);
    if (rc == NO_ERROR) {
        if(laddrLDT != -1) {
            rc = PGMakePresent(((page_t) laddrLDT) >> PAGESHIFT, pptda, 1, 0);
        }
    }
    return(rc);
}
#endif
