/*static char *SCCSID = "@(#)tkforce.c  6.2 91/03/29";*/
#define SCCSID "@(#)tkforce.c   6.2 91/03/29"
/*
 *
 *      IBM/Microsoft Confidential
 *
 *      Copyright (c) IBM Corporation 1987, 1989
 *      Copyright (c) Microsoft Corporation 1987, 1989
 *
 *      All Rights Reserved
 *
 *      Tasking - Force Routines
 */


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

#include <basemac.h>
#include <basedef.h>
#define FNO_NUM FNO_TKFORCE_C
#include <fnotask.h>
#include <osmac.h>
#include <ostypes.h>
#include <infoseg.h>
#include <osconst.h>
#include <bseerr.h>
#include <dossymc.h>
#include <seldesc.h>
#include <sigdef.h>
#include <except.h>
#include <mi.h>
#include <pgexport.h>
#include <vmexport.h>
#include <tkexport.h>
#include <sched.h>
#include <proc.h>
#include <sem.h>
#include <screen.h>
#include <curdir.h>
#include <dirent.h>
#include <find.h>
#include <tkptda.h>
#include <ldrxport.h>
#include <selxport.h>
#include <kmode.h>
#include <bsetib.h>
#include <tklocal.h>
#include <except32.h>
#include <sigxport.h>
#include <exmg.h>

/***EP  TKForceTask - Set Force Flags for Task/Force Task to be Runnable
 *
 *      TKForceTask sets force flags for every thread of a task and optionally
 *      forces threads of the task that are frozen, in critsec or blocked
 *      interruptibly into the ready state.  In general, nothing happens right
 *      away.  As each thread of the task atostatets to leave Kernel Mode,
 *      the force flags are checked and the appropriate action is taken.
 *
 *      If the option to force threads into the ready state is specified,
 *      threads which are blocked interruptibly are woken up with the
 *      special error code ERROR_INTERRUPT.
 *
 *      ENTRY
 *              pptda           pointer to target PTDA.
 *              ForceFlag       Event Flags (TK_FF_??? bits)
 *              Force           TRUE  if to force threads
 *                              FALSE if just to set force flags
 *
 *      EXIT    None
 *
 */

void    TKForceTask(ForceFlag, pptda, Force)
ULONG   ForceFlag;
PPTDA   pptda;
USHORT  Force;
{
    PTCB        ptcb;

    for (ptcb = pptda->ptda_pTCBHead; ptcb != NULL; ptcb = ptcb->TCBpTCBNext) {
        if (Force)
            TKForceThread(ForceFlag, ptcb);
        else
            ptcb->TCBForcedActions |= ForceFlag;
    }
}


/***EP  TKForceThread - Set Thread into Force Pending State
 *
 *      TKForceThread is called to set the specified thread into the
 *      "Force Pending" state.  In general, nothing happens right away.
 *      When the system is ready to transfer control to the thread
 *      (either interrupt exit or system call exit), the force
 *      flags are checked and the appropriate action is taken.
 *
 *      TKForceThread will make the process runnable if its current
 *      state is STATE_CRITSEC, STATE_FROZEN, or STATE_BLOCKED and
 *      the sleep is interruptible.  ForceThread will not alter the
 *      current state except in these instances.  If the state is
 *      either STATE_FROZEN or STATE_CRITSEC, it is assumed that
 *      ForceThread is being called to awaken thread 1 in response
 *      to a signal, and that the corresponding force flags should be
 *      set so that the thread will go back to sleep when signal handling
 *      is done.
 *
 *      TKForceThread ORs in the Force-Pending bits, since it is possible
 *      for a thread to be "ForceThread"ed several times before the forced
 *      actions are carried out.
 *
 *      ENTRY
 *              ForceFlag       Event Flags (TK_FF_??? bits)
 *              ptcb            pointer to TCB
 *
 *      EXIT    None
 *
 */

void TKForceThread(ForceFlag, ptcb)
register ULONG  ForceFlag;
register PTCB   ptcb;
{
        ULONG   procflags;

        ptcb->TCBForcedActions |= ForceFlag;
        if (ForceFlag != TK_FF_ICE) {
            ptcb->TCBWakeFlags |= TK_WF_INTPENDING;
            procflags = TKDisable();
            switch (ptcb->TCBState) {

              case STATE_CRITSEC:       // critical section check automatic
                break;

              case STATE_SUSPENDED:     // check for suspended state
              case STATE_FROZEN:        // check for frozen state
                ptcb->TCBForcedActions &= ~TK_FF_ICE;
                if (ForceFlag != TK_FF_EXIT)
                    ptcb->TCBForcedActions |= TK_FF_ICE;
                break;

              case STATE_BLOCKED:       // check for blocked thread
                if (ptcb->TCBWakeFlags & TK_WF_INTERRUPTIBLE) {
                    ptcb->TCBWakeFlags |= TK_WF_INTERRUPTED;
                    TKWakeThread(ptcb);
                }
                /* Drop through */

              default:
                TKEnable(procflags);
                return;
            }
            /* SCHSetState(ptcb, STATE_READY); */
            ptcb->TCBState = STATE_READY;
            TKUpdateState(ptcb);

            TKEnable(procflags);
        }
}


/***EP  TKEachThread - Call a worker function for each TCB of a process.
 *
 *      TKEachThread calls a worker function for each TCB of a process.
 *
 *      TKEachThread passes an data packet address (possibly NULL) to
 *      the worker without attempting to interpret it's contents.
 *
 *      WORKER ENTRY    pptda           Pointer to PTDA
 *                      ptcb            Pointer to TCB
 *                      pData           Pointer to worker data.
 *
 *      WORKER EXIT     ulong_t
 *                      NO_ERROR
 *                              Worker does not wish to terminate scan yet.
 *
 *                      ERROR_CODE
 *                              Worker requests scan termination.
 *                              TKEachThread aborted immediately,
 *                              returning this error.
 *
 *      ENTRY   pptda           Pointer to PTDA
 *              pfnWorker       Pointer to worker function
 *              pData           Pointer to worker data.
 *
 *      EXIT    ulong_t
 *              NO_ERROR
 *                      No call of worker returned an error.
 *
 *              ERROR_CODE
 *                      Some call worker returned an error.
 *                      TKEachThread aborted immediately.
 *
 */

ulong_t TKEachThread (pptda, pfnWorker, pData)
ptda_t  *pptda;
ulong_t (*pfnWorker) (ptda_t *, tcb_t *, ulong_t *);
ulong_t *pData;
{
        tcb_t   *ptcb;
        ulong_t reply = NO_ERROR;

        ptcb = pptda-> ptda_pTCBHead;
        while ((ptcb != NULL) && (reply == NO_ERROR)) {
                reply = (*pfnWorker) (pptda, ptcb, pData);
                ptcb = ptcb-> TCBpTCBNext;
        }
        return (reply);
}


/***EP TKTidTo_pTCB - From Thread ID, find TCB Pointer
 *
 *      ENTRY   pptda           Pointer to PTDA
 *              Tid             Thread ID
 *              pptcb           Address to return ptcb
 *
 *      EXIT    ulong_t
 *              NO_ERROR
 *                      Tid exists - *piSlot contains Slot index
 *
 *              ERROR_THREAD_NOT_FOUND
 *                      Tid does not exist.
 *
 */

ulong_t TKTidTo_pTCB (pptda, Tid, pptcb)
ptda_t  *pptda;
ulong_t Tid;
tcb_t   **pptcb;
{
        register tcb_t *ptcb;

        ptcb = pptda-> ptda_pTCBHead;

        while ((ptcb != NULL) && ((ulong_t) ptcb-> TCBOrdinal != Tid))
                ptcb = ptcb-> TCBpTCBNext;

        *pptcb = ptcb;

        if (ptcb == NULL)
                return (ERROR_INVALID_THREADID);
        else
                return (NO_ERROR);
}


/***EP  TKTidTo_iSlot - From Thread ID, find Slot index
 *
 *      ENTRY   pptda           Pointer to PTDA
 *              Tid             Thread ID
 *              piSlot          Address to return Slot index
 *
 *      EXIT    ulong_t
 *              NO_ERROR
 *                      *piSlot = Slot Number
 *              ERROR_INVALID_THREADID
 *                      Tid is invalid.
 */

ulong_t TKTidTo_iSlot(pptda, Tid, piSlot)
ptda_t  *pptda;
ulong_t Tid;
ulong_t *piSlot;
{
        tcb_t   *ptcb;

        if (NO_ERROR == TKTidTo_pTCB(pptda, Tid, SSToDS (&ptcb))) {
                *piSlot = ptcb-> TCBNumber;
                return (NO_ERROR);
        } else {
                return (ERROR_INVALID_THREADID);
        }
}


/***EP  TKiSlotTo_pTCB - From Slot Index, find TCB Pointer
 *
 *      Optimization hint - this routine is fast.
 *
 *      ENTRY   pptda           Pointer to PTDA
 *              iSlot           Thread Slot index
 *              pptcb           Address to return ptcb
 *
 *      EXIT    ulong_t
 *              NO_ERROR
 *                      Tid exists - *pptcb contains the TCB pointer
 *
 *              ERROR_INVALID_THREADID
 *                      Slot is empty.
 *
 */

ulong_t TKiSlotTo_pTCB (iSlot, pptcb)
ulong_t iSlot;
tcb_t   **pptcb;
{
        tkAssert ((iSlot != 0) && (iSlot <= TKCTCBMAX));

        if (NULL == (*pptcb = papTCBSlots[iSlot]))
                return (ERROR_INVALID_THREADID);
        else
                return (NO_ERROR);
}


/***LP  tkAsyncPTERM - Post asyncronous PTERM to a thread
 *
 *      tkAsyncPTERM is the worker for tkPostPTERMs. It will post
 *      an asyncronous PTERM to the given thread. If fIgnoreCurrent
 *      is TRUE then if the given thread is also the current thread
 *      an exception will not be posted.
 *
 *      ENTRY   pptda           - pointer to a PTDA
 *              ptcb            - pointer to a TCB
 *              ulTermTID       - !0 = don't post to current thread
 *
 *      EXIT    Success - NO_ERROR
 */

ULONG tkAsyncPTERM(ptda_t *pptda,               /* pointer to PTDA */
                   tcb_t *ptcb,                 /* pointer to TCB */
                   ulong_t *pulTermTID)         /* 'Terminator' */
{
   if ((*pulTermTID != 0) && (ptcb == pTCBCur)) {
      return (NO_ERROR);
   }
   SIGPsig(SIGAPTERM, ptcb);
   return (NO_ERROR);
}


/***LP  tkPostPTERMs - Post asyncronous PTERMs to every thread
 *
 *      tkPostPTERMs sends an asyncronous PTERM to every thread
 *      with the possible exception of the current thread.
 *
 *      ENTRY   pptda           - pointer to a PTDA
 *              ulTermTID       - 0  --> Post async to current thread
 *                                !0 --> ignore current thread
 *
 *      EXIT    None
 */

VOID tkPostPTERMs(PPTDA pptda, ULONG ulTermTID)
{
   ULONG rc;

   /*
    * We need to get the thread serialization semaphore because
    * tkAsyncPTERM can block and we can't modify the thread structure
    * during TKEachThread.
    */

   if ((rc = KSEMRequestMutex((HKSEM)&pptda->ptda_ptdasem, -1))
       == NO_ERROR) {

      pptda->Sig_termtid = ulTermTID;
      TKEachThread(pptda, tkAsyncPTERM, SSToDS(&ulTermTID));
      KSEMReleaseMutex((HKSEM)&pptda->ptda_ptdasem);
   }

}

/***EP  TKInnerExit - Exit this thread.  (before exception handling)
 *
 *      TKInnerExit is the worker routine for TKExit. It is separated
 *      from TKExit because tkDieSoonPTDA will call this layer to avoid
 *      a recursive call to itself.
 *
 *
 *      ENTRY   ulExitCode      : (Proposed) Exit code from this thread.
 *              pERepRec        : pointer to nested exception record
 *
 *      EXIT    Void.  Sometimes does not return.
 *              _TKExit WILL RETURN for threads that have a 32-bit user
 *              exception handler installed!  The caller of _TKExit must
 *              attempt to exit the kernel.
 *
 *              As internal threads may not install (user) exception
 *              handlers, and cannot be made to run ExitList processing,
 *              _TKExit will never return to internal threads.
 *
 */

void    tkInnerExit (ULONG ulExitCode, PEXCEPTIONREPORTRECORD pERepRec)
{


        // Note that we need to clear TK_FF_EXIT after calling TKDieSoonPTDA,
        // but before calling XCPTBuildR3DispatcherStack, which can cause
        // a stack overrun, or can block, allowing another thread to set
        // TK_FF_EXIT on us, via DosExit (All Threads).

        pTCBCur->TCBForcedActions &= ~TK_FF_EXIT;

        // Set up the 'proposed exit code' in the TSD for DosDebug.
        // If this thread called TKExit more than once, the last
        // Exit Code is the one that is used here.

        pTSDCur->TSDulExitCode = ulExitCode;

        // Set up to dispatch to the 32-bit exception handlers as required.
        // The TID we use in XCPTBuildR3DispatcherStack depends on PS_XITALL.
        // TMF_EXIT_TERM is used to skip exception handling, and is set
        // as the default action of the (not handled) PTERM exception.

        if ((pPTDACur->ptda_ProcType == PT_REALMODE) ||
            (pTCBCur->TCBMiscFlags & TMF_EXIT_TERM) ||
            (pTSDCur->TSDUserSS == 0) ||
            (pPTDACur->ptda_ProcStatus & PS_XITLST) ||
            !XCPTCheckExceptionHandler(pTCBCur) ||
            XCPTBuildR3DispatcherStack (EXCSW,                   // Trap #
                                        XCPT_PROCESS_TERMINATE,  // Except #
                                        0,                       // Ct. Params
                                        NULL,                    // Params
                                        TRUE,                    // fNonContinue
                                        (ulong_t *)pERepRec)) {  // Nested record

                // Either this is an internal thread, the thread is in
                // ExitList, or there are no 32-bit exception handlers
                // installed, or we attempted to set up an exception
                // hander, and failed.   Terminate the thread.

                // No PTERM exceptions are allowed in ExitList

                TKTermThread ();

                // Only thread 1 may return from TKTermThread.

                tkAssert (pTCBCur-> TCBOrdinal == 1);
        }
}


/***EP  TKDieSoonPTDA - Mark all threads in a process to die.
 *
 *      TKDieSoonPTDA sets FF_EXIT on all threads in the process, and
 *      sets ptda_ulExitCode, ptda_ulExitType and ptda_ExitTID in the
 *      specified process.  This causes the marked threads to each
 *      call TKExit as it leaves the kernel.
 *
 *      If the specified process is not the current process,
 *      ptda_ExitTID is set to 1.  Otherwise, it is set to the TCBOrdinal
 *      of the current thread.
 *
 *      ENTRY   pptda           Pointer to PTDA
 *              ulExitCode      ExitCode for the process
 *              ulExitType      ExitType for the process (see error.h)
 *                  TC_NORMAL  - Normal termination (DosExit)
 *                      ulExitCode = Exit Code from first DosExit (All Threads)
 *                  TC_HARDERR - Termination due to Hard Error
 *                      ulExitCode = 0
 *                  TC_GP_TRAP - Termination due to GP Fault
 *                      ulExitCode = Trap Number
 *                  TC_SIGNAL  - Termination due to Signal (KillProcess)
 *                      ulExitCode = 0
 *                  TC_XCPT    - Termination due to Exception
 *                      ulExitCode = Exception Number
 *              pERepRec        Pointer to nested exception record (optional)
 *
 *      EXIT    None
 *
 */

void    TKDieSoonPTDA (pptda, ulExitCode, ulExitType, pERepRec)
PPTDA   pptda;
ULONG   ulExitCode;
ULONG   ulExitType;
PEXCEPTIONREPORTRECORD pERepRec;
{
   if (!(pptda-> ptda_ProcStatus & PS_XITALL)) {
      pptda->ptda_ProcStatus |= PS_XITALL;
      pptda->ptda_ulExitCode = ulExitCode;
      pptda->ptda_ulExitType = ulExitType;
      tkUpdatePIB(pptda);       /* Force PIB update */

      if (pptda == pPTDACur) {
         pptda-> ptda_ulExitTID = pTCBCur -> TCBOrdinal;
                        ((struct InfoSegLDT *)Lis2.is_va)->LIS_ProcStatus |=
                                PS_XITALL;
      } else {
         pptda-> ptda_ulExitTID = 1;
      }
   }

   if ((ulExitType != TC_SIGNAL) && (pptda == pPTDACur)) {
      tkPostPTERMs(pptda, pTCBCur->TCBOrdinal);
      TKForceThread(TK_FF_EXIT, pTCBCur);
   } else {
      tkPostPTERMs(pptda, 0);
   }
}


/***EP  TKExit - Exit this thread (and maybe process)
 *
 *      TKExit is the worker routine for DosExit, Dos32Exit, and TK_FF_EXIT.
 *
 *      TKExit is used to exit one (or all) threads of a process.
 *      Note: TKExit CAN return for all but internal threads!
 *
 *      TKExit is the first of a two-stage termination process, and
 *      is only used to decide which threads are to be terminated, and
 *      to save the exit code.  If the thread is selected to die
 *      immediately, TKExit will call TKTermThread to finish up.
 *
 *      Exit Codes
 *      ----------
 *      There are just two ways to set the Exit Code :
 *
 *      1) TKDieSoonPTDA will set ptda_ulExitCode, ptda_ulExitType,
 *         ptda_ulExitTID, PS_XITALL, and will set TK_FF_EXIT on all
 *         the threads.   PS_XITALL controlls the locking of these
 *         fields, and is set only in TKDieSoonPTDA.
 *
 *      2) The Exit Code on the first call to DosExit to exit all
 *         threads will be saved.  The Exit Code is not stored
 *         if the thread is exiting by itself.
 *
 *         Because thread 1 is forced into exiting all other threads,
 *         it's Exit Code will be used if no other thread does an
 *         explicit DosExit (All threads).
 *
 *      Threads that were marked to die via TK_FF_EXIT, and internal
 *      threads, must enter _TKExit with fExitAll == FALSE.
 *
 *      See CWait for how to collect Exit Code and Type.
 *
 *      void TKExit (fExitAll, ulExitCode);
 *
 *      ENTRY   fExitAll        : If TRUE, mark all threads to die
 *              ulExitCode      : (Proposed) Exit code from this thread.
 *
 *      EXIT    Void.  Sometimes does not return.
 *              _TKExit WILL RETURN for threads that have a 32-bit user
 *              exception handler installed!  The caller of _TKExit must
 *              attempt to exit the kernel.
 *
 *              As internal threads may not install (user) exception
 *              handlers, and cannot be made to run ExitList processing,
 *              _TKExit will never return to internal threads.
 *
 */

void    TKExit (fExitAll, ulExitCode)
ULONG   fExitAll;
ULONG   ulExitCode;
{
        // If thread 1, or the user has selected to exit all threads,
        // mark them to die.

        if ((pTCBCur->TCBOrdinal == 1) || fExitAll)
                TKDieSoonPTDA (pPTDACur, ulExitCode, TC_NORMAL, NULL);

        tkInnerExit(ulExitCode, NULL);
}


/***EP  TKForceCritSec - Handle the TK_FF_CRITSEC force flag
 *
 *      TKForceCritSec handles the TK_FF_CRITSEC force flag in the TCB.
 *      This flag is set in TKGtReadyThread if the current thread was
 *      held off by a critical section.
 *
 *      If the thread is meant to be held via a critical section, the
 *      thread's state is changed to STATE_CRITSEC and we switch to
 *      the next thread.
 *
 *      ENTRY   NONE
 *
 *      EXIT    NONE
 *
 */

void
TKForceCritSec (void)
{
    pTCBCur-> TCBForcedActions &= ~TK_FF_CRITSEC;

    if ((pPTDACur-> ptda_pTCBCritSec != NULL) &&
        (pPTDACur-> ptda_pTCBCritSec != pTCBCur)) {

        // Just move ptcbCur into the CritSec state and yield.

        /* SCHSetState (pTCBCur, STATE_CRITSEC); */
        pTCBCur->TCBState = STATE_CRITSEC;
        TKUpdateState(pTCBCur);
        tkSchedNext();
    }
}


#pragma code_seg("SWAP32_27","CODE")
/***EP  VRExit - API Worker for DOSEXIT - Exit thread
 *
 *      ENTRY
 *
 *              resact  ULONG composed of result code and action code.
 *                      Low-word is the result code.
 *                      Hi-word  is the action code.
 *
 *                      The ActionCode is forced nonzero for Thread 1.
 *
 *      EXIT    Thread 1        : ExitList is run (if any)
 *              Internal Threads should InternalError if they return
 *              from TKExit.
 */

int cdecl VRExit(sef, resact)
SEF     sef;
struct  dw_s resact;
{
        return(TKExit(resact.hiword,    // ActionCode
                      resact.loword));  // ResultCode
}

#pragma code_seg()
#pragma code_seg("TKSWAP_TEXT", "CODE")
/***EP  VRResumeThread - API entry for DOSRESUMETHREAD
 *
 *      Resume execution of specified thread
 *
 *      ENTRY   sef     System Entry Frame
 *              tid     thread ID of thread to resume
 *
 *      EXIT    int     NO_ERROR for success
 */

int cdecl VRResumeThread(sef, tid)
SEF     sef;
USHORT  tid;
{
        return(TKResumeThread(tid));
}


/***EP  VRSuspendThread - API entry for DOSSUSPENDTHREAD
 *
 *      Suspend execution of thread
 *
 *      ENTRY   sef     System Entry Frame
 *              tid     thread ID of thread to suspend
 *
 *      EXIT    int     NO_ERROR for success
 *
 */

int cdecl VRSuspendThread(sef, tid)
SEF     sef;
USHORT  tid;
{
        return(TKSuspendThread(tid));
}

