	title "rtx serial port facilities"
	page ,132

	;''''''''''''''''''''''''''''''''''''''''''''''''''''''';
	;  Serial Port Init & I/O Facilities                    ;
	;- - - - - - - - - - - - - - - - - - - - - - - - - - - -;
	;                                                       ;
	;  (C) Copyright, 2000   Wm. R Isherwood                ;
	;      All Rights Reserved.                             ;
	;                                                       ;
	;.......................................................;

	include stddefs.asm


;=================
clearFlags  macro
    push ax
    or ax, 1
    pop ax
    endm
;==================


rtx_text  segment  word public 'CODE'
rtx_text  ends


reg_supp  segment para public 'CODE'
	  assume cs:reg_supp, ds:reg_supp, es:reg_supp, ss:reg_supp


	public  com_ISR_odd    ;odd com ports ISR (reentrant)
	public  com_ISR_even   ;even com ports ISR (reentrant)
	public  RS232_ini      ;init any com port (reentrant)
	public  port_IRQ       ;enable/disable a com port IRQ (reentrant)
	public  getComBuffer   ;read char from a port recv buffer (reentrant)
	public  get_8250       ;directly read a UART (reentrant)
	public  put_8250       ;put char to a com port (reentrant)
	public  crlf_8250      ;put CRLF pair to a com port (reentrant)


;==================================================================
;=== Equates and Data =============================================
;
;
;  number of buffers is 1 - 4 & should equal ActiveCcmPorts
;  each buffer size must only be  2^n  or  2, 4, 8, 16, ... 256 max
;  mask is used to maintain maximum value for head & tail pointer bytes
;  buffers are wrap around technique
;

ActiveComPorts  equ    4   ;Number of active ports
sizeComBuffer   equ   64   ;bytes per buffer

comBuffers      equ   ActiveComPorts         ;how many buffers
comMask         equ   (sizeComBuffer - 1)    ;do not modify

;=========================
; com port buffer(s)    buffers might get moved to data seg later
;=========================
    even
IRQcomBuffer:     db  ((sizeComBuffer + 2) * comBuffers) dup(0)

;=========================
; buffer structure
;=========================

comBuffer struc
    head_ptr      db  ?
    tail_ptr      db  ?
    buffer        db  sizeComBuffer
comBuffer ends


;=========================
; com port physical addresses
;=========================
PORT  dw 03f8h,02f8h,03e8h,02e8h        ;add port addeses here
      dw (16 - (($ - PORT)/2)) dup(0)   ;rounds out to 16 possible ports


;=========================
; port timeout delay value... do 65536 times this value in 'waitComPort'
;=========================
PORT_TIMEOUT_DELAY:   db  01    ;init value for error timeout during port R/W


;=========================
; baud rates table
;=========================
BAUD  dw 0900h    ;    50    0
      dw 0600h    ;    75    1
      dw 0417h    ;   110    2
      dw 0359h    ;   134.5  3
      dw 0300h    ;   150    4
      dw 0180h    ;   300    5
      dw 00c0h    ;   600    6
      dw 0060h    ;  1200    7
      dw 0040h    ;  1800    8
      dw 003ah    ;  2000    9
      dw 0030h    ;  2400   10
      dw 0020h    ;  3600   11
      dw 0018h    ;  4800   12
      dw 0010h    ;  7200   13
      dw 000ch    ;  9600   14
      dw 0008h    ; 14400   15
      dw 0006h    ; 19200   16
      dw 0004h    ; 28800   17
      dw 0003h    ; 38400   18
      dw 0002h	  ; 57600   19
      dw 0001h	  ;115200   20
endOfBaudTable:

;=========================
; UART registers & related values
;=========================
	     ; offsets to various UART registers added to
	     ; physical port address

	     DXR equ 000h
	     IER equ DXR+1
	     IIR equ DXR+2
	     LCR equ DXR+3
	     MCR equ DXR+4
	     LSR equ DXR+5
	     MSR equ DXR+6

	     ; UART control

	  MC_INT equ 08h          ;MCR int enable bit
	  RX_INT equ 01h
	     DTR equ 01h
	     RTS equ 02h
SET_BAUD_COMMAND equ 80h          ;command byte to set baud
    BAUD_MASK    equ 00111110b    ;mask out just baud rate

;=========================
; UART default setup values
;=========================

    DEFAULT_UART equ 00000011b    ;n,8,1
    DEFAULT_BAUD equ 14           ;9600



;=========================
; 8259 programmable interrupt controller ( PIC )
;=========================

    IRQ4 equ 0efh   ;PIC mask to enable irq-4
    IRQ3 equ 0f7h   ;PIC mask for irq-3
     ICR equ 020h   ;interrupt control register
     IMR equ 021h   ;interrupt mask register
     EOI equ 020h   ;end of interrupt


;=== End Equates and Data =========================================
;==================================================================


;==================================================================
;=== Code Begins ==================================================

;=========================
; (local) port timeout routine used by send & rcv routines
;=========================
;   do (BL * 65536) if no port responce
;   call with DX = port address
;	      AL = MCR setup (03h for send; 01h for read)
;	      BL = MSR test  (20h for send; 01h for read)
;	      BH = LSR test  (30h for send; 20h for read)
;   returns   Z = no timeout error;  NZ = timeout error

waitComPort:
    push cx
    add dx, MCR - DXR
    mov cl, al             ;temp save al
    in al, dx
    or al, cl		   ;add req'd bits
    out dx, al
    add dx, MSR - MCR
    push bx
    call pollComPort
    pop bx
    jnz waitComPort_10

    dec dx		   ;address LSR
    mov bh, bl		   ;get user LSR mask
    call pollComPort

waitComPort_10:
    pop cx
    retn


;===== part of waitComPort
pollComPort:
    mov bl,  cs: byte ptr [PORT_TIMEOUT_DELAY]
pollComPort_1:             ;outer loop
    xor cx, cx
pollComPort_2:             ;inner loop
    in al, dx
   ;mov ah, al             ;save status
    and al, bh             ;test with mask in BH
    cmp al, bh             ;
    jz pollComPort_10      ;exit if good -->
    loop pollComPort_2     ;else try again
    dec bl                 ;do outer loop BL times
    jnz pollComPort_1      ;again if BL > 0 -->
    or bh, bh              ;report time out error as NZ
pollComPort_10:
    retn


;=========================
; (local) fetch physical port address from our list
;=========================
;   call with DX = port index .. 1,2,3...n
;   returns   DX = port physical address 3f8, 2f8 ...
;	      CF = 0 successful ; CF = 1 failure
;=================
_getPort:                 ;local only
    push bx
    cmp dx, ActiveComPorts   ;port is only in DL
    Jg _getPort_10        ;out of range -->
    mov bx, dx
    dec bx
    shl bx, 1             ;mul x 2
    mov dx, cs:[PORT + bx]   ;fetch
    or dx, dx
    jnz _getPort_20       ;everything OK, done -->
_getPort_10:
    stc                   ;mark problems
_getPort_20:
    pop bx
    retn


;=========================
; (public) RS232_INI  initialize an 8250 comm port
;=========================
;This routine will initialize COM Port index address 1,2,3....n
;    1) various baud rates, see table above nitilized in AH 0,1,2,3...n
;    2) 5-parities: none,odd,even,mark,space
;    3) 2-stops: 1 or 2
;    4) 4-lengths: 5,6,7,8
;
;    Description    Input   7 6 5 4 3 2 1 0
;==================================================================
;    Initialize      AL     x x - - - - - - (don't care)
;                           - - p p p - - - Parity
;                               x x 0 -none
;                               0 0 1 -odd
;                               0 1 1 -even
;                               1 0 1 -mark
;                               1 1 1 -space
;                           - - - - - s - - Stop Bits
;                                     0 -1
;                                     1 -2
;                           - - - - - - l l Length
;                                       0 0 -5
;                                       0 1 -6
;                                       1 0 -7
;                                       1 1 -8
;                    AH     x x x b b b b b Baud Rate 0,1,2,3...n
;
;=========================
;
; int pascal RS232_ini(int Port, int Init_control)
;
; use to initialize com port 1-4
; returns: 0 = error bad port, -1 = success
; RS232_ini is reentrant


    entry RS232_ini

    iarg  __ctrl
    iarg  __port

    push bx

    getarg dx, __port
    call _getPort
    jnc RS232_INI_5    ;CF clear OK -->
    xor ax, ax	       ;mark error and retrun
    je RS232_INI_30    ;error end -->

RS232_INI_5:
    getarg ax, __ctrl
    mov bl, ah         ;mov baud rate code from AH to BL

    cmp bl, 0          ;baud range test. -1 will force defaults
    jl RS232_INI_10    ;jmp if out of range < 0

    cmp bl, ((endOfBaudTable - BAUD)/2 - 1)   ;range test HI
    jle RS232_INI_15   ;if within table then OK else use default

RS232_INI_10:          ;out of range so set up defaults
    mov bl, DEFAULT_BAUD
    mov al, DEFAULT_UART

RS232_INI_15:          ;do baud rate
    push ax            ;temp save ax
    in al, dx
    add dx, LCR - DXR
    in al, dx
    or al, SET_BAUD_COMMAND
    out dx, al         ;output command

    shl bl, 1          ;fetch clock rate from table
    and bx, BAUD_MASK
    mov ax, cs:[bx+BAUD]
    add dx, DXR - LCR
    out dx, al         ;output low order baud rate
    inc dx             ;(+1) address IER
    mov al, ah         ;set up data transfer
    out dx, al         ;output high order baud rate

    pop ax             ;get fresh AX
    add dx, LCR - IER
    and al, 00111111b  ;mask out valid bits
    out dx, al         ;send length,stops,parity to port

    mov ax, -1	       ;mark success

RS232_INI_30:
    pop bx
    return pascal



;=========================
; (public) get a character from a port buffer
;=========================
; int pascal getComBuffer(int Port)
;
; fetch next available character from port input buffer
; Port is a valid initialized comPort 1,2,3...
; return values:  ax [ ah  al ]
;  ax:  0 = error <port buffer not initialized>
;  ax: -1 = no character in buffer
;  ah:  1, al: valid byte character 0 - 255
;
; fetch next available character from a com port
; receive buffer. Character put there by port ISR.
; NOTE: a character is ready only if  Head <> Tail.
; getComBuffer is reentrant


    entry getComBuffer

    push bx
    push si
    push di
    push ds

    push cs
    pop ds

    iarg __port
    getarg ax, __port           ;get specified port 1, 2, 3, 4
    cmp ax, comBuffers          ;make sure port buffer exists
    jle getComBuffer_5          ;OK -->
    xor ax, ax                  ;report error
    je	getComBuffer_30		;quit -->

getComBuffer_5:
    dec ax 		        ;fix it to 0 - 3
    mov bl, (sizeComBuffer + 2) ;allocation = (size + head & tail)
    mul bl                      ;times (port-1) = offset into total buffer alloc
    mov di, ax                  ;di = index to correct comm buffer

    cli
    mov ax, word ptr [di+IRQcomBuffer].head_ptr  ;get both head & tail in ax
    cmp ah, al
    sti
    jne getComBuffer_10         ;if head<>tail then there is a char to return
    mov ax, -1                  ;else report nothing available
    je getComBuffer_30          ;and quit -->

getComBuffer_10:
    lea bx, [di+IRQcomBuffer].buffer         ;locate buffer
    xor ah, ah                  ;keep just al
    mov si, ax                  ;make an index into buffer memory
    mov al, [bx+si]             ;fetch char from buffer @ buffer head end
    inc [di+IRQcomBuffer].head_ptr           ;let head point to next location
    and [di+IRQcomBuffer].head_ptr, comMask
    mov ah, 1                   ;report 1 character returned
				;(retrun code should change!)

getComBuffer_30:
    pop ds
    pop di
    pop si
    pop bx
    return pascal



;=========================
;(public) directly read a port
;=========================
;
; int pascal get_8250(int Port)
;
; wait for a character to come to serial port
; will timeout and return 0
; returns: 0 = error bad port,  al=char , ah=1
; get_8250 is reentrant


    entry get_8250

    iarg __port
    getarg dx,__port
    call _getPort
    jnc get_8250_10     ;no error -->
get_8250_5:
    xor ax,ax          ;report port error in AX = 0
    je get_8250_20     ;error -->

get_8250_10:
    mov al, 1          ;make DTR
    mov bx, 3001h      ;wait for DSR on MSR & then wait for DR on LSR
    call waitComPort   ;make sure com line is open
    jnz get_8250_5     ;NZ indicates timeout error -->

    add dx, DXR - LSR
    in al, dx          ;read in character
    mov ah, 1

get_8250_20:
    return pascal



;=========================
;(public) Com Port send one character
;=========================
;
; int pascal put_8250(int Port, int putChar)
;
; Port is a valid port index 1,2,3...
; putChar is valid character 0-255
; returns: 0 = error bad port, -1 = success
; put_8250 is reentrant


    entry put_8250
    iarg __sendChar
    iarg __port
    getarg dx, __port
    call _getPort
    jnc put_8250_5  ;no error -->

put_8250_3:
    xor ax, ax      ;report port error in AX = 0
    je put_8250_10  ;error -->

put_8250_5:
    getarg ax, __sendChar
    call putOne8250
    jnc put_8250_3
    mov ax, -1	    ;success

put_8250_10:
    return pascal



;=========================
;(public) Send CRLF out specified com port
;=========================
;
; int pascal crlf_8250(int Port)
;
; Port is a valid port index 1,2,3...
; returns: 0 = error bad port, -1 = success
; crlf_8250 is reentrant


    entry crlf_8250

    iarg __port
    getarg dx, __port
    call _getPort
    jnc crlf_8250_5  ;no error -->

crlf_8250_3:
    xor ax, ax       ;report port error in AX = 0
    je crlf_8250_10  ;error -->

crlf_8250_5:
    mov al, 13
    call putOne8250
    jnc crlf_8250_3
    mov al, 10
    call putOne8250
    jnc crlf_8250_3

    mov ax, -1	     ;success

crlf_8250_10:
    return pascal



;=========================
;(local) local send byte procedure
;=========================
;
; send one byte character out serial port DX
; enter with al=char
; returns CF if successful
; reentrant


putOne8250:

    push ax              ;save send character
    mov al, 3		 ;set DTR & RTS
    mov bx, 3040h  	 ;wait for DSR & CTS on MSR & then for THRE on LSR
    call waitComPort     ;make sure com line is open
    jz putOne8250_10
    pop ax
    jmp  putOne8250_20   ;timeout error -->

putOne8250_10:
    add dx, DXR - LSR
    pop ax               ;get send data
    out dx, al           ;send data

putOne8250_20:
    retn                 ;CF if success



;=========================
; (public) facility to enable / disable com port Receive IRQ
;=========================
;
; int pascal port_IRQ(int Port, int IRQ_Status)
;
; Port is port index 1,2,3...
; IRQ_Status is 1 = enable, 0 = disable
; returns:  0 = error,  -1 = success
; port_IRQ is reentrant


    entry port_IRQ


    cli
    iarg __irq_status
    iarg __port

    getarg dx, __port
    mov bx, dx      ;save port index (1-4)
    call _getPort
    jnc port_IRQ_5  ;no error -->
    xor ax, ax      ;report port error in AX = 0
    je port_IRQ_30  ;error -->

port_IRQ_5:
    getarg ax, __irq_status
    mov ah, al      ;copy status
    or al, ah 	    ;if al = 0 then disabling
    je port_IRQ_10  ;disable IRQs -->

		    ;enable IRQs
    add dx, MCR - DXR
    in al, dx       ;get current MCR
    or al, (DTR + RTS + MC_INT)   ;set these bits in MCR
    out dx, al      ;set RTS & DTR & MC_INT in MCR

    add dx, IER - MCR
    mov al, ah      ;get IRQ status required
    out dx, al      ;and send it to IER

    add dx, LCR - IER
    in al, dx       ;read LCR
    and al, 07fh    ;clear bit 7
    out dx, al      ;send to LCR

    mov dx, IMR     ;setup to diddle PIC IMR
    in al, dx	    ;get IMR
    and bx, 1       ;is it port (1or3) or (2or4)... odd or even?
    jnz port_IRQ_06   ;odd -->
    and al, IRQ3    ;even
    jmp port_IRQ_07
port_IRQ_06:        ;odd
    and al, IRQ4
port_IRQ_07:
    out dx, al      ;send IMR our interrupt mask
    dec dx
    mov al, 020h
    out dx, al
    jmp port_IRQ_20 ;done enables, return -->


port_IRQ_10:        ;disable all IRQs
    push dx
    mov dx, IMR     ;do IMR first
    in al, dx       ;get current interrupt mask
    mov ah, IRQ3    ;remove IRQ3
    xor ah, IRQ4    ;
    or al, ah       ;
    out dx, al      ;set new mask clearing IRQs 3&4
    pop dx
    add dx, IER - DXR
    xor al, al      ;0 to disale IRQs
    out dx, al
    add dx, MCR - IER
    in al, dx	    ;get current MCR
    mov ah, MC_INT
    xor ah, 0ffh
    and al, ah
    out dx, al

port_IRQ_20:
    mov ax, -1      ;report success

port_IRQ_30:
    sti
    return pascal



;=========================
; (special) COM ISR(s)
;=========================
;   test which port caused an interrupt
;        then get char from port
;        and then put char in port buffer
;   1) called far from REGS.ASM IRQ traps with no arguments
;   2) these entry points are reentrant
;
;   This section is currently programmed only for up to 4 ports
;   but can be extended for up to 16 ( or more ) by modifying the
;   following ODD & EVEN entry code.

;====== ODD =========
com_ISR_odd:              ;do in order each odd port starting with 1
    cli
    push ds
    push cs
    pop ds

    mov dx, ICR           ;reset PIC ASAP
    mov al, EOI
    out dx, al            ;reset IRQ line

    xor bx, bx            ;BX flags which port caused interrupt

    mov cl, activeComPorts   ;how many currrently active ports there are
    ThisPort = 1          ;calculate first port to scan for IRQ

com_ISR_odd_1:
    mov ch, ThisPort      ;CL = how many active; CH = which one we're doin
    mov dx, [PORT + ((ThisPort - 1) * 2)]
    add dx, IIR - DXR
    in al, dx
    and al, 0fh
    cmp al, 4
    jne com_ISR_odd_3     ;com1 not pending -->

    call com_ISR
    inc bx                ;mark once

com_ISR_odd_3:
    cmp bx, 1
    jg com_ISR_Done       ;did 3, so -->

    ThisPort = ThisPort + 2
    mov ch, ThisPort      ;CL = how many active; CH = which one we're doin
    cmp cl, ch
    jl com_ISR_Done       ;?gone past limit, yes -->

    mov dx, [PORT + ((ThisPort - 1) * 2)]  ;no next odd port

    add dx, IIR - DXR
    in al, dx
    and al, 0fh
    cmp al, 4
    jne com_ISR_Done      ;com3 not pending -->

    call com_ISR
    inc bx                ;
    cmp bx, 1             ;if LT 2, may have missed 1
    jg com_ISR_odd_1      ;go check 1 again -->
    jmp com_ISR_Done


;====== EVEN ========
com_ISR_even:
    cli
    push ds
    push cs
    pop ds

    mov dx, ICR           ;reset PIC ASAP
    mov al, EOI
    out dx, al            ;reset IRQ line

    xor bx, bx            ;BX flags which port cause interrupt

    mov cl, activeComPorts   ;how many currrently active ports there are
    ThisPort = 2          ;calculate first port to scan for IRQ

com_ISR_even_2:
    mov ch, ThisPort      ;CL = how many active; CH = which one we're doin
    mov dx, [PORT + ((ThisPort - 1) * 2)]
    add dx, IIR - DXR
    in al, dx
    and al, 0fh
    cmp al, 4
    jne com_ISR_even_4    ;com2 not pending -->

    call com_ISR
    jmp com_ISR_Done
    inc bx                ;mark once

com_ISR_even_4:
    cmp bx, 1
    jg com_ISR_Done       ;did 4, so -->

    ThisPort = ThisPort + 2
    mov ch, ThisPort      ;CL = how many active; CH = which one we're doin
    cmp cl, ch
    jl com_ISR_Done       ;?gone past limit, yes -->

    mov dx, [PORT + ((ThisPort - 1) * 2)]  ;no next odd port

    add dx, IIR - DXR     ;look to see if this port has data
    in al, dx
    and al, 0fh
    cmp al, 4
    jne com_ISR_Done      ;com4 not pending -->

    call com_ISR
    inc bx
    cmp bx, 1
    jg com_ISR_even_2
    ;jmp com_ISR_Done

com_ISR_Done:
    pop ds
    sti
    retf


;=====================
;main body of this routine
;

com_ISR:
    push bx
    push cx

    add dx, DXR - IIR     ;dx = data port reg

    ;mov al, 1             ;make DTR
    ;mov bx, 2001h         ;wait for DSR on MSR & then wait for DR on LSR
    ;call waitComPort      ;make sure com line is open
    ;jnz com_ISR_20        ;NZ indicates timeout error -->
    ;add dx, DXR - LSR

    in al, dx            ;read in character
    mov cl, al           ;save char across buffer addressing

    mov al, ch           ;get relative port address 1-4
    dec al
    xor ah, ah
    mov bl, (sizeComBuffer + 2)          ;allocation = (size + head & tail)
    mul bl               ;times (port-1) = offset into total buffer alloc
    mov di, ax           ;di = index to correct comm buffer

    lea bx, [di+IRQcomBuffer].buffer     ;locate buffer
    mov al, [di+IRQcomBuffer].tail_ptr   ;get index value from memory
    xor ah, ah
    mov si, ax
    mov [si+bx], cl      ;put char into buffer @ new buffer tail
    inc [di+IRQcomBuffer].tail_ptr       ;set head point to next location
    and [di+IRQcomBuffer].tail_ptr, comMask
    clearFlags

com_ISR_20:
    pop cx
    pop bx
    retn                 ;CF = port error; NZF = timeout error


;=== End Code =====================================================
;==================================================================

reg_supp        ends

	  end