; INT17.ASM
;
; A short passive TSR that replaces the BIOS' int 17h handler.
; This routine demonstrates the function of each of the int 17h
; 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

OldInt17	dword	?


; BIOS variables:

PrtrBase	equ	8
PrtrTimeOut	equ	78h




; This code handles the INT 17H operation.  INT 17H is the BIOS routine
; to send data to the printer and report on the printer's status.  There
; are three different calls to this routine, depending on the contents
; of the AH register.  The DX register contains the printer port number.
;
; DX=0 -- Use LPT1:
; DX=1 -- Use LPT2:
; DX=2 -- Use LPT3:
; DX=3 -- Use LPT4:
;
; AH=0 -- Print the character in AL to the printer.  Printer status is
;	  returned in AH.  If bit #0 = 1 then a timeout error occurred.
;
; AH=1 -- Initialize printer.  Status is returned in AH.
;
; AH=2 -- Return printer status in AH.
;
;
; The status bits returned in AH are as follows:
;
;  Bit    Function			Non-error values
;  ---   --------------------------     ----------------
;   0     1=time out error			0
;   1       unused				x
;   2       unused				x
;   3     1=I/O error				0
;   4     1=selected, 0=deselected.		1
;   5     1=out of paper			0
;   6     1=acknowledge				x
;   7     1=not busy				x
;
; Note that the hardware returns bit 3 with zero if an error has occurred,
; with one if there is no error.  The software normally inverts this bit
; before returning it to the caller.
;
;
; Printer port hardware locations:
;
; There are three ports used by the printer hardware:
;
; PrtrPortAdrs   ---  Output port where data is sent to printer (8 bits).
; PrtrPortAdrs+1 ---  Input port where printer status can be read (8 bits).
; PrtrPortAdrs+2 ---  Output port where control information is sent to the
;		      printer.
;
; Data output port- 8-bit data is transmitted to the printer via this port.
;
; Input status port:
;			bit 0: unused.
;			bit 1: unused.
;			bit 2: unused.
;
;			bit 3: -Error, normally this bit means that the
;				printer has encountered an error.  However,
;				with the P101 installed this is a data
;				return line for the keyboard scan.
;
;			bit 4: +SLCT, normally this bit is used to determine
;				if the printer is selected or not.  With the
;				P101 installed this is a data return
;				line for the keyboard scan.
;
;			bit 5: +PE, a 1 in this bit location means that the
;				printer has detected the end of paper.  On
;				many printer ports, this bit has been found
;				to be inoperative.
;
;			bit 6: -ACK, A zero in this bit position means that
;				the printer has accepted the last character
;				and is ready to accept another.  This bit
;				is not normally used by the BIOS as bit 7
;				also provides this function (and more).
;
;			bit 7: -Busy, When this signal is active (0) the
;				printer is busy and cannot accept data.
;				When this bit is set to one, the printer
;				can accept another character.
;
;
;
; Output control port:
;
; 			Bit 0: +Strobe, A 0.5 us (minimum) active high pulse
;				on this bit clocks the data latched into the
;				printer data output port to the printer.
;
;			Bit 1: +Auto FD XT - A 1 stored at this bit causes
;				the printer to line feed after a line is
;				printed.  On some printer interfaces (e.g.,
;				the Hercules Graphics Card) this bit is
;				inoperative.
;
;			Bit 2: -INIT, a zero on this bit (for a minimum of
;				50 us) will cause the printer to (re)init-
;				ialize itself.
;
;			Bit 3: +SLCT IN, a one in this bit selects the
;				printer.  A zero will cause the printer to
;				go off-line.
;
;			Bit 4: +IRQ ENABLE, a one in this bit position
;				allows an interrupt to occur when -ACK
;				changes from one to zero.
;
;			Bit 5: Direction control on BI-DIR port. 0=output,
;			       1=input.
;			Bit 6: reserved, must be zero.
;			Bit 7: reserved, must be zero.

MyInt17		proc	far
		assume	ds:nothing

		push	ds
		push	bx
		push	cx
		push	dx

		mov	bx, 40h			;Point DS at BIOS vars.
		mov	ds, bx

		cmp	dx, 3			;Must be LPT1..LPT4.
		ja	InvalidPrtr

		cmp	ah, 0		;Branch to the appropriate code for
		jz	PrtChar		; the printer function
		cmp	ah, 2
		jb	PrtrInit
		je	PrtrStatus

; If they passed us an opcode we don't know about, just return.

InvalidPrtr:	jmp	ISR17Done



; Initialize the printer by pulsing the init line for at least 50 us.
; The delay loop below will delay well beyond 50 usec even on the fastest
; machines.

PrtrInit:	mov	bx, dx			;Get printer port value.
		shl	bx, 1			;Convert to byte index.
		mov	dx, PrtrBase[bx]	;Get printer base address.
		test	dx, dx			;Does this printer exist?
		je	InvalidPrtr		;Quit if no such printer.
		add	dx, 2			;Point dx at control reg.
		in	al, dx			;Read current status.
		and	al, 11011011b		;Clear INIT/BIDIR bits.
		out	dx, al			;Reset printer.
		mov	cx, 0			;This will produce at least
PIDelay:	loop	PIDelay			; a 50 usec delay.
		or	al, 100b		;Stop resetting printer.
		out	dx, al
		jmp	ISR17Done


; Return the current printer status.  This code reads the printer status
; port and formats the bits for return to the calling code.

PrtrStatus:	mov	bx, dx			;Get printer port value.
		shl	bx, 1			;Convert to byte index.
		mov	dx, PrtrBase[bx]	;Base address of printer port.
		mov	al, 00101001b		;Dflt: every possible error.
		test	dx, dx			;Does this printer exist?
		je	InvalidPrtr		;Quit if no such printer.
		inc	dx			;Point at status port.
		in	al, dx			;Read status port.
		and	al, 11111000b		;Clear unused/timeout bits.
		jmp	ISR17Done



; Print the character in the accumulator!

PrtChar:        mov	bx, dx
		mov	cl, PrtrTimeOut[bx]	;Get time out value.
		shl	bx, 1			;Convert to byte index.
		mov	dx, PrtrBase[bx]	;Get Printer port address
		or	dx, dx			;Non-nil pointer?
		jz	NoPrtr2			; Branch if a nil ptr

; The following code checks to see if an acknowlege was received from
; the printer.  If this code waits too long, a time-out error is returned.
; Acknowlege is supplied in bit #7 of the printer status port (which is
; the next address after the printer data port).

		push	ax
		inc	dx			;Point at status port
		mov	bl, cl			;Put timeout value in bl
		mov	bh, cl			; and bh.
WaitLp1:	xor	cx, cx			;Init count to 65536.
WaitLp2:	in	al, dx			;Read status port
		mov	ah, al			;Save status for now.
		test	al, 80h			;Printer acknowledge?
		jnz	GotAck			;Branch if acknowledge.
		loop	WaitLp2			;Repeat 65536 times.
		dec	bl			;Decrement time out value.
		jnz	WaitLp1			;Repeat 65536*TimeOut times.

; See if the user has selected no timeout:

		cmp     bh, 0
		je	WaitLp1

; TIMEOUT ERROR HAS OCCURRED!
;
; A timeout - I/O error is returned to the system at this point.
; Either we fall through to this point from above (time out error) or
; the referenced printer port doesn't exist.  In any case, return an error.

NoPrtr2:	or	ah, 9			;Set timeout-I/O error flags
		and	ah, 0F9h		;Turn off unused flags.
		xor	ah, 40h			;Flip busy bit.

; Okay, restore registers and return to caller.

		pop	cx			;Remove old ax.
		mov	al, cl			;Restore old al.
		jmp	ISR17Done


; If the printer port exists and we've received an acknowlege, then it's
; okay to transmit data to the printer.  That job is handled down here.

GotAck:		mov	cx, 16			;Short delay if crazy prtr
GALp:		loop	GALp			; needs hold time after ack.
		pop	ax			;Get char to output and
		push	ax			; save again.
		dec	dx			;Point DX at printer port.
		pushf				;Turn off interrupts for now.
		cli
		out	dx, al			;Output data to the printer.

; The following short delay gives the data time to travel through the
; parallel lines.  This makes sure the data arrives at the printer before
; the strobe (the times can vary depending upon the capacitance of the
; parallel cable's lines).

		mov	cx, 16			;Give data time to settle
DataSettleLp:	loop	DataSettleLp		; before sending strobe.

; Now that the data has been latched on the printer data output port, a
; strobe must be sent to the printer.  The strobe line is connected to
; bit zero of the printer port.  Also note that this clears bit 5 of the
; control port.  This ensures that the port continues to operate as an
; output port if it is a bidirectional device.  This code also clears bits
; six and seven which IBM claims should be left zero.

		inc	dx			;Point DX at the printer
		inc	dx			; control output port.
		in	al, dx			;Get current control bits.
		and	al, 01eh		;Force strobe line to zero and
		out	dx, al			; make sure it's an output port.


		mov	cx, 16			;Short delay to allow data
Delay0:		loop	Delay0			; to become good.

		or	al, 1			;Send out the (+) strobe.
		out	dx, al			;Output (+) strobe to bit 0

		mov	cx, 16			;Short delay to lengthen strobe
StrobeDelay:	loop	StrobeDelay

		and	al, 0FEh		;Clear the strobe bit.
		out	dx, al			;Output to control port.
		popf				;Restore interrupts.

		pop	dx			;Get old AX value
		mov	al, dl			;Restore old AL value

ISR17Done:	pop	dx
		pop	cx
		pop	bx
		pop	ds
		iret
MyInt17		endp





Main		proc

		mov	ax, cseg
		mov	ds, ax

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

; Patch into the INT 17 interrupt vector.  Note that the
; statements above have made cseg the current data segment,
; so we can store the old INT 17 value directly into
; the OldInt17 variable.

		cli				;Turn off interrupts!
		mov	ax, 0
		mov	es, ax
		mov	ax, es:[17h*4]
		mov	word ptr OldInt17, ax
		mov	ax, es:[17h*4 + 2]
		mov	word ptr OldInt17+2, ax
		mov	es:[17h*4], offset MyInt17
		mov	es:[17h*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		byte	1024 dup ("stack   ")
sseg		ends

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