;--------------------------------------------------------------------
;                             DUMBTERM
;  Original author is CJ Dunford 09/12/83, modified by Jeff Firestone
;  on 01/15/84.  This program sets up the interrupt for COM1.  It uses
;  buffered communications.  The program is based upon PC Tech Journal
;  Jan '84, p144-186.
;---------------------------------------------------------------------

bufsize      equ     4096            ;4K Buffer

LF           equ     0Ah
CR           equ     0Dh
K_ESC          equ     1Bh

; ------- BIOS calls

RS232        equ     14h            ;RS232 service
kbd_io       equ     16h            ;Keyboard service

; ------- INS8250 registers

THR          equ     3F8h           ;Trans holding register (write)
RBR          equ     3F8h           ;Recieve buffer register (read)
IER          equ     3F9h           ;Interrupt inable register
LCR          equ     3FBh           ;Line control register
                                    ;  Bit 7 of LCR is "DLAB". DLAB must
                                    ;  be zero to access THR, RBR, IER.
MCR         equ      3FCh           ;Modem control register
LSR         equ      3FDh           ;Line status register
MSR         equ      3FEh           ;Modem status register

; ------- Comm parameter definition
; Refer to IBM Tech Ref manual page A-20
; See PROC INIT for usage.
; ---------------------------------------
commparm   record baud:3, parity:2, stopbits:1, wordbits:2

; Buad rates
B110        equ      000b
B150        equ      001b
B300        equ      010b
B600        equ      011b
B1200       equ      100b
B2400       equ      101b
B4800       equ      110b
B9600       equ      111b

; Parity
no_parity    equ     00b
odd_parity   equ     01b
even_parity  equ     11b

; Stop bits
stop1        equ     0
stop2        equ     1

; Data bits
data7        equ     10b
data8        equ     11b


;*****************************
;         MACROS
;*****************************

@bioscall MACRO call_num, parm
;; Generates an 'INT call_num', with parm in AH
            IFNB     <parm>
              mov    ah,parm
            ENDIF
            int      call_num
            ENDM


@doscall MACRO function, parm
;; Generates a DOS function call with parm in AH
            IFNB     <parm>
              mov    al,parm
            ENDIF
            @bioscall 21h,function
            ENDM


;******************************
;    DATA & STACK SEGMENTS
;******************************

data segment para public 'data'

; ----- The string section
sgreeting      db      '--- ONLINE -0--',cr,lf,'$'
sgoodbye       db      cr,lf,'--- OFFLINE ---',cr,lf,'$'
serr1          db      '<R>$'          ;RS232 receive error
serr2          db      '<S>$'          ;RS232 send error
serr3          db      '<B>$'          ;Receive buffer overflow error

; ----- Flags
brcv_err       db     0                ;Nonzero on RS232 receive error
boverflow      db     0                ;Nonzero on buffer overflow
bdoneflag      db     0                ;Nonzero after ESC from kbd

; ----- Receive data buffer and associated pointers
; >> Buffer is empty if head point4er = tail pointer
wbufhead       dw     buffer           ;Pointer to head of buffer
wbuftail       dw     buffer           ;Pointer to tail of buffer
buffer         db     BUFSIZE dup (?)
bufend         equ    $
data ends

; ----- Stack
_stack segment para stack 'stack'
              db      256 dup (?)
_stack ends


;* * * * * * * * * * * * * * * * *
;         PROGRAM BODY
;* * * * * * * * * * * * * * * * *

code segment para public 'code'
             assume cs:code, ds:data, ss:_stack

main         proc     far

; ------ Initialize
             push     ds                ;Set up long return to DOS
             sub      ax,ax
             push     ax
             call     init              ;Rest of initialization

; ------ Main program loop
M100:        call     buffer_check      ;Check RS232 buffer, display if char
             call     kb_check          ;Check kbd, Send to RS232.
             test     bdoneflag,0FFh    ;Non-zero if done
             jz       M100              ;Loop till ESC received

; ------ ESC received.  Clean up interrupt & exit
             call     cleanup
             ret                        ;Return to DOS
main         endp

;* * * * * * * * * * * * * * * * * * * *
;         PRIMARY BLOCKS
;* * * * * * * * * * * * * * * * * * * *

; ------ Init ---------------------------------
;Program initialization
;  set up RS232
;  set up vector for RS232 interrupt (INT 0Ch)
;  Enable IRQ4
;  Enable RS232 interrupt on data ready
; --------------------------------------------

init         proc     near

; ----- Initialize RS232 300,8,N,1
             mov      dx,0
             mov      al,commparm <B9600,even_Parity,stop1,data7>
             @bioscall RS232,0

; ----- Set up INT '0C' for IRQ4
            cli                               ;Interrupts off during setup
            push      ds                      ;Save DS
            mov       dx,offset ISR           ;Point to RS232 ISR in DS:DX
            push      cs
            pop       ds
            @doscall 25h,0Ch                  ;Set vector intr for IRQ4
            pop       ds                      ;Restore DS

; ------ Enable IRQ4 on 8259 interrupt controller
            in        al,21h                  ;Get current mask
            and       al,11101111b            ;Reset IRQ4 mask
            out       21h,al                  ;restore to IMR

; ------ Enable 8250 data ready interrupt
            mov      dx,LCR                   ;DX <== LCR
            in       al,dx                    ;reset DLAB for IER access
            and      al,01111111b
            out      dx,al
            mov      dx,IER                   ;address IER
            mov      al,00000001b             ;Enable data-ready interrupt
            out      dx,al

; ------ Enable OUT2 on 8250
            mov      dx,MCR                   ;Address MCR
            mov      al,00001000b             ;Enable OUT2
            out      dx,al
            sti

; ------ Display greeting & return
            mov      ax,data                  ;Establish data seg address
            mov      ds,ax
            mov      dx,offset sgreeting      ;Point to greeting
            call     strdisp                  ;Display it
            ret
init endp

; ------ Buffer Check ------------------
; RS232 buffer check
;
; This block checks the received data buffer.
; It functions as follows:
;
; If the RS232 input buffer is not empty
;   Get the first character
;   Display the character
;   Update buffer pointer
; If the RS232 receive error flag is nonzero
;   Display an error indicator
;
; Entry:
;   No requirement
; Exit
;   AX, BX, DX destroyed
; --------------------------------------


buffer_check proc near

; ------ Check buffer status
          mov      bx,wbufhead               ;buffer head pointer
          cmp      bx,wbuftail               ;buffer empty if head = tail
          je       BC100

; ------ Something in buffer--get 1st char, fix pointers
          mov      al,[bx]                   ;get the char
          call     incptr                    ;Bump buffer head pointer
          mov      wbufhead,bx

; ------ Display character received. Filter CR/LF
          cmp    al,LF                       ;Is it a line feed
          je     BC100                       ;Skip display if yes
          call   chdisp                      ;Display if no
          cmp    al,CR
          jne    BC100
          mov    al,LF
          call   chdisp

; ------ Test RS232 receive status; display errors
BC100:    Test   brcv_err,0FFh               ;Flag nonzero if errors
          jz     BC200                       ;Jump if no errors
          mov    dx,offset serr1             ;Point to error msg
          call   strdisp
          mov    brcv_err,0                  ;Clear error flag

; ----- Test for buffer overflow; display errors
BC200:
         test    boverflow, 0FFh
         jz      BC300
         mov     boverflow,0                 ;Clear the flag
         mov     dx,offset serr3             ;Point to error msg
         call    strdisp

BC300:   ret
buffer_check endp

; ----- KB_CHECK -----------------------------------
; Check the keyboard.  Functions as follows
;
; Check the keyboard status
; If a characteris available
;   If the character is ESC
;     set the done flag
;   ELSE
;     send it to RS232 and watch for errors
;
;  This routine does not echo the characters to the display.
;
; Entry:
;   No requirement
; Exit
;   AX, DX destroyed
; --------------------------------------

kb_check proc near

; ----- Poll keyboard, check chars received
         call    kb_poll                 ;Poll the keyboard
         jz      KBC900                  ;Kbd clear, exit
         cmp     al,K_ESC                  ;Escape?
         jne     KBC100
         mov     bdoneflag,0FFh          ;Yes, set terminate flag
         jmp     short KBC900

; ----- Send the received char, watch for errors
KBC100:  Call    RS232_out               ;Send it
         test    ah,80h                  ;Time out?
         jz      KBC900
         mov     dx,offset serr2         ;Point to error msg
         call    strdisp                 ; and display

KBC900:  ret
kb_check endp

; ----- ISR ------------------------------------------------------
; This is the RS232 interrupt service routine.  It is entered
; whenever the RS232 port interrupts on a 'data ready'
; condition.  The routine simply reads the data from the asynch
; chip and stuffs it in the buffer.  Note that the process of reading
; the received data register in the 8250 clears IRQ4.  However, the
; 8259 must be told specifically that the interrupt service is complete.
;
; This replaces the function 2 of BIOS interrupt 14h (receive a character
; over the comm line).  Since it cannot return errors in a register, it
; puts the error marker in memory at 'brcv_err'.  The error flag is
; sticky -- a successful read will not clear a prior error indication.
; This allows the program mainline to examine the error status at its
; leisure.  Error bits are the same as in RS232OUT, above, except that
; ONLY the error bits are set, and bit 7 is not used (always 0).  In
; other words, brcv_err is nonzero only on an error.  Timeout errors
; are not possible here.
;
; The ISR will set the overflow flag if the buffer should overflow.
; This shouldn't happen.
; --------------------------------------------------------------------

ISR      proc   near
         sti                      ;Allow other interrupts
         push   ax                ;Save all regs used
         push   bx
         push   dx
         push   si
         push   ds

; ---- Establish data addressability
         mov    ax,data
         mov    ds,ax

; ---- Get error bits
         mov    dx,LSR            ;Save address of RS232
         in     al,dx             ;Get status
         and    al,00011110b      ;Mask non-error bits
         jz     ISR010            ;Skip error set if OK
         mov    brcv_err,al       ;Set error indicator

; ---- Get incoming character and buffer it
ISR010:  mov    dx,RBR            ;Receive buffer
         in     al,dx             ;Get input buffer
         mov    bx,wbuftail       ;Buffer input pointer
         mov    si,bx             ;Set pointer before increment
         call   incptr            ;Bump input pointer
         cmp    bx,wbufhead       ;Overflow if head = tail
         je     ISR020            ;Overflow
         mov    [si],al           ;No overflow, save char in buf
         mov    wbuftail,bx       ;And new input pointer
         jmp short ISR999

ISR020:  mov    boverflow, 0FFh   ;Set overflow flag

; ---- Signal end of interrupt to 8259
ISR999:  cli
         mov    al,20h            ;Non-specific EOI
         out    20h,al            ;Send it

; ---- Restore regs & return.  IRET reenables interrupts
         pop    ds
         pop    si
         pop    dx
         pop    bx
         pop    ax
         iret
ISR endp

; ---- CLEANUP --------------------
; End of program housekeeping
;   -- Disable IRQ4
;   -- Disable 8250 interrupts
;   -- Disable OUT2
;   -- Display offline message
; ---------------------------------

cleanup proc near

; ---- Disable IRQ4 on 8259
         cli
         in      al,21h                ;IMR
         or      al,00010000b          ;Mask bit 4 -- IRQ4
         out     21h,al

; ---- Disable 8250 data ready interrupt
         mov     dx, LCR               ;DX <=== LCR
         in      al,dx                 ;Reset DLAB for IER access
         and     al,01111111b
         out     dx,al
         mov     dx,IER                ;Address IER
         mov     al,0                  ;Disable all 8250 interrupts
         out     dx,al

; ---- Disable OUT2 on 8250
       mov      dx,MCR                ;Address MCR
       mov      al,0                  ;Disable OUT2
       out      dx,al
       sti

; ---- Display bye-bye
       mov     dx,offset  sgoodbye
       call    strdisp
cleanup endp


; * * * * * * * * * * * * * *
;   I/O & General Subroutines
; * * * * * * * * * * * * * *

; ---- KB_POLL ------------------------------
; Set/reset Z flag on keyboard buffer status.
;
; Entry:
;   No requirements
; Exit:
;   Z = 1 if nothing available
;   Z = 0 if char available from kbd
;   IF Z = 0
;     AL = char
;     AH = scan code
;   Other regs preserved
; ---------------------------------------------

kb_poll proc near
        @bioscall  kbd_io,1             ;Poll KB. Sets Z flag if KB bfr empty
        jz         KB999                ;Nothing there
        pushf                           ;Save flag status
        @bioscall  kbd_io,0             ;Something there; get it
        popf
KB999:
        ret
kb_poll endp

; -- RS232_OUT ----------------------------------------
; RS232 output routine
;
; This routine sends one character to the RS232 port.
; It replaces function 1 of BIOS int 14K.  This is
; necessary because BIOS will disable the RS232
; interrupt (by disabling OUT2) every time it is
; called.
;
; Entry:
;   AL = character to be transmitted
; Exit:
;   AH = send status
;     Bit 7 = 1 if RS232 timeout occurred
;     If bit 7 = 0
;       bit 6: trans shift register empty
;       bit 5: trans holding register empty
;       bit 4: break detect
;       bit 3: framing error
;       bit 2: parity error
;       bit 1: overrun error
;       bit 0: data ready
;  Other regs preserved.
; -------------------------------------------------------

RS232_out proc near
          push   bx             ;Save regs used
          push   cs
          push   dx

; ----- Set up RS232
          mov    bl,al          ;Save char to bl temporarily
          mov    dx,MCR         ;Modem Control Register
          mov    al,00001011b   ; OUT2, DTR, RTS
          out    dx,al
          sub    cx,cx          ;Initialize timeout count
          mov    dx,MSR         ;Modem Status Register

; ----- Wait for DSR
RS100:
          in     al,dx
          test   al,20h         ;Data set ready?
          jnz    RS150          ;Yes
          loop   RS100          ;No, retry till timeout
          mov    ah,80h         ;Set timeout
          jmp    short RSXIT    ;and quit

; ----- Wait for CTS
RS150:
          sub    cx,cx          ;Another timeout count
RS200:
          in     al,dx
          test   al,10h         ;Clear to send?
          jnz    RS250          ;Yes
          loop   RS200          ;No, loop till timeout
          mov    ah,80h         ;TImeout,set flag
          jmp short RSXIT       ;And quit

; ----- Wait for THRE
RS250:
          mov    dx,LSR         ;Line Status Register
          sub    cx,cx          ;Yes another timeout count
RS300:
          in     al,dx          ;LSR status
          test   al,20h         ;Transmit holding reg empty?
          jnz    RS350          ;Yes
          loop   RS300          ;No, loop till timeout
          mov    ah,80h         ;Timeout, set flag
          jmp short RSXIT

; ------ Get line status, send char
RS350:
          mov    ah,al          ;Get line status for return
          and    ah,01111111b   ;Mask out bit 7
          mov    al,bl          ;Restore char to AL
          mov    dx,THR         ;Transmit holding register
          out    dx,al          ;Output it to RS232
RSXIT:
          pop    dx
          pop    cx
          pop    bx
          ret
RS232_out endp


; ---- CHDISP ----------------------------------
; Display the character in AL on the CRT
; Entry:
;   AL = char
; Exit:
;   All regs restored
; ----------------------------------------------

chdisp proc near
       push   ax
       push   dx
       mov    dl,al
       @doscall 2
       pop    dx
       pop    ax
       ret
chdisp endp


; ---- STRDISP ----------------------------
; Display the string at DS:DX on the CRT
; Entry:
;   DS:DX ==> string
; Exit:
;   All regs restored
; -----------------------------------------

strdisp proc near
        push      ax
        @doscall 9
        pop       ax
        ret
strdisp endp


; ----- INCPTR ------------------------------
; Increments the buffer pointer in reg BX.
; If the pointer goes beyond the end of the
; buffer, wrap around to start.
;
; Entry:
;   BX = buffer pointer
; Exit
;   BX = advanced buffer pointer
;   Other regs restored
; --------------------------------------------

incptr proc near
       inc     bx                            ; Bump pointer
       cmp     bx,offset bufend              ; Past end?
       jne     IP100                         ; Jump if not
       mov     bx,offset buffer              ; Else point to start
IP100:
       ret
incptr endp
code   ends
       end  main
