;       SCCSID = @(#)npx.asm    6.15 92/02/28
NAME NPX
TITLE NPX.ASM - NPX Support Routines
PAGE 58,132

SCCSID  equ     "@(#)npx.asm    6.15 92/02/28"

CpuMode 386
.387

; ****************************************************************************
; *                                                                          *
; *                       IBM/Microsoft Confidential                         *
; *                                                                          *
; *                 Copyright (c) IBM Corporation  1987, 1990                *
; *                 Copyright (c) Microsoft Corp.  1987, 1990                *
; *                           All Rights Reserved                            *
; *                                                                          *
; ****************************************************************************

;**************************************************************************
;
;  COMPONENT NAME: NPX
;
;  SOURCEFILE NAME: NPX.ASM
;
;  DESCRIPTIVE NAME: NPX MANAGER
;
;  STATUS: RELEASE 2 LEVEL 0
;
;  FUNCTION: This file contains support routines for the NPX coprocessor.
;       Functions of these routines include:
;               NPX Initialization
;               NPX Context Switching
;               NPX Mode Switching
;               NPX Error Handling
;               NPX Wait to Complete Processing
;               NPX MVDM Support Routines
;
;  NOTES:
;       - There is no operationally difference between the 80386/80387 and
;         the 80486 (which has a built in NPX).  Thus all references to
;         387 should be interpreted to mean the 387 and the 486 NPX.
;
;       - 80287 is not supported.
;
;       - Many errata exist on the 80386 and 80387. The ones of interest
;         are listed and explained:
;
;         MACHINE:  IBM PS/2 early model 80s (and model 50 and 60's)
;         PROBLEM:  The busy latch imbedded in the DMA chip (specifically
;                   version 4 of the DMA chip) will inadvertantly generate
;                   an NPX interrupt request for every NPX instruction
;                   executed while an NPX error is pending.
;         RAMIFICATIONS: Every NPX instruction executed from the time the NPX
;                   initially signals its exception until the exception is
;                   cleared will cause an NPX interrupt request to be generated.
;                   Specifically, a single NPX exception will cause multiple
;                   interrupts.
;         SOLUTION: For protect mode: clear the NPX error force flag after
;                   the exception has been cleared. For real mode: single
;                   step each NPX instruction until the exception has been
;                   cleared, however this is impossible with MVDM.
;
;
;  ENTRY POINT NAMES:
;       NPX16_Interrupt         - NPX Interrupt Handler Thunk
;       InitNPXEM               - Initialize NPX Floating Point Emulator
;       InitNPX                 - Initialize NPX environment
;       _NPX_Con_Busy           - Inform BMP whether a block is free/inuse
;       SwitchNPX               - Switch the NPX context between threads
;       SaveNPX                 - Save the NPX context
;       WaitNPX                 - Wait for the NPX to complete processing
;       _ReleaseNPX             - Give up ownership of the NPX
;       NPX_Interrupt_Handler   - NPX Interrupt Handler
;       NPX_Error_Handler       - NPX Error Handler
;       NPXRegisterVDD          - Register VNPX entry points w/ VDM Mgr
;       NPXClearBusy            - VNPX Clear busy latch
;       NPXReset                - VNPX Reset NPX
;       _NPXReadContext         - Read a thread's NPX context
;       _NPXWriteContext        - Write a thread's NPX context
;
;
;  EXTERNAL REFERENCES:
;       DosHlpClrBusyNPX        - Dos Help Clear Busy Latch
;       DosHlpInitNPX           - Dos Help Initialize NPX
;       DosHlpValidNPXSwitch    - Validate NPX switch state
;       DosHlpWaitNPX           - Wait for NPX to complete processing
;       DosHlpAckIntNPX         - Acknowledge NPX interrupt
;       DosHlpVNPXClrBusy       - VNPX interface to clear NPX busy latch
;       DosHlpVNPXReset         - VNPX interface to reset NPX
;       TRAPCheckExceptHandler  - Check if 16-bit handler registered
;       TRAPArmExceptHandler    - Setup to deliver error to 16-bit handler
;       VNPXException           - MVDM NPX interrupt handler
;       INTSetIRQ               - Register the NPX interrupt handler
;       DelayHardErr            - Display hard error message
;       HexToAscii32            - Covert hex number to ascii string
;       KMPUSHCLIENTSTACK       - Push values on user stack
;       w_getmodhandle          - Get module handle
;       _LDRGetProcAddr         - Get procedure address
;       _BMPInit                - Initialize BMP object
;
;**************************************************************************

.XLIST
        include dosseg.inc
        include except.inc
        include osconst.inc
        include mi.inc
        include mhandle.inc
        include npx.inc
        include pmode.inc
        include struc.inc
        include harderr.inc
        include basemid.inc
        include doshlp.inc
        include int.inc
        include basemaca.inc
        include fnotask.inc
        include osmaca.inc
        include dossymc.inc
        include error.inc
        include pgexport.inc
        include vmexport.inc
        include tkexport.inc
        include proc.inc
        include infoseg.inc
        include sigdef.inc
        include selxport.inc
        include dirent.inc
        include curdir.inc
        include find.inc
        include screen.inc
        include sft.inc
        include sem.inc
        include tkptda.inc
        include dbgapi.inc
        include bsexcpt.inc
        include exmg.inc
        include kmode.inc
        include bsedosp.inc
.LIST

        EXTRN32         _VMAllocKHB
        EXTRN32         _VMFreeKHB
        EXTRN32         _XCPTBuildR3DispatcherStack
        EXTRN32         KMQUERYCLIENTREGISTER
        EXTRN32         _SELVirtToLin
        EXTRN32         _TKFuBuff
        EXTRN32         _SELGetDescInfo
        EXTRN32         KMALLOCCONTEXTHOOK
        EXTRN32         KMARMCONTEXTHOOK
        EXTRN32         UserPageFault
        EXTRN32         _TKScanTasks                                    ;b726833
        EXTRN           _BMPInit:NEAR
        EXTRN           _BMPGet:NEAR
        EXTRN           _BMPFree:NEAR
        EXTRN           TRAPCheckExceptHandler:NEAR
        EXTRN           TRAPArmExceptHandler:NEAR
        EXTRN           KMPUSHCLIENTSTACK:NEAR
        EXTRN           INTSetIRQ:NEAR
        EXTRN           HexToAscii32:NEAR
        EXTRN           _LDRGetProcAddr:NEAR
        EXTRN16         DelayHardErr
        EXTRN16         w_getmodhandle

        ReferGlobal     NPXNAVec,QWORD
        ReferGlobal     IDTTrap0E, QWORD
        ReferGlobal     DHF,DWORD
        ReferGlobal     TaskNumber, WORD
        ReferGlobal     _papTCBSlots, DWORD
        ReferGlobal     MaxThreads, WORD
        ReferGlobal     mathCop, BYTE
        ReferGlobal     _CpuStepping,DWORD
        ReferFlat       _pTCBCur, DWORD
        ReferFlat       _pTSDCur, DWORD
        ReferFlat       _pPTDACur,DWORD
        ReferFlat       NPX_IRQ, WORD
        ReferFlat       _pFPEMVMBH,DWORD
        ReferFlat       _R2CS,WORD
        ReferFlat       _R0CS,WORD
        ReferFlat       _clDescTaskCopy,DWORD
        ReferFlat       _clDescThreadCopy,DWORD
        ReferFlat       _clDescTIBOffset, DWORD
;       ReferInitGlobal SysInit_NPXType, BYTE

        PUBLIC          NPX_Owner
        PUBLIC          pemulahndlr
        PUBLIC          _FPEMDataSize
        PUBLIC          _FPEMHandler                                    ;b726833
        PUBLIC          FPEMHandler
        PUBLIC          _FPEMpfnHandler


        ModName npx, FNO_NPX_ASM

DATA segment

NPXMsg  label   word
        dw      MSG_NPXIEMSG            ; <NPX Error - Invalid Operation>
        dw      MSG_NPXDEMSG            ; <NPX Error - Denormalized Operand>
        dw      MSG_NPXZEMSG            ; <NPX Error - Zero Divide>
        dw      MSG_NPXOEMSG            ; <NPX Error - Overflow>
        dw      MSG_NPXUEMSG            ; <NPX Error - Underflow>
        dw      MSG_NPXPEMSG            ; <NPX Error - Precision>

Env             NPX_ENV <>              ; Buffer for NPX environment

Formatbuf       db      18 dup(?)       ; used to format the hex CS:IP

DATA ends


CONSTANTS SEGMENT

NPX_Owner       dw      0               ; Thread number of NPX owner

CONSTANTS ENDS


CpuMode 386


DATA    SEGMENT

; The following 2 variables hold the same value on all systems, so why do exist?
; When the 287 was supported they held different values based on the coprocessor
; type.  However, the real reason they remain is in the event that a new
; chip (the 586, 686, etc.) requires a different context buffer size, then
; this processor can easily be added.

NPX_CON_SIZE    dd      0               ; Size of NPX context buffer
NPX_TYPE        dw      0               ; Type of NPX (used by debugger)

DATA    ENDS


_DATA SEGMENT

PUBLIC  _fB1NPX, _pErrata17Hook

_fB1NPX         dd      0               ; 0 ==> Not using 387 w/ 386-B1.
_pErrata17Hook  dd      ?

_pfirstblock    dd      ?               ; bmpinit returns pointer to first block
_pcurrent_block dd      ?               ; object for NPX context buffers

pexcephndlr     dd      ?               ; pointer to VNPX exception handler
pemulahndlr     dd      ?               ; pointer to VNPX emulation handler

_FPEMDataSize   dd      ?               ; size of FP emulator data area
_FPEMHandler    label   dword           ; for c program reference       ;b726833
FPEMHandler     dd      ?               ; FP emulator handler address
_FPEMpfnHandler dd      ?               ; Offset of local handler pointer

FPToExcept      label   dword
        dd      0                               ; Invalid Operation bit
        dd      XCPT_FLOAT_DENORMAL_OPERAND     ; Denormal bit
        dd      XCPT_FLOAT_DIVIDE_BY_ZERO       ; Divide by Zero bit
        dd      XCPT_FLOAT_OVERFLOW             ; Overflow bit
        dd      XCPT_FLOAT_UNDERFLOW            ; Underflow bit
        dd      XCPT_FLOAT_INEXACT_RESULT       ; Inexact Result bit

_DATA ENDS


INITDATA segment

pbEmulatorName  db      "NPXEMLTR",0    ; NPX emulator name

INITDATA ends


CINIT_DATA segment

FPEMHandlerName  db     "NPXNPHandler",0        ; FP emulator entry point
FPEMDataSizeName db     "NPXFPDataSize",0       ; FP emulator data size
FPEMpfnHandlerName db   "NPXpfnLocalHandler",0  ; Pointer to local FPE entry

CINIT_DATA ends


CpuMode 386

        FARCODE HIGH3

HIGH3CODE SEGMENT


BREAK <NPX16_Interrupt>
;**************************************************************************
;***    NPX16_Interrupt  Thunk used to call the interrupt handler
;
;       This is a thunk which is used to call the 32 bit NPX
;       interrupt handler.
;
;       ENTRY POINT: NPX16_Interrupt
;
;       ENTRY:  NONE
;
;       EXIT:   NONE
;
;       USES:   NONE
;
;       INTERNAL REFERENCES:
;        ROUTINES: NPX_Interrupt_Handler
;
;       EXTERNAL REFERENCES: NONE
;
;**************************************************************************
Procedure NPX16_Interrupt,<faronly,far16,near>
        SaveReg <ds,es,fs,gs>
        FlatContext <ds,es>
        CALLF32    NPX_Interrupt_Handler         ; 32 bit handler
        RestoreReg <gs,fs,es,ds>
        RETP 2
EndProc NPX16_Interrupt

HIGH3CODE ENDS


        FARCODE HIGH32
CINIT_TEXT SEGMENT

BREAK <InitNPXEM>
;**************************************************************************
;***    InitNPXEM  Initialize floating point emulator
;
;       This routine initializes the system floating point emulator.
;       It first loads the floating point emulator DLL (NPXEMLTR.DLL) and
;       then it gets the address of the FP emulator routine and data size
;       variable which are located in NPXEMLTR.DLL. It then allocates
;       memory for the FP emulator context buffers. Finally it points the
;       NPX not present IDT entry directly to the FP emulator routine within
;       the DLL.
;
;
;       ENTRY POINT: InitNPXEM
;
;       ENTRY:  NONE
;
;       EXIT:   Carry clear
;
;       EXIT-ERROR: Carry set - insufficient memory
;
;       USES:   Flags
;
;       EXTERNAL REFERENCES:
;        ROUTINES:
;           w_getmodhandle          - Get module handle
;           _LDRGetProcAddr         - Get procedure address
;           _BMPInit                - Initialize BMP object
;
;**************************************************************************
Procedure InitNPXEM,far32
        ASSUME DS:FLAT, ES:NOTHING, SS:NOTHING

        pushad
        SaveReg <es>
        DosInitContext <es>,ax
        lea     di,es:pbEmulatorName    ; (ES:DI) = Emulator Name
        CALL16  w_getmodhandle
        RestoreReg <es>
        ASSUME ES:NOTHING
        .if <nonzero ax>
            Panic <'InitNPXEM: NPXEMLTR not found'>
        .endif

        movzx   ebx,bx                  ; (EBX) = Emulator hmte
        CCALL32 _LDRGetProcAddr,<ebx,0,<offset FLAT:FPEMHandlerName>, \
                                 <offset FLAT:FPEMHandler>, TRUE, 0>
        .if   <nonzero eax> near
            Panic <'InitNPXEM: NPXNPHandler not found'>
        .endif

        CCALL32 _LDRGetProcAddr,<ebx, 0, <offset FLAT:FPEMDataSizeName>, \
                                 <offset FLAT:_FPEMDataSize>, TRUE, 0>
        .if   <nonzero eax> near
            Panic <'InitNPXEM: FPEMDataSize not found'>
        .endif
        mov     _FPEMDataSize.HighWord,ax

        CCALL32 _LDRGetProcAddr,<ebx, 0, <offset FLAT:FPEMpfnHandlerName>, \
                                 <offset FLAT:_FPEMpfnHandler>, TRUE, 0>
        .if   <nonzero eax> near
            Panic <'InitNPXEM: FPEMpfnHandler not found'>
        .endif
        mov     _FPEMpfnHandler.HighWord,ax

        ; Initialize per thread FP emulator data areas

        movzx   eax,[MaxThreads]
        CCALL32 _BMPInit,<eax, [_FPEMDataSize], <PG_SWAPPABLE+PG_U>, \
                          NPXOWNER, 0, <offset FLAT:_pFPEMVMBH>>
        .if     <nonzero eax> near
            popad
            stc
            RETC                 ; return w carry set
        .endif

;**@xxxx        mov     ecx,[_pTCBCur]                  ; assign first buffer to
;**@xxxx        mov     eax,[_pFPEMVMBH]                ; current thread
;**@xxxx        mov     [ecx].TCB_NPX_Buffer,eax

        mov     ecx,[_pTCBCur]                  ; assign first buffer to ;@xxxx
        lea     ecx,[ecx].TCB_NPX_Buffer        ; current thread         ;@xxxx
        CCALL32 _BMPGet,<[_pFPEMVMBH], ecx>                              ;@xxxx

        ; Set the NPX Not Present IDT entry to point directly to the
        ; emulator entry point.

        push    ds
        DosContext ds,ax                        ; gain access to IDT
        mov     ax,[_R2CS]
        mov     [NPXNAVec].g_sel,ax             ; set sel. to flat R2 CS
        mov     [NPXNAVec].g_access,D_TRAP332   ; set access byte
        mov     eax,[FPEMHandler]
        mov     [NPXNAVec].g_lowoffset,ax       ; set low word of offset
        shr     eax,16                          ; (AX) = high word of EAX
        mov     [NPXNAVec].g_extoffset,ax       ; set high word of offset
        pop     ds
        ASSUME DS:FLAT

        add     [_clDescTaskCopy],2     ; copy FP desc during context switch
        add     [_clDescThreadCopy],2   ; copy FP desc during context switch
        sub     [_clDescTIBOffset],8    ; See pga.asm

                                                                        ;b726833
        CCALL32 _TKScanTasks,<<TK_TG_ALL>,0,<offset FLAT:NPXPtdaFixup>,0>

        popad
        clc                             ; no error
        RETC
EndProc InitNPXEM

;**     NPXPtdaFixup   - Worker routine for ScanTasks                   ;b726833
;                                                                       ;b726833
;       Fixup the PTDA NPX emulator fields for tasks started before NPX ;b726833
;       was initialized                                                 ;b726833
;                                                                       ;b726833
;       ENTRY   0       no data pointer passed                          ;b726833
;               pPTDA   pointer to PTDA                                 ;b726833
;                                                                       ;b726833
;       EXIT    eax = 0 scan all tasks                                  ;b726833
;                                                                       ;b726833
        ASSUME  DS:FLAT,ES:FLAT,SS:NOTHING                              ;b726833
                                                                        ;b726833
Procedure NPXPtdaFixup,near,local                                       ;b726833
ArgVar  pPTDA,DWORD                                                     ;b726833
ArgVar  pNull,Dword                                                     ;b726833
        EnterProc                                                       ;b726833
        SaveReg <ebx>                                                   ;b726833
                                                                        ;b726833
        mov     eax,pPTDA                                               ;b726833
        mov     bx, _R2CS                                               ;b726833
        mov     [eax].ptda_NPX_em_cs, bx                                ;b726833
        mov     ebx, FPEMHandler                                        ;b726833
        mov     [eax].ptda_NPX_em_eip, ebx                              ;b726833
                                                                        ;b726833
        xor     eax,eax                     ; keep scanning             ;b726833
        RestoreReg <ebx>                                                ;b726833
        LeaveProc                                                       ;b726833
        RETC                                                            ;b726833
                                                                        ;b726833
EndProc  NPXPtdaFixup                                                   ;b726833

BREAK <InitNPX>
;**************************************************************************
;***    InitNPX  Initialize the Numeric Processor Extension environment
;
;       This routine initializes both the hardware and the kernel to
;       handle a Numeric Coprocessor (NPX).  It checks to see if the
;       NPX is present.  If it is, the MP bit is set and the EM bit
;       is cleared in the MSW. If the NPX is not present, the MP is
;       cleared and EM is set. The TS bit is cleared in either case.
;       In addition, if an NPX is present, this routine allocates memory
;       for NPX context buffers.
;
;       NOTES:
;       This is a Protect Mode only procedure.
;
;       ENTRY POINT: InitNPX
;       LINKAGE: Call Near
;
;       ENTRY:  NONE
;
;       EXIT:   CARRY CLEAR
;
;       EXIT-ERROR: NONE
;
;       USES: CR0 and flags
;
;       INTERNAL REFERENCES: None
;
;       EXTERNAL REFERENCES:
;        ROUTINES:
;           DosHlpClrBusyNPX        - Dos Help Clear Busy Latch
;           DosHlpInitNPX           - Dos Help Initialize NPX
;           INTSetIRQ               - Register the NPX interrupt handler
;
;**************************************************************************

Procedure InitNPX,far32
        ASSUME DS:FLAT, ES:NOTHING, SS:NOTHING

        SaveReg <ebx, ecx, edx, esi>
        CCALL32 DHF.DosHlpClrBusyNPX

;       Start with a MSW that won't trap on NPX instructions that follow

        mov     ebx,cr0                 ; EBX <-- CR0
        or      ebx,CR0_MP+CR0_PE       ; set MP and PE
        and     ebx,NOT CR0_EM+CR0_TS   ; clear EM  and clear TS
        mov     cr0,ebx                 ; CR0 <-- EBX

;       Determine if there is an NPX in the System.  We set mathCop
;       ONLY if the NPX does exist on the machine.

        mov     eax, [_CpuStepping]
        CCALL32 DHF.DosHlpInitNPX         ; determines presence or absence
        mov     [mathCop],0               ; set mathCop to not present
        jnz     inx10                     ; if a npx exists
;       cmp     [_CpuStepping], CPU_386B1 ; if a B1 install emulator b722559/b732814
;       jz      inx10                     ; b722559/b732814
;       NPX is PRESENT so set mathCop, the NPX context buffer size and
;       NPX type. MSW is set correctly for NPX (MP & PM set, EM cleared)

        mov     [mathCop],NPX_PRESENT        ; indicate the NPX is present b722559/b732814
        mov     NPX_CON_SIZE,NPX_CONTEXT_387 ; store context size   b722559/b732814
        mov     NPX_TYPE,DBG_CO_387          ; NPX type is 387      b722559/b732814
        cmp     [_CpuStepping], CPU_386B1    ; b722559/b732814
        .if     <z>                          ; Enable 386/B1 errata fixes b722559/b732814
           inc   [_fB1NPX]                   ; b722559/b732814
        .endif                               ; b722559/b732814

;       Hook the NPX_INTERRUPT_HANDLER to IRQ 13

        mov    ax,offset HIGH3CODE:NPX16_Interrupt ; int. handler
        mov    cx,DOSHIGH3CODE        ; int handler's selector
        mov    bx,NPX_IRQ             ; irq level
        mov    dh,mask irqf_fShared+mask irqf_fNPX ; shared and NPX interrupt
        mov    si,ds                  ; data selector
        CCALL32 INTSetIRQ             ; set the interrupt handler
        .if <c>                       ; if carry set attempt not shared
            mov   ax,offset HIGH3CODE:NPX16_Interrupt ; int. handler
            mov   cx,DOSHIGH3CODE     ; int handler's selector
            mov   bx,NPX_IRQ          ; irq level
            mov   dh,mask irqf_fNPX   ; NPX interrupt
            mov   si,ds               ; data selector
            CCALL32 INTSetIRQ         ; set the interrupt handler
            .if <c>                   ; if carry set an error occurred
                Panic <"InitNPX: INTSetIRQ">
            .endif
        .endif
        jmp   short inx20

inx10:                                ; begin NPX not present or enabled

;       (EBX) = current contents of CR0
;       (AX)  = return code from DosHlpInitNPX

        and    ebx,NOT CR0_MP         ; make MSW reflect absence of NPX
        or     ebx,CR0_EM             ; clear MP; set EM.
        mov    cr0,ebx                ; CR0 <-- EBX
        .if <ax eq NPX_287_PRESENT>
          mov  ax, MSG_SYSINIT_287_DISABLED
          stc
        .else
          clc
        .endif

inx20:                                  ; end NPX not present or enabled

        RestoreReg <esi, edx, ecx, ebx>
        RETC
EndProc InitNPX

CINIT_TEXT ENDS

        FARCODE HIGH32

HIGH32CODE SEGMENT

BREAK   <SwitchNPX>
;**************************************************************************
;***    SwitchNPX        Context switch the NPX
;
;       This routine performs a Context Switch of the NPX.
;
;       NOTES: See notes for SaveNPX.
;
;       ENTRY POINT: SwitchNPX
;         LINKAGE: Call Near
;
;       ENTRY: ss = PTDA of current thread
;
;       EXIT: Carry Flag Clear
;
;       EXIT-ERROR: Carry Flag Set (Nothing done because EM was set.)
;
;       USES: flags
;
;       INTERNAL REFERENCES:
;        ROUTINES:
;           SaveNPX             - Save the NPX context
;           WaitNPX             - Wait for the NPX to complete processing
;
;       EXTERNAL REFERENCES:
;        ROUTINES:
;           _VMAllockhb         - Allocate memory buffer
;
;       ALGORITHM: (updated 10/6/89)
;
;       if emulating
;           return          /* with carry bit set */
;       else
;           if NPX Owner == Current Running Task then
;               return      /* with carry bit clear */
;
;           call SaveNPX to save current NPX context to NPX Owner's context buf
;
;           get pointer to TCB of requesting thread
;           if Requesting thread has no context to restore
;               get a context buffer (CAN TASK SWITCH!!)
;               mark context buffer busy
;               save pointer to context buffer in TCB
;               call SaveNPX to save current NPX context (COULD HAVE SWITCHED!!)
;               initialize the NPX (fninit)
;           else
;               restore state of the NPX from requesting thread's context buffer
;           endif
;           indicate ownership of NPX is requesting thread
;           return      /* with carry bit clear */
;       endif
;
;**************************************************************************

Procedure SwitchNPX,NEAR
        ASSUME  DS:FLAT, ES:NOTHING, SS:NOTHING

;       If emulating, set carry bit and return.

        push    eax
        smsw    ax
        test    ax,CR0_EM                   ; are we emulating ?
        pop     eax
        .if     <nz>
          stc                               ; - yes, set carry and return

        .else near                          ; - no, make thread owner of NPX

;         NPX is present.  Check to see if this thread currently owns the
;         NPX. If so, clear carry bit and return. Otherwise, switch the
;         NPX to the current thread.

          SaveReg <ecx>                     ; save register
          mov     cx,TaskNumber             ; get current thread number
          .if     <cx ne NPX_Owner> near    ; does current thread own NPX ?
            SaveReg <ds,eax,edi,ebp>        ; - no, save registers
            CCALL32 SaveNPX                 ; save the NPX context first
            clts                            ; hide TS from FNRSTOR
            assume  ss:TASKAREA             ; giving NPX to current thread
            mov     bp,ss:[TKOPTDA.CurrTCB]
            mov     edi,ss:[bp].TCB_NPX_Buffer
            or      edi,edi                 ; does thread have a context buf?
            .if z

;             Thread does not have a NPX context buffer; allocate one to thread
              SaveReg <ecx>
              mov     eax, NPX_CON_SIZE
              lea     ecx,[_pcurrent_block]

;             Allocate  heap buffer for current thread:
;             The buffer must be resident to prevent blocking during
;             critical section in SaveNPX
;
;             rc = VMAllocKHB(hkh, cb, hobOwner, fl, ppbData)

              CCALL32 _VMAllocKHB <VM_HKH_PUB_RESRW, eax, NPXOWNER, \
                    0, ecx>                 ; Allocate resident heap buffer
              or      eax,eax               ; test for success
              .if <nz>
                Panic <"SwitchNPX: Error allocating context buffer">
              .endif
;
;             Allocate a context hook for B1 Errata #17 use. It is put in
;             the TSDdescFPEM spot because we know the emulator wont use it.
;
              cmp   [_fB1NPX], 0            ; Can the errata occur?
              je    short snx10

              mov   eax, [_pTSDCur]
              lea   eax, [eax].TSDdescFPEM
              PCALL32 KMALLOCCONTEXTHOOK, <offset FLAT:B1NPX_Errata_17HookFn, 8, [_pTCBCur], eax>
ifdef NPXSTRICT
              or    eax, eax
              .if <nz>
                Panic <SwitchNPX: Error allocating context hook.">
              .endif
endif

snx10:        RestoreReg <ecx>

;             Save pointer to Context Buffer in TCB

              mov     eax,[_pcurrent_block]
              mov     ss:[bp].TCB_NPX_Buffer,eax
                                            ; save pointer to buffer
              CCALL32 SaveNPX               ; in case _BmpGet blocked
              clts                          ; in case _BmpGet blocked
              fninit                        ; reset the NPX
              fninit                        ; (2 inits needed for 387 errata)

            .else

;             Thread has a NPX context buffer; restore from it

              fnclex                        ; For B1 NPX Errata #3
              mov     [_pcurrent_block],edi
              fnrstor [edi]                 ; restore the NPX context

            .endif

;           Make current thread the owner of the NPX

            assume          ss:nothing
            CCALL32   WaitNPX               ; wait for NPX to go idle
            mov       NPX_Owner,cx          ; make current thread NPX owner
            RestoreReg <ebp,edi,eax,ds>     ; restore registers

          .endif

          RestoreReg <ecx>                  ; restore registers
          assume  ds:nothing
          clc                               ; no carry => all OK

        .endif

        clts                                ; CPU owner is now NPX owner
        RETC
EndProc SwitchNPX


BREAK   <SaveNPX>
;**************************************************************************
;***    SaveNPX        Save the NPX context
;
;       This routine saves the NPX context.
;
;       NOTES:
;
;       This procedure assumes that WaitNPX has already been called.
;       That is one of the reasons for calling WaitNPX from SchedNext
;       before switching threads.
;
;       This procedure is NOT re-enterent.  Therefore, we require
;       that no procedures called while saving the NPX context of
;       the descending thread block since blocking would allow this
;       procedure to be re-entered.
;
;       ENTRY POINT: SaveNPX
;       LINKAGE: Call Near
;
;       ENTRY:
;           EM bit is off
;           NPX_Owner = thread number of thread which owns the NPX
;
;       EXIT:
;           NPX_Owner = 0 (0 => NPX is not owned)
;           MSW.TS = true (true => NPX does not belong to current thread)
;
;       EXIT-ERROR: NONE
;
;       USES: flags
;
;       INTERNAL REFERENCES: None.
;
;       EXTERNAL REFERENCES:
;        ROUTINES:
;           DosHlpValidNPXSwitch        - Validate NPX switch state
;           DosHlpClrBusyNPX            - Clear the NPX busy latch
;
;       ALGORITHM: (updated 10/6/89)
;
;       if the NPX is currently owned by a thread then
;           save the NPX context in that thread's NPX context buffer
;           indicate NPX is free (NPX_Owner = 0)
;           indicate CPU owner is not NPX owner (set TS bit in MSW)
;       end_if
;       return
;
;**************************************************************************
Procedure SaveNPX, NEAR
        ASSUME  DS:FLAT, ES:NOTHING, SS:NOTHING

ifdef   NPXSTRICT
;       Validate we're not emulating.
        push    eax
        smsw    ax
        test    ax,CR0_EM
        pop     eax
        .if     <nz>
          Panic  <"SaveNPX: emulating">
        .endif
endif   ;NPXSTRICT

        SaveReg <ebx,ds>                ; save registers

        mov     bx,NPX_Owner            ; if NPX not owned -
        .if     <nonzero bx> near       ; - skip over the save stuff
          SaveReg   <eax,ecx,esi,es>    ; save more registers

ifdef     NPXSTRICT

;         One of the design points is that we never switch the NPX while an NPX
;         error is pending. The following code validates this.

          CCALL32 DHF.DosHlpValidNPXSwitch

          .if <ax ne VNS_NOERROR>
            .if <ax eq VNS_ERROR_INT_PENDING>
                Panic   <SaveNPX: interrupt pending>

            .elseif <ax eq VNS_ERROR_INT_DISABLED>
                Panic   <SaveNPX: interrupts disabled>

            .elseif <AX eq VNS_ERROR_INT_IN_SERVICE>
                Panic   <SaveNPX: higher priority interrupt in service>

            .else
                Panic   <SaveNPX: invalid return from DosHlpValidNPXSwitch>

            .endif
          .endif

endif     ;NPXSTRICT

          BeginNoBlock                      ; begin critical section (debug
                                            ;   only code)
            clts                            ; hide TS from FNSAVE & FWAIT
            FlatContext <ds>
            movzx   ebx,bx                  ; ebx = slot number
            mov     eax,[_papTCBSlots]      ; eax = pointer to tcb slot array
            mov     ebx,[eax+ebx*4]         ; ebx = ptcb
            mov     ebx,[ebx].TCB_NPX_Buffer;ebx = address of npx context buffer
            fnsave  [ebx]                   ; save the NPX context
            CCALL32 DHF.DosHlpClrBusyNPX    ; to handle the PAL hardware glitch
            fwait
            mov     NPX_Owner,0             ; indicate NPX is free

          EndNoBlock                        ; end critcal section (debug code)

          RestoreReg <es,esi,ecx,eax>       ; restore more registers
          assume    es:nothing
        .endif

        smsw    bx                          ; CPU owner is not NPX owner
        or      bx,CR0_TS
        lmsw    bx

        RestoreReg <ds,ebx>                 ; restore registers
        assume  ds:nothing
        RETC
EndProc SaveNPX


BREAK   <NPXTick>
;**************************************************************************
; This routine may be needed later for 386 errata, it is commented now!
;
; SUBROUTINE NAME: NPXTick
;
; DESCRIPTIVE NAME: Give the NPX manager a shot in the arm.
;
; FUNCTION: This routine clears the busy latch on an AT.  This is
;           needed because the 80287 glitches its error signal
;           once in a while and causes the busy latch to indicate
;           the 287 is busy but no error is pending.
;
; NOTES:
;       See the note on WaitNPX
;
; ENTRY POINT:  NPXTick
;     LINKAGE:  Call Near from DosCode segment
;
; INPUT:        DS = DosGroup
;
; EXIT-NORMAL:  nothing
;
; EXIT-ERROR:   none
;
; EFFECTS:
;       Uses: ax, flags
;
; INTERNAL REFERENCES:
;    ROUTINES:  none
;
; EXTERNAL REFERENCES:
;    ROUTINES:  none
;**************************************************************************

;Procedure NPXTick, NEAR
;        assume  ds:DosGroup, es:nothing, ss:nothing
;        DUALMODE
;
;        test    errata_flags,errata_Glitch
;        .if     <nz>
;          CCALL32 DHF.DosHlpClrBusyNPX  ; to handle the PAL hardware glitch
;        .endif
;        RETC
;EndProc   NPXTick

BREAK <WaitNPX>
;**************************************************************************
;***    WaitNPX        Wait for the NPX
;
;       This routine waits for the NPX to complete processing.
;       It is used to synchronize operations with the NPX
;
;       NOTES:
;
;       If the task switch bit is on (TS in MSW) when the FWAIT is executed, a
;       trap 7 will occur.  To avoid this, TS is cleared around the FWAIT.
;
;       BUGBUG - Read the following dissertation.  It could save you the grief
;       we've already been thru.  Note that familiarity with the CPU and NPX is
;       assumed.
;
;       If life was simple, IBM would have connected the NPX to the CPU the way
;       Intel designed them to be connected.  But life simply is not simple.
;
;       IBM put a piece of hardware, known as "The Busy Latch" between the NPX
;       and the CPU and routed the NPX's error signal thru the busy latch and
;       into the interrupt controller on level 13 (0Dh).  This was done on an AT
;       to make the 80286/80287 look and act the same as the 8088/8087 did on
;       the original PC, thus preserving compatibility.
;
;       As Intel designed it, the CPU would have seen the busy signal go false
;       when the NPX finished executing the current instruction.  If the NPX had
;       detected an error, the error signal would have gone true before the busy
;       signal went false.
;
;       As IBM implemented it, the CPU still sees the busy signal go false when
;       the NPX finishes an instruction if no error was detected.  But if an
;       error was detected, the busy latch would cause the CPU to still see the
;       NPX as being busy even tho it had finished executing the instruction.
;       Also, the NPX's error signal would cause an interrupt to be made pending
;       at the interrupt controller.  The NPX interrupt service handler would
;       then clear the busy latch, thus allowing the CPU to see the true state
;       of the NPX's busy signal (now false) and removing the interrupt request
;       from the interrupt controller.  All this is well and good, for now at
;       least.
;
;       One other problem arose out of the existance of the busy latch.  It
;       seems a glitch on the NPX's error signal can occur during the execution
;       of a particular NPX instruction sequence.  This glitch is enough to
;       latch the busy signal and cause the CPU to see the NPX as being busy,
;       but not enough for the interrupt controller to make an interrupt request
;       pending.
;
;       Now, remember that the CPU waits to see the busy signal go false before
;       executing another NPX instruction.  Because of the glitch, the CPU will
;       NEVER see the busy signal go false and will therefore wait FOREVER!
;       This is a real problem since the kernel needs to wait for the NPX to
;       become not busy before doing such things as a thread switch, mode switch
;       or moving or freeing up memory.
;
;       The solution to this problem was to clear the busy latch before the wait
;       in the kernel.  Thats where the CLEARPAL macro came from.  Again, all is
;       well and good.
;
;       One side affect of this solution is that the busy latch is also latching
;       the NPX's interrupt request.  When the busy latch is cleared, the
;       interrupt request is removed.  But the interrupt controllers on the AT
;       are EDGE sensitive.  Therefore, the interrupt request is remembered by
;       the controller and not forgotten.  Hence, no problem on the AT.
;
;       One side note here.  When the NPX has detected an error, the CPU will
;       always see busy true until the NPX's interrupt is handled.  Any NPX
;       instruction will cause the CPU to hang forever waiting for busy to go
;       false if that NPX instruction is executed when interrupts are disabled.
;
;       When PS/2s came along, they also implemented the busy latch to preserve
;       compatibility between the AT and PS/2s.
;
;       However, PS/2 interrupt controllers are LEVEL sensitive (remember AT
;       controllers are EDGE sensitive).  Hence, CLEARING THE BUSY LATCH WILL
;       ALSO DISCARD THE NPX'S INTERRUPT REQUEST!  Therefore, DO NOT clear the
;       NPX busy latch on PS/2s anywhere except in the NPX interrupt handler.
;
;       Still, one question eludes an answer.  The glitch in the AT is cause by
;       a bug in the 80287.  PS/2 models 50 & 60 also use the 80287.  Therefore,
;       they may also have the glitch problem.  Remember, the solution to the
;       glitch problem was to clear the busy latch before executing any NPX
;       instruction.  But on a PS/2, clearing the busy latch will result in the
;       interrupt being discarded.  These two statements lead to the following
;       conclusion:  If the model 50 & 60 (and any future 80287 PS/2s) have the
;       glitch problem, OS/2 CAN NOT support them!  I am researching to find the
;       answer, but until I do this warning will serve to make you aware of the
;       potential problem.
;
;       One more side note:  not all PS/2 use the microchannel and therefore not
;       all PS/2 interrupt controllers are level sensitive.  In the above
;       dissertation, where I use the name "PS/2", read "MicroChannel Machine"
;       or "machine with level sensitive interrupts".  Non-microchannel PS/2
;       machines use the AT bus structure and therefore have edge sensitive
;       interrupt controllers (eg:  PS/2 Model 30 286).
;
;       ENTRY POINT: WaitNPX
;       LINKAGE: Call Near
;
;       ENTRY: NONE
;
;       EXIT: NONE
;
;       EXIT-ERROR: NONE
;
;       EFFECTS: flags
;
;       INTERNAL REFERENCES: NONE
;
;       EXTERNAL REFERENCES:
;         ROUITNES:
;           DosHlpWaitNPX           - Wait for NPX to complete processing
;
;**************************************************************************
Procedure WaitNPX, NEAR
        ASSUME  DS:FLAT, ES:NOTHING, SS:NOTHING

        SaveReg <eax>

        mov     ax,NPX_Owner            ; check if current thread owns NPX
        .if     <TaskNumber eq ax>      ; if so then
          CCALL32 DHF.DosHlpWaitNPX     ;   call DosHlpWaitNPX
        .endif                          ; endif (runner owns NPX)

        RestoreReg <eax>
        RETC
EndProc WaitNPX

BREAK   <_ReleaseNPX>
;**************************************************************************
;***    _ReleaseNPX    Release the NPX
;
;       This routine gives up ownership of the NPX if the current
;       thread is the owner of the NPX.  It also frees the NPX
;       context buffer if there is one for the current thread.
;
;       NOTES: NONE
;
;
;       ENTRY POINT: _ReleaseNPX
;        LINKAGE: Call Near
;
;       ENTRY: ss = PTDA of current thread
;
;       EXIT: NONE
;
;       EXIT-ERROR: NONE
;
;       USES: flags
;
;       INTERNAL REFERENCES:
;        ROUTINES:
;           WaitNPX             - Wait for NPX to finish processing
;
;       EXTERNAL REFERENCES:
;        ROUTINES:
;           _VMAllocKHB         - Free a context buffer
;
;       ALGORITHM: (updated 10/6/89)
;
;       if emulating then return
;
;       Wait for NPX to complete processing
;
;       if current thread owns NPX then
;           reset NPX (fninit)
;           indicate NPX is free (NPX_Owner = 0)
;           indicate CPU owner is not NPX owner (set TS bit in MSW)
;       end
;
;       if current thread has an NPX context buffer
;           free thread's NPX context buffer
;       end
;       return
;
;**************************************************************************
Procedure _ReleaseNPX,<near,far32>
        ASSUME  DS:FLAT, ES:NOTHING, SS:TASKAREA

        SaveReg <eax>
        smsw    ax
        test    ax,CR0_EM
        jnz     rlnx

        SaveReg <edi, ebp>
        CCALL32 WaitNPX                     ; wait for NPX to finish up
        mov     bp,ss:[TKOPTDA.CurrTCB]     ; ss:bp -> TCB

;       Free ownership of the NPX if the current thread is the owner

        mov     ax, TaskNumber              ; if current thread owns NPX
        .if  <ax eq NPX_Owner>
          clts                              ; hide TS from FINIT
          fninit                            ; reset the NPX
          fninit                            ; 2 inits needed for 387 errata
          mov     NPX_Owner, 0              ; indicate NPX is free
          smsw    ax                        ; CPU owner is not NPX owner
          or      ax,CR0_TS
          lmsw    ax
        .endif

;       We're throwing away the context, so throw away the force flag with it.

        and     ss:[bp].TCBForce_Flg,not TK_FF_NPX  ; clear thread's forceflag

;       Free the BMP NPX context buffer if there is one for this thread.

        mov     edi,ss:[bp].TCB_NPX_Buffer
        or      edi, edi                    ; if context buffer allocated
        .if    nz
          CCALL32 _VMFreeKHB <VM_HKH_PUB_RESRW, edi>
          mov     ss:[bp].TCB_NPX_Buffer,0  ; indicate freed
        .endif
        RestoreReg      <ebp, edi>

rlnx:   RestoreReg      <eax>
        RETC
EndProc _ReleaseNPX

BREAK   <NPX_Interrupt_Handler>
;**************************************************************************
;***    NPX_Interrupt_Handler  Interrupt handler for NPX error (IRQ13)
;
;       This procedure is executed whenever a processor extension error
;       occurs. This interrupt comes in over IRQ13. This routine is called
;       from the NPX16_Interrupt thunk.
;
;       NOTES: NONE
;
;       ENTRY POINT: NPX_Interrupt_Handler
;        LINKAGE: Call Near
;
;       INPUT:  NONE
;
;       EXIT:   NPX Error force flag set
;               Carry flag clear
;
;       EXIT-ERROR: none
;
;       USES: flags
;
;       INTERNAL REFERENCES: None.
;
;       EXTERNAL REFERENCES:
;        ROUTINES:
;           DosHlpClrBusyNPX            - Clear the NPX busy latch
;           DosHlpAckIntNPX             - Acknowledge NPX interrupt
;           VNPXException               - MVDM NPX interrupt
;
;**************************************************************************

Procedure NPX_Interrupt_Handler,far32
        ASSUME  DS:FLAT, ES:NOTHING, SS:NOTHING

        SaveReg <eax,ebx,esi,es,ds>

ifdef   NPXSTRICT

;       Validate we're not emulating.

        smsw    ax
        test    ax,CR0_EM
        .if     <nz>
            Panic <"NPX_Interrupt_Handler: emulating">
        .endif

;       Validate an error is pending. Note this is done BEFORE issuing the
;       EOI to avoid getting duplicate interrupts caused by the DMA4 bug.
;       See the comment in NPX_Error_Handler for details on the bug.

        CCALL32 DHF.DosHlpClrBusyNPX
        fnstsw  ax              ;fetch the NPX status
        test    ax,npxsw_error  ;check the error bit
        .if     <z>             ;if no error is pending
          mov     bx,ax           ;save previous status for debugging
          CCALL32 DHF.DosHlpClrBusyNPX
          fnstsw  ax              ;try it again for debugging
          Panic   <"NPX_Interrupt_Handler: no error pending">
        .endif
endif   ;NPXSTRICT

        movzx   ebx,NPX_Owner            ;bl = NPX_Owner
        .if     <nonzero bx>            ;#####

ifdef   NPXSTRICT

;       Another design point is that NPX errors be handled in the context of
;       the thread which caused them.  This point is validated here.

          .if   <bx ne TaskNumber>
            Panic        <"NPX_Interrupt_Handler: interrupt out of context">
          .endif
endif   ;NPXSTRICT

          FlatContext <ds>
          movzx ebx,bx                  ;ebx = slot number
          mov   eax,[_papTCBSlots]      ;eax = pointer to tcb slot array
          mov   ebx,[eax+ebx*4]         ;ebx = ptcb

; if the process is a vdm, call VNPXException to reflect the exception to VDM

          mov     eax,[_pPTDACur]           ; (EAX) = current PTDA pointer
          cmp     [eax].ptda_infoseg.LIS_ProcType,PT_REALMODE
          jnz     setflag
          SaveReg <eax,esi,ebx>
          CCALL32 [pexcephndlr]           ; call VNPXException
          RestoreReg <ebx,esi,eax>
          jmp     nosetflag

setflag:
          or    [ebx].TCBForce_Flg,TK_FF_NPX ;set thread's force flg

nosetflag:
          .endif                                ;#####

	; defect 98808 - formerly the dos help routine DosHlpClrBusyNPX was used
	; to reset the busy/error latch. Defect 91403 (and others) modified
	; this routine to conditionally (using fnstsw) do the reset. On
	; 386/387 systems this algorithm hangs the system. The solution here
	; is to guarentee one reset so the fnstsw does not have a problem.
	xor	al, al
	out	NPX287_BUSY_PORT, al ;reset the busy/error latch else we'll hang
        CCALL32 DHF.DosHlpAckIntNPX             ;dismiss the interrupt
        RestoreReg <ds,es,esi,ebx,eax>

        clc                             ;tell IntMgr we handled it
        RETP
EndProc NPX_Interrupt_Handler

BREAK   <NPX_Error_Handler>
;**************************************************************************
;***    NPX_Error_Handler  Clean up routine for process causing NPX
;                          error exception
;
;       This routine is called from Exit_Kmode if the NPX_Error ForceFlg
;       is set. Two actions are possible depending on whether the user
;       has installed a handler. If a handler has been installed, then
;       the stack frames are edited so that control goes back to the
;       handler.  If no handler is installed, default error processing
;       is taken, involving writing error and register information to the
;       screen, and termination of the process .
;
;       NOTES: See note in code about DMA4 chip bug.
;
;       ENTRY POINT: NPX_Error_Handler
;        LINKAGE: Call Near
;
;       ENTRY:  EDI = current TCB pointer
;               ESI = current PTDA pointer
;
;       EXIT:   NONE
;
;       EXIT-ERROR: NONE
;
;       USES: eax, ebx, ecx, edx, esi, edi, ds, es
;
;       INTERNAL REFERENCES:
;        ROUTINES:
;           SwitchNPX               - Switch the NPX context
;           WaitNPX                 - Wait for NPX to become idle
;           Con_CS_IP               - Covert CS:IP to a ascii string
;
;       EXTERNAL REFERENCES:
;       ROUTINES:
;           TRAPCheckExceptHandler  - Check if 16-bit handler registered
;           TRAPArmExceptHandler    - Setup to deliver error to 16-bit handler
;           KMPUSHCLIENTSTACK       - Push values on user stack
;           DelayHardErr            - Display hard error message
;           DosHlpClrBusyNPX        - Clear NPX busy latch
;
;**************************************************************************

        ASSUME  DS:FLAT, ES:NOTHING, SS:TASKAREA

Procedure NPX_Error_Handler, NEAR

LocalVar  neh_status, WORD
LocalVar  neh_control, WORD

        EnterProc

;       Demand context switching is designed to use when an app
;       touches the NPX.  It forces entering and exiting kernel mode. But
;       here, we're already in kernel mode. In fact, we're trying to get
;       out!  So we do a controlled context switch to keep the NPX interrupt
;       handler from setting flags which we have already checked and cleared
;       (e.g., TCBForce_Flg.TK_FF_NPX).

        CCALL32 SwitchNPX               ;force NPX context switch
        CCALL32 WaitNPX

ifdef   NPXSTRICT
;       Validate we're not emulating.
        .if     <c>
          Panic   <"NPX_Error_Handler: emulating">
        .endif
endif   ;NPXSTRICT

;       Validate an error is pending.
        fnstsw  ax
        test    ax,npxsw_error                  ; check the error bit
        .if     <z>

;         It has been observed that the FNSTSW above sometimes gets bad data
;         and that doing another FNSTSW will get good data.  So get the status
;         again so we can compare (if we're on a debug kernel, that is.)

          mov     bx,ax
          fnstsw  ax
          Panic <"NPX_Error_Handler: no error pending">
        .endif

        FlatContext <es>,ebx                    ; need both ds and es flat

        mov     neh_status,ax                 ; Save the NPX status word
        fnstcw  neh_control                   ; Get the NPX control word
        movzx   ebx,neh_control               ; (EBX) = NPX control word
        not     ebx                           ; Eliminate any flagged multiple
        and     eax,ebx                       ;  exceptions that were masked off

        mov     ebx, XCPT_FLOAT_INVALID_OPERATION    ; default exception
        bsf     ecx, eax                          ; ECX = bit # of exception
npe10:  cmp     ecx,5                         ; Random bit set?
        jg      short npe40                   ;   Yes. Defaut xcpt
        jecxz   short npe30                   ; If invalid op look at SF bit
        mov     ebx,FPToExcept[ecx*4]         ;  else Get except from table
        jmp     short npe40                   ; Fill in other fields

npe30:  test    neh_status,XCPT_FP_STACK_FAULT ; Was it a stack problem?
        jz      short npe40                   ; No. So it was invalid op
        mov     ebx,XCPT_FLOAT_STACK_CHECK

npe40:  SaveReg <ds,edi>
        CCALL32 _XCPTBuildR3DispatcherStack, <EXCFPERR, ebx, 0, 0, FALSE, 0>
        RestoreReg <edi,ds>

;       Watch out for machines with DMA version 4 chips in them.  The busy
;       latch, which is part of the DMA4 chip, has a bug.  If the NPX's busy
;       line goes active when the NPX has an error pending (such as when the
;       NPX exception handler does an FNSTSW to get the error flags to find
;       out what error is pending), the busy latch will generate ANOTHER NPX
;       interrupt.  Thats ok up till now, since the interrupt handler only
;       sets TK_FF_NPX.  But we're about to clear it, so we get REAL
;       CAREFUL here.  Executing the FNCLEX will cause an NPX interrupt, so
;       first disable interrupts.  Then clear the busy/error latch just after
;       the FNCLEX so the interrupt will not get thru (we can get away with
;       this because machines with DMA4s have level sensitive interrupts.)
;       Now clear TK_FF_NPX and re-enable interrupts.  Doing this will
;       have no effect on machines which do not have the DMA4 chip in them.

npe90:  cli
        fnclex                                  ; clear the exception
        CCALL32 DHF.DosHlpClrBusyNPX            ; reset the busy/error latch
        and     ds:[edi].TCBForce_Flg,not TK_FF_NPX ;clear NPX error flag
        sti
        LeaveProc
        RETC
EndProc NPX_Error_Handler

;**************************************************************************
;***    Con_CS_IP          Convert CS:IP to a string
;
;       Format the strings which tells the user where (cs:ip) was when the
;       error occurred.
;
;       NOTES: NONE
;
;       ENTRY POINT: Con_CS_IP
;        LINKAGE: Call Near
;
;       ENTRY:  Env has the CS:IP address to convert
;
;       EXIT:   NONE
;
;       EXIT-ERROR: NONE
;
;       USES: eax, esi, edi
;
;       INTERNAL REFERENCES: NONE
;
;       EXTERNAL REFERENCES:
;        ROUTINES:
;           HexToAscii32     - Covert hex number to ascii string
;
;**************************************************************************
Procedure Con_CS_IP,near
        ASSUME  DS:NOTHING, ES:NOTHING, SS:TASKAREA


        mov     edi,offset DOSGROUP:Formatbuf; (ES:EDI) = temp buffer
        mov     esi,edi                         ; (DS:ESI) = temp buffer

        mov     eax,Env.npx_css
        CCALL32 HexToAscii32

        mov     al,":"                          ; put addr seperator in string
        stosb

        mov     eax,Env.npx_ipo
        CCALL32 HexToAscii32

        xor     al,al                           ; null terminate string
        stosb
        RETC
EndProc Con_CS_IP


;**************************************************************************
;***    NPXRegisterVDD     Register VNPX entry points with the NPX Manager
;
;       The VNPX calls VTONPXRegisterVDD (a VDH service within the VDM
;       Manager) which in turn calls NPXRegisterVDD.  The VDM Manager
;       is the link between VNPX and the NPX Manager to register
;       VNPX entry points and to send back information to the VNPX.
;
;       NOTES: This routine is entered in kernel mode, ring 0
;
;       ENTRY POINT: NPXRegisterVDD
;        LINKAGE: Call Near
;
;       ENTRY:  ECX = pointer to VNPX table with entry points
;               EDX = pointer to buffer to be filled in with ports
;
;       EXIT:   EAX = 0 if NPX is present
;               EAX = 1 if NPX is not present
;
;       EXIT-ERROR: NONE
;
;       USES: eax
;
;       INTERNAL REFERENCES: NONE
;
;       EXTERNAL REFERENCES: NONE
;
;**************************************************************************

Procedure NPXRegisterVDD,near
        ASSUME  DS:FLAT, ES:NOTHING, SS:TASKAREA

; fill in pointers to the VNPX exception handler and the emulation handler
        mov     eax,[ecx]          ; pointer is the address of the VNPX table
        mov     [pexcephndlr],eax  ; VNPX exception handler

        mov     eax,[ecx+4]
        mov     [pemulahndlr],eax  ; VNPX emulation handler

; fill in buffer with the NPX reset port, the NPX busy latch and the
; irq number
        movzx   eax, NPX_IRQ
        mov     dword ptr [edx],eax      ; irq number
        mov     dword ptr [edx+4],NPX_BUSY_PORT ; busy latch
        mov     dword ptr [edx+8],NPX_REAL_PORT ; reset port

; if NPX is present return 0 in AX and 1 if NPX is not present
        mov     eax,1                ; hiding is true
        cmp     mathCop,NPX_PRESENT  ; if an NPX really exists
        jne     retvnpx
        xor     eax,eax              ; hiding is false
retvnpx:
        RETC
EndProc NPXRegisterVDD

;**************************************************************************
;***    NPXClearBusy       Called from VNPX to write to NPX Busy latch.
;
;       The VNPX calls VTONPXClearBusy (a VDH service within the VDM
;       Manager) which in turn calls NPXClearBusy.  The VDM Manager
;       is the link between VNPX and the NPX Manager.  Only a return
;       is performed here, it is not possible to emulate the BUSY#
;       latch.
;
;       NOTES: This routine is entered in kernel mode, ring 0
;
;       ENTRY POINT: NPXClearBusy
;        LINKAGE: Call Near
;
;       ENTRY:  ECX = port to clear
;
;       EXIT:   NONE
;
;       EXIT-ERROR: NONE
;
;       USES: NONE
;
;       INTERNAL REFERENCES: NONE
;
;       EXTERNAL REFERENCES:
;         ROUTINES:
;           DosHlpVNPXClrBusy       - VNPX interface to clear NPX busy latch
;
;**************************************************************************

Procedure NPXClearBusy,near
        ASSUME  DS:FLAT, ES:NOTHING, SS:TASKAREA
        CCALL32 DHF.DosHlpVNPXClrBusy
        RETC
EndProc NPXClearBusy

;**************************************************************************
;***    NPXReset           Called from VNPX to write to reset NPX (port F1).
;
;       The VNPX calls VTONPXReset (a VDH service within the VDM
;       Manager) which in turn calls NPXReset.  The VDM Manager
;       is the link between VNPX and the NPX Manager.  The write to
;       port F1 is treated as finit.  The status word is then
;       manipulated to look like a hardware reset.
;
;       NOTES: This routine is entered in kernel mode, ring 0
;
;       ENTRY POINT: NPXReset
;        LINKAGE: Call Near
;
;       ENTRY:  ECX = port to reset
;
;       EXIT:   NONE
;
;       EXIT-ERROR: NONE
;
;       USES: NONE
;
;       INTERNAL REFERENCES:
;        ROUTINES:
;
;       EXTERNAL REFERENCES:
;        ROUTINES:
;           DosHlpVNPXReset         - VNPX interface to reset NPX
;
;**************************************************************************

Procedure NPXReset,near
        ASSUME  DS:FLAT, ES:NOTHING, SS:TASKAREA

        CCALL32 DHF.DosHlpVNPXReset
        jnc     NPXrst_exit
        Panic <"NPXReset : wrong port specified">
NPXrst_exit:
        RETC
EndProc NPXReset


;**************************************************************************
;***    _NPXReadContext    Read the NPX context
;
;        Allows reading of a thread's NPX context
;
;        NOTES:
;
;        This routine is called by the debugger to read a thread's
;        NPX context.  Note that the thread whose context is requested,
;        does not have to be the owner of the NPX.
;
;        ENTRY POINT: _NPXReadContext
;        LINKAGE: Call Near
;
;       ENTRY:
;       ptcb - pointer to thread control block to retrieve
;       addr - 0:32 linear address of a buffer to store the read
;                   context in (pointer)
;       length - pointer to length of buffer to store the NPX context
;       coproc - type of the coprocessor
;
;       EXIT: AX = 0
;
;       EXIT-ERROR: AX = error code
;
;       the call fails if the thread has not used the NPX,
;                      if the thread is emulating,
;                      or if the buffer is too small
;
;       USES: length is replaced with the length of the context
;             returned
;
;       INTERNAL REFERENCES:
;        ROUTINES:
;           WaitNPX         - Wait for NPX to go idle
;           SaveNPX         - Save the context of the NPX
;
;       EXTERNAL REFERENCES: None.
;
;**************************************************************************

Procedure _NPXReadContext,near
ArgVar  ptcb,DWORD            ; pointer
ArgVar  addr,DWORD            ; pointer
ArgVar  buflength,DWORD       ; pointer
ArgVar  coproc,DWORD

        EnterProc
        SaveReg <ecx,edx,ebx,esi,edi>
        mov     ecx,[ptcb]                      ; thread control block
; access the npx buffer
         mov     esi,es:[ecx].TCB_NPX_Buffer

; are we emulating?

        smsw    dx
        test    dx,CR0_EM                       ; test for emulation
        jnz     reterror

; Once an NPX instruction is executed, TS is set so SwitchNPX is called
; which allocates a buffer!!!
; has the NPX been used?

        or      esi,esi
        jz      reterror                        ; has not used NPX

; Is debugger requesting a 387 buffer and is the buffer big enough?

        mov     eax,[buflength]                 ; eax = pointer to length
        mov     ebx,[eax]                       ; ebx = length
        movzx   edx,NPX_TYPE
        cmp     coproc,edx                      ; 387?
        jnz     nocoproc
        cmp     ebx,NPX_CON_SIZE
        je      flushcon
        jl      toosmall
        mov     ebx,NPX_CON_SIZE                ; buffer length being xfer'd
        mov     DWORD PTR [eax],ebx             ; move in length read

flushcon:
        CCALL32 WaitNPX                         ; wait for NPX to go idle
        CCALL32 SaveNPX                         ; save NPX context

; at this point we know that the NPX context buffer is up-to-date
; put the NPX context into the buffer

        mov     edi,[addr]                      ; place to put NPX context
        mov     eax,NPX_CON_SIZE                ; copy size (words)
        shr     eax,1
        mov     ecx,eax
        rep     movsw
        xor     eax,eax
        jmp     finish

nocoproc:
        mov   eax,ERROR_BAD_DEV_TYPE
        jmp   finish
toosmall:
        mov   eax,ERROR_INSUFFICIENT_BUFFER
        jmp   finish
reterror:
        mov   eax,ERROR_DEV_NOT_EXIST         ; isn't using NPX
finish:
        RestoreReg <edi,esi,ebx,edx,ecx>
        LeaveProc
        RETC                                     ; do not clear off stack
EndProc   _NPXReadContext


;**************************************************************************
;***    _NPXWriteContext    Write the NPX context
;
;        Allows writing of a thread's NPX context
;
;        NOTES:
;
;        This routine is called by the debugger to write a thread's
;        NPX context.  Note that the thread whose context is
;        requested, does not have to be the owner of the NPX.
;
;        ENTRY POINT: _NPXWriteContext
;        LINKAGE: Call Near
;
;       ENTRY:
;       ptcb - pointer to thread control block to retrieve
;       addr - 0:32 linear address of a buffer to store the read
;                   context in (pointer)
;       length - pointer to length of buffer to store the NPX context
;       coproc - type of the coprocessor
;
;       EXIT: AX = 0
;
;       EXIT-ERROR: AX = error code
;
;       the call fails if the thread has not used the NPX,
;                      if the thread is emulating,
;                      or if the buffer is too small
;
;       USES: length is replaced with the length of the context
;             returned
;
;       INTERNAL REFERENCES:
;        ROUTINES:
;           WaitNPX         - Wait for NPX to go idle
;           SaveNPX         - Save the context of the NPX
;
;       EXTERNAL REFERENCES: None.
;
;**************************************************************************
Procedure _NPXWriteContext,near
ArgVar  ptcb,DWORD            ; pointer
ArgVar  addr,DWORD            ; pointer
ArgVar  buflength,DWORD       ; pointer
ArgVar  coproc,DWORD

        EnterProc
        SaveReg <ecx,edx,ebx,esi,edi>
        mov     ecx,[ptcb]                      ; thread control block
; access the npx buffer
         mov     edi,es:[ecx].TCB_NPX_Buffer

; are we emulating?
        smsw    dx
        test    dx,CR0_EM                       ; test for emulation
        jnz     reterror1

; has the NPX been used?
        or      edi,edi
        jz      reterror1                       ; has not used NPX

; are we using a 287 or a 387 and is the buffer big enough?
        mov     eax,[buflength]                 ; eax = pointer to length
        mov     ebx,[eax]                       ; ebx = length
        movzx   edx,NPX_TYPE
        cmp     coproc,edx                      ; 387?
        jnz     nocoproc1
        cmp     ebx,NPX_CON_SIZE
        je      flushcon1
        jl      toosmall1
        mov     ebx,NPX_CON_SIZE                ; buffer length being xfer'd
        mov     DWORD PTR [eax],ebx             ; move in length written

flushcon1:
        CCALL32 WaitNPX                         ; wait for NPX to go idle
        CCALL32 SaveNPX                         ; save NPX context

; at this point we know that the NPX context buffer is up-to-date
; put the buffer into the NPX context

        mov     esi,[addr]                      ; place to read NPX context
        mov     eax,NPX_CON_SIZE                ; copy size (words)
        shr     eax,1
        mov     ecx,eax                         ; copy size (words)
        rep     movsw
        xor     eax,eax
        jmp     finish1

nocoproc1:
        mov   eax,ERROR_BAD_DEV_TYPE
        jmp   finish1
toosmall1:
        mov   eax,ERROR_INSUFFICIENT_BUFFER
        jmp   finish1
reterror1:
        mov   eax,ERROR_DEV_NOT_EXIST         ; isn't using NPX
finish1:
        RestoreReg <edi,esi,ebx,edx,ecx>
        LeaveProc
        RETC                                     ; do not clear off stack


EndProc   _NPXWriteContext

;***EP  B1NPX_Errata_02 - detect 386-B1/NPX Errata02
;
;       This code detects if a page fault will cause 386-B1 Errata #2:
;
;       Problem: FSAVE, FRESTOR, FSTENV and FLDENV Anomalies with Paging
;
;       If either of the last two bytes of an FSAVE or and FSTENV
;       operand are for any reason no writeable, or either of the
;       last two bytes of an FRESTOR or FLDENV are for any reason not
;       readable, the instruction is not restartable. This problem
;       will arise only in demand-paged systems, or demand-segmented
;       systems which increase segment size on demand.
;
;       MS Solution: Detect above situation and kill process.
;
;       We will presume this situation will occur when:
;          1) The faulting instruction is one of the above and
;          2) The last one or two bytes of the operand are on a
;             different page and
;          3) The last page is not present.
;
;       If the above conditions are met, we will kill the process.
;
;       ENTRY   Faulting Address in CR2
;
;       EXIT    EAX  = NO_ERROR  ==> No problem with page fault
;               EAX != NO_ERROR  ==> Errata Encountered. Process was killed.

RM16    label   dword

        dd      offset FLAT:rm000
        dd      offset FLAT:rm001
        dd      offset FLAT:rm002
        dd      offset FLAT:rm003
        dd      offset FLAT:rm004
        dd      offset FLAT:rm005
        dd      offset FLAT:rm006
        dd      offset FLAT:rm007

MOD16   label   dword

        dd      offset FLAT:mod00
        dd      offset FLAT:mod01
        dd      offset FLAT:mod02

SEG16   label   dword

        dd      KM_REG_DS
        dd      KM_REG_DS
        dd      KM_REG_SS
        dd      KM_REG_SS
        dd      KM_REG_DS
        dd      KM_REG_DS
        dd      KM_REG_SS
        dd      KM_REG_DS

RM32    label   dword
INDEX   label   dword

        dd      KM_REG_EAX
        dd      KM_REG_ECX
        dd      KM_REG_EDX
        dd      KM_REG_EBX
        dd      KM_REG_ESP                      ; S-I-B
        dd      KM_REG_EBP
        dd      KM_REG_ESI
        dd      KM_REG_EDI

MOD32   label   dword

        dd      offset FLAT:mod0
        dd      offset FLAT:mod1
        dd      offset FLAT:mod2

SEG32   label   dword

        dd      KM_REG_DS
        dd      KM_REG_DS
        dd      KM_REG_DS
        dd      KM_REG_DS
        dd      KM_REG_SS
        dd      KM_REG_SS
        dd      KM_REG_DS
        dd      KM_REG_DS

OVERRIDE        label   dword

        dd      KM_REG_CS
        dd      KM_REG_SS
        dd      KM_REG_DS
        dd      KM_REG_ES
        dd      KM_REG_FS
        dd      KM_REG_GS

PREFIX_TBL      label   byte

        db      MI_SEGCS
        db      MI_SEGSS
        db      MI_SEGDS
        db      MI_SEGES
        db      MI_SEGFS
        db      MI_SEGGS
        db      MI_ADDRESSSIZE
        db      MI_OPERANDSIZE

PREFIX_TBL_LEN equ ($ - PREFIX_TBL)

;
; The following flags will be used later in this procedure so we don't have
; to decode the instruction prefixes twice.
;

PREFIX_CS       equ     00000001h
PREFIX_SS       equ     00000002h
PREFIX_DS       equ     00000004h
PREFIX_ES       equ     00000008h
PREFIX_FS       equ     00000010h
PREFIX_GS       equ     00000020h
PREFIX_ALLSEL   equ     PREFIX_CS + PREFIX_SS + PREFIX_DS + PREFIX_ES + PREFIX_FS + PREFIX_GS

PREFIX_OPERAND  equ     00000040h
PREFIX_ADDR     equ     00000080h
PREFIX_ADDR32   equ     80000000h               ; Use 32-bit Addresses
PREFIX_OPER32   equ     40000000h               ; Use 32-bit registers

;
; The Prefix flag table is in opposite order from the prefix table
;

PREFIX_FLAG_TBL label   dword

        dd      PREFIX_OPERAND
        dd      PREFIX_ADDR
        dd      PREFIX_GS
        dd      PREFIX_FS
        dd      PREFIX_ES
        dd      PREFIX_DS
        dd      PREFIX_SS
        dd      PREFIX_CS

Procedure B1NPX_Errata_02

LocalVar bne_CR2, DWORD
LocalVar bne_Address, DWORD                     ; Address of F* operand
LocalVar bne_Instruction, 16                    ; Maximum instruction size
LocalVar bne_Reg, DWORD                         ; For KMQueryClientRegister.
                                                ;  *MUST* follow bne_Instruction
LocalVar bne_EFlags, DWORD                      ; Flags. *MUST* follow bne_Reg
LocalVar bne_SelDesc, <size seldesc_s>          ; CS Descriptor
LocalVar bne_Offset, DWORD                      ; Offset of opcode in inst.
LocalVar bne_DataMask, DWORD                    ; To select 8, 16, or 32 bit regs
LocalVar bne_PrefixFlags, DWORD
LocalVar bne_OldTrap0E, 8

        EnterProc

        mov     eax, cr2
        mov     [bne_CR2], eax

        mov     edi, [_pTCBCur]
        SSToDS  ebx, [bne_EFlags]
        PCALL32 KMQUERYCLIENTREGISTER, <edi, KM_REG_EFLAG, ebx>
;
; First, Copy the instruction onto the stack. Note that it is theoretically
; possible that we're looking at the wrong instruction because the 387
; executes asyncronously but if that happened there's nothing we can do
; anyway.
;
        add     ebx, 4                          ; Move up 1 DWord to bne_Reg
        PCALL32 KMQUERYCLIENTREGISTER, <edi, KM_REG_EIP, ebx>
        mov     esi, [bne_Reg]                  ; ESI = User CS
        PCALL32 KMQUERYCLIENTREGISTER, <edi, KM_REG_CS, ebx>
        test    [bne_EFlags], F_VM              ; V86 Mode?
        jz      short bne05                     ;  No.
        mov     eax, [bne_Reg]
        shl     eax, 4
        add     eax, esi                        ; EAX = Linear Inst Address
        jmp     short bne06

bne05:  CCALL32 _SELVirtToLin, <esi, [bne_Reg]> ; EAX = Linear Inst Address
bne06:  mov     esi, eax
        cmp     esi, [bne_CR2]                  ; Paging in code?
        je      bne90                           ; Yes. Don't look for errata

;
; The instruction needs to be fetched 1 byte at a time because we might
; be near the end of a page and don't want to take the fault (if we do,
; the the errata is not encountered. So we'll install a local PF handler
; and fetch the instruction (up to 16 bytes).
;

        add     ebx, 4                          ; EBX = bne_Instruction
        mov     edi, ebx
        mov     ecx, 4
        mov     eax, 90909090h                  ; 4 NOPs
        rep     stosd                           ; Clear instruction field
        mov     ecx, 16
        mov     edi, ebx                        ; Backup EDI to field start.
        DosContext <ds>,edx
        pushf                                   ; Save old Interrupt Flag
        cli                                     ; Don't want nested interrupts
                                                ;  w/ a special PF Handler!
        mov     edx, dword ptr [IDTTrap0E]      ; Save old Trap 0E
        mov     dword ptr [bne_OldTrap0E], edx
        mov     edx, dword ptr [IDTTrap0E + 4]
        mov     dword ptr [bne_OldTrap0E + 4], edx

        mov     dx, [_R0CS]
        mov     [IDTTrap0E].g_sel, dx
        mov     [IDTTrap0E].g_access, D_TRAP032
        mov     edx, offset FLAT:bne300
        mov     [IDTTrap0E].g_lowoffset, dx
        shr     edx, 16
        mov     [IDTTrap0E].g_extoffset, dx

        FlatContext <ds>, edx
        rep     movsb

bne07:  DosContext <ds>,edx
        mov     edx, dword ptr [bne_OldTrap0E]  ; Restore Trap0E Handler
        mov     dword ptr [IDTTrap0E], edx
        mov     edx, dword ptr [bne_OldTrap0E + 4]
        mov     dword ptr [IDTTrap0E + 4], edx
        FlatContext <ds>, edx
        popf                                    ; Restore Interrupt flag

;
; Now decode enough of the instruction to decide if it's one we care about.
;

        xor     ecx,ecx
        mov     [bne_Offset], ecx
        xor     edx,edx                         ; EDX = Prefixes found

bne10:  mov     al, [ebx + ecx]                 ; AL = Opcode byte #ECX
        mov     edi, offset FLAT:PREFIX_TBL
        mov     ecx, PREFIX_TBL_LEN
        repne   scasb
        jnz     short bne20                     ; CX = 0 --> AL not a prefix
        inc     [bne_Offset]
        or      edx, PREFIX_FLAG_TBL[ecx * 4]
        mov     ecx, [bne_Offset]
        jmp     short bne10

bne20:  mov     ecx, [bne_Offset]

;
; At this point, we've figured out what instruction prefixes (if any) we
; have. Registers contain:
;       AL = First byte of instruction that is not a prefix.
;      EBX = Flat address of [bne_instruction]
;      ECX = Offset into [bne_instruction] of opcode
;      EDX = Flags indicating which prefixes we've found.
;[bne_Reg] = User CS
;
; Note that the check for the errata opcodes is done in several stages to
; minimize overhead for the common case (not an errata opcode).
;

bne85:  and     al, MI_ESCMASK
        cmp     al, MI_ESC                      ; FP Operation?
        je      short bne100
bne90:  xor     eax, eax
bne95:  LeaveProc
        RETC

;
; The first byte of FSAVE, FRSTOR, FLDENV, FSTENV is 11011?01b
;

bne100: mov     ax, [ebx + ecx]
        xor     al, 11011001b
        and     al, 11111011b
        jnz     short bne90                     ; Not an opcode we care about

;
; With the MOD and R/M fields masked off, the instructions of interest are
; 001?0000.
;

        and     ah, NOT (MI_MODMASK + MI_RMMASK)
        xor     ah, 00100000b
        and     ah, 11101111b
        jnz     short bne90                     ; Not an opcode we care about

;
; At this point we definitely have an FSAVE, FRSTOR, FLDENV, or FSTENV.
; Now we need to figure out if the buffer crosses a page boundary.
;

;
; First, figure out what address and operand sizes we're using. This is
; determined by a combination of SELECTOR flags and prefix bytes.
;

        mov     edi, edx                        ; EDI = Prefix flags
        test    [bne_EFlags], F_VM              ; V86 Mode ==> Default 16-bit
        jnz     short bne110

        SSToDS  edx, [bne_SelDesc]              ; EBX = SSToDS (bne_SelDesc)
        CCALL32 _SELGetDescInfo, <[bne_Reg], 0, edx>
        or      eax,eax
        jnz     short bne90
        test    [bne_SelDesc].desc_attr, D_DBIG ; D bit ==> Default 32-bit
        jz      short bne110
        or      edi, (PREFIX_ADDR32 + PREFIX_OPER32)

;
; Look for prefix overrides to the operand or address size.
;

bne110: test    edi, PREFIX_OPERAND             ; Operand size override?
        jz      short bne115                    ; No.
        xor     edi, PREFIX_OPER32

bne115: test    edi, PREFIX_ADDR                ; Address size override?
        jz      short bne120                    ; No.
        xor     edi, PREFIX_ADDR32
;
; Now the top two bits of EDI tell the truth about which type of addresses
; and operands are being used.
;

bne120: mov     [bne_PrefixFlags], edi
        xor     esi, esi                        ; ESI = Address of buffer
        test    edi, PREFIX_ADDR32              ; 32-bit Addresses?
        jnz     bne150                          ;  Yes.

;
; Calculate the 16-bit form of Mod R/M byte
;

        test    edi, PREFIX_ADDR32              ; Use 32-bit Addresses?
        jz      short bne125                    ;  No.
        mov     edi, 0FFFFFFFFh                 ;  Yes.
        jmp     short bne128

bne125: mov     edi, 0000FFFFh                  ; Default 16-bit operands
bne128: SSToDS  ebx, [bne_Reg]                  ; Scratch space
        mov     ecx, [bne_Offset]
        mov     al, [bne_Instruction + ecx + 1] ; AL = Mod R/M byte
        and     eax, MI_RMMASK
        jmp     RM16[eax*4]

rm000:  PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], KM_REG_EBX, ebx>
        and     [bne_Reg], edi                  ; Maybe mask off part of reg.
        add     esi, [bne_Reg]
        PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], KM_REG_ESI, ebx>
        and     [bne_Reg], edi                  ; Maybe mask off part of reg.
        add     esi, [bne_Reg]
        jmp     mod16

rm001:  PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], KM_REG_EBX, ebx>
        and     [bne_Reg], edi                  ; Maybe mask off part of reg.
        add     esi, [bne_Reg]
        PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], KM_REG_EDI, ebx>
        and     [bne_Reg], edi                  ; Maybe mask off part of reg.
        add     esi, [bne_Reg]
        jmp     mod16

rm002:  PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], KM_REG_EBP, ebx>
        and     [bne_Reg], edi                  ; Maybe mask off part of reg.
        add     esi, [bne_Reg]
        PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], KM_REG_ESI, ebx>
        and     [bne_Reg], edi                  ; Maybe mask off part of reg.
        add     esi, [bne_Reg]
        jmp     mod16

rm003:  PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], KM_REG_EBP, ebx>
        and     [bne_Reg], edi                  ; Maybe mask off part of reg.
        add     esi, [bne_Reg]
        PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], KM_REG_EDI, ebx>
        and     [bne_Reg], edi                  ; Maybe mask off part of reg.
        add     esi, [bne_Reg]
        jmp     mod16

rm004:  PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], KM_REG_ESI, ebx>
        and     [bne_Reg], edi                  ; Maybe mask off part of reg.
        add     esi, [bne_Reg]
        jmp     mod16

rm005:  PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], KM_REG_EDI, ebx>
        and     [bne_Reg], edi                  ; Maybe mask off part of reg.
        add     esi, [bne_Reg]
        jmp     mod16

rm006:  mov     ecx, [bne_Offset]
        mov     al, [bne_Instruction + ecx + 1]
        and     al, MI_MODMASK                  ; MOD 00 ==> Disp only
        jnz     short rm006a
        mov     ax, word ptr [bne_Instruction + ecx + 2] ; AX = 16-bit displacement
        movzx   eax, ax
        add     esi, eax
        mov     eax, KM_REG_DS
        jmp     short bne145

rm006a: PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], KM_REG_EBP, ebx>
        and     [bne_Reg], edi                  ; Maybe mask off part of reg.
        add     esi, [bne_Reg]
        jmp     mod16

rm007:  PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], KM_REG_EBX, ebx>
        and     [bne_Reg], edi                  ; Maybe mask off part of reg.
        add     esi, [bne_Reg]

mod16:  mov     ecx, [bne_Offset]
        mov     al, [bne_Instruction + ecx + 1] ; AL = Mod R/M byte
        shr     al, MI_MODSHIFT
        mov     dx, word ptr [bne_Instruction + ecx + 2] ; DX = Displacement, if any
        movzx   eax, al
        jmp     MOD16[eax*4]

mod01:  movzx   edx, dl
        add     esi, edx
        jmp     short mod00

mod02:  movzx   edx, dx
        add     esi, edx

mod00:  mov     al, [bne_Instruction + ecx + 1]
        and     eax, MI_RMMASK
        mov     eax, SEG16[eax*4]

bne145: mov     edi, [bne_PrefixFlags]
        and     edi, PREFIX_ALLSEL
        jz      short bne146
        bsf     eax, edi
        mov     eax, OVERRIDE[eax*4]
bne146: PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], eax, ebx>
        SSToDS  edx, [bne_SelDesc]
        test    [bne_EFlags], F_VM              ; V86 Mode?
        jnz     short bne147
        CCALL32 _SELGetDescInfo, <[bne_Reg], 0, edx>
        add     esi, [bne_SelDesc].desc_laddr
        jmp     Common

bne147: mov     eax, [bne_Reg]
        shl     eax, 4
        add     esi, eax
        jmp     Common
;
; Calculate the 32-bit Address format of Mod R/M bytes (1-byte form)
;

bne150: test    edi, PREFIX_ADDR32              ; Use 32-bit Addresses?
        jz      bne155                          ;  No.
        mov     edi, 0FFFFFFFFh                 ;  Yes.
        jmp     short bne160

bne155: mov     edi, 0000FFFFh                  ; Default 16-bit operands
bne160: SSToDS  ebx, [bne_Reg]                  ; Scratch space
        mov     ecx, [bne_Offset]
        mov     al, [bne_Instruction + ecx + 1] ; AL = Mod R/M byte
        and     al, MI_RMMASK
        cmp     al, MI_RMSIB                    ; Two byte version?
        je      short bne190                    ;  Yes.

        mov     al, [bne_Instruction + ecx + 1] ; AL = Mod R/M byte
        and     al, (MI_MODMASK + MI_RMMASK)
        cmp     al, MI_RMDISP                   ; Disp only?
        jne     short bne165                    ;  No.
        mov     eax, dword ptr [bne_Instruction + ecx + 2]
        add     esi, eax
        mov     eax, KM_REG_DS
        jmp     bne145

bne165: and     eax, MI_RMMASK
        mov     eax, RM32[eax*4]
        PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], eax, ebx>
        mov     eax, [bne_Reg]
        and     eax, edi
        add     esi, eax

bne166: mov     ecx, [bne_Offset]
        movzx   eax, byte ptr [bne_Instruction + ecx + 1]
        shr     eax, MI_MODSHIFT
        jmp     MOD32[eax*4]

mod1:   movzx   eax, byte ptr [bne_Instruction + ecx + 2]
        jmp     short mod0

mod2:   mov     eax, dword ptr [bne_Instruction + ecx + 2]

mod0:   add     esi, eax                        ; Note EAX = 0 if we get
                                                ;  here via jmp Mod0...

        mov     al, [bne_Instruction + ecx + 1]
        and     eax, MI_RMMASK
        mov     eax, SEG32[eax*4]
        jmp     bne145

;
; Calculate the 32-bit Address format of Mod R/M bytes (2-byte form)
;


bne190: mov     al, [bne_Instruction + ecx + 2]
        and     eax, MI_SIB_INDEXMASK
        cmp     al, MI_SIB_INDEXNONE
        je      short bne195
        shr     eax, MI_SIB_INDEXSHIFT
        mov     eax, INDEX[eax*4]
        PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], eax, ebx>
        mov     eax, [bne_Reg]
        and     eax, edi                        ; EAX = Index

        mov     ecx, [bne_Offset]
        movzx   edx, [bne_Instruction + ecx + 2]
        shr     edx, MI_SIB_SSSHIFT
        xchg    edx, ecx
        shl     eax, cl                         ; EAX = Index * scale
        mov     ecx, edx
        add     esi, eax

bne195: mov     al, [bne_Instruction + ecx + 1]
        and     al, MI_MODMASK                  ; Mod 00?
        jnz     short bne200                    ;  No.
        mov     al, [bne_Instruction + ecx + 2]
        and     al, MI_SIB_BASEMASK
        cmp     al, MI_SIB_BASENONE             ; Is there a base reg?
        jne     short bne200                    ;  Yes.

        mov     eax, dword ptr [bne_Instruction + ecx + 3]
        add     esi, eax
        mov     eax, KM_REG_DS
        jmp     bne145

bne200: mov     al, [bne_Instruction + ecx + 2]
        and     eax, MI_SIB_BASEMASK
        mov     eax, RM32[eax*4]
        PCALL32 KMQUERYCLIENTREGISTER, <[_pTCBCur], eax, ebx>
        mov     eax, [bne_Reg]
        and     eax, edi                        ; EAX = Base
        add     esi, edi
        mov     ecx, [bne_Offset]
        inc     ecx                             ; To look at SIB
        jmp     bne166

;
; At this point, ESI contains the linear address of the F* buffer. Now
; we need to figure out where the end is.
;

Common: mov     ecx, [bne_Offset]
        mov     al, [bne_Instruction + ecx]
        cmp     al, MI_ESC + 101b               ; FSAVE/FRESTORE?
        jne     short bne210                    ;  No.
        add     esi, 80                         ;  Yes. Add stack image size
bne210: test    [bne_PrefixFlags], PREFIX_OPER32  ; 32-bit operands?
        jz      short bne220                    ;  No.
        add     esi, 0Eh                        ;  Yes. Double size of Env.
bne220: add     esi, 0Eh                        ; Always have at least 16-bit Env.

        mov     [bne_Address], esi
        and     esi, PAGEMASK
        cmp     esi, 2                          ; > 2 bytes on last page?
        jge     bne90                           ;  Yes. No problem.

        mov     esi, [bne_Address]
        and     esi, NOT PAGEMASK
        cmp     esi, [bne_CR2]                  ; Are we faulting that page?
        jne     bne90                           ;  No.

;
; We get here if we hit the errata. So kill the process.
;

        CCALL32 _XCPTBuildR3DispatcherStack, <EXCSW, XCPT_B1NPX_ERRATA_02, \
                0, 0, 0, 0>
        mov     eax, 1                          ; ERROR Return
        jmp     bne95
;
; This trap handler gets hit if we have trouble accessing memory while
; fetching the current instruction. If we get a fault, just stop fetching
; and continue.
;

bne300: add     esp, 16                         ; Kill Flags, CS:EIP, ErrCode
        jmp     bne07

EndProc B1NPX_Errata_02

;***    B1NPX_Errata_17 - Detect and fix B1-NPX Errata #17
;
;       This code detects if 386-B1 Errata #17 has occurred and
;       loads the 'problem page'. At entry to this procedure, we
;       know:
;             1) The IP is w/in 8 bytes of a page end.
;             2) We are running w/ a 386-B1 / 387 combination
;
;       Problem: Coprocessor Instructions Crossing Page/Segment Boundaries
;
;       If the first byte of a coprocessor (ESC) instruction is
;       located on the last byte of a page or segment, and the second
;       byte is located on a page or segment which would create a
;       fault, then the processor will hang when it tries to signal
;       the fault. The processor remains stopped until an interrupt,
;       NMI, or RESET occurs. This errata applies only to coprocessor
;       instructions in systems which use virtual memory.
;
;       MS Solution: Detect above situation and fault in the page.
;
;       ENTRY   GS:EDI points to the interrupt stack frame (ISF) for
;                   a clock tick.
;
;       EXIT    Condition detected - A context hook has been set to
;                                    page in the errant page.
;
;               Condition not detected - None
;
;       USES    EAX, EBX, ECX, EDX, EBP, ESI, EDI, DS, ES, FS, GS, Flags

Procedure B1NPX_Errata_17, far32

LocalVar bnx_OldTrap0E, 8
LocalVar bnx_Trap0EStatus, DWORD                ; 0 --> Trap0E NOT error.
LocalVar bnx_ErrCode, DWORD
LocalVar bnx_phook,DWORD

        EnterProc

        pushf
        mov     [bnx_Trap0EStatus], 0
        test    gs:[edi].isf_eflag, F_VM        ; Are we in V86 mode?
        jz      short bnx10                     ;  No. Convert SEL:OFFSET
        movzx   eax, word ptr gs:[edi].isf_cs   ;  Yes. Convert SEG:OFFSET
        shl     eax, 4
        movzx   edx, word ptr gs:[edi].isf_eip
        add     eax, edx                        ; EAX = Linear address
        jmp     short bnx15

bnx10:  movzx   eax, gs:[edi].isf_cs
        CCALL32 _SELVirtToLin, <gs:[edi].isf_eip, eax>

;
; At this point we have a linear address that may or may not be valid. If
; it's not, the errata is not encountered but we can't take the fault at
; interrupt time, so we'll change the Trap E entry in the IDT to catch
; the fault. At this point:
;
;       EAX = Linear Address of Instruction
;

bnx15:  mov     ebx, eax
        or      eax, PAGEMASK                   ; EAX = Last byte on page

        DosContext <ds>,edx
        cli                                     ; Don't want nested interrupts
                                                ;  w/ a special PF Handler!
        mov     edx, dword ptr [IDTTrap0E]      ; Save old Trap 0E
        mov     dword ptr [bnx_OldTrap0E], edx
        mov     edx, dword ptr [IDTTrap0E + 4]
        mov     dword ptr [bnx_OldTrap0E + 4], edx

        mov     dx, [_R0CS]
        mov     [IDTTrap0E].g_sel, dx
        mov     [IDTTrap0E].g_access, D_TRAP032
        mov     edx, offset FLAT:bnx100
        mov     [IDTTrap0E].g_lowoffset, dx
        shr     edx, 16
        mov     [IDTTrap0E].g_extoffset, dx

        mov     dl, es:[eax]
        and     dl, MI_ESCMASK
        cmp     dl, MI_ESC                      ; FP Operation?
        je      short bnx25                     ;  Yes.

bnxx:   mov     edx, dword ptr [bnx_OldTrap0E]  ; Restore Trap0E Handler
        mov     dword ptr [IDTTrap0E], edx
        mov     edx, dword ptr [bnx_OldTrap0E + 4]
        mov     dword ptr [IDTTrap0E + 4], edx
        FlatContext <ds>, edx
        popf
        LeaveProc
        RETC

;
; We get here if we have a FP instruction. Now see if all bytes leading up
; to the ESC opcode are valid prefix bytes.
;

bnx25:  mov     esi, ebx
        and     esi, PAGEMASK
        neg     esi
        add     esi, PAGESIZE - 1               ; ESI = # Prefix bytes.
        or      esi, esi                        ; Are the none?
        jz      short bnx50
        mov     edx, esi

bnx30:  mov     edi, offset FLAT:PREFIX_TBL
        mov     ecx, PREFIX_TBL_LEN
        mov     al, es:[ebx + edx]              ; AL = Instruction byte
        repne   scasb                           ; Which prefix?
        jcxz    short bnxx                      ;  None. No Errata
        sub     edx, 1                          ; More prefix bytes?
        jnz     short bnx30                     ;  Yes. Go check them.
;
; At this point we have verified all the conditions of errata #17 except
; that the next page is not present. To make life simpler, we aren't actually
; going
;

bnx50:  inc     [bnx_Trap0EStatus]
        mov     eax, es:[ebx + esi + 1]         ; Touch first byte on next page.
        jmp     bnxx                            ; Getting here implies errata
                                                ;  not encountered

;
; This trap handler gets hit if we have trouble accessing memory. If this
; occurs and bnx_Trap0EStatus = 0, then we had trouble accessing the initial
; page of code and the errata doesn't occur. If bnx_Trap0EStatus is not
; 0 then the errata is detected.
;

bnx100: mov     edx, [esp]                      ; EDX = Error code
        add     esp, 16                         ; Kill Flags, CS:EIP, ErrCode
        cmp     [bnx_Trap0EStatus], 0           ; Errata encountered?
        jnz     short bnx110                    ;  Yes.
        jmp     short bnxx

;
; Errata encountered. Arm context hook to page in errant page.
;

bnx110: FlatContext <ds>,ebx
        SSToDS  ebx, [bnx_phook]
        PCALL32 KMALLOCCONTEXTHOOK, <offset FLAT:B1NPX_Errata_17HookFn, 8, [_pTCBCur], ebx> ; b733550
ifdef NPXSTRICT                                                ; b733550
        or    ebx, ebx                                         ; b733550
        .if <nz>                                               ; b733550
          Panic <SwitchNPX: Error allocating context hook.">   ; b733550
        .endif                                                 ; b733550
endif
        mov     ebx, [bnx_phook]                ; EBX = PHOOK structure
        mov     eax, cr2
        movzx   ecx, [ebx].hook_offRefData      ; EBX = Ptr to Hook Data
        mov     [ebx + ecx], eax                ; Data = CR2
        mov     [ebx + ecx + 4], edx            ; Data = Error code
        PCALL32 KMARMCONTEXTHOOK, <ebx, [_pTCBCur]>
        DosContext <ds>,ebx
        jmp     bnxx

EndProc B1NPX_Errata_17

;***LP  B1NPX_Errata_17HookFn - Called at KMode time to fix Errata 17
;
;       The context hook for this function is armed at interrupt time.
;       since the fix for Errata 17 is to swap in a page, we can't fix
;       the errata at interrupt time when we detect it, thus the
;       use of a context hook.
;
;       ENTRY   pFrameBase - Pointer to stack frame base
;               pRefData - Pointer to reference data
;
;       EXIT    none

Procedure B1NPX_Errata_17HookFn,pascal
ArgVar  pFrameBase,DWORD
ArgVar  pRefData,DWORD

        EnterProc

        mov     eax, cr2                        ; Save current CR2
        SaveReg <eax, ebx, esi, edi>            ; C calling convention + CR2
        mov     eax, [pRefData]
        mov     edx, [eax + 4]                  ; EDX = Error code
        mov     eax, [eax]
        mov     cr2, eax                        ; Restore 'faulting' CR2
        mov     eax, 0Eh                        ; EAX = 'Trap' number

        CCALL32 UserPageFault

        RestoreReg <edi, esi, ebx, eax>
        mov     cr2, eax                        ; Restore current CR2

        LeaveProc
        RETC

EndProc B1NPX_Errata_17HookFn

BREAK <forceEMHnadler>                                                  ;b726833
;******************************************************************     ;b726833
;                                                                       ;b726833
;SUBROUTINE NAME: forceEMHandler                                        ;b726833
;                                                                       ;b726833
;DESCRIPTIVE NAME:                                                      ;b726833
;       force trap 07 emulation handler for his process                 ;b726833
;                                                                       ;b726833
;FUNCTION:                                                              ;b726833
;       Change the default emulation hander set a process creation time ;b726833
;       to requested routine. This value will be set in the idt for trap;b726833
;                                                                       ;b726833
;NOTES:                                                                 ;b726833
;       None                                                            ;b726833
;                                                                       ;b726833
;ENTRY POINT:                                                           ;b726833
;       forceEMHandler:near                                             ;b726833
;                                                                       ;b726833
;INPUT:                                                                 ;b726833
;       ds=es= flat data selector                                       ;b726833
;       eax = linear address of emulation routine                       ;b726833
;             0 to restore value to system defaults                     ;b726833
;                                                                       ;b726833
;EXIT NORMAL:                                                           ;b726833
;       nothing                                                         ;b726833
;                                                                       ;b726833
;EXIT ERROR:                                                            ;b726833
;       none                                                            ;b726833
;                                                                       ;b726833
;EFFECTS:                                                               ;b726833
;                                                                       ;b726833
;INTERNAL REFERENCES:                                                   ;b726833
;       none                                                            ;b726833
;                                                                       ;b726833
;EXTERNAL REFERENCES:                                                   ;b726833
;                                                                       ;b726833
;******************************************************************     ;b726833
Procedure forceEMHandler,near                                           ;b726833
        ASSUME  DS:FLAT, ES:NOTHING, SS:NOTHING, FS:NOTHING, GS:NOTHING ;b726833
                                                                        ;b726833
EndProc forceEMHandler                                                  ;b726833
        mov     ecx,[_pPTDACur]                 ; (ECX) = pPTDACur      ;b726833
        mov     dx, cs                                                  ;b726833
        or      eax,eax                         ; set or restore?       ;b726833
        jnz     short femh10                    ; jump if set           ;b726833
        mov     dx, [_R2CS]                     ; get restore values    ;b726833
        mov     eax,[FPEMHandler]                                       ;b726833
femh10:                                                                 ;b726833
        cli                                     ; freeze idt            ;b726833
        mov     [ecx].ptda_NPX_em_eip, eax      ; set address           ;b726833
        mov     [ecx].ptda_NPX_em_cs, dx        ; and flat code selector;b726833
                                                                        ;b726833
        mov     [NPXNAVec].g_sel,dx             ; set up idt trap 7 gate;b726833
        mov     [NPXNAVec].g_lowoffset,ax                               ;b726833
        shr     eax,16                                                  ;b726833
        mov     [NPXNAVec].g_extoffset,ax                               ;b726833
        sti                                                             ;b726833
                                                                        ;b726833
        ret                                                             ;b726833
                                                                        ;b726833
HIGH32CODE ENDS

        END
