    title COM - Serial Line I/O for PC

; Initialize access to Serial Line,
; set up Interrupt Service.
;   void com_init()

; Disable Interrupt Service
;   void com_disable()

; Send byte out on Serial Line
;   void com_putc(c)
;   int c;

; Recieve byte from Serial Line (de-queue from buffer)
;   int com_getc()
;   returns -1 if no character available,
;   otherwise returns character.



        .MODEL LARGE,C

; Interrupt Number base for PIC chip on IBM PC
PIC             EQU 8


; MS-DOS System-Call
DOS             EQU 21h

Device   EQU 3F8h       ;  COM1
Intno    EQU 4
;Device   EQU 2F8h       ; COM2
;Intno    EQU 3


; Un-initialized data segment
        .DATA?

OLD_VECTOR DW 2 DUP(?)

; Allocation of Circular Character Queue for Serial Input
QUEUE           DB 3200 DUP(?)
QEND            DB ?

QP_IN           DW ?            ; store-pointer
QP_OUT          DW ?            ; retrieve-pointer



        .CODE
        EXTRN TASK:FAR
                   
COM_INIT  PROC USES CX DX DI

; Set UART to 19.2K baud, No Parity, 8 Bits
; Magic stuff from IBM Tech ref Manual
        MOV DX,Device
        ADD DX,3           ; UCR 
        MOV AL,80h
        OUT DX,AL         ; set DLAB=1
        NOP

        SUB DX,2
        MOV AL,0          ; High order of baud rate divisor
        OUT DX,AL
        NOP

        MOV AL,6        ; 19.2K   
;        MOV AL,12       ; 9600 Baud
        DEC DX
        OUT DX,AL       ; low order of Baud-rate divisor
        NOP

        ADD DX,3
        MOV AL,3          ; no parity, 1 Stop, 8 bits
        OUT DX,AL
        NOP

; Modem Control (none)
        MOV DX,Device
        ADD DX,2        ; MCR
        MOV AL,023h     ; REN+DTR+RTS
        OUT DX,AL
        NOP

; Clear the input UART
        MOV DX,Device
        IN AL,DX

; Initialize Queue Pointers
        LEA AX,QUEUE
        MOV QP_IN,AX
        MOV QP_OUT,AX

; Define Interrupt Service for input

; Remember original Interrupt Vector
        PUSH ES
        MOV AX,0         ; access low memory
        MOV ES,AX
        MOV DI,4*(Intno+PIC)
        MOV AX,ES:[DI]
        MOV OLD_VECTOR,AX
        MOV AX,ES:[DI+2]
        MOV OLD_VECTOR+2,AX
        POP ES

; set new vector
        LEA DX,COM_ISR         ; offset  of service routine
        MOV AL,PIC
        ADD AL,Intno            ; Interrupt Number
        MOV AH,25h              ; "Set Interrupt Vector"
        PUSH DS
        MOV CX,CS               ; segment of service routine
        MOV DS,CX
        INT DOS
        POP DS

; IBM PC requires GP02 modem bit to be on
; in order to get serial interrupts
        MOV DX,Device
        ADD DX,4
        MOV AL,0Bh              ; GP02+RTS+DTR
        OUT DX,AL

; Enable the Interrupt
        MOV DX,Device
        INC DX                  ; enable the Chip
        MOV AL,1
        OUT DX,AL
        IN AL,21h               ; Unmask the PIC bit
        MOV AH,1
        MOV CL,Intno
        SHL AH,CL
        NOT AH
        AND AL,AH
        OUT 21h,AL

        RET
COM_INIT  ENDP


COM_DISABLE PROC USES CX

        IN AL,21h
        MOV AH,1
        MOV CL,Intno
        SHL AH,CL
        OR AL,AH              ; Mask the PIC bit
        OUT 21h,AL

; restore original interrupt vector

        CLI             ; disable interrupts
        PUSH ES
        PUSH DI
        MOV AX,0         ; access low memory
        MOV ES,AX
        MOV DI,4*(Intno+PIC)
        MOV AX,OLD_VECTOR
        MOV ES:[DI],AX       ; stuff vector
        MOV AX,OLD_VECTOR+2
        MOV ES:[DI+2],AX
        POP DI
        POP ES
        STI             ; re-enable interrupts

        RET

COM_DISABLE ENDP


; Interrupt Service Routine for serial input
; ------------------------------------------

COM_ISR     PROC FAR
        PUSH DS
        PUSH DX
        PUSH AX
        PUSH DI

; Access data segment
        MOV AX,@DATA
        MOV DS,AX   

; Read Input byte
        MOV DX,Device
        IN AL,DX

; Store in Queue
        MOV DI,QP_IN
        MOV [DI],AL
        INC DI                     ; advance pointer
        CMP DI,OFFSET DGROUP:QEND
        JNE IL1
        MOV DI,OFFSET DGROUP:QUEUE ; wrap to beginning
IL1:    MOV QP_IN,DI

; Dismiss interrupt
        MOV AL,60h                 ; specific EOI
        ADD AL,Intno
        OUT 20h,AL

        POP DI
        POP AX
        POP DX
        POP DS
        IRET
COM_ISR     ENDP



COM_GETC  PROC USES CX SI

; Get Character for serial input queue
; (data is stored by ISR)
        MOV SI,QP_OUT
        CMP SI,QP_IN
        JE NOGOT
        MOV AL,[SI]             ; get character
        MOV AH,0                ; make int
        INC SI                  ; advance queue pointer
        LEA CX,QEND
        CMP SI,CX               ; if at end,
        JNE STOR
        LEA SI,QUEUE            ;   wrap to beginning
STOR:   MOV QP_OUT,SI
        JMP PCSXIT
NOGOT:  
; do keyboard check so that CTL-C will work
        MOV AH,0Bh
        INT DOS
        MOV AX,-1               ; return -1
PCSXIT: RET
COM_GETC  ENDP



COM_PUTC  PROC USES DX, OUTBYTE:WORD

        MOV DX,Device
        MOV AX,OUTBYTE          ; get character
        OUT DX,AL               ; send out

        ADD DX,5
PL1:    CALL TASK
        IN AL,DX                ; Check Status
        TEST AL,20h             ; XMTR buffer empty?
        JZ PL1                  ; not yet

TDONE:
        RET
COM_PUTC  ENDP

; pcit -- return value of PC's interval timer
; (useful for consistent timeouts)

;   long pcit()
;   -- returns value in units of 1/18.2065 seconds
;      (value goes to 0 at midnight)

PCIT    PROC USES CX

; make BIOS call: Int 1A/00
        MOV AH,0
        INT 1Ah
; result in CX:DX

; C convention is DX:AX
        MOV AX,DX
        MOV DX,CX

        RET
PCIT    ENDP

        END



