#define SCCSID "@(#)em86.c      6.16 92/05/26"

/***    EM86.C
 *
 *
 *      IBM/Microsoft Confidential
 *
 *      Copyright (c) IBM Corporation 1987, 1989
 *      Copyright (c) Microsoft Corporation 1987, 1989
 *
 *      All Rights Reserved
 *
 *      Title:
 *
 *      8086 emulation "C" code and data
 *
 *      Description:
 *
 *      This module contains 8086 emulation global and instance data,
 *      8086 emulation's initialization, VDM creation and some of the
 *      VDH service code.  This module contains the part of a VDH
 *      service that installs or arms handlers where the other 8086
 *      emulation assembly modules contain the dispatch code for these
 *      services.
 *
 *      Public routines:
 *
 *      EM86Init
 *      EM86CreateVDM
 *      VDHInstallIOHook
 *      VDHSetIOHookState
 *      VDHInstallIntHook
 *      VDHAllocHook
 *      VDHQueryHookData
 *      VDHFreeHook
 *      VDHArmContextHook
 *      VDHArmReturnHook
 *      VDHArmSTIHook
 *      VDHArmBPHook
 *      VDHPushFarCall
 *
 *      Modification History:
 *
 *    - Bx001  Bill Madden    07/17/91
 *      CP20 PTR B724717, Fixed EM86CreateVDM to not set the TSS in the PTDA
 *                        until the selector is fully initialized
 *    - Bx002  Bill Madden    07/19/91
 *      CP20 PTR B724928, Removed asserts in VDHArmReturnHook and just return
 *                        an error if the hook is already armed.
 * 6.7  09/15/91  CP20  B726841  Jeff Muir  Delete IOPL property
 *    - Bill Madden    09/25/91
 *      CP20 DCR 1531, Changed VDHPushFarCall to use an fpfn instead of a
 *                     VPVOID, and to be of type BOOL instead of VOID.
 * 6.10 10/29/91  CP20  B728873  Jeff Muir  Windows interrupt fix
 *    - Bx003  Bill Madden    11/19/91
 *      CP20 PTR B728898, Removed IFDEF VDHSTRICT from VDHArmReturnHook so that
 *                        it will return an error under the retail kernel.
 *    - Bx004  Bill Madden    01/21/92
 *      CP20 PTR B731032, Added initialization of vpInitINT01 and vpInitINT03.
 *    - Bx005  Bill Madden    02/27/92
 *      CP20 PTR B733206, Added VDHARH_ALWAYS_ARM_HOOK flag for VDHArmReturnHook
 *    - Bx006  Bill Madden    03/02/92
 *      CP20 PTR B733891, Initialize int 1 and 3 in IVT to point at an IRET.
 *    - Bx007  Bill Madden    03/08/92
 *      CP20 PTR AZ09818, Added cli and sti to VDHArmSTIHook
 *    - Bx008  Bill Madden    05/26/92
 *      CP20 PTR B735416, Added pMapMemLockList.
 *    - Bx009  Bill Madden    09/10/92    DEF 25346
 *      Changed default IO routines from em86Sim* to em86Default*.
 *    - Bx010  Bill Madden    01/18/93    DEF 64091
 *      Added VDHRegsiterAPI
 *    - Bx011  Bill Madden    03/16/93    DEF 63777
 *      Fixed VDHInstallIOHook to set bits in IOPM bitmap.
 *    - Bx012  Ken Luo        03/20/93    DEF 63248
 *      Added Dekko Hooks
 *    - Bx013  Bill Madden    03/24/93    DEF xxxxx
 *      Added P5 support.
 *
 *   -  93272   8/11/94   Scott Cottrill   ChgTeam
 *      Added code to allow a way to shut off VME via
 *      software.  The VME command was added to config.sys
 *      processing to allow for this.  VME=YES is the default
 *      value.
 *    -        bjn            09/19/94
 *      Changed segment names for page tuning
 *
 *    - 122474 9/21/95    ChgTeam
 *      Added protection from NMI hits while modifying critical
 *      TSS structure elements.
 *
 *    - 134780  9/29/95  BZ  ChangeTeam
 *      Added code to EM86CreateVDM to set the ptda_vme flag for
 *      a VME enable VDM.
 */

#define FNO_NUM  FNO_EM86_C
#include <fnomvdm.h>

#define INCL_NONE               // for mvdm.h
#define INCL_TYPES              // include base types
#define INCL_MVDMTYPES          // include mvdm types
#define INCL_VDHALL             // include VDH services

#include <vdmvdh.h>
#include <seldesc.h>
#include <sft.h>
#include <kmode.h>
#include <em86.h>
#include <int2f.h>
#include <dpmi.h>
#ifdef MMIOPH
  #include <mmph.h>
#endif

/* External and misc definitions */

typedef BOOL (PASCAL *PFNEACHIOPORT)(PORT, PIOP, PORT, ULONG, PIOH);
extern UCHAR GlobalVME;                                                //93272
extern FNHOOK INT2FV86Dispatcher;
extern ULONG flVdmStatus;
extern SEL selVPMBP;
extern BOOL PRIVENTRY vdmmStrcmpi(PSZ, PSZ);
VOID  PASCAL VDHDetectProcessor(PULONG, PULONG, PULONG, PULONG);        //BN013
VOID  PASCAL em86SetCR4(ULONG);                                         //BN013
ULONG PASCAL em86GetCR4(VOID);                                          //BN013
extern UCHAR ABIOS_present;                                             //122474

/* Internal routine prototypes */

static BOOL PASCAL em86EachIOPort(HVDM, PORT, ULONG, PIOH, PFNEACHIOPORT);
static VOID PASCAL em86RemoveIOHook(HVDM, PORT, ULONG);
static VOID PASCAL em86SetIOP(PIOP, BOOL);
static BOOL PASCAL em86ValidateRemove(PORT, PIOP, PORT, ULONG, PIOH);
static BOOL PASCAL em86ValidateSet(PORT, PIOP, PORT, ULONG, PIOH);
static VPVOID PASCAL em86AllocVDMBP(PHOOK, HVDM, VPVOID);
static VOID PASCAL em86FreeVDMBP(PHOOK);
static PVOID PASCAL em86salloc(int);
static FNHOOK em86InvalidOpcode;
static FNARM em86CreateExitHook;

/***LD+ GLOBAL - Global data
 *
 *      Start of global 8086 emulation data
 */

extern  TSS PASCAL tss;                 // system TSS

extern  UCHAR MiniVDMCreate;                                            //@xxxx

#pragma BEGIN_MVDM_SWAP_DATA

/***    iopDefault - Default I/O Hook Entry
 *
 *      This is the initial IOH that each entry in apiop
 *      points to.  em86DefaultIOHook performs the input
 *      or output request, and then unhooks the port.
 */

IOP iopDefault = {
        (PBIH) em86DefaultIOInHook,     // iop_ioh: byte input
        (PBOH) em86DefaultIOOutHook,    //          byte output
        em86DefaultInWordHook,          //          word input          //BC009
        em86DefaultOutWordHook,         //          word output         //BC009
        em86DefaultOtherIOHook,         //          other I/O           //BC009
        (PBIH) NULL,                    // iop_iohVDD:  byte input
        (PBOH) NULL,                    //              byte output
        (PWIH) NULL,                    //              word input
        (PWOH) NULL,                    //              word output
        (POTH) NULL,                    //              other I/O
        IOP_ALWAYS_TRAP | IOP_ASM_HOOK, // iop_flags
};

/***    iopIgnore - I/O Hook Entry for ports to ignore
 *
 *      This is the IOH that is put in apiop for ports
 *      that are set to ignore by VDHInstallIOHook
 */

IOP iopIgnore = {
        (PBIH) em86DefaultIgnoreIOHook, // iop_ioh: byte input
        (PBOH) em86DefaultIgnoreIOHook, //          byte output
        em86SimWordInput,               //          word input
        em86SimWordOutput,              //          word output
        em86SimOtherIO,                 //          other I/O
        (PBIH) NULL,                    // iop_iohVDD:  byte input
        (PBOH) NULL,                    //              byte output
        (PWIH) NULL,                    //              word input
        (PWOH) NULL,                    //              word output
        (POTH) NULL,                    //              other I/O
        IOP_IGNORE | IOP_ASM_HOOK,      // iop_flags
};

/***    iohCHookStubs - assembly to "C" hook stubs
 *
 *      When the VDD specifies "C" hooks this IOH is copied
 *      to the iop_ioh field.  This IOH points to assembly
 *      language hooks that call the "C" hooks.
 */

static IOH iohCHookStubs = {
        em86CHookStubBIH,               // byte input
        em86CHookStubBOH,               // byte output
        em86CHookStubWIH,               // word input
        em86CHookStubWOH,               // word output
        em86CHookStubOTH,               // other I/O
};

/***    acbHook - array of hook block sizes
 *
 *      Used by VDHAllocHook to get the size of the hook specific
 *      data. The hook type is used as an index to this array.
 */

static BYTE acbHook[MAX_HOOK_TYPE] = {
        0,                              // 0 context hook
        0,                              // 1 sti hook
        sizeof(RETH) - sizeof(HOOK),    // 2 return hook
        sizeof(TIMH) - sizeof(HOOK),    // 3 timer hook
        sizeof(RETH) - sizeof(HOOK),    // 4 raw breakpoint hook
};

/***    pvdmArpl - VDM BP ARPL instruction linear address
 *
 *      Contains the linear address of the VDM BP ARPL instruction
 *      in every VDM's address space.
 */

PBYTE   pvdmArpl = (PBYTE) NULL;

/***    vpvdmIRet - VDM IRET instruction V86 address
 *
 *      Contains the V86 address (segment:offset) of the VDM IRet instruction
 *      in every VDM's address space.
 */

VPVOID  vpvdmIRet = (PBYTE) NULL;

#pragma END_MVDM_SWAP_DATA
/*end*/

/***LD+ PERVDM - Per-VDM data
 *
 *      Start of per-VDM 8086 emulation data
 */

#pragma BEGIN_MVDM_INSTANCE_DATA

extern  HVDM    vdmid_HVDM;             // current HVDM
extern  ULONG   vdmid_flVDM;

/***    pvsfFrameBase - pointer to VSF base
 *
 *      This is an instance data copy of the TCB_pFrameBase variable
 *      for VDMs.  Since the stack frame never changes for VDMs, this
 *      variable is set at VDM creation and never changes.  It is used
 *      by the VDM enter kmode functions and various other pieces of
 *      MVDM code.
 */

PVSF pvsfFrameBase = NULL;

/***    hookheadSTIH - STI hook list head (VDHArmSTIHook)
 *
 *      This is a per VDM pointer to the head of the list
 *      of STIH structures containing handlers waiting to be
 *      called when interrupts are enabled in a VDM.
 */

HOOKHEAD hookheadSTIH;

#pragma END_MVDM_INSTANCE_DATA


#pragma BEGIN_MVDM_SWAP_INSTANCE

/***    apiop - I/O Port Hook Table
 *
 *      For ports < PORT_HASH_START this table is a direct lookup table
 *      indexed by the I/O port number to find the IOP containing the
 *      VDD port trap handlers.
 */

PIOP apiop[PORT_HASH_START] = { 0 };

/***    appha - I/O Port Hash Table
 *
 *      For port numbers >= PORT_HASH_START, this table contains the
 *      head of the list of PHA's for the hashed I/O port number.  The
 *      hash function is the logical AND of the port number and the
 *      number of entries in this table minus one (NUM_IO_HASH_ENTRIES - 1).
 */

PPHA appha[NUM_HASH_ENTRIES] = { 0 };

/***    apinthHead - Interrupt Hook List Head
 *
 *      The VDHInstallIntHook services uses this table indexed
 *      by the interrupt number to find the head of the INTH
 *      list of VDD interrupt handlers.
 */

PINTH apinthHead[NUM_INTS] = { 0 };

/***    apvdmbp - VDM Breakpoint Table
 *
 *      This is an array of VDMBP structure pointers.  The segment
 *      portion of the VDM breakpoint address minus a base is used
 *      as index into this table.  VDM BP addresses all point to the
 *      same ARPL instruction, but there many different combinations
 *      of segment:offsets that can point to it.  We start off with
 *      low base segment and a high offset and, for each new VDM
 *      breakpoint needed, we add 1 to the segment and subtract 10h
 *      from the offset.
 */

PVDMBP apvdmbp[NUM_VDMBP_ENTRIES] = { 0 };

/***    ivdmbpFreeListHead - apvdmbp free list head
/***    cbvdmbp - maximum number of VDM BPs
 *
 *      This is the head of the VDM breakpoint table's free
 *      list.  Note that this is the head of the apvdmbp
 *      array entries; not VDMBP blocks themselves.  The free
 *      list is kept as an index.  ivdmbpFreeListHead is the
 *      index to the first free apvdmbp entry which contains
 *      the index for the next free entry.  The end of the free
 *      list is when the index is => NUM_VDMBP_ENTRIES.
 */

int ivdmbpFreeListHead = 0;
int cbvdmbp = NUM_VDMBP_ENTRIES;

/***    vpVdmBpArpl - segment:offset address of ARPL
 *
 *      Contains a segment:offset address of the VDM BP ARPL
 *      instruction in the VDM's address space.  The segment
 *      portion of this address is normalized to the lowest
 *      value (while the offset is the highest) that still
 *      points to the ARPL.  The index into the apvdmbp table
 *      is added to the segment portion (and subtracted
 *      from the offset portion) to form the actual VDM BP
 *      address.
 */

VPVOID vpVdmBpArpl = (VPVOID)NULL;

/***    ptss - pointer to VDM TSS and IOPM
 *
 *      Contains the linear pointer to this VDM's TSS and IOPM.
 */

PTSS ptss = (PTSS)NULL;

/***    cbtss - count of bytes of the VDM's TSS.
 *
 *      Contains the size of the IOPM plus the fixed size of
 *      a 386 TSS.
 */

int cbtss = 0;


/***    fAdvancedProperty - substitute for hardware enable/disable property
 *
 *      true for avanced hardware support enabled
 */

//BD009 BOOL    fAdvHardwareSupport = TRUE;

/***    pMapMemLockList - linked list of locked memory objects
 *
 *      Linked list of locked memory objects created by VDHMapMemBlock.
 */

PLOCKENTRY pMapMemLockList = NULL;                                      //BN008

/***    pVAPIList - pointer to VDD API hook list                        //BN010
 *
 *      Start of the list of VDD API hooks for this VDM.
 */

PVDDAPI pVAPIList = NULL;                                               //BN010

/***    pswIntBitmap - Pointer to S/W interrupt redirection bitmap
 *
 *      This is a pointer to the software interrupt redirection bitmap.
 */

PBYTE   pswIntBitmap = (PBYTE) NULL;                                    //BN013

#pragma END_MVDM_SWAP_INSTANCE

/*end*/

#pragma BEGIN_MVDM_INIT_CODE

/***EP+ EM86Init - Initialize 8086 emulation
 *
 *      Initializes the 8086 emulation component.
 *
 *      ENTRY   NONE
 *
 *      EXIT    SUCCESS
 *                  returns NO_ERROR
 *
 *              FAILURE - VDM creation failure
 *                  returns !0
 *
 *      CONTEXT Initialization
 *
 *      PSEUDO-CODE
 */

INT PASCAL
EM86Init()
/*end*/
{
    em86AssertCreate(IOP, &iopDefault);
    em86AssertCreate(IOP, &iopIgnore);
    return(NO_ERROR);
}

#pragma END_MVDM_INIT_CODE
#pragma code_seg ("SWAP32_23","CODE")
/***EP+ EM86CreateVDM - 8086 emulation VDM creation routine
 *
 *      This entry point is called when a new VDM is created
 *      so this component can initialize the IOPM in the TSS,
 *      etc.  This routine is called in the context of the
 *      new created VDM and after the VDM manager has done
 *      its VDM creation logic.
 *
 *      ENTRY   NONE
 *
 *      EXIT    SUCCESS
 *                  returns NO_ERROR
 *
 *              FAILURE - VDM creation failure
 *                  returns !0
 *
 *      CONTEXT VDM Task
 *
 *      PSEUDO-CODE
 *
 *      allocate a TSS and IOPM object (put in ptss)
 *      allocate a TSS selector (put in seltss)
 *      initialize the TSS SS0, ESP0, I/O map base, etc
 *      initialize the IOPM memory to trap all ports
 *      put ARPL instruction into the a byte in DOS area
 *      save linear address in per-VDM data (pvdmArpl)
 *      convert the ARPL address to lowest segment and high offset
 *      save ARPL segment:offset in per-VDM data (vpVdmBpArpl)
 *      initialize each entry in apiop to iopDefault
 *      initialize the VDM BP free list (apvdmbp)
 *      initialize STI hook head
 *      always wrap/A20 enabled
 *      install invalid opcode int hook
 *      load TR register with TSS selector
 */

INT PASCAL
EM86CreateVDM()
/*end*/
{
    register int i, rc;
    HHOOK hhook;
    SEL seltss;                                                      // BN001
    ULONG cpu_family;                                                   //BN013
    ULONG cpu_model;                                                    //BN013
    ULONG cpu_step;                                                     //BN013
    ULONG CR4_val;                                                      //BN013
    ULONG cpu_features = 0;                                             //BN013

    /* setup initial VDM flags and crf_perf */
    pvsfFrameBase = pTCBCur->TCB_pFrameBase;
    em86AssertVSF(pTCBCur);

    // Detect processor type. If processor is P5 set appropriate VDM status
    // and set VM and VIF bits in EFLAGS, do not set IOPL=3.  There is a bug
    // in early version of VME support on 486's so if it is a 486DX, SX, or DX2
    // with A1 or A0 stepping (cpu_step = 4) don't enable the VME.

    VDHDetectProcessor(SSToDS(&cpu_family), SSToDS(&cpu_model),
                       SSToDS(&cpu_step), SSToDS(&cpu_features));       //BN013 begin
    if ((cpu_features & VME_supported) &&
        (GlobalVME) &&                                                  //93272
        (!((cpu_family == P_80486) && (cpu_step <= 4) &&
          ((cpu_model == 3) | (cpu_model == 2) | (cpu_model == 1))))) {
       flVdmStatus |= VDM_STATUS_VME;
       pPTDACur->ptda_vme = 1;                                          //134780
       VSF_EFLAG(pvsfFrameBase) = F_VM + F_VIF;
       CR4_val = em86GetCR4();
       CR4_val |= (CR4_VME | CR4_PVI); // Enable VME extensions
       em86SetCR4(CR4_val);
    } else {                                                            //BN013 end
       VSF_EFLAG(pvsfFrameBase) = F_VM + F_IOPL3;
    }

    /* allocate and initialize the TSS and IOPM for this VDM */
    cbtss = sizeof(TSS) + ((!MiniVDMCreate) ? INITIAL_IOPM_SIZE : 8193);//@xxxx

    /*
     * Check the processor on which the VDM is running. If it is running
     * on a P5 processor allocate an extra 32 bytes in TSS for software
     * redirection bitmap
     */
    if (flVdmStatus & VDM_STATUS_VME) {                                 //BN013
       cbtss += NUM_INTS/8;                                             //BN013
    }                                                                   //BN013

    if((rc = VMAllocKHB(VM_HKH_PUB_RESRW, cbtss, (VMHOB)EM86TSSOWNER, NA,
                        &ptss)) != NO_ERROR) {
        return(rc);
    }
    if((rc = SELAllocGDT(SSToDS(&seltss))) != NO_ERROR) {            // BC001
        VMFreeKHB(VM_HKH_PUB_RESRW, ptss);      // clean up tss allocated
        return(rc);
    }
// disable NMI here, don't bother with reading port 71                  122474

_asm {  pushf;  save state of interrupt flag                            122474
        cli;    disable interrupt to stop clk drvr from enabling NMI    122474
        push ax                         ;                               122474
        mov al,0dh                      ; prepare for MCA               122474
        cmp ABIOS_present,1             ; Is ABIOS present              122474
        jz  short nmi_isa1              ; jump if it is (MCA machine)   122474
        mov al,8dh                      ; no, it's an ISA machine       122474
nmi_isa1:                               ;                               122474
        out 70h,al                      ;                               122474
        pop ax};                                                      //122474

    SELSetDescInfo(SELSD_ALL, D_TSS032, seltss, cbtss, (ULONG)ptss,
      HOBNULL, HOBNULL, 0);                                          // BC001
    memcpy(ptss, &tss, sizeof(TSS));

    /*
     * If VDM is running on a P5 set IO bitmap offset in TSS appropriately
     * and set S/W interrupt redirection bitmap to reflect all interrupts
     */
    if (flVdmStatus & VDM_STATUS_VME) {                                 //BN013
       i = sizeof(TSS) + (NUM_INTS/8);                                  //BN013
       pswIntBitmap = (PBYTE)ptss + sizeof(TSS);                        //BN013
       memset((PBYTE)ptss + sizeof(TSS), 0, NUM_INTS/8);                //BN013
    } else {
       i = sizeof(TSS);
    }
    ptss->tss_iomap = (USHORT)i;
    memset((PBYTE) ptss + i,
           (!MiniVDMCreate) ? 0xff : 0, cbtss-i);                       //BC013

    // last byte in TSS IO Permission Bit Map must be 0xff              //@xxxx
    if(MiniVDMCreate)                                                   //@xxxx
      *((PBYTE)ptss + cbtss - 1) = 0xff;                                //@xxxx

//122474  _disable();                     // Disable ints                     BN001
    pPTDACur->seltss = seltss;      // update TSS selector in PTDA      BN001
//122474  _enable();                      // Enable ints                      BN001

    // set V86 mode ESP0
    ptss->tss_esp0 = GET_VDM_V86_ESP0(pTCBCur);

    /* setup the VDM's VDM breakpoint ARPL instruction */
    vpVdmBpArpl = LOSEGVPFROMP(pvdmArpl);
    i = (OFFSETOF16(vpVdmBpArpl) >> 4) + 1;
    if (i < cbvdmbp)
        cbvdmbp = i;

    /* initialize instance data */
    for(i = 0; i < PORT_HASH_START; i++) {
        apiop[i] = &iopDefault;
    }
    for(i = 0; i < NUM_VDMBP_ENTRIES; i++) {
        (int) apvdmbp[i] = i + 1;
    }
    ivdmbpFreeListHead = 0;
    KMInitHookHead(&REFHVDM(vdmid_HVDM,HOOKHEAD,hookheadSTIH));

    // always wrap/A20 enabled
    //VDHMapPages(A20_WRAP_AT, A20_WRAP_TO, NUM_A20_PAGES, VDHMT_LINEAR);

    // allocate and arm a context hook that hooks the invalid opcode exception
    if((rc = KMAllocHook(em86CreateExitHook, 0, 0, sizeof(HHOOK), pTCBCur,
      SSToDS(&hhook))) != NO_ERROR) {
        VMFreeKHB(VM_HKH_PUB_RESRW, ptss);      // clean up tss allocated
// enable NMI here, don't bother with reading port 71                   122474

_asm {  push ax                         ;                               122474
        mov al,8dh                      ; prepare for MCA               122474
        cmp ABIOS_present,1             ; Is ABIOS present              122474
        jz  short nmi_isa2              ; jump if it is (MCA machine)   122474
        mov al,0dh                      ; ISA machine                   122474
nmi_isa2:                               ;                               122474
        out 70h,al                      ;                               122474
        pop ax                          ;                               122474
        popf};  //   restore interrupt flag to prior state before cli   122474
        return(rc);
    }
    *((PHHOOK) KMQueryHookData((PHOOK)hhook)) = hhook;
    KMArmContextHook(hhook, pTCBCur);

    // set the TR register to the new TSS selector
    em86LoadTR();
// enable NMI here, don't bother with reading port 71                   122474

_asm {  push ax                         ;                               122474
        mov al,8dh                      ; prepare for MCA               122474
        cmp ABIOS_present,1             ; Is ABIOS present              122474
        jz  short nmi_isa3              ; jump if it is (MCA machine)   122474
        mov al,0dh                      ; ISA machine                   122474
nmi_isa3:                               ;                               122474
        out 70h,al                      ;                               122474
        pop ax                          ;                               122474
        popf};  //   restore interrupt flag to prior state before cli   122474
    return(NO_ERROR);
}
#pragma code_seg()
#pragma BEGIN_MVDM_SWAP_CODE
static VOID HOOKENTRY
em86CreateExitHook(phhook, pcrf)
PHHOOK phhook;
PCRF pcrf;
{
    pcrf;                           // get rid of compilation warning
    KMFreeHook(*phhook);
    VDHInstallIntHook(0, EXCOP, em86InvalidOpcode, 0);
    VDHInstallIntHook(0, INT2F_INT, INT2FV86Dispatcher, VDH_PRE_HOOK);

    // For the retail build, have int 1 and int 3 in IVT point to an IRET
#ifndef PMDEBUG
    *((PVPVOID)(EXCSSI*sizeof(ULONG))) = vpvdmIRet;
    *((PVPVOID)(EXCBP*sizeof(ULONG))) = vpvdmIRet;
#endif
}

#pragma END_MVDM_SWAP_CODE
#pragma code_seg("SWAP32_27","CODE")
/***EP+ EM86DestroyVDM - 8086 emulation VDM termination routine
 *
 *      Cleans up the memory and objects allocated for this VDM
 *      by the 8086 emulation component.
 *
 *      ENTRY   NONE
 *
 *      EXIT    NONE
 *
 *      CONTEXT VDM Task
 *
 *      PSEUDO-CODE
 *
 *      free the TSS and IOPM object
 *      free the TSS selector
 *      reset TR register to the protected mode selector
 *      free the IOP entries in apiop
 *      free the PHA entries in appha
 *      free the INTH entries on apinthHead list
 */

VOID PASCAL
EM86DestroyVDM()
/*end*/
{
    register int i;
    PPHA ppha, pphaNext;
    PINTH pinth, pinthNext;
    PLOCKENTRY plockentry;
    SEL sel;
    PVDDAPI pvapiNext;                                                  //BN010

    em86VPMDestroyVDM();
    sel = pPTDACur->seltss;
    pPTDACur->seltss = TKGetProtTSS();
    em86LoadTR();
    VMFreeKHB(VM_HKH_PUB_RESRW, ptss);
    SELFreeGDT(sel);

    /* free the IOP entries */
    for(i = 0; i < PORT_HASH_START; i++) {
        if(apiop[i] != &iopDefault) {
            em86AssertP(IOP, apiop[i]);
            em86AssertDestroy(IOP, apiop[i]);
            VMFreeKHB(VM_HKH_PUB_SWAPRW,apiop[i]);
        }
    }

    /* free the PHA entries */
    for(i = 0; i < NUM_HASH_ENTRIES; i++) {
        ppha = appha[i];
        while(ppha != NULL) {
            em86AssertP(PHA, ppha);
            em86AssertP(IOP, &ppha->pha_iop);
            em86AssertDestroy(PHA, ppha);
            em86AssertDestroy(IOP, &ppha->pha_iop);
            pphaNext = ppha->pha_ppha;
            VMFreeKHB(VM_HKH_PUB_SWAPRW,ppha);
            ppha = pphaNext;
        }
    }

    /* free the INTH entries */
    for(i = 0; i < NUM_INTS; i++) {
        pinth = ALIGNP(apinthHead[i]);
        while(pinth != NULL) {
            em86AssertP(INTH, pinth);
            em86AssertDestroy(INTH, pinth);
            pinthNext = pinth->inth_pinth;
            VMFreeKHB(VM_HKH_PUB_SWAPRW,pinth);
            pinth = pinthNext;
        }
    }

    while (pMapMemLockList) {                                           //BN008
       VMUnlock(&(pMapMemLockList->le_hlock));                          //BN008
       plockentry = pMapMemLockList;                                    //BN008
       pMapMemLockList = pMapMemLockList->pnextlockentry;               //BN008
       VMFreeKHB(VM_HKH_PUB_SWAPRW, plockentry);                        //BN008
    }                                                                   //BN008

    // Free all entries in VDD API handler list
    while (pVAPIList) {                                                 //BN010 begin
       em86AssertP(VDDAPI, pVAPIList);
       if (pVAPIList->vapi_hhookV86) {
          VDHFreeHook(pVAPIList->vapi_hhookV86);
       }
       if (pVAPIList->vapi_hhookVPM) {
          VDHFreeHook(pVAPIList->vapi_hhookVPM);
       }
       pvapiNext = pVAPIList->vapi_next;
       em86AssertDestroy(VDDAPI, pVAPIList);
       VMFreeKHB(VM_HKH_PUB_SWAPRW, pVAPIList);
       pVAPIList = pvapiNext;
   }                                                                    //BN010 end
}
#pragma code_seg()
#pragma BEGIN_MVDM_SWAP_CODE

#pragma code_seg("SWAP32_19","CODE")
/***EP+ VDHInstallIOHook - Install I/O port hooks
 *
 *      Install I/O port trap handlers for a contiguous range
 *      of I/O addresses.  Trapping is initially enabled for the
 *      specified ports.  The I/O hook entry contains the handlers
 *      for each kind of I/O operation.  If an entry in the table is
 *      NULL, that I/O type is simulated using a lower I/O type (e.g.,
 *      word I/O is simulated by doing two byte I/O operations).
 *      I/O port hooks are kept on a per-VDM basis; a VDD will generally
 *      install I/O hooks at VDM creation.
 *
 *      VDHSetIOHookState can be used to enable or disable I/O trapping
 *      on a per-port per-VDM basis, except if the VDHIIH_ALWAYS_TRAP
 *      option is specified, in which case VDHSetIOHookState cannot
 *      be called on any port in the range for the current VDM.
 *
 *      I/O hooks may take their parameters either from registers or
 *      from the stack.  High-performance hooks should take register
 *      parameters (the I/O hook router is optimized for this case).
 *      Hooks with lower performance requirements should take stack
 *      parameters, and hence can be written in C.
 *
 *      NOTE: The default behavior of unhooked ports is to access the
 *            hardware port.
 *
 *      ENTRY
 *          hvdm - VDM handle
 *                      0 => current VDM
 *          portVDD - starting port number
 *          cportVDD - number of ports from starting port
 *          piohVDD - Pointer to I/O port hook entry
 *
 *              typedef struct ioh {
 *                  PBIH ioh_pbihByteInput;
 *                  PBOH ioh_pbohByteOutput;
 *                  PWIH ioh_pwihWordInput;
 *                  PWOH ioh_pwohWordOutput;
 *                  POTH ioh_pothOther;
 *              } IOH;
 *
 *              NOTES
 *                   (1)  The values of the fields of the IOH are ignored
 *                        when VDHIIH_IGNORE is specified.  The piohVDD
 *                        parameter is used for validation of the (possible)
 *                        VDHRemoveIOHook request.
 *
 *              BIH ByteInput(port, pcrf)
 *                  ENTRY   EDX (port) - port number
 *                          EBX (pcrf) - client register frame pointer
 *
 *                  EXIT    AL (C function returns BYTE) - Data read
 *
 *                  USES    EAX, ECX, EDX, FS, GS, Flags
 *
 *                  CONTEXT VDM Task
 *
 *              BOH ByteOutput(bOutputData, port, pcrf)
 *                  ENTRY   AL  (bOutputData) - data to write
 *                          EBX (pcrf) - client register frame pointer
 *                          EDX (port) - port number
 *
 *                  EXIT    NONE
 *
 *                  USES    EAX, ECX, EDX, FS, GS, Flags
 *
 *                  CONTEXT VDM Task
 *
 *              WIH WordInput(port, pcrf)
 *                  ENTRY   EBX (pcrf) - client register frame pointer
 *                          EDX (port) - port number
 *
 *                  EXIT    AX (C function returns WORD) - Data read
 *
 *                  USES    EAX, ECX, EDX, FS, GS, Flags
 *
 *                  CONTEXT VDM Task
 *
 *              WOH WordOutput(usOutputData, port, pcrf)
 *                  ENTRY   AX  (usOutputData) - data to write
 *                          EDX (port) - port number
 *                          EBX (pcrf) - client register frame pointer
 *
 *                  EXIT    NONE
 *
 *                  USES    EAX, ECX, EDX, FS, GS, Flags
 *
 *                  CONTEXT VDM Task
 *
 *              OTH Other(ulOutputData, pulInputData, port, flType, pcrf)
 *                  ENTRY   EAX (ulOutputData) - data for dword writes
 *                          (pulInputData) - pointer to put input data
 *                          EDX (port) - port number
 *                          ECX (flType) - I/O type
 *                              IO_TYPE_SHIFT - shift for type
 *                              IO_TYPE_MASK  - mask for type
 *                              IO_TYPE_INPUT - if set input else output
 *                              IO_TYPE_BYTE  - if set byte else word/dword
 *                              IO_TYPE_DWORD
 *                              IO_TYPE_STRING
 *                              IO_TYPE_REVERSE
 *                              IO_TYPE_ADDR32
 *                              IO_TYPE_REP
 *                              IO_SEG_SHIFT  - shift for SEG  field
 *                              IO_SEG_MASK   - mask for SEG field
 *                              IO_SEG_CS
 *                              IO_SEG_SS
 *                              IO_SEG_ES
 *                              IO_SEG_FS
 *                              IO_SEG_GS
 *                          EBX (pcrf) - client register frame pointer
 *
 *                  EXIT    if assembly language hook,
 *                              EAX - data from dword reads
 *                              if carry set,
 *                                  simulate the I/O operation
 *                              if carry clear,
 *                                  I/O is done
 *                          else if "C" hook
 *                              *pulInputData - data from dword reads
 *                              if returns FALSE,
 *                                  simulate the I/O operation
 *                              if returns TRUE,
 *                                  I/O is done
 *
 *                  USES    EAX, ECX, EDX, FS, GS, Flags
 *
 *                  CONTEXT VDM Task
 *
 *                  NOTES   (1) The ASM Other hook does not need the
 *                              pulInputData parameter because it returns
 *                              dword input values in EAX, with the carry
 *                              flag used to indicate done/simulate.
 *
 *                              The C hook has to use EAX to return status,
 *                              and so needs the additional parameter.
 *
 *          flags - options
 *                  VDHIIH_ASM_HOOK
 *                              Assembly language hook interface,
 *                              otherwise "C" (Pascal calling convention)
 *                              hook interface.
 *                  VDHIIH_ALWAYS_TRAP
 *                              This range of addresses will always be
 *                              trapped.  The VDD may not call
 *                              VDHSetIOHookState on any ports in the range.
 *
 *                              It is recommended that VDDs hook ports in all
 *                              cases, even if the desired behavior is to
 *                              behave as though direct port access was
 *                              allowed, since this keeps the IOPM small.
 *                              Only a port which requires high performance
 *                              (e.g., graphics card) should be allowed to
 *                              go straight through.  The existence of such
 *                              ports increases the size of the IO Permissions
 *                              Map in the TSS, even if VDHSetIOHookState is
 *                              never called for them.
 *                  VDHIIH_NO_SIMULATE
 *                              Doesn't change word or other I/O handlers to
 *                              simulation routines and allows word handlers
 *                              for 1 port ranges.
 *                  VDHIIH_IGNORE
 *                              Sets a system handler that does the following:
 *                              WRITES: the OUT succeeds, but no actual IO is
 *                              performed
 *                              READS:  the IN succeeds, and always returns
 *                              -1 (all 1 bits)
 *      EXIT-SUCCESS
 *          returns TRUE
 *
 *      EXIT-FAILURE
 *          returns FALSE
 *          VDHGetError may return:
 *              ERROR_INVALID_BLOCK
 *                  One or more of the ports in the range is already
 *                  hooked.
 *              ERROR_NOT_ENOUGH_MEMORY
 *                  A memory allocation failed during the installation
 *                  attempt.
 *          System Halt.
 *              If hvdm is invalid, or piohVDD is invalid, or portVDD or
 *              cportVDD are out of range.
 *
 *      VDM TERMINATION IMPLICATIONS
 *          None.  I/O hooks are automaticly removed during VDM termination.
 *
 *      CONTEXT
 *          Task
 *
 *      NOTES
 *          (1) The WordInput, WordOutput and Other hooks may be NULL,
 *              in which case the VDM Manager will simulate these
 *              routines by using the ByteInput and ByteOutput routines.
 *
 *          (2) If the ByteInput or ByteOutput handlers are NULL, the VDM
 *              Manager uses default internal routines that do input and
 *              output to the hardware ports.  This is most useful in
 *              the case where a VDD wants to shadow the hardware state
 *              (by trapping OUT instructions) but doesn't care what
 *              is returned by IN instructions.
 *
 *          (3) The client register frame (CRF) will be updated as
 *              the instruction is simulated.  For example, when the
 *              string I/O instructions are simulated, the client's
 *              EDI/ESI and ECX are incremented/decremented.
 *
 *          (4) In a partcular VDM, a particular I/O port address may
 *              only be hooked by a single VDD.
 *
 *          (5) If this function fails, no I/O hooks are installed.
 *
 *          (6) The routing of I/O instructions to the supplied port hooks
 *              covers the specified range exactly.  In the case where an
 *              I/O instruction overlaps port address ranges hooked on
 *              separate VDHInstallIOHook calls, the instruction is simulated
 *              with smaller I/O operations to ensure that the hooks in
 *              each range are called correctly.
 *
 *              For example, consider a VDD that hooks 100..103 and
 *              104..107:
 *
 *                  VDHInstallIOHook(hvdm,0x100,4,&iohA,NULL);
 *                  VDHInstallIOHook(hvdm,0x104,4,&iohB,NULL);
 *
 *              Now, the following I/O instructions cause the following
 *              hooks to be called:
 *
 *                  Addr  Size  Hooks Called
 *                  ----  ----  ---------------------
 *                  100     4   iohA.Other(100,dword)
 *
 *                  101     4   iohA.Word(101)
 *                              iohA.Byte(103)
 *                              iohB.Byte(104)
 *
 *                  102     4   iohA.Word(102)
 *                              iohB.Word(104)
 *
 *                  103     4   iohA.Byte(103)
 *                              iohB.Word(104)
 *                              iohB.Byte(106)
 *
 *
 *          (7) VDHRemoveIOHook must be called with identical portVDD
 *              and cportVDD in order to remove these I/O hooks.  You
 *              cannot remove port hooks for a subset of the range
 *              of ports hooked on VDHInstallIOHook call.
 *
 *          (8) If the piohVDD is in instance data, the address passed
 *              to VDHInstallIOHook must the same address passed to
 *              VDHRemoveIOHook or VDHSetIOHookState i.e. not hvdm relative
 *              in one case and 0-relative (current VDM) in another.
 *
 *          (9) It is advantages for a VDD to use VDHInstallIOHook with
 *              the VDHIIH_IGNORE flag rather than supplying their own
 *              handler for this situation, since this allows us to avoid
 *              allocating some of our data structures for the port(s) in
 *              question.
 *
 *      PSEUDO-CODE
 *
 *      validate parameters
 *      for each port requested
 *          if port number is not hashed
 *              if port has already been set
 *                  return error
 *              if VDHIIH_IGNORE is specified
 *                  put a pointer to the system ignore IOP in port hook table
 *              else
 *                  allocate a IOP block
 *                  put pointer to IOP in port hook table (apiop)
 *          else
 *              get I/O port hash table index from port number
 *              search I/O port hash table list for port number (appha)
 *              if port has already been added to the I/O port hash table
 *                  return error
 *              allocate port hash entry (PHA)
 *              if VDHIIH_IGNORE is specified
 *                  copy system ignore IOP into hash entry
 *              add to I/O port hash table list (appha)
 *          save flags in iop (VDHIIH_ASM_HOOK and VDIIH_ALWAYS_TRAP)
 *          if VDHIIH_IGNORE is not specified
 *              copy the VDD's IOH to the IOP's VDD IOH (iop_iohVDD)
 *          if the last port
 *              set word input/output handler in main ioh to NULL
 *          if one of the last three ports
 *              set the dword simulate flag in the IOP flags
 *          set up IOP for trapping
 *          set bits in IOPM bitmap
 *      if VDHIIH_ALWAYS_TRAP or VDHIIH_IGNORE not specified
 *          grow IOPM
 *      return ok
 */

BOOL VDHENTRY VDHInstallIOHook(hvdm,portVDD,cportVDD,piohVDD,flags)
HVDM hvdm;
PORT portVDD;
ULONG cportVDD;
PIOH piohVDD;
FLAGS flags;
/*end*/
{
    register PPHA ppha;                 // pointer to port hash entry
    register PIOP piop;                 // pointer to I/O port block
    PPHA *pppha;                        // pointer to "appha" array entry
    PORT port;                          // current port number
    ULONG cport;                        // current number of ports left to do
    PVOID piopmEnd;                     // pointer to end of iopm       //BN011
    PULONG pul;                         // pointer into IOPM            //BN011
    ULONG ulMask;                       // mask for cur port in IOPM    //BN011

    // validate calling context and parameters
    ASSERTTASKONLY;
#ifdef MMIOPH
    MMPHPreVDHInstallIOHook()
#endif
    CHECKHVDM(hvdm);
#ifdef VDHSTRICT
    if((portVDD > MAX_PORT_NUM) || (cportVDD > MAX_PORT_NUM) ||
      (portVDD + cportVDD > MAX_PORT_NUM + 1) || (piohVDD == NULL) ||
      ((piohVDD->ioh_pwihWordInput != NULL ||
      piohVDD->ioh_pwohWordOutput != NULL) &&
      cportVDD < 2 && !(flags & VDHIIH_NO_SIMULATE)) ||
      flags & ~(VDHIIH_ASM_HOOK | VDHIIH_ALWAYS_TRAP | VDHIIH_NO_SIMULATE |
      VDHIIH_IGNORE)) {
        em86InvalidParameter(("VDHInstallIOHook: invalid port, cport or pioh"));
    }
#endif

    piopmEnd = REFHVDM(hvdm,PBYTE,ptss) + REFHVDM(hvdm,int,cbtss);      //BN011
    pul = (PULONG) ((PBYTE)REFHVDM(hvdm,PTSS,ptss) + sizeof(TSS)        //BN011
      + (portVDD/NBITS_ULONG) * sizeof(ULONG));
    ulMask = EXP2(portVDD % NBITS_ULONG);                               //BN011

    // Install I/O hooks
    for(port = portVDD, cport = 0; cport < cportVDD; port++, cport++) {
        if(port < PORT_HASH_START) {
            if(REFHVDM(hvdm,PIOP,apiop[port]) != &iopDefault) { // if hooked
                em86RemoveIOHook(hvdm, portVDD, cport);
                VDHSetError(ERROR_INVALID_BLOCK);
#ifdef MMIOPH
                MMPHPostVDHInstallIOHook()
#endif
                return(FALSE);
            }
            if(flags & VDHIIH_IGNORE) {
#ifdef VDHSTRICT
                if((piop = em86salloc(sizeof(IOP))) == NULL) {
                   em86RemoveIOHook(hvdm, portVDD, cport);
#ifdef MMIOPH
                   MMPHPostVDHInstallIOHook()
#endif
                   return(FALSE);
                }
                em86AssertCreate(IOP, piop);
                *piop = iopIgnore;
#else
                piop = &iopIgnore;
#endif
            }
            else {
                if((piop = em86salloc(sizeof(IOP))) == NULL) {
                   em86RemoveIOHook(hvdm, portVDD, cport);
#ifdef MMIOPH
                   MMPHPostVDHInstallIOHook()
#endif
                   return(FALSE);
                }
                em86AssertCreate(IOP, piop);
            }
            REFHVDM(hvdm,PIOP,apiop[port]) = piop;
        }
        else {
            pppha = &REFHVDM(hvdm,PPHA,appha[port & PORT_HASH_MASK]);
            ppha = *pppha;                      // head of list
            while(ppha != NULL) {               // ensure not already hooked
                em86AssertP(PHA, ppha);
                em86AssertP(IOP, &ppha->pha_iop);
                if(ppha->pha_port == port) {
                    em86RemoveIOHook(hvdm, portVDD, cport);
                    VDHSetError(ERROR_INVALID_BLOCK);
#ifdef MMIOPH
                    MMPHPostVDHInstallIOHook()
#endif
                    return(FALSE);
                }
                ppha = ppha->pha_ppha;          // next PHA entry
            }
            if((ppha = em86salloc(sizeof(PHA))) == NULL) {
                em86RemoveIOHook(hvdm, portVDD, cport);
#ifdef MMIOPH
                MMPHPostVDHInstallIOHook()
       #endif
                return(FALSE);
            }
            em86AssertCreate(PHA, ppha);
            piop = &ppha->pha_iop;
            em86AssertCreate(IOP, piop);
            ppha->pha_port = port;
            if(flags & VDHIIH_IGNORE) {
                ppha->pha_iop = iopIgnore;
            }
            ppha->pha_ppha = *pppha;            // link into hash table chain
            *pppha = ppha;
        }
        /*
         *  (piop) points to the IOP block
         */
#ifdef VDHSTRICT
        piop->iop_piohVDD = piohVDD;            // save VDD ioh's address
        piop->iop_port = portVDD;               // save for remove I/O hook
        piop->iop_cport = cportVDD;
#endif

#if VDHIIH_ALWAYS_TRAP != IOP_ALWAYS_TRAP
#error Flags should match: VDHIIH_ALWAYS_TRAP == IOP_ALWAYS_TRAP
#endif

#if VDH_ASM_HOOK != IOP_ASM_HOOK
#error Flags should match: VDH_ASM_HOOK == IOP_ASM_HOOK
#endif

#if VDHIIH_ASM_HOOK != IOP_ASM_HOOK
#error Flags should match: VDHIIH_ASM_HOOK == IOP_ASM_HOOK
#endif

#if VDHIIH_IGNORE != IOP_IGNORE
#error Flags should match: VDHIIH_IGNORE == IOP_IGNORE
#endif

        if(!(flags & VDHIIH_IGNORE)) {
            piop->iop_flags = flags & (VDHIIH_ASM_HOOK |
              VDHIIH_ALWAYS_TRAP);
            piop->iop_iohVDD = *piohVDD;        // copy VDD's ioh to iop's ioh

            // Always simulate word I/O on the last port of a range
            if((cportVDD - cport) == 1 && !(flags & VDHIIH_NO_SIMULATE)) {
                piop->iop_iohVDD.ioh_pwihWordInput = NULL;
                piop->iop_iohVDD.ioh_pwohWordOutput = NULL;
            }

            // Always simulate dword I/O on the last 3 ports of a range
            if((cportVDD - cport) <= 3 && !(flags & VDHIIH_NO_SIMULATE)) {
                piop->iop_flags |= IOP_SIMULATE_DWORD;
            }
            em86SetIOP(piop, TRUE);
        }

        // Set the bits in the IOPM bitmap
        if(pul < piopmEnd) {           // inside current IOPM bitmap?   //BN011 begin
            *pul |= ulMask;            // yes, set IOPM bit
        }
        ulMask <<= 1;                  // to next I/O port
        if (ulMask == 0) {             // need to step to next ULONG
            ++pul;
            ulMask = 1;
        }                                                               //BN011 end
    }
    // Grow the IOPM in the TSS if the port might not be trapped
    if(!(flags & (VDHIIH_ALWAYS_TRAP | VDHIIH_IGNORE))) {
        if(!em86GrowIOPM(hvdm, portVDD + cport - 1)) {
            em86RemoveIOHook(hvdm, portVDD, cport);
            VDHSetError(ERROR_NOT_ENOUGH_MEMORY);
#ifdef MMIOPH
            MMPHPostVDHInstallIOHook()
#endif
            return(FALSE);
        }
    }
#ifdef MMIOPH
    MMPHPostVDHInstallIOHook()
#endif
    return(TRUE);
}


#pragma code_seg()
#pragma code_seg("SWAP32_19","CODE")
/****LP em86GrowIOPM - grow IOPM if necessary
 *
 *      Grow the TSS to accomodate new ports.  Trapping is initially
 *      turned on for all new ports included in the IOPM.  If the tss for
 *      a currently executing VDM is reallocated, the task register is
 *      reloaded.
 *
 *      ENTRY
 *          hvdm - VDM handle
 *                      0 => current VDM
 *          port - port number of first port
 *
 *      EXIT-SUCCESS
 *          returns TRUE
 *
 *
 *      EXIT-FAILURE
 *          returns FALSE, tss unchanged
 *
 *      CONTEXT Task
 *
 *      PSEUDO-CODE
 *
 *      if port is in range for current IOPM size
 *          return true
 *      allocate new TSS/IOPM
 *      if allocation is unsuccessful
 *          return false
 *      set up TSS selector for new TSS/IOPM
 *      copy old TSS/IOPM to new
 *      initialize new part of IOPM to trap ports
 *      free old TSS
 *      if hvdm == current hvdm
 *          reload tr register
 *      put new tss value in vdm data
 *      return pointer to true
 */

BOOL PASCAL em86GrowIOPM(hvdm, port)
HVDM hvdm;
PORT port;
{
    int cbtssOld;                       // size of old TSS
    int cbtssNew;                       // size of new TSS
    PTSS ptssNew;                       // pointer to new TSS (if grown)

    cbtssNew = (port + (NBITS_BYTE - 1))/NBITS_BYTE;
    cbtssNew += sizeof(TSS);
    if (REFHVDM(hvdm,ULONG,flVdmStatus) & VDM_STATUS_VME) {             //BN013
        cbtssNew += NUM_INTS/8;                                         //BN013
    }                                                                   //BN013
    cbtssNew = (cbtssNew + 3) & ~3;             // round up to dword boundary
    cbtssOld = REFHVDM(hvdm,int,cbtss);

// disable NMI here, don't bother with reading port 71                  122474

_asm {  pushf    ;save interrupt flag before clearing it                122474
        cli                             ;                               122474
        push ax                         ;                               122474
        mov al,0dh                      ; prepare for MCA               122474
        cmp ABIOS_present,1             ; Is ABIOS present              122474
        jz  short nmi_isa4              ; jump if it is (MCA machine)   122474
        mov al,8dh                      ; no, it's an ISA machine       122474
nmi_isa4:                               ;                               122474
        out 70h,al                      ;                               122474
        pop ax};                                                      //122474

    if(cbtssNew > cbtssOld) {           // allocate and init new TSS
        if(VMAllocKHB(VM_HKH_PUB_RESRW, cbtssNew, (VMHOB)EM86TSSOWNER, NA,
          (PVOID *)SSToDS(&ptssNew)) != NO_ERROR) {

// enable NMI here, don't bother with reading port 71                   122474

_asm {  push ax                         ;                               122474
        mov al,8dh                      ; prepare for MCA               122474
        cmp ABIOS_present,1             ; Is ABIOS present              122474
        jz  short nmi_isa5              ; jump if it is (MCA machine)   122474
        mov al,0dh                      ; ISA machine                   122474
nmi_isa5:                               ;                               122474
        out 70h,al                      ;                               122474
        pop ax                          ;                               122474
        popf};  //   restore interrupt flag to state it was upon entry  122474

            return(FALSE);
        }
        SELSetDescInfo(SELSD_ALL, D_TSS032, QUERYPPTDA(hvdm)->seltss,
          cbtssNew, (ULONG) ptssNew, 0, 0, 0);
        memcpy(ptssNew, REFHVDM(hvdm,PTSS,ptss), cbtssOld);
        memset((PBYTE) ptssNew + cbtssOld, 0xff, cbtssNew - cbtssOld);
        VMFreeKHB(VM_HKH_PUB_RESRW, REFHVDM(hvdm,PTSS,ptss));
        REFHVDM(hvdm,PTSS,ptss) = ptssNew;
        REFHVDM(hvdm,int,cbtss) = cbtssNew;
        REFHVDM(hvdm,PBYTE,pswIntBitmap) = (PBYTE)ptssNew + sizeof(TSS);
        if((hvdm == CURRENT_VDM) || (hvdm == REFHVDM(hvdm,HVDM,vdmid_HVDM))) {
            em86LoadTR();                       // reload TR register
        }
    }

// enable NMI here, don't bother with reading port 71                   122474

_asm {  push ax                         ;                               122474
        mov al,8dh                      ; prepare for MCA               122474
        cmp ABIOS_present,1             ; Is ABIOS present              122474
        jz  short nmi_isa6              ; jump if it is (MCA machine)   122474
        mov al,0dh                      ; ISA machine                   122474
nmi_isa6:                               ;                               122474
        out 70h,al                      ;                               122474
        pop ax                          ;                               122474
        popf};  //   restore interrupt flag to state it was upon entry  122474

    return(TRUE);
}

#pragma code_seg()
#pragma code_seg("SWAP32_20","CODE")
/***EP+ VDHRemoveIOHook - Removes hooks for I/O ports
 *
 *      Removes the hooks for the specified I/O ports.  See notes (7)
 *      and (8) in VDHInstallIOHook for usage restrictions.
 *
 *      ENTRY
 *          hvdm - VDM handle
 *              0 => current VDM
 *          portVDD  - Starting port number
 *          cportVDD - Number of ports in the range
 *          piohVDD  - Pointer to installed I/O hook entry
 *                         This is used to verify that the calling VDD
 *                         installed the I/O hook.
 *
 *      EXIT-SUCCESS
 *          None.
 *
 *      EXIT-FAILURE
 *          System Halt.
 *              If hvdm is invalid, or piohVDD is invalid, or portVDD or
 *              cportVDD are out of range.
 *
 *      VDM TERMINATION IMPLICATIONS
 *          None.  I/O hooks are automaticly removed during VDM termination.
 *
 *      CONTEXT
 *          Task
 *
 *      NOTES
 *          (1) This call must be made with hvdm, piohVDD, portVDD and
 *              cportVDD parameters that exactly match those passed on
 *              a previous VDHInstallIOHook call.
 *
 *          (2) If the piohVDD is in instance data, the address passed
 *              to VDHInstallIOHook must the same address passed to
 *              VDHRemoveIOHook or VDHSetIOHookState i.e. not hvdm relative
 *              in one case and 0-relative (current VDM) in another.
 *
 *      PSEUDO-CODE
 *
 *      enable trapping for the I/O ports
 *      validates parameters and verifies piohVDD ownershiop
 *      for each port requested
 *          if port number less than the number of local ports
 *              check if the calling VDD installed the I/O hook
 *              set apiop entry to iopDefault
 *          else
 *              get I/O port hash table index from port number
 *              search I/O port hash table list for port number (appha)
 *              check if the calling VDD installed the I/O hook
 *              remove PHA entry from linked list
 *              free PHA entry
 *      return ok
 */

VOID VDHENTRY VDHRemoveIOHook(hvdm,portVDD,cportVDD,piohVDD)
HVDM hvdm;
PORT portVDD;
ULONG cportVDD;
PIOH piohVDD;
/*end*/
{
//  register PIOP piop;                 // pointer to IOP for port
//  register PPHA pphaCurr;             // pointer to current port hash entry
//  register PPHA *ppphaPrev;           // pointer to previous pha_ppha
//  PORT port;                          // current port number

    // validate calling context and parameters
    ASSERTTASKONLY;
#ifdef MMIOPH
    MMPHPreVDHRemoveIOHook()
#endif
    CHECKHVDM(hvdm);
#ifdef VDHSTRICT
    if((portVDD > MAX_PORT_NUM) || (cportVDD > MAX_PORT_NUM) ||
      (portVDD + cportVDD > MAX_PORT_NUM + 1) || (piohVDD == NULL)) {
        em86InvalidParameter(("VDHRemoveIOHook: invalid port, cport, pioh"));
    }
    em86EachIOPort(hvdm,portVDD,cportVDD,piohVDD,em86ValidateRemove);
#endif
    em86SetIOHookState(hvdm, portVDD, cportVDD, TRUE);
    em86RemoveIOHook(hvdm, portVDD, cportVDD);
#ifdef MMIOPH
    MMPHPostVDHRemoveIOHook()
#endif
}
#pragma code_seg()
#pragma BEGIN_MVDM_SWAP_CODE
#ifdef VDHSTRICT
/***LP  em86ValidateRemove - worker for VDHRemoveIOHook validations
 *
 *      ENTRY   port     - port number
 *              piop     - pointer to IOP
 *              portVDD  - starting port number
 *              cportVDD - number of ports in the range
 *              piohVDD  - pointer to VDD's ioh
 *
 *      EXIT    TRUE - continue em86EachIOPort
 *              FALSE - stop em86EachIOPort
 *
 *      CONTEXT Task
 *
 *      PSEUDO-CODE
 *
 *      if iop entry not owned by VDD
 *         invalid parameter
 *      if starting port and number of ports in the IOP not the same
 *         invalid parameter
 *      continue em86EachIOPort
 */

static BOOL PASCAL
em86ValidateRemove(port, piop, portVDD, cportVDD, piohVDD)
PORT port;
PIOP piop;
PORT portVDD;
ULONG cportVDD;
PIOH piohVDD;
{
    port;                           // eliminate compiler warning
    em86AssertP(IOP, piop);
    if(piop->iop_piohVDD != piohVDD) {
        em86InvalidParameter(("VDHRemoveIOHook: invalid piohVDD"));
        return(FALSE);
    }
    if(piop->iop_port != portVDD || piop->iop_cport != cportVDD) {
        em86InvalidParameter(("VDHRemoveIOHook: invalid portVDD or cportVDD"));
        return(FALSE);
    }
    return(TRUE);
}
#endif

/***LP  em86RemoveIOHook - removes hooks for I/O ports
 *
 *      Removes the hooks for the specified I/O ports.
 *
 *      ENTRY
 *          hvdm  - VDM handle
 *              0 => current VDM
 *          port  - starting port number
 *          cport - number of ports in the range
 *
 *      EXIT-SUCCESS
 *          None.
 *
 *      EXIT-FAILURE
 *          None.
 *
 *      PSEUDO-CODE
 *
 *      for each port requested
 *          if port number less than the number of local ports
 *              check if the calling VDD installed the I/O hook
 *              set apiop entry to iopDefault
 *              if iop is not ignore iop
 *                  free iop
 *          else
 *              get I/O port hash table index from port number
 *              search I/O port hash table list for port number (appha)
 *              check if the calling VDD installed the I/O hook
 *              remove PHA entry from linked list
 *              free PHA entry
 */

static VOID PASCAL
em86RemoveIOHook(hvdm,port,cport)
HVDM hvdm;
PORT port;
ULONG cport;
{
    register PIOP piop;                 // pointer to I/O port block
    register PPHA pphaCurr;             // pointer to current port hash entry
    register PPHA *ppphaPrev;           // pointer to previous pha_ppha

    for(; cport > 0; port++, cport--) {
        if(port < PORT_HASH_START) {
            piop = REFHVDM(hvdm,PIOP,apiop[port]);
            em86AssertP(IOP, piop);
#ifndef VDHSTRICT
            if(piop != &iopIgnore) {
#endif
                em86AssertDestroy(IOP, piop);
                VMFreeKHB(VM_HKH_PUB_SWAPRW,piop);
#ifndef VDHSTRICT
            }
#endif
            REFHVDM(hvdm,PIOP,apiop[port]) = &iopDefault;
        }
        else {
            ppphaPrev = &REFHVDM(hvdm,PPHA,appha[port & PORT_HASH_MASK]);
            pphaCurr = *ppphaPrev;
            while(pphaCurr != NULL) {
                em86AssertP(PHA, pphaCurr);
                em86AssertP(IOP, &pphaCurr->pha_iop);
                if(pphaCurr->pha_port == port) {
                    em86AssertDestroy(PHA, pphaCurr);
                    em86AssertDestroy(IOP, &pphaCurr->pha_iop);
                    *ppphaPrev = pphaCurr->pha_ppha;    // remove PHA entry
                    VMFreeKHB(VM_HKH_PUB_SWAPRW,pphaCurr);
                    break;
                }
                ppphaPrev = &pphaCurr->pha_ppha;        // next PHA entry
                pphaCurr = *ppphaPrev;
            }
        }
    }
}

#pragma END_MVDM_SWAP_CODE
#pragma code_seg("SWAP32_19","CODE")
/***EP+ VDHSetIOHookState - Enables/Disables the I/O port trapping
 *
 *      Enable/Disable I/O port trapping for a range of ports.
 *
 *      When trapping is enabled, the I/O port hooks in the specified
 *      range get control when a VDM does I/O to that range.
 *
 *      When trapping is disabled, VDM I/O goes directly to the physical
 *      hardware ports.
 *
 *      None of the ports in the range may have been marked as
 *      VDHIIH_ALWAYS_TRAP or VDHIIH_IGNORE by VDHInstallIOHook.
 *
 *      ENTRY
 *          hvdm - VDM handle
 *                      0 => current VDM
 *          portVDD  - Starting port number
 *          cportVDD - Number of ports in the range
 *          piohVDD  - Pointer IOH used to install I/O hooks
 *                         This is used to verify that the calling VDD
 *                         installed the I/O hooks.
 *          fEnable  - Enable/Disable flag
 *              TRUE => enable I/O hooks
 *              FALSE => disable I/O hooks
 *
 *      EXIT-SUCCESS
 *          None.
 *
 *      EXIT-FAILURE
 *          System Halt.
 *              If hvdm is invalid, piohVDD is invalid, portVDD or
 *              cportVDD are out of range, or portVDD and cportVDD
 *              overlap a range of ports previously marked as
 *              VDHIIH_ALWAYS_TRAP or VDHIIH_IGNORE by VDHInstallIOHook.
 *
 *      VDM TERMINATION IMPLICATIONS
 *          None.
 *
 *      CONTEXT
 *          Task
 *
 *      NOTES
 *          (1) If the piohVDD is in instance data, the address passed
 *              to VDHInstallIOHook must the same address passed to
 *              VDHRemoveIOHook or VDHSetIOHookState i.e. not hvdm relative
 *              in one case and 0-relative (current VDM) in another.
 *
 *      PSEUDO-CODE
 *
 *      validate parameters
 *      verify calling VDD installed the hooks
 *      call em86SetIOHookState
 */

VOID VDHENTRY VDHSetIOHookState(hvdm,portVDD,cportVDD,piohVDD,fEnable)
HVDM hvdm;
PORT portVDD;
ULONG cportVDD;
PIOH piohVDD;
BOOL fEnable;
/*end*/
{
    // validate calling context and parameters
    ASSERTTASKONLY;
#ifdef MMIOPH
    MMPHPreVDHSetIOHookState()
#endif
    CHECKHVDM(hvdm);
#ifdef VDHSTRICT
    if((portVDD > MAX_PORT_NUM) || (cportVDD > MAX_PORT_NUM) ||
      (portVDD + cportVDD > MAX_PORT_NUM + 1) || (piohVDD == NULL)) {
        em86InvalidParameter(("VDHSetIOHookState: invalid port, cport, pioh"));
    }
    em86EachIOPort(hvdm,portVDD,cportVDD,piohVDD,em86ValidateSet);
    if((int)(sizeof(TSS) +
             ((portVDD + cportVDD - 1) + (NBITS_BYTE - 1))/NBITS_BYTE +
             ((REFHVDM(hvdm,ULONG,flVdmStatus) & VDM_STATUS_VME) ?      //BN013
              (NUM_INTS/8):0)) >                                        //BN013
        REFHVDM(hvdm,int,cbtss)) {
        em86InternalError(("VDHSetIOHookState: IOPM not large enough"));
    }
#endif
    em86SetIOHookState(hvdm,portVDD,cportVDD,fEnable);
#ifdef MMIOPH
    MMPHPostVDHSetIOHookState()
#endif
}
#pragma code_seg()
#pragma BEGIN_MVDM_SWAP_CODE

#ifdef VDHSTRICT
/***LP  em86ValidateSet - worker that for VDHSetIOHookState
 *
 *      This function validates that each port requested is owned
 *      the calling VDD and optionally if it is not marked as ALWAYS_TRAP
 *      or IGNORE
 *
 *      ENTRY   port     - port number
 *              piop     - pointer to IOP
 *              portVDD  - starting port number
 *              cportVDD - number of ports in the range
 *              piohVDD  - pointer to VDD's ioh
 *
 *      EXIT    TRUE - continue em86EachIOPort
 *              FALSE - stop em86EachIOPort
 *
 *      CONTEXT Task
 *
 *      PSEUDO-CODE
 *
 *      if iop entry not owned by VDD
 *         invalid parameter
 *      else if iop entry was marked ALWAYS_TRAP or IGNORE
 *         shut down VDMM
 *      else
 *         continue em86EachIOPort
 */

static BOOL PASCAL
em86ValidateSet(port, piop, portVDD, cportVDD, piohVDD)
PORT port;
PIOP piop;
PORT portVDD;
ULONG cportVDD;
PIOH piohVDD;
{
    port;                     // eliminate compiler warnings
    portVDD;
    cportVDD;

    em86AssertP(IOP, piop);
    if(piop->iop_piohVDD != piohVDD) {
        em86InvalidParameter(("VDHSetIOHookState: invalid piohVDD"));
        return(FALSE);
    }
    if(piop->iop_flags & IOP_ALWAYS_TRAP) {
        em86InvalidParameter(("VDHSetIOHookState: port is ALWAYS_TRAP"));
        return(FALSE);
    }
    if(piop->iop_flags & IOP_IGNORE) {
        em86InvalidParameter(("VDHSetIOHookState: port is IGNORE"));
        return(FALSE);
    }
    return(TRUE);
}
#endif
#pragma END_MVDM_SWAP_CODE

#pragma code_seg("SWAP32_19","CODE")
/***LP  em86SetIOHookState - Enables/Disables the I/O port trapping
 *
 *      This is the worker routine for VDHSetIOHookState.
 *
 *      ENTRY
 *          hvdm - VDM handle
 *                      0 => current VDM
 *          portVDD  - Starting port number
 *          cportVDD - Number of ports in the range
 *          fEnable  - Enable/Disable flag
 *              TRUE => enable I/O hooks
 *              FALSE => disable I/O hooks
 *
 *      EXIT-SUCCESS
 *          None.
 *
 *      EXIT-FAILURE
 *          None.
 *
 *      VDM TERMINATION IMPLICATIONS
 *          None.
 *
 *      CONTEXT
 *          Task
 *
 *      NOTES
 *
 *      PSEUDO-CODE
 *
 *      validate parameters
 *      for each port in range
 *          if not using default IOP or ignore IOP
 *              if enable trapping
 *                  set up trap iop
 *              else
 *                  set real I/O byte handlers and simulate the rest
 *      calculate the index into IOPM from starting port number
 *      for the number of ports specified
 *          if exceeded size of IOPM, stop loop
 *          clear/set the bits in the IOPM based
 */

VOID PASCAL
em86SetIOHookState(hvdm,portVDD,cportVDD,fEnable)
HVDM hvdm;
PORT portVDD;
ULONG cportVDD;
BOOL fEnable;
{
    PPHA ppha;
    PORT port;
    ULONG cport;
    PVOID piopmEnd;                             // pointer to end of iopm
    register PULONG pul;                        // pointer into IOPM
    register ULONG ulMask;                      // mask for cur port in IOPM
    register PIOP piop;

    if(portVDD < 3) {                           // if less than 3
        port = 0;                               //  start at 0
        cport = cportVDD + portVDD;
    }
    else {
        port = portVDD - 3;                     // else start 3 ports before
        cport = cportVDD + 3;
    }
    for(; cport > 0; port++, cport--) {
        if(port < PORT_HASH_START) {
            piop = REFHVDM(hvdm,PIOP,apiop[port]);
            em86AssertP(IOP, piop);
        }
        else {
            ppha = REFHVDM(hvdm,PPHA,appha[port & PORT_HASH_MASK]);
            piop = &iopDefault;
            while(ppha != NULL) {               // search hash table list
                em86AssertP(PHA, ppha);
                em86AssertP(IOP, &ppha->pha_iop);
                if(ppha->pha_port == port) {
                    piop = &ppha->pha_iop;
                    break;
                }
                ppha = ppha->pha_ppha;          // next PHA entry
            }
        }
        if((piop != &iopDefault) && !(piop->iop_flags & VDHIIH_IGNORE)) {
            if(port >= portVDD) {
                em86SetIOP(piop, fEnable);
            }
            else {
                if(fEnable) {
                    em86SetIOP(piop, piop->iop_flags & IOP_TRAP_ENABLED);
                }
                else {
                    if(piop->iop_iohVDD.ioh_pothOther == NULL) {
                        piop->iop_ioh.ioh_pothOther = em86SimOtherIO;
                    }
                    else {
                        if(piop->iop_flags & IOP_ASM_HOOK) {
                            piop->iop_ioh.ioh_pothOther = em86SimDwordIO;
                        }
                        else {
                            piop->iop_ioh.ioh_pothOther = em86CSimDwordIO;
                        }
                    }
                    if(port >= portVDD - 1) {
                        piop->iop_ioh.ioh_pwihWordInput = em86SimWordInput;
                        piop->iop_ioh.ioh_pwohWordOutput = em86SimWordOutput;
                    }
                }
            }
        }
    }
    piopmEnd = REFHVDM(hvdm,PBYTE,ptss) + REFHVDM(hvdm,int,cbtss);
    pul = (PULONG) ((PBYTE)REFHVDM(hvdm,PTSS,ptss) + sizeof(TSS)
      + (portVDD/NBITS_ULONG) * sizeof(ULONG));
    if (REFHVDM(hvdm,ULONG,flVdmStatus) & VDM_STATUS_VME) {             //BN013
        (PBYTE)pul += NUM_INTS/8;                                       //BN013
    }                                                                   //BN013
    ulMask = EXP2(portVDD % NBITS_ULONG);
    for(cport = cportVDD; cport > 0; cport--) {
        if(pul >= piopmEnd) {           // end of iopm?
            break;                      //  yes, break
        }
        if(fEnable) {
            *pul |= ulMask;             // set IOPM bit
        }
        else {
            *pul &= ~ulMask;            // clear IOPM bit
        }
        ulMask <<= 1;                   // to next I/O port
        if(ulMask == 0) {               // need to step to next ULONG
            ++pul;
            ulMask = 1;
        }
    }
}

#pragma code_seg()
#pragma BEGIN_MVDM_SWAP_CODE

/***LP  em86SetIOP - set up IOP
 *
 *      Sets the main IOH in the IOP for trapping enabled or disabled.
 *
 *      ENTRY   piop      = pointer to iop to set
 *              fEnable   = enable/disable flag
 *                  TRUE  => enable I/O hooks
 *                  FALSE => disable I/O hooks
 *
 *      EXIT    NONE
 *
 *      CONTEXT Task
 *
 *      PSEUDO-CODE
 *
 *      if trapping enabled
 *          if assembly hook interface
 *              copy the VDD's IOH (iop_iohVDD) to the main IOH (iop_ioh)
 *          else
 *              copy the assembly to C hook stubs to the main IOH (iop_ioh)
 *          set the NULL entries in IOP's IOH to the simulation routines
 *          if always simulate dword I/O
 *              if assembly hook interface
 *                  set other handler to dword simulation routine
 *              else
 *                  set other handler to "C" dword simulation routine
 *      else // trapping disabled
 *          set real I/O byte handlers
 *          set word and other simulate handlers
 */

static VOID PASCAL
em86SetIOP(piop, fEnable)
register PIOP piop;
BOOL fEnable;
{
    em86AssertP(IOP, piop);

    if(piop->iop_flags & IOP_IGNORE) {
        em86InvalidParameter(("VDHSetIOHookState: port is IGNORE"));
    }

    if(fEnable) {
        piop->iop_flags |= IOP_TRAP_ENABLED;

        // Set the main ioh according to the hook type
        if(piop->iop_flags & IOP_ASM_HOOK) {    // if assembly hooks
            piop->iop_ioh = piop->iop_iohVDD;   // copy VDD's ioh to iop's ioh
        }
        else {
            piop->iop_ioh = iohCHookStubs;      // copy the "C" hook stubs
        }

        // Adjust the ioh for the NULL entries
        if(piop->iop_iohVDD.ioh_pbihByteInput == NULL) {
            piop->iop_ioh.ioh_pbihByteInput = em86ByteInput;
        }
        if(piop->iop_iohVDD.ioh_pbohByteOutput == NULL) {
            piop->iop_ioh.ioh_pbohByteOutput = em86ByteOutput;
        }
        if(piop->iop_iohVDD.ioh_pwihWordInput == NULL) {
            piop->iop_ioh.ioh_pwihWordInput = em86SimWordInput;
        }
        if(piop->iop_iohVDD.ioh_pwohWordOutput == NULL) {
            piop->iop_ioh.ioh_pwohWordOutput = em86SimWordOutput;
        }
        if(piop->iop_iohVDD.ioh_pothOther == NULL) {
            piop->iop_ioh.ioh_pothOther = em86SimOtherIO;
        }
        else {
            if(piop->iop_flags & IOP_SIMULATE_DWORD) {  // if always sim dword
                if(piop->iop_flags & IOP_ASM_HOOK) {    // if assembly hooks
                    piop->iop_ioh.ioh_pothOther = em86SimDwordIO;
                }
                else {
                    piop->iop_ioh.ioh_pothOther = em86CSimDwordIO;
                }
            }
        }
    }
    else {
        piop->iop_flags &= ~IOP_TRAP_ENABLED;
        piop->iop_ioh.ioh_pbihByteInput = em86ByteInput;
        piop->iop_ioh.ioh_pbohByteOutput = em86ByteOutput;
        piop->iop_ioh.ioh_pwihWordInput = em86SimWordInput;
        piop->iop_ioh.ioh_pwohWordOutput = em86SimWordOutput;
        piop->iop_ioh.ioh_pothOther = em86SimOtherIO;
   }
}

#ifdef VDHSTRICT
/***LP  em86EachIOPort - for each port requested, do pfn
 *
 *      This function calls the routine supplied for each
 *      port requested.
 *
 *      ENTRY   hvdm     - VDM handle
 *                     0 => current VDM
 *              portVDD  - starting port number
 *              cportVDD - number of ports in the range
 *              piohVDD  - pointer to ioh from VDD
 *              pfneachioport - function to call
 *                  fneachioport(port, piop, portVDD, cportVDD, piohVDD)
 *                      ENTRY   port     - current port number
 *                              piop     - pointer to piop (NULL if none)
 *                              portVDD  - starting port number
 *                              cportVDD - number of ports in range
 *                              piohVDD  - pointer to ioh from VDD
 *
 *                      EXIT    TRUE to continue
 *                              FALSE to stop
 *
 *      EXIT    if return TRUE, iterated thru all ports
 *              if return FALSE, stopped
 *
 *      CONTEXT Task
 *
 *      PSEUDO-CODE
 *
 *      for each port
 *          if port not hashed
 *              piop = apiop[port]
 *              if piop is iopDefault
 *                 piop = NULL
 *          else
 *              hash port number
 *              search list of pha's
 *              if found pha for port number
 *                 piop is in pha
 *                 break
 *          call pfn(port, piop)
 */

static BOOL PASCAL
em86EachIOPort(hvdm, portVDD, cportVDD, piohVDD, pfneachioport)
HVDM hvdm;
PORT portVDD;
ULONG cportVDD;
PIOH piohVDD;
PFNEACHIOPORT pfneachioport;
{
    PIOP piop;
    PPHA ppha;
    PORT port;
    ULONG cport;

    for(port = portVDD, cport = cportVDD; cport > 0; port++, cport--) {
        if(port < PORT_HASH_START) {
            piop = REFHVDM(hvdm,PIOP,apiop[port]);
            em86AssertP(IOP, piop);
            if(piop == &iopDefault) {
               piop = NULL;
            }
        }
        else {
            ppha = REFHVDM(hvdm,PPHA,appha[port & PORT_HASH_MASK]);
            piop = NULL;
            while(ppha != NULL) {               // search hash table list
                em86AssertP(PHA, ppha);
                em86AssertP(IOP, &ppha->pha_iop);
                if(ppha->pha_port == port) {
                    piop = &ppha->pha_iop;
                    break;
                }
                ppha = ppha->pha_ppha;          // next PHA entry
            }
        }
        if(!(*pfneachioport)(port, piop, portVDD, cportVDD, piohVDD)) {
            return(FALSE);
        }
    }
    return(TRUE);
}
#endif
#pragma END_MVDM_SWAP_CODE
#pragma code_seg("SWAP32_23","CODE")
/***EP+ VDHInstallIntHook - Hook an interrupt
 *
 *      Sets a handler for a V86 interrupt.  The VDD handler gets
 *      control after any subsequently hooked DOS interrupt handlers.
 *      The effect of this service is per-VDM; the software interrupt
 *      handler must be installed as each VDM is created
 *
 *      The return value from the int hook controls whether the next
 *      VDD is chained to or not.  An explicit VDHPopInt is needed
 *      to eat the software interrupt (so that the ROM code isn't
 *      executed).
 *
 *      ENTRY
 *          hvdm - VDM handle
 *              0 => current VDM
 *          iVector - interrupt vector number (0 - 255)
 *          pfnhook - hook routine address
 *              BOOL fnhook(pcrf)
 *                  ENTRY   EBX (pcrf) - client register rrame pointer
 *
 *                  EXIT    if carry set (C function returns FALSE),
 *                              chain to next VDD
 *                          if carry clear (C function returns TRUE),
 *                              don't chain to the next VDD
 *
 *                  USES    EAX, ECX, EDX, FS, GS, Flags
 *
 *                  CONTEXT VDM Task
 *
 *          flags - options
 *                  VDH_ASM_HOOK -
 *                      If set, assembly language hook interface.
 *                      Otherwise "C" (Pascal calling convention) interface.
 *                  VDH_PRE_HOOK -
 *                      If set, install handler as a pre-reflection hook
 *
 *      EXIT-SUCCESS
 *          returns TRUE
 *
 *      EXIT-FAILURE
 *          returns FALSE
 *                  ERROR_NOT_ENOUGH_MEMORY
 *          System Halt.
 *              If hvdm is invalid, or pfnhook is invalid, or iVector
 *              is out of range.
 *
 *      VDM TERMINATION IMPLICATIONS
 *          None.  Int hooks are automaticly removed during VDM termination.
 *
 *      CONTEXT
 *          Task
 *
 *      PSEUDO-CODE
 *
 *      validate parameters
 *      allocate interrupt handler entry (INTH)
 *      if this is the first entry
 *          get address for interrupt from interrupt vector table
 *          allocate a hook block with KMAllocHook
 *          set pointer to pinthHead
 *          allocate a VDM BP with em86AllocVDMBP
 *          put VDM BP address into interrupt vector table (IVT) entry
 *      put handler address in entry
 *      add entry to head of hook list for the interrupt number (apinthHead)
 */

BOOL VDHENTRY VDHInstallIntHook(hvdm,iVector,pfnhook,flags)
HVDM hvdm;
ULONG iVector;
PFNHOOK pfnhook;
FLAGS flags;
/*end*/
{
    register PINTH pinthNew;            // pointer to new INTH entry
    PVPVOID pvp;                        // interrupt vector address
    PHOOK phook;
    VPVOID vp;
    register PINTH *ppinthHead;         // pointer to INTH linked list head
    ULONG flIntFlags;
    PBYTE PSoftwareIntBitMap;           //77070

    // Validate calling context
    ASSERTTASKONLY;
#ifdef MMIOPH
    MMPHPreVDHInstallIntHook()
#endif
    CHECKHVDM(hvdm);
#ifdef VDHSTRICT
    if(iVector > NUM_INTS || pfnhook == NULL) {
        em86InvalidParameter(("VDHInstallIntHook: invalid iVector, pfnhook"));
    }
#endif
    ppinthHead = &REFHVDM(hvdm,PINTH,apinthHead[iVector]);
    flIntFlags = GETBITSP(*ppinthHead);
    if((pinthNew = em86salloc(sizeof(INTH))) == NULL) {
        VDHSetError(ERROR_NOT_ENOUGH_MEMORY);
#ifdef MMIOPH
        MMPHPostVDHInstallIntHook()
#endif
        return FALSE;
    }
    _assert(!GETBITSP(pinthNew));
    em86AssertCreate(INTH, pinthNew);
    (PFNHOOK)pinthNew->inth_pfn = pinthNew->inth_pfnhook = pfnhook;

    if (flags & VDH_PRE_HOOK) {
        flIntFlags |= INT_PRE_HOOK;

        /*
         * Disable S/W interrupt redirection for this interrupt ie
         * set the corresponding bit in the bitmap
         */
        if (REFHVDM(hvdm,ULONG,flVdmStatus) & VDM_STATUS_VME) {         //BN013
             PSoftwareIntBitMap=(PBYTE)REFHVDM(hvdm,ULONG,pswIntBitmap); //77070
             PSoftwareIntBitMap[iVector>>3] |= (1<<(iVector & 0x7));    //77070
        }                                                               //BN013
        if (flags & VDH_ASM_HOOK)
            pinthNew->inth_pfnhook = (PFNHOOK)(-1);
        else
            pinthNew->inth_pfn = em86CHookStubPreINTH;
    }
    else {
        if (!(flIntFlags & INT_BP_HOOK)) {
            if(KMAllocHook(em86IntHook, -1, sizeof(VDMBP) - sizeof(HOOK),
              sizeof(PINTH *), QUERYPTCB(hvdm), SSToDS(&phook)) != NO_ERROR) {
                em86AssertDestroy(INTH, pinthNew);
                VMFreeKHB(VM_HKH_PUB_SWAPRW,pinthNew);
                VDHSetError(ERROR_NOT_ENOUGH_MEMORY);
#ifdef MMIOPH
                MMPHPostVDHInstallIntHook()
#endif
                return FALSE;
            }
            pvp = (PVPVOID) ((PBYTE) hvdm + (iVector * sizeof(ULONG)));
            *((PINTH **) KMQueryHookData(phook)) = ppinthHead;
            if((vp = em86AllocVDMBP(phook, hvdm, *pvp)) == NULL) {
                em86AssertDestroy(INTH, pinthNew);
                VMFreeKHB(VM_HKH_PUB_SWAPRW,pinthNew);
                VDHSetError(ERROR_NOT_ENOUGH_MEMORY);
#ifdef MMIOPH
                MMPHPostVDHInstallIntHook()
#endif
                return FALSE;
            }
            *pvp = vp;
            flIntFlags |= INT_BP_HOOK;
        }
        if (flags & VDH_ASM_HOOK)
            pinthNew->inth_pfnhook = NULL;
        else
            pinthNew->inth_pfn = em86CHookStubINTH;
    }
    pinthNew->inth_pinth = ALIGNP(*ppinthHead);
    *ppinthHead = SETBITSP(pinthNew, flIntFlags);
#ifdef MMIOPH
    MMPHPostVDHInstallIntHook()
#endif
    return TRUE;
}


#pragma code_seg("SWAP32_20","CODE")

/***EP+ VDHAllocHook - allocate a hook
 *
 *      Allocates a hook handle for the various arm hook services.
 *
 *      ENTRY
 *          ulType
 *              hook type
 *                  VDH_CONTEXT_HOOK   (VDHArmContextHook)
 *                  VDH_STI_HOOK       (VDHArmSTIHook)
 *                  VDH_RETURN_HOOK    (VDHArmReturnHook)
 *                  VDH_TIMER_HOOK     (VDHArmTimerHook)
 *                  VDH_BP_HOOK        (VDHArmBPHook)
 *          pfnarm
 *              arm hook function
 *          cbRefData
 *              size of reference data
 *
 *      EXIT-SUCCESS
 *          hhook
 *              hook handle used in the arm services
 *
 *      EXIT-FAILURE
 *          Returns NULL,
 *              If out of memory.
 *              VDHGetError may return:
 *                  ERROR_NOT_ENOUGH_MEMORY
 *          System Halt,
 *              If ulType or pfnarm is invalid.
 *
 *      VDM TERMINATION IMPLICATIONS
 *          During initialization time, hooks allocated by VDDs are global,
 *          i.e., are not associated with a VDM process.  These hooks are not
 *          automaticly freed when a VDM terminates.
 *
 *          During VDM creation time, or thereafter, hooks allocated by VDDs
 *          are associated with the current VDM, and are freed when the
 *          VDM terminates.
 *
 *      CONTEXT
 *          Init
 *             VDH_RETURN_HOOK and VDH_BP_HOOK require VDM Task
 *          VDM Task
 *
 *      PSEUDO-CODE
 *
 *      if(init time)
 *          ptcb = KM_GLOBAL_HOOK
 *      else
 *          ptcb = pTCBCur
 *      allocate hook block(pfnarm, cbRefData, ptcb, &hhook)
 *      return hhook
 */

HHOOK VDHENTRY VDHAllocHook(ulType,pfnarm,cbRefData)
ULONG ulType;
PFNARM pfnarm;
ULONG cbRefData;
/*end*/
{
    HHOOK hhook;

    // validate calling context and parameters
    ASSERTTASKONLY;
#ifdef MMIOPH
    MMPHPreVDHAllocHook()
#endif
#ifdef VDHSTRICT
    if(ulType >= MAX_HOOK_TYPE || pfnarm == NULL) {
        em86InvalidParameter(("VDHAllocHook: invalid ulType or pfnarm"));
    }
#endif
    if(KMAllocHook(pfnarm, (BYTE)ulType, acbHook[ulType], cbRefData,
      (ISINITTIME ? KM_GLOBAL_HOOK : pTCBCur), SSToDS(&hhook)) != NO_ERROR) {
        VDHSetError(ERROR_NOT_ENOUGH_MEMORY);
#ifdef MMIOPH
        MMPHPostVDHAllocHook()
#endif
        return(NULL);
    }
    switch(ulType) {
        case VDH_BP_HOOK:
        case VDH_RETURN_HOOK:
            ASSERTVDMTASKONLY;
            ((PRETH)hhook)->reth_vpvdmbp = em86AllocVDMBP(hhook,CURRENT_VDM,0);
            if(((PRETH)hhook)->reth_vpvdmbp == NULL) {
                KMFreeHook(hhook);
                VDHSetError(ERROR_NOT_ENOUGH_MEMORY);
#ifdef MMIOPH
                MMPHPostVDHAllocHook()
#endif
                return(NULL);
            }
            em86AssertCreate(RETH, (PRETH)hhook);
            break;
#ifdef EM86STRICT
        case VDH_TIMER_HOOK:
            em86AssertCreate(TIMH, (PTIMH)hhook);
            break;
#endif
    }
#ifdef MMIOPH
    MMPHPostVDHAllocHook()
#endif
    return(hhook);
}

#pragma code_seg("SWAP32_20","CODE")
/***EP+ VDHFreeHook - free a hook
 *
 *      Disarm and free a hook.
 *
 *      ENTRY
 *          hhook
 *              hook handle
 *
 *      EXIT-SUCCESS
 *          None.
 *
 *      EXIT-FAILURE
 *          System Halt.
 *              If hhook is invalid.
 *
 *      VDM TERMINATION IMPLICATIONS
 *          Any context hooks that were allocated in the context of the
 *          terminating VDM are deallocated.
 *
 *      CONTEXT
 *          Init
 *          VDM Task
 *
 *      PSEUDO-CODE
 *
 *      if a return hook
 *          free VDM BP
 *      free hook block
 */

VOID VDHENTRY VDHFreeHook(hhook)
HHOOK hhook;
/*end*/
{
    // validate calling context
    ASSERTTASKONLY;
#ifdef MMIOPH
    MMPHPreVDHFreeHook()
#endif
    em86AssertP(HOOK, (PHOOK)hhook);
    switch(KMQueryHookType(hhook)) {
        case VDH_BP_HOOK:
        case VDH_RETURN_HOOK:
            em86AssertP(RETH, (PRETH)hhook);
            em86AssertP(VDMBP, (PVDMBP)hhook);
            // free the VDM breakpoint
            em86FreeVDMBP(((PRETH) hhook)->reth_vpvdmbp);
            em86AssertDestroy(RETH, (PRETH)hhook);
            em86AssertDestroy(VDMBP, (PVDMBP)hhook);
            break;
        case VDH_TIMER_HOOK:
            VDHDisarmTimerHook(hhook);
            em86AssertDestroy(TIMH, (PTIMH)hhook);
            break;
    }
    KMFreeHook(hhook);
#ifdef MMIOPH
    MMPHPostVDHFreeHook()
#endif
}

#pragma code_seg()

#pragma BEGIN_MVDM_GLOBAL_CODE

/***EP+ VDHQueryHookData - get a pointer to a hook's reference data
 *
 *      Returns a pointer to the reference data allocated with the
 *      hook during the VDHAllocHook call.
 *
 *      ENTRY
 *          hhook
 *              hook handle
 *
 *      EXIT-SUCCESS
 *          pRefData - pointer to reference data block
 *
 *
 *      EXIT-FAILURE
 *          System Halt.
 *              If hhook is invalid or there is no reference
 *              data (cbRefData == 0).
 *
 *      VDM TERMINATION IMPLICATIONS
 *          None.
 *
 *      CONTEXT
 *          Init
 *          Task
 *          Interrupt
 *
 *      PSEUDO-CODE
 *
 *      return pointer to hook specific data
 */

PVOID VDHENTRY VDHQueryHookData(hhook)
HHOOK hhook;
/*end*/
{
#ifdef MMIOPH
    MMPHPreVDHQueryHookData(hhook)
#endif
    em86AssertP(HOOK, (PHOOK)hhook);
#ifdef MMIOPH
    MMPHPostVDHQueryHookData(KMQueryHookData((PHOOK)hhook))
#endif
    return(KMQueryHookData((PHOOK)hhook));
}

/***EP+ VDHArmContextHook - Set global or local context hook
 *
 *      This service adds a handler to the list of routines called either
 *      the next time "task time" is entered (global context) or the next
 *      time a specific VDM "task time" is reached (local context).  This
 *      is a one-shot service; the handler is removed from the list after
 *      it is executed.
 *
 *      After calling this routine, the caller is guaranteed that the
 *      context hook will be executed before respectively: 1) any
 *      user-space code (global context), or 2) any v86 code in the
 *      specified VDM (local context).
 *
 *      ENTRY
 *          hhook - hook handle allocated with VDHAllocHook
 *          hvdm - VDM handle
 *                      -1 => global context
 *      EXIT-SUCCESS
 *          None.
 *
 *      EXIT-FAILURE
 *          System Halt.
 *              If hvdm or hhook is invalid, or the hook is already armed.
 *
 *      VDM TERMINATION IMPLICATIONS
 *          Any context hooks allocated in the context of the terminating
 *          VDM are deallocated.
 *
 *      CONTEXT
 *          Task
 *          Interrupt
 *
 *      NOTES
 *          Hook routine interface:
 *              VOID fnarm(pRefData, pcrf)
 *                  ENTRY   (pRefData) - pointer to reference data
 *                          (pcrf) - pointer to client register frame
 *
 *                  EXIT    NONE
 *
 *                  USES    EAX, ECX, EDX, FS, GS, Flags
 *
 *                  CONTEXT Task (global) or VDM Task (local)
 *
 *                  "pcrf" is valid only for local context hook handlers.
 *
 *      PSEUDO-CODE
 *
 *      validate parameters
 *      convert hvdm to ptcb
 *      arm context hook
 */

VOID VDHENTRY VDHArmContextHook(hhook,hvdm)
HHOOK hhook;
HVDM hvdm;
/*end*/
{
    register PTCB ptcb = GLOBAL_CONTEXT_HOOK;

#ifdef MMIOPH
    MMPHPreVDHArmContextHook(hhook,hvdm)
#endif
    em86AssertP(HOOK, (PHOOK)hhook);
    VDHAssertTRUE(KMQueryHookType(hhook) == VDH_CONTEXT_HOOK);
    if(hvdm != GLOBAL_CONTEXT_HOOK) {
        CHECKHVDM(hvdm);
        ptcb = QUERYPTCB(hvdm);
    }
    if(KMArmContextHook(hhook, ptcb)) {
        em86InternalError(("VDHArmContextHook: already armed"));
    }
#ifdef MMIOPH
   MMPHPostVDHArmContextHook()
#endif
}

/***EP+ VDHArmSTIHook - Execute a routine when interrupts enabled
 *
 *      Sets a handler to receive control when the current VDM
 *      enables interrupts.  This handler is called only once.  If
 *      interrupts are already enabled in the VDM, the handler is
 *      called immediately.
 *
 *      The detection of reenabling of interrupts is done by causing
 *      the VDM to run with IOPL == 0, which causes any instructions
 *      which might modify the interrupt flag to fault.
 *
 *      ENTRY
 *          hhook - hook handle allocated with VDHAllocHook
 *          hvdm - VDM handle
 *
 *      EXIT-SUCCESS
 *          None.
 *
 *      EXIT-FAILURE
 *          System Halt.
 *              If hvdm or hhook is invalid, or if this hook is already
 *              armed.
 *
 *      VDM TERMINATION IMPLICATIONS
 *          Any STI hooks that were allocated in the context of the
 *          terminating VDM are deallocated.
 *
 *      CONTEXT
 *          Task
 *
 *      NOTES
 *          Hook routine interface:
 *              VOID fnarm(pRefData, pcrf)
 *                  ENTRY   (pRefData) = pointer to reference data
 *                          (pcrf) - pointer to client register frame
 *
 *                  EXIT    NONE
 *
 *                  USES    EAX, ECX, EDX, FS, GS, Flags
 *
 *                  CONTEXT VDM Task
 *
 *      PSEUDO-CODE
 *
 *      validate parameters
 *      if the VDM's interrupts are enabled
 *          arm context hook
 *      else
 *          arm sti hook
 *          change the VDM's IOPL to 0 to start emulated IF instructions
 */

VOID VDHENTRY VDHArmSTIHook(hhook,hvdm)
HHOOK hhook;
register HVDM hvdm;
/*end*/
{
    register PVSF pvsf;                 // pointer to client stack frame
    register PTCB ptcb;

    // Validate calling context and parameters
    ASSERTTASKONLY;
#ifdef MMIOPH
    MMPHPreVDHArmSTIHook(hhook,hvdm)
#endif
    CHECKHVDM(hvdm);
    em86AssertP(HOOK, (PHOOK) hhook);
    VDHAssertTRUE(KMQueryHookType(hhook) == VDH_STI_HOOK);
    ptcb = QUERYPTCB(hvdm);
    pvsf = REFHVDM(hvdm,PVSF,pvsfFrameBase);
    em86AssertVSF(ptcb);

    // We must always use the HVDM relative address for hookheadSTIH
    hvdm = REFHVDM(hvdm, HVDM, vdmid_HVDM);
    if(KMArmHook(hhook, &REFHVDM(hvdm,HOOKHEAD,hookheadSTIH))) {
        em86InternalError(("VDHArmSTIHook: already armed"));
    }
    // When the IOPL3 option bit is set, we leave the VDM at IOPL3 and
    // try to catch the next opportunity when we're exiting kernel mode;
    // the option is for apps like F15 which depend on IOPL-sensitive
    // instructions executing out of the prefetch queue...
    _disable();                                                         //BN007

    /*
     * In P5 if the VIF is enabled force an STI hook dispatching otherwise
     * set VIP bit to enable STI, POPF and IRET emulation
     */
    if (REFHVDM(hvdm,ULONG,flVdmStatus) & VDM_STATUS_VME) {             //BN013
        if (VSF_EFLAG(pvsf) & F_VIF) {                                  //BN013
            ptcb->TCBForcedActions |= TK_FF_STIH;    // set STI flag    //BN013 ce flag
        } else {                                                        //BN013
            ptcb->TCBForcedActions &= ~TK_FF_STIH;   // clear STI flag  //BN013
            VSF_EFLAG(pvsf) |= F_VIP; // start IF instruction emulation //BN013
        }                                                               //BN013
    } else {                                                            //BN013
        if(
          //  REFHVDM(hvdm, ULONG, vdmid_flVDM) & VDM_IOPL3 ||     // 6.7
           ((VSF_EFLAG(pvsf) & F_INTERRUPT) &&
            (!(REFHVDM(hvdm,ULONG,flVdmStatus) & VDM_STATUS_VPM_EXEC) ||
             (REFHVDM(hvdm,ULONG,flVdmStatus) & VDM_STATUS_VPM_IF_FLAG)))) {
            ptcb->TCBForcedActions |= TK_FF_STIH;   // set STI hook force flag
        } else {
// 6.10  Removed assertion and cleared ForceAction if state has changed
//      // Assert that no other STI hooks have triggered
//          em86AssertFALSE(ptcb->TCBForcedActions & TK_FF_STIH);
            ptcb->TCBForcedActions &= ~TK_FF_STIH;   // turn off tasking hook
            VSF_EFLAG(pvsf) &= ~F_IOPLMASK; // start IF instruction emulation
        }
    }
    _enable();                                                          //BN007
#ifdef MMIOPH
    MMPHPostVDHArmSTIHook()
#endif
}

#pragma END_MVDM_GLOBAL_CODE


#pragma code_seg("SWAP32_18","CODE")
/***EP+ VDHArmReturnHook - Set a IRET or RETF hook
 *
 *      Sets a function to receive control on an IRET or RETF executed
 *      in V86 mode (or when a VDHPopInt is executed).
 *
 *      VDHARH_NORMAL_IRET and VDHARH_RECURSIVE_IRET return hook types may
 *      be made ONLY in the following contexts:
 *
 *      1)  Within a software interrupt handler set up by VDHInstallIntHook.
 *      2)  After VDHPushInt has been called (but before returning
 *          to V86 mode).
 *
 *      VDHARH_NORMAL_RET and VDHARH_RECURSIVE_RET return hook types may
 *      be made ONLY in the following contexts:
 *
 *      1)  After VDHPushFarCall has been called (but before returning
 *          to V86 mode).
 *
 *      VDHARH_CSEIP_HOOK and VDHARH_RECURSIVE_CSEIP_HOOK are meant to be
 *      used before VDHPushInt/VDHPushFarCall.  It is more efficient to
 *      do this type of return hook first and then a VDHPushInt or a
 *      VDHPushFarCall instead the other way around using the other type
 *      of return hooks.
 *
 *      ENTRY
 *          hhook
 *              hook handle allocated with VDHAllocHook
 *          ulType
 *              return hook type
 *                  VDHARH_NORMAL_IRET
 *                  VDHARH_NORMAL_RET
 *                  VDHARH_RECURSIVE_IRET
 *                  VDHARH_RECURSIVE_RET
 *                  VDHARH_CSEIP_HOOK
 *                  VDHARH_RECURSIVE_CSEIP_HOOK
 *                  VDHARH_ALWAYS_ARM_HOOK
 *
 *      EXIT-SUCCESS
 *          Returns TRUE
 *
 *      EXIT-FAILURE
 *          If hhook is invalid
 *              System Halt
 *          if the VDHARH_NORMAL_* type return hook is already armed
 *              Returns FALSE
 *
 *      VDM TERMINATION IMPLICATIONS
 *          Any return hooks that were allocated in the context of the
 *          terminating VDM are deallocated.
 *
 *      CONTEXT
 *          VDM Task
 *
 *      NOTES
 *          1) Hook routine interface:
 *              VOID armh(pRefData, pcrf)
 *                  ENTRY   (pRefData) - pointer to reference data
 *                          (pcrf) - pointer to client register frame
 *
 *                  EXIT    NONE
 *
 *                  USES    EAX, ECX, EDX, FS, GS, Flags
 *
 *                  CONTEXT VDM Task
 *
 *          2) The VDHARH_RECURSIVE* types push two extra words on the
 *             client's stack before the IRET or RET frame.
 *
 *          3) Only the normal and recursive CS:EIP hooks will work in
 *             VDM protected mode.
 *
 *          4) If the VDHARH_ALWAYS_ARM_HOOK flag is specified, the hook will
 *             ALWAYS be (re)armed, regardless of its current state.
 *
 *      PSEUDO-CODE
 *
 *      validate parameters
 *      get the VDM BP from return hook block
 *      switch (ulType)
 *          VDHARH_NORMAL_IRET
 *          VDHARH_NORMAL_RET
 *              pop the CS:IP on the top of the client's stack
 *              save CS:IP away in VDMBP structure
 *              push the VDMBP back on the client's stack
 *              break
 *          VDHARH_RECURSIVE_IRET
 *              pop the iret off of the client's stack
 *              push the old CS:IP from the iret frame on the client's stack
 *              change the CS:IP in the iret frame to the VDMBP
 *              push the iret frame back on the client's stack
 *              set recursive return hook dispatcher
 *              break
 *          VDHARH_RECURSIVE_RET
 *              push the VDMBP on the client's stack
 *              set recursive return hook dispatcher
 *              break
 *          VDHARH_CSEIP_HOOK:
 *              save current CS:IP in reth block
 *              set CS:IP to VDMBP address
 *              break
 *          VDHARH_RECURSIVE_CSEIP_HOOK:
 *              save the CS:IP on the client's stack
 *              set CS:IP to VDMBP address
 *              set recursive return hook dispatcher
 *              break
 */

BOOL VDHENTRY VDHArmReturnHook(hhook,ulType)
HHOOK hhook;
ULONG ulType;
/*end*/
{
    register PVSF pvsf;
    register PVDMBP pvdmbp = hhook;
    register PRETH preth = hhook;
    IRET16 iret16;
    VPVOID vpOldCSIP;

    // Validate calling context and parameters
    ASSERTVDMTASKONLY;
#ifdef MMIOPH
    MMPHPreVDHArmReturnHook(hhook,ulType)
#endif
    em86AssertP(HOOK, (PHOOK)hhook);
    em86AssertP(VDMBP, pvdmbp);
    em86AssertP(RETH, preth);
    VDHAssertTRUE(KMQueryHookType(hhook) == VDH_RETURN_HOOK);
    em86AssertVSF(pTCBCur);
    pvsf = pvsfFrameBase;
    pvdmbp->vdmbp_pfnDispatch = (PFN)em86VDMBPDefaultDispatcher;
    switch(ulType & ~VDHARH_ALWAYS_ARM_HOOK) {                          //BC005
        case VDHARH_NORMAL_IRET:        // FALSE
        case VDHARH_NORMAL_RET:
//BD003 #ifdef VDHSTRICT
//BD002     VDHAssertFALSE(KMIsHookLinked((PHOOK)hhook));
            if (KMIsHookLinked((PHOOK)hhook) &&                         //BN002
                !(ulType & VDHARH_ALWAYS_ARM_HOOK)) {                   //BN005
#ifdef MMIOPH
               MMPHPostVDHArmReturnHook(FALSE)
#endif
               return FALSE;                                            //BN002
            }                                                           //BN002
            KMInitHookHead((PHOOKHEAD)hhook);
//BD003 #endif
            VDHPopStack(sizeof(VPVOID), &pvdmbp->vdmbp_vpOldCSIP);
            VDHPushStack(sizeof(VPVOID), &preth->reth_vpvdmbp);
            break;
        case VDHARH_RECURSIVE_IRET:
            VDHPopStack(sizeof(IRET16), SSToDS(&iret16));
            VDHPushStack(sizeof(VPVOID), SSToDS(&iret16.iret16_ip));
            iret16.iret16_ip = OFFSETOF16(preth->reth_vpvdmbp);
            iret16.iret16_cs = SEGMENTOF16(preth->reth_vpvdmbp);
            VDHPushStack(sizeof(IRET16), SSToDS(&iret16));
            pvdmbp->vdmbp_pfnDispatch = (PFN)em86VDMBPRecursiveDispatcher;
            break;
        case VDHARH_RECURSIVE_RET:
            VDHPushStack(sizeof(VPVOID), &preth->reth_vpvdmbp);
            pvdmbp->vdmbp_pfnDispatch = (PFN)em86VDMBPRecursiveDispatcher;
            break;
        case VDHARH_CSEIP_HOOK:
//BD003 #ifdef VDHSTRICT
//BD002     VDHAssertFALSE(KMIsHookLinked((PHOOK)hhook));
            if (KMIsHookLinked((PHOOK)hhook) &&                         //BN002
                !(ulType & VDHARH_ALWAYS_ARM_HOOK)) {                   //BN005
#ifdef MMIOPH
               MMPHPostVDHArmReturnHook(FALSE)
#endif
               return FALSE;                                            //BN002
            }                                                           //BN002
            KMInitHookHead((PHOOKHEAD)hhook);
//BD003 #endif
            if(flVdmStatus & VDM_STATUS_VPM_EXEC) {
                em86AssertTRUE(flVdmStatus & VDM_STATUS_VPM_APP);
                OFFSETOF32(pvdmbp->vdmbp_fpfnOldCSEIP) = VSF_EIP(pvsf);
                SEGMENTOF32(pvdmbp->vdmbp_fpfnOldCSEIP) = VSF_CS(pvsf);
                VSF_CS(pvsf) = selVPMBP;
                VSF_EIP(pvsf) = (ULONG)((SEGMENTOF16(preth->reth_vpvdmbp) -
                  SEGMENTOF16(vpVdmBpArpl)) + VPM_VDMBP_START);
                pvdmbp->vdmbp_pfnDispatch = (PFN)em86VDMBPDefaultDispatcher32;
            }
            else {
                OFFSETOF16(pvdmbp->vdmbp_vpOldCSIP) = VSF_IP(pvsf);
                SEGMENTOF16(pvdmbp->vdmbp_vpOldCSIP) = VSF_CS(pvsf);
                VSF_IP(pvsf) = OFFSETOF16(preth->reth_vpvdmbp);
                VSF_CS(pvsf) = SEGMENTOF16(preth->reth_vpvdmbp);
            }
            break;
        case VDHARH_RECURSIVE_CSEIP_HOOK:
            if(flVdmStatus & VDM_STATUS_VPM_EXEC) {
                em86AssertTRUE(flVdmStatus & VDM_STATUS_VPM_APP);
                // assumes eip, cs, padcs are together
                VDHPushStack(sizeof(ULONG) * 2, &VSF_EIP(pvsf));
                pvdmbp->vdmbp_pfnDispatch = (PFN)em86VDMBPRecursiveDispatcher;
                VSF_CS(pvsf) = selVPMBP;
                VSF_EIP(pvsf) = (ULONG)((SEGMENTOF16(preth->reth_vpvdmbp) -
                  SEGMENTOF16(vpVdmBpArpl)) + VPM_VDMBP_START);
            }
            else {
                OFFSETOF16(vpOldCSIP) = VSF_IP(pvsf);
                SEGMENTOF16(vpOldCSIP) = VSF_CS(pvsf);
                VDHPushStack(sizeof(VPVOID), SSToDS(&vpOldCSIP));
                VSF_IP(pvsf) = OFFSETOF16(preth->reth_vpvdmbp);
                VSF_CS(pvsf) = SEGMENTOF16(preth->reth_vpvdmbp);
                pvdmbp->vdmbp_pfnDispatch = (PFN)em86VDMBPRecursiveDispatcher;
            }
            break;
        default:
            em86InternalError(("VDHArmReturnHook: invalid ulType"));
            break;
    }
#ifdef MMIOPH
    MMPHPostVDHArmReturnHook(TRUE)
#endif
    return TRUE;
}
#pragma code_seg()
#pragma code_seg("SWAP32_23","CODE")

/***EP+ VDHArmBPHook - Obtain a v86 breakpoint
 *
 *      Obtains the address of the V86 breakpoint allocated by
 *      a previous call to VDHAllocHook with the VDH_BP_HOOK flag.
 *      Unlike other allocated/armed hooks, breakpoint hooks are
 *      armed until explicitly disarmed by a call to VDHFreeHook.
 *
 *      When the V86 breakpoint address is called from V86 mode,
 *      the hook function is called.  Any changes made to the client
 *      register frame cause the corresponding registers to take on the
 *      values upon return to V86 mode.  The PVOID parameter points to
 *      the cbRefData bytes of data allocated by VDHAllocHook.
 *
 *      The VDHArmBPHook service provides a "raw" breakpoint service.
 *      Upon entry to the arm hook function, the client's CS:IP
 *      is randomly set; hence, it is crucial that the VDD's hook handler
 *      set the CS:IP to a valid address.  Typically, the CS:IP is retrieved
 *      from the client's stack (via VDHPopStack).
 *
 *      This is a low-level service.  Appropriate care should be
 *      exercised to ensure that the client's CS:IP and stack
 *      remain valid.
 *
 *      ENTRY
 *          hhook
 *              hook handle allocated with VDHAllocHook
 *
 *      EXIT-SUCCESS
 *          vpvoid
 *              v86 breakpoint address
 *
 *      EXIT-FAILURE
 *          System Halt.
 *              If hhook is invalid
 *
 *      CONTEXT
 *          VDM Task
 *
 *      NOTES
 *          1) Hook routine interface:
 *              VOID HOOKENTRY arm(p, pcrf)
 *                  ENTRY  p - pointer to hook-specific reference data
 *                         pcrf - pointer to client register frame
 *
 *                  EXIT   none.
 *
 *                  the function must use Pascal calling conventions.
 *
 *                  CONTEXT VDM Task
 *
 *      PSEUDO-CODE
 *
 *      validate calling context and parameters
 *      get breakpoint address from the hook
 */

VPVOID VDHENTRY VDHArmBPHook(hhook)
HHOOK hhook;
/*end*/
{
    // Validate calling context and parameters
    ASSERTVDMTASKONLY;
    em86AssertP(HOOK, (PHOOK)hhook);
    em86AssertP(VDMBP, (PVDMBP)hhook);
    em86AssertP(RETH, (PRETH)hhook);
    VDHAssertTRUE(KMQueryHookType(hhook) == VDH_BP_HOOK);
    return(((PRETH)hhook)->reth_vpvdmbp);
}
#pragma code_seg()
#pragma code_seg("SWAP32_24","CODE")
/***EP+ VDHPushFarCall - Call specified V86 or VPM code
 *
 *      Simulate far call to V86 code, as if a DOS program had
 *      executed a far call instruction.
 *
 *      In most cases, VDHArmReturnHook should be called after
 *      this call.  This allows the VDD to regain control when the
 *      called V86 code executes its RETF.
 *
 *      ENTRY
 *          fpfn - v86-mode: seg:off address of v86-mode code to call.
 *               - VPM-mode: sel:off address of VPM-mode code to call.
 *
 *      EXIT-SUCCESS
 *          Returns TRUE
 *
 *      EXIT-FAILURE
 *          Returns FALSE, and an exception will be simulated to the VDM.
 *
 *      VDM TERMINATION IMPLICATIONS
 *          None.
 *
 *      NOTES
 *          (1) This call does not take effect until OS/2 returns to V86
 *              mode.
 *
 *      CONTEXT
 *          VDM Task
 *
 *      PSEUDO-CODE
 *
 *      put the client's CS:IP in the dword on the client's stack
 *      change the client's CS:IP on the kernel stack to the address passed
 */

BOOL VDHENTRY VDHPushFarCall(fpfn)
FPFN fpfn;
/*end*/
{
    register PVSF pvsf;
    register int rc;

    // validate calling context
    ASSERTVDMTASKONLY;
#ifdef MMIOPH
    MMPHPreVDHPushFarCall(fpfn)
#endif
    pvsf = pvsfFrameBase;
    em86AssertVSF(pTCBCur);

    // Is this a 32 bit DPMI application
    if ((flVdmStatus & VDM_STATUS_VPM_EXEC) &&
        (flVdmStatus & VDM_STATUS_VPM_32)) {
       RETF32 retf32;

       retf32.retf32_cs = VSF_CS(pvsf);
       retf32.retf32_eip = VSF_EIP(pvsf);

       // Use VDHPushStack to push the data onto the stack, if there isn't
       // enough room and the app is in VPM mode, VDHPushStack will raise
       // an exception into the VDM, if it is a V86 app, the stack will wrap
       // and no exception will occur.
       if (rc = VDHPushStack(sizeof(RETF32), SSToDS(&retf32))) {
          VSF_CS(pvsf) = SEGMENTOF32(fpfn);
          VSF_EIP(pvsf) = OFFSETOF32(fpfn);
       }

    // Must be a 16 bit DPMI or DOS app
    } else {
       RETF16 retf16;

       retf16.retf16_cs = VSF_CS(pvsf);
       retf16.retf16_ip = VSF_IP(pvsf);

       // Use VDHPushStack to push the data onto the stack, if there isn't
       // enough room and the app is in VPM mode, VDHPushStack will raise
       // an exception into the VDM, if it is a V86 app, the stack will wrap
       // and no exception will occur.
       if (rc = VDHPushStack(sizeof(RETF16), SSToDS(&retf16))) {
          VSF_CS(pvsf) = SEGMENTOF32(fpfn);
          VSF_IP(pvsf) = WORDOF(fpfn,0);
       }
    }
#ifdef MMIOPH
    MMPHPostVDHPushFarCall(rc)
#endif
    return(rc);
}
#pragma code_seg()
#pragma code_seg("SWAP32_23","CODE")
/***EP+ VDHRegisterAPI - Register a VDD's API handlers                  //BN010
 *
 *      Sets either a V86 mode or protect mode (or both) API handler for
 *      a particular VDD.  Each VDD must register with a unique VDD device
 *      identifier.  This service provides a mechanism for DOS application
 *      to directly communicate with the VDD.
 *
 *      ENTRY
 *          pszName    - Name of the VDD; maximum length is MAXLENXDD.
 *          pfnV86Hook - V86 mode hook routine address (or NULL if no V86 mode
 *                       handler is needed).
 *          pfnVPMHook - VPM mode hook routine address (or NULL if no VPM mode
 *                       handler is needed).
 *
 *      EXIT-SUCCESS
 *          returns TRUE
 *
 *      EXIT-FAILURE
 *          returns FALSE
 *                out of memory or VDD name already registered
 *
 *      VDM TERMINATION IMPLICATIONS
 *          None.  The hook(s) will be freed when the VDM is terminated.
 *
 *      CONTEXT
 *          Task
 *
 *      NOTES
 *          1) Hook routine interface:
 *              VOID HOOKENTRY pfnhook(p, pcrf)
 *                  ENTRY  PVOID p    - reserved
 *                         PCRF  pcrf - pointer to client register frame
 *
 *                  EXIT    none
 *
 *                  USES    EAX, ECX, EDX, FS, GS, Flags
 *
 *                  CONTEXT VDM Task
 *
 *          2) V86/VPM mode calling procedure (int 0x2f)
 *
 *              Get OS/2 version:
 *
 *                DOS apps can use this to determine if they are running under
 *                OS/2 or not.
 *
 *                Entry:
 *                 AX = 4010
 *                Exit
 *                 If OS/2 is present
 *                  AX = 0
 *                  BX = OS/2 version number (ex: BX=1400 for OS/2 2.0)
 *                 If not running OS/2, AX will be unchanged
 *
 *             Get VDD API Entry point:
 *
 *                DOS apps can use this to get the address to call to invoke
 *                the VDD entry points.  If the int 2f is issued in V86 mode,
 *                a V86 mode breakpoint that invokes the V86 VDD API handler
 *                will be returned, if issued in VPM mode, a protected mode
 *                breakpoint that corresponds to the VDD VPM API handler will
 *                be returned.
 *
 *              Entry
 *               AX=4011
 *               DS:(E)SI=pointer to asciiz name of VDD register with
 *                        VDHRegisterAPI
 *
 *              Exit:
 *               If VDD API handler exists:
 *                ES:DI = breakpoint address
 *               otherwise:
 *                ES:DI = 0
 *
 *      PSEUDO-CODE
 *          if vdd already registered
 *             return FALSE
 *          if pfnV86Hook
 *             allocate V86 mode hook
 *             arm the hook
 *             add hook to hook list
 *          if pfnVPMHook
 *             allocate VPM mode hook
 *             add hook to VPM hook list
 *
 */

BOOL VDHENTRY VDHRegisterAPI(pszName,pfnV86Hook,pfnVPMHook)             //BN010
PSZ     pszName;
PFNHOOK pfnV86Hook;
PFNHOOK pfnVPMHook;
{
   PVDDAPI pvddapi;

#ifdef  VDHSTRICT
    // Validate string length
    if (strlen(pszName) > MAXLENXDD)
        Panic(("VDHRegisterAPI: Invalid VDD Name length"));
#endif

   // Look for VDD in VDD API list to see if it is already registered
   pvddapi = pVAPIList;
   while (pvddapi) {
      if (vdmmStrcmpi(pvddapi->vapi_szVDD,pszName)) {
         return(FALSE);
      } else {
         pvddapi = pvddapi->vapi_next;
      }
   }

   // VDD not in list, allocate new VDD API entry and put in list
   if (VMAllocKHB(VM_HKH_PUB_SWAPRW, sizeof(VDDAPI),
      (VMHOB)EM86MALLOCOWNER, NA, SSToDS(&pvddapi)) != NO_ERROR) {
      return(FALSE);
   }

   // Initialize the host data block
   memset(pvddapi, 0, sizeof(VDDAPI));
   em86AssertCreate(VDDAPI,pvddapi);            // initialize signature

   // Add entry to front of list
   pvddapi->vapi_next = pVAPIList;
   pVAPIList = pvddapi;

   // Set VDD API ID
   strcpy(pvddapi->vapi_szVDD,pszName);

   // If they passed in a V86 API handler, allocate a BP hook
   if (pfnV86Hook) {
      pvddapi->vapi_hhookV86 = VDHAllocHook(VDH_BP_HOOK,
                                            (PFNARM)pfnV86Hook,
                                            0);
      pvddapi->vapi_vpV86BP = VDHArmBPHook(pvddapi->vapi_hhookV86);
   }

   // If they passed in a VPM API handler, allocate a BP hook
   if (pfnVPMHook) {
      pvddapi->vapi_hhookVPM = VDHAllocHook(VDH_BP_HOOK,
                                            (PFNARM)pfnVPMHook,
                                            0);
   }

   return(TRUE);
}
/*end*/
#pragma code_seg()
#pragma BEGIN_MVDM_SWAP_CODE
/***LP  em86InvalidOpcode - invalid opcode int hook
 *
 *      If a VDM application doesn't hook trap 6 (invalid opcode) control
 *      comes here and an exception popup is displayed and the VDM is
 *      terminated.
 *
 *      ENTRY   pcrf
 *                  client register frame pointer
 *
 *      EXIT    if carry set (C function returns FALSE),
 *                  chain to next VDD
 *              if carry clear (C function returns TRUE),
 *                  don't chain to the next VDD
 *
 *      USES    EAX, ECX, EDX, FS, GS, Flags
 *
 *      CONTEXT VDM Task
 *
 *      PSEUDO-CODE
 *
 *      pop the iret frame
 *      change the trap number to invalid opcode
 *      call trap manager to display exception popup
 *      return don't chain indication
 */

static BOOL HOOKENTRY
em86InvalidOpcode(pcrf)
PCRF pcrf;
{
     VDHPopInt();
     TRAPProcessFatalFault(EXCOP, ((PVSF)pcrf)->vsf_errcode);
     return(TRUE);
}

/***LP  em86AllocVDMBP - Allocate a VDM breakpoint
 *
 *      This routine returns an address of a VDM breakpoint.  When
 *      the instruction at the VDM breakpoint address is executed
 *      in V86 mode by a VDM control is transfer to the handler
 *      specified.
 *
 *      ENTRY   phook - hook block pointer
 *              hvdm - VDM handle
 *                  0 => current VDM
 *              vp - CS:IP used to replace the VDM BP address on the
 *                       return stack frame when the VDM BP is hit.
 *
 *      EXIT    if success
 *                      VDM breakpoint segment:offset
 *              if out of VDM BPs
 *                      NULL
 *
 *      CONTEXT Initialization
 *              Task
 *
 *      PSEUDO-CODE
 *
 *      put vp into hook block
 *      get free VDM breakpoint table entry (apvdmbp)
 *      put address of VDM breakpoint block into table entry
 *      add VDM breakpoint table index to segment portion of ARPL address
 *      subtract index * 16 from offset portion
 *      return VDM BP segment:offset
 */

static VPVOID PASCAL
em86AllocVDMBP(phook, hvdm, vp)
PHOOK phook;
register HVDM hvdm;
VPVOID vp;
{
    register PVDMBP pvdmbp = (PVDMBP)phook;
    register int i;

    em86AssertP(HOOK, phook);
    em86AssertCreate(VDMBP, pvdmbp);
    pvdmbp->vdmbp_vpOldCSIP = vp;
    pvdmbp->vdmbp_pfnDispatch = (PFN)em86VDMBPDefaultDispatcher;

    /* get free VDM breakpoint table entry */
    i = REFHVDM(hvdm,int,ivdmbpFreeListHead);
    if(i >= REFHVDM(hvdm,int,cbvdmbp)) {
        return(NULL);
    }
    REFHVDM(hvdm,int,ivdmbpFreeListHead) = REFHVDM(hvdm,int,apvdmbp[i]);
    REFHVDM(hvdm,PVDMBP,apvdmbp[i]) = pvdmbp;

    /* calculate and return the VDM BP */
    SEGMENTOF16(vp) = SEGMENTOF16(REFHVDM(hvdm,VPVOID,vpVdmBpArpl))+(WORD)i;
    OFFSETOF16(vp) = OFFSETOF16(REFHVDM(hvdm,VPVOID,vpVdmBpArpl))-(WORD)(i*16);
    return(vp);
}

/***LP  em86FreeVDMBP - Free a VDM breakpoint
 *
 *      Frees a VDM BP from the hook block pointer passed.
 *
 *      ENTRY   vp - VDM BP
 *
 *      EXIT    NONE
 *
 *      CONTEXT VDM Task
 *
 *      PSEUDO-CODE
 *
 *      get index into vdmbp table from vdm bp address
 *      add vdm bp table entry to free list
 */

static VOID PASCAL
em86FreeVDMBP(vp)
PHOOK vp;
{
    register int i;

    em86AssertTRUE(((SEGMENTOF16(vp)*16)+OFFSETOF16(vp)) == (int) pvdmArpl);
    i = SEGMENTOF16(vp) - SEGMENTOF16(vpVdmBpArpl);
    em86AssertTRUE((ULONG)apvdmbp[i] > (ULONG)cbvdmbp); // assert not free
    (int) apvdmbp[i] = ivdmbpFreeListHead;
    ivdmbpFreeListHead = i;
}

/***LP  em86salloc - allocate swappable memory from system heap
 *
 *      This is a private version of malloc built out of the VM's
 *      alloc function that calls VDHSetError if an out of memory
 *      error is encountered.
 *
 *      ENTRY   cb - number of bytes to allocate
 *
 *      EXIT-SUCCESS
 *          PVOID - pointer to allocated memory
 *      EXIT-FAILURE
 *          returns NULL
 *              VDHGetError may return:
 *                  ERROR_NOT_ENOUGH_MEMORY
 *
 *      CONTEXT Task
 *
 *      PSEUDO-CODE
 *
 *      if allocKSHB() fails
 *          return error
 */

static PVOID PASCAL
em86salloc(cb)
int cb;
{
    PVOID p;

    if(VMAllocKHB(VM_HKH_PUB_SWAPRW, cb, (VMHOB)EM86MALLOCOWNER, NA,
                  (PVOID *)SSToDS(&p)) != NO_ERROR) {
        VDHSetError(ERROR_NOT_ENOUGH_MEMORY);
        return(NULL);
    }
    return(p);
}
#pragma END_MVDM_SWAP_CODE
