WindowsXP/Source/XPSP1/NT/base/mvdm/dpmi/ntintr32.asm
2024-08-03 16:30:48 +02:00

938 lines
29 KiB
NASM

include intmac.inc
public rgw032Stack
dw 64 dup (?) ; DOSX Ring -> Ring 0 transition stack
;
; Interrupts in the range 0-1fh cause a ring transition and leave
; an outer ring IRET frame right here.
;
Ring0_EH_DS dw ? ; place to put user DS
Ring0_EH_AX dw ? ; place to put user AX
Ring0_EH_BX dw ? ; place to put user BX
Ring0_EH_CX dw ? ; place to put user CX
Ring0_EH_BP dw ? ; place to put user BP
Ring0_EH_PEC dw ? ; lsw of error code for 386 page fault
; also near return to PMFaultEntryVector
Ring0_EH_EC dw ? ; error code passed to EH
Ring0_EH_IP dw ? ; interrupted code IP
dw ?
Ring0_EH_CS dw ? ; interrupted code CS
dw ?
Ring0_EH_Flags dw ? ; interrupted code flags
dw ?
Ring0_EH_SP dw ? ; interrupted code SP
dw ?
Ring0_EH_SS dw ? ; interrupted code SS
dw ?
rgw032Stack label word
; ------------------------------------------------------------------
; PMFaultAnalyzer -- This routine is the entry point for
; the protected mode fault/trap/exception handler. It tries
; to distinguish between bona fide processor faults and
; hardware/software interrupts which use the range of
; interrupts that is reserved by Intel. If a fault is
; detected, then format the stack for a DPMI fault handler,
; then vector to the handler whose address is stored in
; PMFaultVector. If it looks more like an interrupt, then
; set up the stack for an interrupt handler, jump to the
; handler whose address is stored in PMIntelVector.
;
; Input: none
; Output: none
assume ds:NOTHING,es:NOTHING,ss:DGROUP
public PMFaultAnalyzer
PMFaultAnalyzer proc near
;
; Make sure we are on the right stack. Else, something fishy is going on.
; Note that stack faults won't do too well here.
;
push ax
mov ax,ss
cmp ax,SEL_DXDATA or STD_RING
pop ax
je pmfa_stack_passed
jmp pmfa_bad_stack
pmfa_stack_passed:
;
; Is the stack pointer pointing at a word error code (minus 2)?
;
cmp sp,offset DGROUP:Ring0_EH_PEC
je pmfa_fault ; Yes, processor fault.
;
; Is it pointing to where it is supposed to be for a hardware or
; software interrupt?
;
cmp sp,offset DGROUP:Ring0_EH_EC
je pmfa_20
jmp pmfa_bad_stack
pmfa_20:jmp pmfa_inspect
pmfa_fault:
;
; Getting here, we have a known exception with a word error code of some
; sort on the stack. Perform an outward ring transition, switch to the
; client stack, then vector through the exception handler vector to the
; appropriate handler.
;
push bp
push cx
push bx
push ax
push ds
lea bp,Ring0_EH_SS
mov ax,word ptr [bp]
mov cx,selEHStack
cmp ax,cx
jne pmfa_stack_OK
mov bx,[bp-2]
jmp pmfa_copy_stack
pmfa_stack_OK:
mov bx,npEHStackLimit
pmfa_copy_stack:
mov ds,cx ; DS:BX = user SS:SP
mov cx,13
add bp,2 ; put both halves of ss
pmfa_copy_stack_loop:
dec bx
dec bx
mov ax,word ptr [bp]
mov word ptr [bx],ax
dec bp
dec bp
loop pmfa_copy_stack_loop
; DS:BX points to stack on entry to PMFaultReflector
;
; Build a far return frame on user stack, switch to user stack, and return
;
lea bp,Ring0_EH_PEC
mov word ptr [bp+6],ds
mov word ptr [bp+4],bx
sub bx,2
mov word ptr ds:[bx],SEL_DXPMCODE OR STD_RING; push cs
sub bx,2
mov ds:[bx],offset DXPMCODE:PMFaultReflector; push ip
lea bp,Ring0_EH_PEC
mov ax,Ring0_EH_CX ; get BP value
sub bx,2
mov ds:[bx],ax ; push bp
mov Ring0_EH_PEC,bx ; sp for lss
mov bx,ds
mov Ring0_EH_EC,bx ; ss for lss
pop ds
pop ax
pop bx
pop cx
.386p
lss sp,[bp] ; switch stack
.286p
pop bp
retf
pmfa_inspect:
;
; Stack is set up as for an interrupt or exception without error code.
; Adjust the stack pointer and put an error code of zero. Then try to
; determine whether we have an exception or an interrupt. First test
; is the interrupt number.
;
push Ring0_EH_EC
mov Ring0_EH_EC,0
cmp Ring0_EH_PEC,offset PMFaultEntryVector + ((7 + 1) * 3)
ja pfma_50
push Ring0_EH_PEC
mov Ring0_EH_PEC,0
jmp pmfa_fault ; Yes, definitely a fault.
pfma_50:
;
; At this point, all valid exceptions have been eliminated except for
; exception 9, coprocessor segment overrun, and exception 16, coprocessor
; error.
;
; **********************************************
; * Your code to detect these exceptions here. *
; **********************************************
push bp
push cx
push bx
push ax
push ds ; SP -> Ring0_EH_DS
;
; Point to the user's stack.
;
lea bp,Ring0_EH_SS
mov cx,[bp]
mov ds,cx
mov bx,[bp-2] ; DS:[BX] -> user stack
;
; Copy the IRET frame to the user stack.
;
lea bp,Ring0_EH_Flags
mov cx,6
pmfa_copy_IRET:
mov ax,[bp]
dec bx
dec bx
mov [bx],ax
dec bp
dec bp
loop pmfa_copy_IRET
;
; Point BP at vector entry for this (reserved) interrupt.
;
mov ax,Ring0_EH_PEC ; fetch near return address
sub ax,offset DXPMCODE:PMFaultEntryVector+3
mov cl,3
div cl ; AX = interrupt number
shl ax,2 ; AX = vector entry offset
lea bp,PMIntelVector
add bp,ax ; BP -> interrupt handler address
mov ax,[bp] ; AX = IP of handler
mov cx,[bp+2] ; CX = CS of handler
;
; Build a far return frame on user stack, switch to user stack, and return
;
sub bx,2
mov ds:[bx],cx ; push cs
sub bx,2
mov ds:[bx],ax ; push ip
lea bp,Ring0_EH_PEC
mov ax,Ring0_EH_BP
sub bx,2
mov ds:[bx],ax ; push bp
mov Ring0_EH_PEC,bx ; sp for lss
mov bx,ds
mov Ring0_EH_EC,bx ; ss for lss
pop ds
pop ax
pop bx
pop cx
.386p
lss sp,[bp] ; switch stack
.286p
pop bp
retf ; Out of here.
pmfa_bad_stack:
if DEBUG
mov ax,ss
mov bx,sp
Trace_Out "Fault Handler Aborting with SS:SP = #AX:#BX"
pop ax
sub ax, (offset DXPMCODE:PMFaultEntryVector) + 3
mov bx,3
div bl
Trace_Out "Fault Number #AX"
pop ax
pop bx
pop cx
pop dx
Trace_Out "First four stack words: #AX #BX #CX #DX."
endif
push selDgroupPM
push offset DGROUP:rgwStack
rpushf
push SEL_DXPMCODE or STD_RING
push offset DXPMCODE:pmfr_death
riret
pmfr_death:
mov ax,cs
mov ds,ax
mov dx,offset szRing0FaultMessage
pmdossvc 09h
jmp PMAbort
PMFaultAnalyzer endp
FR_Stack Struc
FR_BP dd ?
FR_AX dd ?
FR_BX dd ?
FR_DS dw ?
FR_ENTRY dd ? ; SS:[SP] points here on entry
; to PMReservedReflector
FR_Toss dd ? ; DPMI return IP
FR_Ret_IP dd ? ; actual fault handler gets put
FR_Ret_CS dd ? ; here to return to
FR_IP dd ?
FR_CS dd ?
FR_FL dd ?
FR_SP dd ?
FR_SS dd ?
FR_Stack Ends
;
; Alternate names so the structure above makes more sense to
; PMFaultReflector.
;
FR_Handler_IP equ FR_DS
FR_Handler_CS equ FR_ENTRY
FR_Handler_Ret_IP equ FR_Toss
FR_Handler_Ret_CS equ FR_Ret_IP
FR_Handler_Entry equ FR_Handler_Ret_CS
FR_EC equ FR_Ret_CS
; ------------------------------------------------------------------
; PMFaultReflector -- Dispatch a fault to a fault handler installed
; in PMFaultVector. When the fault handler returns, return
; to the faulting code, using the addresses placed on the
; DPMI fault handler stack by the last called fault handler.
;
; Input:
; Entry is by a NEAR call, with an IP within the range
; of PMFaultEntryVector on the stack. The stack has been
; set up for use by a DPMI fault handler.
;
; Output:
; Controlled by fault handler.
;
; Uses:
; Controlled by fault handler.
;
; Notes:
; Fault handlers are called on a static stack. This routine
; is NOT REENTRANT.
;
public PMFaultReflector
public PMFaultReflectorIRET
PMFaultReflector proc near
assume ss:nothing,ds:nothing,es:nothing
sub sp,6
push bx
push eax
push bp
mov bp,sp
push ds
mov ax,SEL_DXDATA or STD_RING
mov ds,ax
assume ds:dgroup
mov ax,[bp.FR_Handler_Entry]
sub ax,offset DXPMCODE:PMFaultEntryVector+3
mov bl,3
div bl ; AX = interrupt number
shl ax,3 ; AX = offset of fault handler
lea bx,PMFaultVector
add bx,ax ; SS:[BX] -> fault vector entry
mov eax,word ptr ds:[bx]
mov [bp.FR_Handler_IP],eax
mov ax,word ptr ds:[bx+2]
mov [bp.FR_Handler_CS],ax
lea ax,pmfr_cleanup
movzx eax,ax
mov [bp.FR_Handler_Ret_IP],eax
push cs
pop [bp.FR_Handler_Ret_CS]
pop ds
assume ds:nothing
pop bp
pop ax
pop bx
db 066h,0CBh ; This calls the fault handler.
PMFaultReflectorIRETCall:
dd (SEL_RZIRET or STD_RING) shl 10h
pmfr_cleanup:
;
; Unwind the fault handler stack. Return to the faulting code.
; This works by calling a Ring 0 procedure to do the actual IRET.
; If we do it that way, we can return to the faulting code without
; actually touching the faulting code's stack.
;
PMFaultReflectorIRET:
; BUGBUG Daveh using user stack this way is less robust!!!
.386p
add sp,4 ; pop error code
push bp
mov bp,sp
push ebx
push ds
push eax
mov ax,[bp + 18]
mov ds,ax
mov ebx,[bp + 14] ; ds:bx -> user stack
sub ebx,4
mov eax,[bp + 10]
mov ds:[ebx],eax ; push flags
sub ebx,4
mov eax,[bp + 4]
mov ds:[ebx],eax ; push cs
sub ebx,4
mov eax,[bp + 2]
mov ds:[ebx],eax ; push ip
sub ebx,2
mov ax,[bp]
mov ds:[ebx],ax ; push bp
mov [bp + 8],bx
pop eax
pop ds
pop ebx
pop bp
add sp,6 ; point to ss:sp
mov bp,sp
lss esp,[bp]
.286p
pop bp ; restore bp
riretd
endif
PMFaultReflector endp
;
; -------------------------------------------------------
; PMReservedReflector -- This routine is for reflecting
; exceptions to a protected mode interrupt handler.
; The default for exceptions 1, 2, and 3 is to have
; a near call to this routine placed in the PMFaultVector.
;
; This routine strips off the fault handler stack set
; up by PMFaultAnalyzer, switches to the stack pointed
; to by the pushed SS and SP values, sets up an IRET
; frame for use by PMIntrReflector, and jumps to
; PMIntrReflector. Eventual return is via an IRET
; from PMIntrReflector.
;
; Input:
; Entry is by a NEAR call, with an IP within the range
; of PMReservedEntryVector on the stack. The stack has been
; set up for use by a DPMI fault handler.
;
; Output:
; Switch to stack registers set up by any previous fault
; handler, jump to PMIntrReflector with an IRET frame set up
; for direct return to the interrupted code.
;
; Errors: none
;
; Uses: Modifies SS, SP. Does not return to caller.
;
assume ds:NOTHING,es:NOTHING,ss:NOTHING
public PMReservedReflector
PMReservedReflector:
push ds
push ebx
push eax
push ebp
mov bp,sp
;
; BP now points to a stack frame described by the structure
; above. This will be copied to a stack frame on the stack pointed to
; by FR_SS:FR_SS. In most cases, the destination stack is actually
; the same as the present stack, offset by four bytes. The following
; block of code is therefore very likely an overlapping copy. Think
; carefully before modifying how it works.
;
mov bx,[bp.FR_SS]
mov ds,bx
mov ebx,[bp.FR_SP] ; DS:[BX] -> interrupted code's stack
sub bx, (size FR_Stack) - 8 ; (not copying SP or SS)
; DS:[BX] -> place to copy our stack frame
mov eax,[bp.FR_FL] ; Push user IRET frame onto the destination
mov [ebx.FR_FL],eax ; stack.
mov eax,[bp.FR_CS]
mov [ebx.FR_CS],eax
mov eax,[bp.FR_IP]
mov [ebx.FR_IP],eax
mov eax,[bp.FR_ENTRY] ; Copy our caller's near return.
mov [ebx.FR_ENTRY],eax
mov ax,[bp.FR_DS] ; Copy saved registers.
mov [ebx.FR_DS],ax
mov eax,[bp.FR_BX]
mov [ebx.FR_BX],eax
mov eax,[bp.FR_AX]
mov [ebx.FR_AX],eax
mov eax,[bp.FR_BP]
mov [ebx.FR_BP],eax
mov ax,ds ; Switch to user stack.
mov ss,ax
mov esp,ebx
mov ebp,esp
mov ax,[ebp.FR_ENTRY] ; AX = offset of caller
sub ax,offset DXPMCODE:PMReservedEntryVector + 3
mov bl,3
div bl ; AX = interrupt number
shl ax,2 ; AX = offset into PMIntelVector
mov ds,SelDgroupPM
assume ds:DGROUP
lea bx,PMIntelVector
add bx,ax ; DS:[BX] -> interrupt handler
mov eax,[bx] ; Place vector entry just below
mov [ebp.FR_Ret_IP],eax ; IRET frame.
mov eax,[bx+4]
mov [ebp.FR_Ret_CS],eax
lea esp,[ebp.FR_BP] ; Point to saved registers.
pop ebp ; Pop 'em.
pop eax
pop ebx
pop ds
add esp,4 ; Fix up stack.
db 066h,0CBh ; jump to interrupt handler via far return
DXPMCODE ends
; -------------------------------------------------------
subttl Real Mode Interrupt Reflector
page
; -------------------------------------------------------
; REAL MODE INTERRUPT REFLECTOR
; -------------------------------------------------------
DXCODE segment
assume cs:DXCODE
; -------------------------------------------------------
; RMIntrEntryVector -- This table contains a vector of
; near jump instructions to the real mode interrupt
; reflector. Real mode interrupts that have been hooked
; by the protected mode application have their vector
; set to entry the real mode reflector through this table.
public RMIntrEntryVector
RMIntrEntryVector:
rept 256
call RMIntrReflector
endm
; -------------------------------------------------------
; RMIntrReflector -- This routine is the entry point for
; the real mode interrupt reflector. This routine
; is entered when an interrupt occurs (either software
; or hardware) that has been hooked by the protected mode
; application. It switches the processor to protected mode
; and transfers control to the appropriate interrupt
; service routine for the interrupt. After the interrupt
; service routine completes, it switches back to real
; mode and returns control to the originally interrupted
; real mode code.
; Entry to this routine comes from the RMIntrEntryVector,
; which contains a vector of near call instructions, which
; all call here. The interrupt number is determined from
; the return address of the near call from the interrupt
; entry vector.
; The address of the protected mode interrupt service routine
; to execute is determined from the protected mode interrupt
; descriptor tabel and the interrupt number.
;
; Input: none
; Output: none
; Errors: none
; Uses: The segment registers are explicitly preserved by
; this routine. Other registers are as preserved or
; modified by the interrutp service routine.
assume ds:NOTHING,es:NOTHING,ss:NOTHING
public RMIntrReflector
RMIntrReflector:
;
; On entry, the stack layout is:
; [6] FLAGS - "
; [4] CS - "
; [2] IP - from original interrupt
; [0] IP - from interrupt entry vector call
;
FCLI
cld
push ds
IFDEF ROM
SetRMDataSeg
ELSE
mov ds,selDgroup
ENDIF
assume ds:DGROUP
if DEBUG
;
; Are we on a DOSX interrupt reflector stack?
;
push ax
push cx
mov ax,ss
mov cx,ds
cmp ax,cx
pop cx
jne @F
cmp sp,offset bReflStack
jb @F
cmp sp,offset pbReflStack
jnb @F
;
; If so, have we overflowed a stacklet?
;
mov ax,pbReflStack
cmp sp,ax
ja @F
add ax,CB_STKFRAME
cmp sp,ax
jb @F
pop ax
Real_Debug_Out "DOSX:RMIntrReflector--Reflector stack overflow."
push ax
@@:
pop ax
endif ;DEBUG
mov regUserAX,ax ;save user AX for later
push bp ;stack -> BP DS IP IP CS FL
mov bp,sp ; [0] [2] [4] [6] [8] [A]
mov ax,[bp+0Ah] ;get the interrupted routine's flags
and ax,NOT 4100h ;clear the trace flag in case we got
; an interrupt on an instruction about
; to be single stepped
mov regUserFL,ax ;and save for later
mov ax,es
xchg ax,[bp+4] ;save ES and get entry vector address
pop bp
; Some software (like older versions of Smartdrv.sys) may enable A20 on
; their own, and get very 'suprised' to find it turned off by our PM->RM
; mode switch. If they used Himem.sys, this wouldn't be necessary, but...
if VCPI
cmp fVCPI,0
jnz @f
endif
push ax ;get/save current A20 state on stack
push bx
xmssvc 7
mov regUserSP,ax ;use regUserSP as a temp var
pop bx
pop ax
@@:
push regUserSP
; The state that we want to save on the user's stack has been set up.
; Convert the entry vector return address into an interrupt number.
sub ax,offset RMIntrEntryVector+3
push cx
mov cl,3
div cl
pop cx
if DEBUG
mov PMIntNo,ax
endif
; Allocate a new stack frame, and then switch to the reflector stack
; frame.
mov regUserSP,sp ;save entry stack pointer so we can
mov regUSerSS,ss ; switch to our own stack
IFDEF ROM
push ds
pop ss
ELSE
mov ss,selDgroup ;switch to the reflector stack frame
ENDIF
mov sp,pbReflStack
push pbReflStack ;save stack frame ptr on stack
sub pbReflStack,CB_STKFRAME ;adjust pointer to next stack frame
; We are now running on our own stack, so we can switch into protected mode.
push ax ;save interrupt vector table offset
SwitchToProtectedMode
pop ax
if DEBUG ;--------------------------------------------------------
push 0DEADh ;debugging id & interrupt number
push PMIntNo
cmp fTraceReflect,0
jz @f
push ax
mov ax,PMIntNo
Trace_Out "(rp#AL)",x
pop ax
@@:
; Perform a too-late-to-save-us-now-but-we-want-to-know check on the
; reflector stack.
cmp StackGuard,1022h
jz @f
Debug_Out "DOSX:RMIntrReflector--Global reflector stack overflow."
@@:
endif ;DEBUG ---------------------------------------------------------
; Build an IRET frame on the stack so that the protected mode interrupt service
; routine will return to us when it is finished.
push regUserSS ;save user stack address on our own stack
push regUserSP ; frame so we can restore it later
push ds
push regUserFL
push cs
push offset rmrf50
; Build an IRET frame on the stack to use to transfer control to the
; protected mode ISR
and byte ptr regUserFL+1,not 02h ;use entry flags less the
push 0 ; high half esp
push regUserFL ; interrupt flag (IF)
xchg bx,ax ;interrupt vector offset to BX, preserve BX
cmp bx,CRESERVED ;Interrupt in reserved range?
jc rmrf_reserved
shl bx,3
mov es,selIDT
jmp rmrf_setISR
rmrf_reserved:
shl bx,2
mov es,SelDgroupPM
add bx,offset DGROUP:PMIntelVector
rmrf_setISR:
push dword ptr es:[bx+4] ;push segment of isr
push dword ptr es:[bx] ;push offset of isr
xchg bx,ax
mov ax,regUserAX ;restore entry value of AX
push ds
pop es
; At this point the interrupt reflector stack looks like this:
;
; [18] previous stack frame pointer
; [16] stack segment of original stack
; [14] stack pointer of original stack
; [12] protected mode dos extender data segment
; [10] dos extender flags
; [8] segment of return address back to interupt reflector
; [6] offset of return address back to interrupt reflector
; [4] user flags as on entry from original interrupt
; [2] segment of protected mode ISR
; [0] offset of protected mode ISR
;
; Execute the protected mode interrupt service routine
iretd
; The protected mode ISR will return here after it is finsished.
rmrf50: pop ds
pushf ;save flags as returned by PM Int routine
FCLI ;We have to clear interrupts here, because
cld ; the interrupt routine may have returned
; with interrupts on and our code that uses
; static variables must be protected. We
; turn them off after to pushf instruction so
; that we can preserve the state of the
; interrupt flag as returned by the ISR.
mov regUserAX,ax
pop ax
pop regUserSP
pop regUserSS
if DEBUG
add sp,4 ;'pop' off debugging info
endif
pop pbReflStack ;deallocate stack frame(s)
; Switch back to real mode.
push ax ;preserve AX
SwitchToRealMode
pop ax
; Switch back to the original stack.
mov ss,regUserSS
mov sp,regUserSP
; Make sure the A20 line matches whatever state it was when the int occured.
; This is for the benefit of any software that diddles A20 without using
; an XMS driver
pop regUserSP ;A20 state at time of interrupt to temp var
if VCPI
cmp fVCPI,0
jnz rmrf75
endif
push ax ;save current ax
mov ax,regUserSP ;ax = A20 state at time of interrupt
or ax,ax ;if it was off, don't sweat it
jz rmrf70
push bx ;save bx (XMS calls destroy bl)
push ax
xmssvc 7 ;ax = current A20 state
pop bx ;bx = old A20 state
cmp ax,bx ;if A20 is still on, don't need to diddle
jz @f
xmssvc 5 ;force A20 back on
inc A20EnableCount ; and remember that we did this
if DEBUG
or fA20,04h
endif
@@:
pop bx
rmrf70:
pop ax
rmrf75:
; Put the flags returned by the real mode interrupt routine back into
; the caller's stack so that they will be returned properly.
push bp ;stack -> BP DS ES IP CS FL
mov bp,sp ; [0] [2] [4] [6] [8] [10]
and [bp+10],0300h ;clear all but the interrupt and trace flags
; in the caller's original flags
or [bp+10],ax ;combine in the flags returned by the
; interrupt service routine. This will cause
; us to return to the original routine with
; interrupts on if they were on when the
; interrupt occured, or if the ISR returned
; with them on.
pop bp
; And return to the original interrupted program.
mov ax,regUserAX
pop ds
pop es
iret
DXCODE ends
WowIntr3216 proc
FCLI
push ebp
mov ebp,esp
mov regUserAX,eax
mov regUserBX,ebx
mov regUserDS,ds
mov regUserES,es
mov regUserFlags,[ebp + 16]
mov ax,SEL_DXDATA OR STD_RING
mov ds,ax
assume ds:dgroup
mov ebx,esp
mov regUserSp,eax
mov ax,ss
mov regUserSs,ax
mov bx,pbReflStack ; get pointer to new frame
sub bpReflStack,CB_STKFRAME
;
; put user stack pointer on new stack
;
sub bx,4
mov [bx],regUserSs
sub bx,4
mov [bx],dword ptr regUserSp
mov ax,[ebp + 4]
;
; switch to new stack
;
push ds
pop ss
movzx esp,bx
;
; Save ss:esp for lss esp
;
push regUserSs
push regUserSp
;
; Create an int frame
;
pushf
push cs
push offset wi30
;
; Put handler address on stack
;
shl ax,3
mov es,selDgroupPM
add bx,offset DGROUP:Intr16Vector
push [bx + 2]
push [bx]
;
; Restore Registers
;
mov eax,regUserAx
mov ebx,regUserBX
mov es,regUserES
mov ds,regUserDS
assume ds:nothing
;
; call handler
;
retf
wi30:
;
; handler will return here
;
;
; Switch stacks
;
mov ebp,esp
lss esp,[ebp]
mov ebp,esp
push eax
push ds
mov ds,SEL_DXDATA OR STD_RING
assume ds:DGROUP
;
; Deallocate stack frame
;
add pbReflStack,CB_STKFRAME
pop eax
pop ds
;
; Return flags from int handler
;
pushf
pop [ebp + 16]
;
; Return to interrupted code
;
pop ebp
riretd
WowIntr3216 endp