; INT16.ASM
;
; A short passive TSR that replaces the BIOS' int 16h handler.
; This routine demonstrates the function of each of the int 16h
; functions that a standard BIOS would provide.
;
; Note that this code does not patch into int 2Fh (multiplex interrupt)
; nor can you remove this code from memory except by rebooting.
; If you want to be able to do these two things (as well as check for
; a previous installation), see the chapter on resident programs.  Such
; code was omitted from this program because of length constraints.
;
;
; cseg and EndResident must occur before the standard library segments!

cseg		segment	para public 'code'
cseg		ends

; Marker segment, to find the end of the resident section.

EndResident	segment	para public 'Resident'
EndResident	ends

		.xlist
		include 	stdlib.a
		includelib	stdlib.lib
		.list


byp		equ	<byte ptr>

cseg		segment	para public 'code'
		assume	cs:cseg, ds:cseg

OldInt16	dword	?


; BIOS variables:

KbdFlags1	equ	<ds:[17h]>
KbdFlags2	equ	<ds:[18h]>
AltKpd		equ	<ds:[19h]>
HeadPtr		equ	<ds:[1ah]>
TailPtr		equ	<ds:[1ch]>
Buffer		equ	1eh
EndBuf		equ	3eh

KbdFlags3	equ	<ds:[96h]>
KbdFlags4	equ	<ds:[97h]>

incptr		macro	which
		local	NoWrap
		add	bx, 2
		cmp	bx, EndBuf
		jb	NoWrap
		mov	bx, Buffer
NoWrap:		mov	which, bx
		endm


; MyInt16-	This routine processes the int 16h function requests.
;
;		AH	Description
;		--	------------------------------------------------
;		00h	Get a key from the keyboard, return code in AX.
;		01h	Test for available key, ZF=1 if none, ZF=0 and
;			AX contains next key code if key available.
;		02h	Get shift status.  Returns shift key status in AL.
;		03h	Set Autorepeat rate.  BH=0,1,2,3 (delay time in
;			quarter seconds), BL=0..1Fh for 30 char/sec to
;			2 char/sec repeat rate.
;		05h	Store scan code (in CX) in the type ahead buffer.
;		10h	Get a key (same as 00h in this implementation).
;		11h	Test for key (same as 01h).
;		12h	Get extended key status.  Returns status in AX.


MyInt16		proc	far
		test	ah, 0EFh		;Check for 0h and 10h
		je	GetKey
		cmp	ah, 2			;Check for 01h and 02h
		jb	TestKey
		je	GetStatus
		cmp	ah, 3			;Check for AutoRpt function.
		je	SetAutoRpt
		cmp	ah, 5			;Check for StoreKey function.
		je	StoreKey
		cmp	ah, 11h			;Extended test key opcode.
		je	TestKey
		cmp	ah, 12h			;Extended status call
		je	ExtStatus

; Well, it's a function we don't know about, so just return to the caller.

		iret

; If the user specified ah=0 or ah=10h, come down here (we will not
; differentiate between extended and original PC getc calls).

GetKey:		mov	ah, 11h
		int	16h			;See if key is available.
		je	GetKey			;Wait for keystroke.

		push	ds
		push	bx
		mov	ax, 40h
		mov	ds, ax
		cli				;Critical region! Ints off.
		mov	bx, HeadPtr		;Ptr to next character.
		mov	ax, [bx]		;Get the character.
		incptr	HeadPtr			;Bump up HeadPtr
		pop	bx
		pop	ds
		iret				;Restores interrupt flag.

; TestKey- 	Checks to see if a key is available in the keyboard buffer.
;               We need to turn interrupts on here (so the kbd ISR can
;		place a character in the buffer if one is pending).
;		Generally, you would want to save the interrupt flag here.
;		But BIOS always forces interrupts on, so there may be some
;		programs out there that depend on this, so we won't "fix"
;		this problem.
;
;		Returns key status in ZF and AX.  If ZF=1 then no key is
;		available and the value in AX is indeterminate.  If ZF=0
;		then a key is available and AX contains the scan/ASCII
;		code of the next available key.  This call does not remove
;		the next character from the input buffer.

TestKey:	sti				;Turn on the interrupts.
		push	ds
		push	bx
		mov	ax, 40h
		mov	ds, ax
		cli				;Critical region, ints off!
		mov	bx, HeadPtr
		mov	ax, [bx]		;BIOS returns avail keycode.
		cmp	bx, TailPtr		;ZF=1, if empty buffer
		pop	bx
		pop	ds
		sti				;Inst back on.
		retf	2			;Pop flags (ZF is important!)


; The GetStatus call simply returns the KbdFlags1 variable in AL.

GetStatus:	push	ds
		mov	ax, 40h
		mov	ds, ax
		mov	al, KbdFlags1		;Just return Std Status.
		pop	ds
		iret


; StoreKey-	Inserts the value in CX into the type ahead buffer.

StoreKey:	push	ds
		push	bx
		mov	ax, 40h
		mov	ds, ax
		cli				;Ints off, critical region.
		mov	bx, TailPtr		;Address where we can put
		push	bx			; next key code.
		mov	[bx], cx		;Store the key code away.
		incptr 	TailPtr			;Move on to next entry in buf.
		cmp	bx, HeadPtr		;Data overrun?
		jne	StoreOkay		;If not, jump, if so
		pop	TailPtr			; ignore key entry.
		sub	sp, 2			;So stack matches alt path.
StoreOkay:	add	sp, 2			;Remove junk data from stk.
		pop	bx
		pop	ds
		iret				;Restores interrupts.


; ExtStatus-	Retrieve the extended keyboard status and return it in
;		AH, also returns the standard keyboard status in AL.

ExtStatus:	push	ds
		mov	ax, 40h
		mov	ds, ax

		mov	ah, KbdFlags2
		and	ah, 7Fh			;Clear final sysreq field.
		test	ah, 100b		;Test cur sysreq bit.
		je	NoSysReq		;Skip if it's zero.
		or	ah, 80h			;Set final sysreq bit.
NoSysReq:
		and	ah, 0F0h		;Clear alt/ctrl bits.
		mov	al, KbdFlags3
		and	al, 1100b		;Grab rt alt/ctrl bits.
		or	ah, al			;Merge into AH.
		mov	al, KbdFlags2
		and	al, 11b			;Grab left alt/ctrl bits.
		or	ah, al			;Merge into AH.

		mov	al, KbdFlags1		;AL contains normal flags.
		pop	ds
		iret

; SetAutoRpt-	Sets the autorepeat rate.  On entry, bh=0, 1, 2, or 3 (delay
;		in 1/4 sec before autorepeat starts) and bl=0..1Fh (repeat
;		rate, about 2:1 to 30:1 (chars:sec).

SetAutoRpt:     push	cx
		push	bx

		mov	al, 0ADh		;Disable kbd for now.
		call	SetCmd

		and	bh, 11b			;Force into proper range.
		mov	cl, 5
		shl	bh, cl			;Move to final position.
		and	bl, 1Fh			;Force into proper range.
		or	bh, bl			;8042 command data byte.
		mov	al, 0F3h		;8042 set repeat rate cmd.
		call    SendCmd			;Send the command to 8042.
		mov	al, bh			;Get parameter byte
		call	SendCmd			;Send parameter to the 8042.

		mov	al, 0AEh		;Reenable keyboard.
		call	SetCmd
		mov	al, 0F4h		;Restart kbd scanning.
		call	SendCmd

		pop	bx
		pop	cx
		iret

MyInt16		endp



; SetCmd-	Sends the command byte in the AL register to the 8042
;		keyboard microcontroller chip (command register at
;		port 64h).

SetCmd		proc	near
		push	cx
		push	ax		;Save command value.
		cli			;Critical region, no ints now.

; Wait until the 8042 is done processing the current command.

		xor	cx, cx		;Allow 65,536 times thru loop.
Wait4Empty:	in	al, 64h		;Read keyboard status register.
		test	al, 10b		;Input buffer full?
		loopnz	Wait4Empty	;If so, wait until empty.

; Okay, send the command to the 8042:

		pop	ax		;Retrieve command.
		out	64h, al
		sti			;Okay, ints can happen again.
		pop	cx
		ret
SetCmd		endp




; SendCmd-	The following routine sends a command or data byte to the
;		keyboard data port (port 60h).

SendCmd		proc	near
		push	ds
		push	bx
		push	cx
		mov	cx, 40h
		mov	ds, cx
		mov	bx, ax		;Save data byte

		mov	bh, 3		;Retry cnt.
RetryLp:	cli			;Disable ints while accessing HW.

; Clear the Error, Acknowledge received, and resend received flags
; in KbdFlags4

		and	byte ptr KbdFlags4, 4fh

; Wait until the 8042 is done processing the current command.

		xor	cx, cx			;Allow 65,536 times thru loop.
Wait4Empty:	in	al, 64h			;Read keyboard status register.
		test	al, 10b			;Input buffer full?
		loopnz	Wait4Empty		;If so, wait until empty.

; Okay, send the data to port 60h

		mov	al, bl
		out	60h, al
		sti				;Allow interrupts now.

; Wait for the arrival of an acknowledgement from the keyboard ISR:

		xor	cx, cx			;Wait a long time, if need be.
Wait4Ack:	test	byp KbdFlags4, 10h	;Acknowledge received bit.
		jnz	GotAck
		loop	Wait4Ack
		dec	bh			;Do a retry on this guy.
		jne     RetryLp

; If the operation failed after 3 retries, set the error bit and quit.

		or	byp KbdFlags4, 80h	;Set error bit.

GotAck:		pop	cx
		pop	bx
		pop	ds
		ret
SendCmd		endp



Main		proc

		mov	ax, cseg
		mov	ds, ax

		print
		byte	"INT 16h Replacement",cr,lf
		byte	"Installing....",cr,lf,0

; Patch into the INT 9 and INT 16 interrupt vectors.  Note that the
; statements above have made cseg the current data segment,
; so we can store the old INT 9 and INT 16 values directly into
; the OldInt9 and OldInt16 variables.

		cli				;Turn off interrupts!
		mov	ax, 0
		mov	es, ax
		mov	ax, es:[16h*4]
		mov	word ptr OldInt16, ax
		mov     ax, es:[16h*4 + 2]
		mov	word ptr OldInt16+2, ax
		mov	es:[16h*4], offset MyInt16
		mov	es:[16h*4+2], cs
		sti				;Okay, ints back on.


; We're hooked up, the only thing that remains is to terminate and
; stay resident.

		print
		byte	"Installed.",cr,lf,0

		mov	ah, 62h			;Get this program's PSP
		int	21h			; value.

		mov	dx, EndResident		;Compute size of program.
		sub	dx, bx
		mov	ax, 3100h		;DOS TSR command.
		int	21h
Main		endp
cseg		ends

sseg		segment	para stack 'stack'
stk		db	1024 dup ("stack   ")
sseg		ends

zzzzzzseg	segment	para public 'zzzzzz'
LastBytes	db	16 dup (?)
zzzzzzseg	ends
		end	Main
