	.386p
	.387
;************************************************************************/
;*	Copyright (C) 1986-1990 Phar Lap Software, Inc.			*/
;*	Unpublished - rights reserved under the Copyright Laws of the	*/
;*	United States.  Use, duplication, or disclosure by the 		*/
;*	Government is subject to restrictions as set forth in 		*/
;*	subparagraph (c)(1)(ii) of the Rights in Technical Data and 	*/
;*	Computer Software clause at 252.227-7013.			*/
;*	Phar Lap Software, Inc., 60 Aberdeen Ave., Cambridge, MA 02138	*/
;************************************************************************/

;
; This program demonstrates how to write a protected mode hardware 
; interrupt handler.  It takes over the coprocessor exception (IRQ13),
; and then causes a 387 exception to let the handler get invoked.
;

;
; Constants and data structures
;
include	dosx.ah

;
; Segment definitions and ordering
;
_codeseg	segment	byte public use32 'code'
_codeseg	ends
_data		segment	dword public use32 'data'
_data		ends
_stack		segment dword stack use32 'stack'
	db	2048 dup (?)	; 2K stack
_stack		ends

;
; Global data
;
_data	segment	

	public	irq8_vec,irq13_offs,irq13_sel,envblk
irq8_vec	db	?	; interrupt vector for IRQ8 interrupt
	align	4
irq13_offs	dd	?	; original IRQ13 handler address
irq13_sel	dw	?		;
	align	4
ctl_387	dw	0		; 387 control word with exceptions unmasked
	align	4
minus_one dq	-1.0		; floating point constant
envblk	dd	7 dup (?)	; buffer for 387 environment block

no387msg db	'No 387 coprocessor on this machine',0Dh,0Ah,'$'
pmenvmsg db	 '80387 environment block:',0Dh,0Ah,'$'

_data	ends

;****************************************************************************
; Program entry point
;****************************************************************************

	assume	cs:_codeseg,ds:_data
_codeseg	segment	

	public	main
main proc	near

;
; Check for a 387 present, exit if not
;
	int	11h			; BIOS equipment check
	test	ax, 2			; branch if no coprocessor
	jz	#no_coproc			;

;
; Save current IRQ13 handler vector and install our own handler.  We
; only need to hook protected mode interrupts because we are only going
; to execute coprocessor instructions in protected mode, so the coprocessor
; exception will never occur in real mode.
;
	mov	ax, 250Ch		; get H/W int vector mappings
	int	21h				;
	mov	irq8_vec, ah			; 
	mov	cl, irq8_vec		; save current IRQ13 handler
	add	cl, 5				; 
	mov	ax, 2502h			;
	int	21h				; 
	mov	irq13_sel, es			;
	mov	irq13_offs, ebx			;
	push	ds			; install our IRQ13 handler
	lea	edx, irq13_hndlr		; 
	mov	cl, irq8_vec			;
	add	cl, 5				; 
	mov	ax, cs				;
	mov	ds, ax				; 
	mov	ax, 2504h			;
	int	21h				;
	pop	ds				;

;
; Cause a coprocessor exception
;
	finit				; init the 387	
	fldcw	ctl_387			; unmask all exceptions
	fld	minus_one		; square root of a negative number
	fsqrt					;

;
; Restore original IRQ13 handler and exit
;
	push	ds			; restore original handler
	mov	cl, irq8_vec			;
	add	cl, 5				; 
	mov	edx, irq13_offs			; 
	mov	ax, irq13_sel			;
	mov	ds, ax				;
	mov	ax, 2504h			; 
	int	21h				;
	pop	ds				;
	mov	ax, 4C00h		; exit to DOS
	int	21h				;

#no_coproc:
;
; No 387;  print an error message and exit
;
	lea	edx, no387msg		; print message
	mov	ah, 9				;
	int	21h				;
	mov	ax, 4C01h		; exit to DOS
	int	21h				; 
main endp

;****************************************************************************
; IRQ13_HNDLR - Protected Mode Coprocessor Exception Handler
;	This handler just stores the coprocessor environment and prints it
;	out, clears the source of the exception, and returns.
;****************************************************************************
	public	irq13_hndlr
irq13_hndlr proc	near
;
; Save regs and re-enable interrupts.  It's good to re-enable interrupts
; when possible in an interrupt handler, so other hardware interrupts can
; occur.
;
	sti				; re-enable interrupts
	push	eax			; save all registers we modify
	push	ds				;

;
; Acknowledge the interrupt.  Until we do this, neither this interrupt
; (IRQ13) nor any lower priority hardware interrupt can occur.  After
; we do this, we must be prepared to deal with being reentered.  In the
; case of a coprocessor exception handler, we don't have to worry about
; reentrancy because we won't cause another coprocessor exception inside
; the handler.
;
	mov	al, 20h			; issue EOI to master 8259 interrupt
	out	20h, al				; controller
	out	0A0h, al		; issue EOI to slave 8259
	xor	ax, ax			; Clear 387 BUSY signal
	out	0F0h, al			;

;
; Get the coprocessor environment and print it out
;
	mov	ax, SS_DATA		; set DS to our data segment
	mov	ds, ax				; 
	fstenv	envblk			; get 387 environment
	call	display_env		; displays coprocessor environment

;
; Clear the 387 status and return to interrupted code.
;
	wait				; clear exception bits in 387 status
	fclex					; 
	fldcw	ctl_387			; unmask all 387 exceptions
	pop	ds			; restore regs 
	pop	eax				;
	iretd				; return to interrupted code
irq13_hndlr endp

;****************************************************************************
; DISPLAY_ENV - Routine that displays the saved 387 environment block
;	Destroys EAX, preserves all other registers.
;****************************************************************************
	public	display_env
display_env	proc	near
	push	ecx			; save regs we use
	push	edx				;

	lea	edx, pmenvmsg		; print message 
	mov	ah, 09h				;
	int	21h				;

	mov	ecx, 7			; 7 DWORDs in environment
	lea	edx, envblk		; ptr to beginning of block
#loop:
	mov	ax, [edx+2]		; print this doubleword
	call	hwout				;
	mov	ax, [edx]			;
	call	hwout				;
	call	newline			; output newline
	add	edx, 4			; step to next dword in env
	loop	#loop			; continue loop

	pop	edx			; restore registers and exit
	pop	ecx				;
	ret					;

display_env	endp

;****************************************************************************
; hwout - Print word in AX in ascii hex, without modifying any regs except
;	EAX.
;****************************************************************************
hwout	proc	near
	push	edx			; save regs

	push	ax			; save current AX value
	shr	ax,8			; convert MS byte to ascii
	call	btohex				;
	push	ax			; save result
	mov	dl,al			; print MS digit
	mov	ah,2				;
	int	21h				;
	pop	dx			; print LS digit
	mov	dl,dh				;
	int	21h				;
	pop	ax			; convert LS byte to ascii
	call	btohex				;
	push	ax			; save result
	mov	dl,al			; print MS digit
	mov	ah,2				;
	int	21h				;
	pop	dx			; print LS digit
	mov	dl,dh				;
	int	21h				;

	pop	edx			; restore modifed regs & exit
	ret
hwout	endp

btohex	proc	near	; convert byte to hex
	push	bx			; save regs
	mov	ah,0			; make sure AH is 0
	mov	bx,ax			; save value to convert
	call	ntohex			; get LS hex digit into BL
	xchg	ax,bx				;
	shr	ax,4			; get MS hex digit into AL
	call	ntohex				;
	shl	bx,8			; combine the two digits in AX
	or	ax,bx				;
	pop	bx			; restore regs & exit
	ret
btohex	endp

ntohex	proc	near	; convert nibble to ascii hex
	and	ax,0Fh			; mask out all but low nibble
	cmp	ax,10			; branch if digit from 0 - 9
	jb	short #digit			;
	add	ax,'A' - 10		; digit from A-F
	ret					;
#digit:
	add	ax,'0'			; digit from 0-9
	ret
ntohex	endp

;****************************************************************************
; newline - output newline without modifying any regs
;****************************************************************************
newline	proc	near
	push	eax			; save regs
	push	edx				;

	mov	dl,0Dh			; output CR/LF
	mov	ah,2				;
	int	21h				;
	mov	dl,0Ah				;
	int	21h				;

	pop	edx			; restore regs & exit
	pop	eax				;
	ret
newline	endp

_codeseg	ends

	end main
