	INCLUDE	C:TITLE.MAC
	.TITLE	<LPT_PKG -- LPT1: Routines for Lattice C>
	.SBTTL	<History and Copyright Notice>

; lpt_pkg.asm  18 Nov 83  Craig Milo Rogers at USC/ISI
;	Use int_pkg routines to set/restore interrupt vectors.
; lpt_pkg.asm  16 Nov 83  Craig Milo Rogers at USC/ISI
;	Converted to PDP-11-like TITLEs.
;	Converted to STRUCT for control data.
; lpt_pkg.asm  9 Nov 83  Craig Milo Rogers at USC/ISI
;	Created from com_pkg.asm.
;
;	These routines provide an interrupt-driven circular buffer
; interface to the LPT1: device.  This version interfaces with the
; multi-model Lattice C compiler version 1.05.  This package was
; adapted from the "com_pkg", which in turn was adapted from "COMM-PKG".
; See below:
;
; COM_PKG1 provides a library of serial port routines
; Adapted from code by John Romkey and Jerry Saltzer of MIT
; by Richard Gillmann (GILLMANN@ISIB), 1983
;

	.SBHED	Overview

;	This is a module of routines for interfacing with the
; LPT1: communications interface on the IBM PC.  The code has
; been carefully constructed to properly drive the printer interface
; and the 8259 Interrupt Controller.  Internal circular buffers
; are used for transmit and receive.

;	Only one LPT: is supported at present.  A unit number is
; included in the calls to provide for multiple-printer support in
; the future.

;	The LPT: interrupt source is the -ACKNOWLEGE signal from
; the printer.  In the case of the Epson MX and FX printers, -ACKNOWLEGE
; goes active (low) for 5 usec when a character has been processed,
; then returns to inactive (high).  Since the -ACKNOWLEGE signal is
; inverted by the printer interface card before it is presented to
; the IRQ7 line on the IBM-PC's bus, it is the high-to-low transition
; of -ACKNOWLEGE which causes the low-to-high transition of IRQ7, which
; in turn triggers the interrupt sequence in the 8259.  This, in turn,
; triggers an interrupt sequence in the 8088 processor.
;
;	Thus, it is the high-to-low transition of -ACKNOWLEGE which
; starts the interrupt sequence.  However, since -ACKNOWLEGE is low
; for only 5 usec, it may have returned to its high state before the
; 8088 sends INTA to the 8259 to acknowlege the interrupt.  This is
; a violation of the 8259 specification.  The 8259 will then generate
; a "DEFAULT" interrupt request cycle, instead of a normal IR7 cycle.
; However, since the DEFAULT cycle just happens to be IR7, too, it
; all works out OK.
;
;	The 8259's restriction on IRn pulse length is intended to
; catch static on the IRn lines, and/or malfunctioning devices.
; It is entirely possible that a DEFAULT IR7 may be generated for
; some reason other than the line printer.  The line printer interrupt
; routine attempts to protect against this case by checking the
; BUSY bit in the interface.  Everything would probably work a lot
; better if the line printer interface fed the -ACKNOWLEGE signal
; directly to IR7, instead of inverting it first.

;	Entry points (Lattice C 1.05 calling conventions):

; void
; lpt_ini(unit, tbuf, tbuflen, pinit)
;			/* Initializes port and interrupt vector. */
; int unit;		/* 1 ==> LPT1:, 2 ==> LPT2:. */
; char *tbuf;		/* Transmit buffer address. */
; int tbuflen;		/* Transmit buffer length. */
; bool pinit;		/* TRUE ==> force printer initialization. */

; void
; lpt_trm(unit)		/* Turns off interrupts from the aux port. */
; int unit;		/* 1 ==> LPT1:, 2 ==> LPT2:. */

; int			/* Number of free bytes in output buffer. */
; lpt_ocnt(unit)	/* Returns number of free bytes in output buffer. */
; int unit;		/* 1 ==> LPT1:, 2 ==> LPT2:. */

; bool			/* Returns FALSE if no more room. */
; lpt_putc(unit, ch)	/* Writes a character to the output buffer. */
; int unit;		/* 1 ==> LPT1:, 2 ==> LPT2:. */
; char ch;		/* The character to write. */

; int			/* Returns printer status bits. */
; lpt_stat(unit)	/* Reads printer hardware status. */
; int unit;		/* 1 ==> LPT1:, 2 ==> LPT2:. */


	.SBHED	Declarations

IF1
	INCLUDE	DOS.MAC		; C segments.
	INCLUDE BMAC.MAC	; C calling conventions.
ENDIF

				; int_pkg routines:
	BEXTRN	INT_SETU	; Setup an interrupt vector.
	BEXTRN	INT_REST	; Restore an interrupt vector.

				; LPT: parameters:
LPT_INT      EQU     7		; Interrupt number for printer port.
PRINTER_BASE EQU     408H	; Address of BIOS table containing
				; the addresses of the printers.

INT_OFF EQU     08H		; Converts 8029 interrupt numbers to
				; 8088 interrupt numbers.

				; Printer device registers:
DATREG  EQU     0H		; Data register.
STATREG EQU	1H		; Status bits from the printer.
CMDREG	EQU	2H		; Command bits to the printer. 

				; Printer status bits:
BUSY	EQU	80H		; Printer is busy, issue no commands.
ACK	EQU	40H		; Printer ready acknowlegement pulse.
PAPER	EQU	20H		; Paper end -- out of paper.
SELECTD	EQU	10H		; Printer is selected.
ERR	EQU	08H		; Error line.

				; Printer command bits:
IRQE	EQU	10H		; Interrupt request enable.
SELECT	EQU	08H		; Select input to printer.
INIT	EQU	04H		; Initialize printer.
AUTOF	EQU	02H		; Auto line feed after carriage return.
STROBE	EQU	01H		; Data strobe pulse.

				; 8259 interrupt controller:
IMR	EQU	21H		; Interrupt mask register.
OCW2    EQU     20H             ; Operational control word.
EOI     EQU     60H		; Specific end of interrupt.

				; Interface to C language
TRUE    EQU     1               ; Truth.
FALSE   EQU     0               ; Falsehood.


LPTX_CTRL	STRUC		; Line printer control structure:

TBUF_SEG	DW	?	; Transmit buffer segment number.
TBUF_OFF	DW	?	; Transmit buffer offset.
TBUF_SIZE	DW	?	; Transmit buffer size.

START_TDATA     DW      ?       ; Index to first character in x-mit buffer.
END_TDATA       DW      ?       ; Index to first free space in x-mit buffer.
SIZE_TDATA      DW      ?       ; Number of characters in x-mit buffer.

LPTX_BASE	DW	?	; I/O base address of printer registers.

LPTX_CTRL	ENDS		; End of the LPT control structure.

	.SBHED	<Data Storage>

	DSEG

LPT1_CTRL	LPTX_CTRL <>	; Control parameters for LPT1:

	ENDDS

	PSEG			; All the rest is code.

	.SBHED	<LPT: Interrupt Handler>
;
; INT_HNDLR - Handles Interrupts Generated by LPT:
;
;	WARNING!
;	Note the impure use of DATASEG below.  This code is not ROMmable.
;
;	There is no provision for recovery in the face of printer errors.
;
DATASEG DW      0		; Holds our data segment number.

INT_HNDLR PROC  FAR		;;; Enter here on interrupt.
        PUSH    DS		;;; Save data segment register.
        PUSH	CS:DATASEG	;;; Set up new data segment.
        POP	DS		;;;

        PUSH    ES		;;; Save previous context on existing stack.
        PUSH    BP		;;;
        PUSH    SI		;;;
        PUSH    DI		;;;
        PUSH    AX		;;;
        PUSH    BX		;;;
        PUSH    CX		;;;
        PUSH    DX		;;;

	MOV	SI,OFFSET LPT1_CTRL ;;; Setup pointer to control structure.

				;;; Clear the interrupt request first
				;;; so new request pulses will not
				;;; be ignored.
        MOV     DX,OCW2         ;;; Tell the 8259 that I'm done.
        MOV     AL,EOI		;;; Get the End-of-Interrupt code.
	OR	AL,LPT_INT	;;; Set to specific int. number.
        OUT     DX,AL		;;;

REPOLL:
	MOV	DX,[SI].LPTX_BASE	;;; Get LPT: base register.
        ADD	DX,CMDREG	;;; Point to command bits.
        IN      AL,DX		;;; Read the command bits.
        TEST	AL,IRQE		;;; Is interrupt request enable on?
         JZ     INT_END		;;; No, return from interrupt.
	ADD	DX,(STATREG-CMDREG)	;;; Point to status bits.
	IN	AL,DX		;;; Read the status bits.
	TEST	AL,BUSY		;;; Is the printer still busy?
	 JZ	INT_END		;;; Yes, ignore this interrupt.

GOODTX: CMP     [SI].SIZE_TDATA,0    ;;; See if any more data to send.
	 JNE	HAVE_DATA       ;;; If not equal then there is data to send.

;;; If no data to send then reset tx interrupt and return.
        ADD	DX,(CMDREG-STATREG) ;;;
        MOV     AL,(SELECT+INIT) ;;;
        OUT     DX,AL		;;;
        JMP SHORT INT_END	;;;

HAVE_DATA:
	MOV	ES,[SI].TBUF_SEG ;;; Get transmit buffer segment number.
	MOV	DI,[SI].TBUF_OFF ;;; Get transmit buffer offset.
        MOV     BX,[SI].START_TDATA  ;;; BX points to next char. to be sent.
	MOV	DX,[SI].LPTX_BASE	;;;
        ADD	DX,DATREG       ;;; DX equals port to send data to.
        MOV     AL,ES:[BX+DI]   ;;; Get data from buffer.
        OUT     DX,AL           ;;; Put data in output register.
	ADD	DX,(CMDREG-DATREG) ;;; Point to command register.
        MOV     AL,(IRQE+SELECT+INIT+STROBE) ;;; Prepare to strobe data.
	OUT	DX,AL		;;; Set strobe high.
        MOV     AL,(IRQE+SELECT+INIT) ;;;
	OUT	DX,AL		;;; Set strobe low.
        INC     BX              ;;; Increment START_TDATA.
        CMP     BX,[SI].TBUF_SIZE	;;; See if gone past end.
	 JB	NTADJ           ;;; If not then skip.
        XOR     BX,BX		;;; Reset to beginning.
NTADJ:  MOV     [SI].START_TDATA,BX  ;;; Save START_TDATA.
        DEC     [SI].SIZE_TDATA      ;;; One less character in x-mit buffer.
	JMP	REPOLL		;;; Check again is ready for next char.

INT_END:
        POP     DX		;;; Restore previous context.
        POP     CX		;;;
        POP     BX		;;;
        POP     AX		;;;
        POP     DI		;;;
        POP     SI		;;;
        POP     BP		;;;
        POP     ES		;;;
        POP     DS		;;;
        IRET			;;; Return from interrupt.

INT_HNDLR ENDP

	.SBHED	<LPT_INI -- Initialize Communication Port>

; void
; lpt_ini(unit, tbuf, tbuflen, pinit)
;			/* Initializes port and interrupt vector. */
; int unit;		/* 1 ==> LPT1:, 2 ==> LPT2:. */
; char *tbuf;		/* Transmit buffer address. */
; int tbuflen;		/* Transmit buffer length. */
; bool pinit;		/* TRUE ==> force printer initialization. */

;	Initialize the Intel 8250 and set up interrupt vector to int_hndlr.

IF LDATA
	BENTRY	LPT_INI	<UNIT,TBOFF,TBSEG,TBLEN,PINIT>
ELSE
	BENTRY	LPT_INI	<UNIT,TBOFF,TBLEN,PINIT>
ENDIF

        MOV     AX,DS		; Copy our data segment number.
IFE LDATA
	MOV	ES,AX		; Save for buffer addresses.
ENDIF
        MOV     CS:DATASEG,AX	; Store segment # in code space (gulp!).

	MOV	SI,OFFSET LPT1_CTRL ; Setup pointer to control structure.

				; Pickup printer port from BIOS:
        PUSH    DS		; Save current data segment.
        XOR     AX,AX		; Zero AX.
        MOV     DS,AX		; Switch to segment zero.
	MOV	AX,WORD PTR DS:PRINTER_BASE ; Get the printer port.
	POP	DS		; Restore our data segment.
	MOV	[SI].LPTX_BASE,AX	; Save printer base address.

IF LDATA
	MOV	AX,TBSEG	; Get the transmit buffer segment number.
ELSE
	MOV	AX,ES		; Default transmit buffer segment number.
ENDIF
	MOV	[SI].TBUF_SEG,AX	; Save it.
	MOV	AX,TBOFF	; Copy the transmit buffer offset.
	MOV	[SI].TBUF_OFF,AX	;
	MOV	AX,TBLEN	; Copy the transmit buffer length.
	MOV	[SI].TBUF_SIZE,AX	;

	XOR	AX,AX		; Clear the accumulator.
	MOV	[SI].START_TDATA,AX	; Reset start of transmitted data.
	MOV	[SI].END_TDATA,AX	; Reset end of transmitted data.
	MOV	[SI].SIZE_TDATA,AX	; Reset number of transmitted chars.

	CMP	WORD PTR PINIT,0	; Do we want a printer init?
	 JE	NOINIT		;   (nope)
	MOV	DX,[SI].LPTX_BASE	; Get printer base address.
	ADD	DX,CMDREG	; Point to command register.
        MOV     AL,(SELECT)
        OUT     DX,AL		; Start initialization.

	MOV	AX,1000		; Prepare to burn some time.
INILOP:	DEC	AX
	JNZ	INILOP

        MOV     AL,(SELECT+INIT)
        OUT     DX,AL		; Stop initialization.
NOINIT:

				; Setup the LPT interrupt vector:
	MOV	AX,(LPT_INT+INT_OFF)	; Get the LPT: interrupt number.
	MOV	BX,OFFSET INT_HNDLR	; Start of the interrupt routine.
	BCALL	INT_SETU <AX BX CS>	; Call int_setup(vec, newip, newcs).

        CLI			; ******* Disable Interrupts *******

				;;; Enable interrupts on 8259:
        IN      AL,IMR          ;;; Get current enable bits on 8259.
	MOV	CL,LPT_INT	;;; Get interrupt number.
	MOV	BL,1		;;; Convert to
	SHL	BL,CL		;;;   bit position.
	NOT	BL		;;; Clear current
        AND     AL,BL		;;;   interrupt bit.
        OUT     IMR,AL		;;; Set enable on 8259.

        STI			;;; ******* Enable Interrupts *******
				;;; (Next instruction still disabled)
	BEND	LPT_INI

	.SBHED	<LPT_TRM -- Turn Off Interrupts and Shutdown>

; void
; lpt_trm(unit)		/* Turns off interrupts from the LPT: port. */
; int unit;		/* 1 ==> LPT1:, 2 ==> LPT2:. */

	BENTRY	LPT_TRM <UNIT>

	MOV	SI,OFFSET LPT1_CTRL ; Setup pointer to control structure.

	MOV	DX,[SI].LPTX_BASE
	ADD	DX,CMDREG	; Turn off line printer interface.
        MOV     AL,(SELECT+INIT)
        OUT     DX,AL

        IN      AL,IMR		; Turn off 8259 interrupt controller.
	MOV	CL,LPT_INT	; Get interrupt number.
	MOV	BL,1		; Convert to
	SHL	BL,CL		;   bit position.
        OR      AL,BL		; Disable this interrupt.
        OUT     IMR,AL

				; Restore the LPT interrupt vector:
	MOV	AX,(LPT_INT+INT_OFF)	; Get the LPT: interrupt number.
	BCALL	INT_REST <AX>		; Call int_restore(vec).

	BEND	LPT_TRM

	.SBHED	<LPT_OCNT -- Returns Number of Free Bytes>

; int			/* Number of free bytes in output buffer. */
; lpt_ocnt(unit)	/* Returns number of free bytes in output buffer. */
; int unit;		/* 1 ==> LPT1:, 2 ==> LPT2:. */

	BENTRY	LPT_OCNT <UNIT>

	MOV	SI,OFFSET LPT1_CTRL ; Setup pointer to control structure.

        MOV     AX,[SI].TBUF_SIZE	; Get the size of the x-mit buffer.
        SUB     AX,[SI].SIZE_TDATA	; Subtract the number of bytes used.

	BEND	LPT_OCNT

	.SBHED	<LPT_PUTC -- Queue a Character for Output>

; bool			/* Returns FALSE if no more room. */
; lpt_putc(unit, ch)	/* Writes a character to the output buffer. */
; int unit;		/* 1 ==> LPT1:, 2 ==> LPT2:. */
; char ch;		/* The character to write. */


	BENTRY	LPT_PUTC <UNIT,OCHAR>

	MOV	SI,OFFSET LPT1_CTRL ; Setup pointer to control structure.

        MOV     AX,[SI].TBUF_SIZE	; Get the size of the x-mit buffer.
        SUB     AX,[SI].SIZE_TDATA	; Subtract the number of bytes used.
	 JE	L24		; No more free space.

	MOV	ES,[SI].TBUF_SEG	; Get transmit buffer segment number.
	MOV	DI,[SI].TBUF_OFF	; Get transmit buffer offset.
        MOV     BX,[SI].END_TDATA	; BX points to free space.
        MOV     AL,OCHAR        ; Move data from stack to x-mit buffer.
        MOV     ES:[BX+DI],AL
        INC     BX              ; Increment END_TDATA to point to free space.
        CMP     BX,[SI].TBUF_SIZE	; See if past end.
         JB      L20		; If not then skip.
        XOR     BX,BX		; Adjust to beginning.
L20:	MOV     [SI].END_TDATA,BX	; Save new END_TDATA.

        INC     [SI].SIZE_TDATA	; One more character in x-mit buffer.
	MOV	DX,[SI].LPTX_BASE ; Prepare to manipulate printer interrupts.
        ADD	DX,CMDREG	; Point to printer command register.
	IN	AL,DX		; Read command register.
	TEST	AL,IRQE		; Are printer interrupts enabled?
	 JNZ	L22		; Yes, so output is active.
        MOV     AL,(IRQE+SELECT+INIT) ; No, so enable printer interrupts.
        OUT     DX,AL		;
	INT	(LPT_INT+INT_OFF) ; Request an interrupt to start output.
L22:
	MOV	AX,TRUE		; Indicate all's OK.
	JMP SHORT L26		; Go join common return code.

L24:	MOV	AX,FALSE	; No more space in buffer.

L26:	BEND	LPT_PUTC

	.SBHED	<LPT_STAT -- Return Line Printer Hardware Status>

; int			/* Returns printer status bits. */
; lpt_stat(unit)	/* Reads printer hardware status. */
; int unit;		/* 1 ==> LPT1:, 2 ==> LPT2:. */

	BENTRY	LPT_STAT <UNIT>

	MOV	SI,OFFSET LPT1_CTRL ; Setup pointer to control structure.

	MOV	DX,[SI].LPTX_BASE	; Get LPT: base register.
        ADD	DX,STATREG	; Point to status bits.
        IN      AL,DX		; Read the status bits.
	XOR	AH,AH		; Clear high bits.
				; Return result in AX.
	BEND	LPT_STAT

	ENDPS
        END
