; Kermit system dependent module for VICTOR 9000/SIRIUS

; Edit History
;  Original version, BGP, 23 November 1985
; Add global entry point vtstat for use by Status routine in mssset.
; Cleared terminal emulation flag, flags.vtflg, in procedure lclini.
; Add register save/restore in procedure getbaud.
; Joe R. Doupnik 12 March 1986

        public  serini, serrst, clrbuf, outchr, coms, vts, vtstat, dodel
        public  ctlu, cmblnk, locate, lclini, prtchr, dobaud
        public  clearl, dodisk, getbaud, beep, puthlp, putmod
        public  clrmod, poscur, sendbr, showkey

        public  xofsnt, machnam, setktab, setkhlp, count

        include mssdef.h

FALSE   EQU     0
TRUE    EQU     1
MNTRGH  EQU     BUFSIZ*3/4      ; High point = 3/4 of buffer full.
MNTRGL  EQU     BUFSIZ/4        ; Low point = 1/4 of buffer full.
DEF_BAUD EQU    B1200           ; Default to 1200 baud

; constants used by serial port handler

SEG_7201 EQU    0E004H          ; Segment for 7201 serial controller
DATAA_7201 EQU  0               ; DATA A offset
STATA_7201 EQU  2               ; STATUS A offset
DATAB_7201 EQU  1               ; DATA B offset
STATB_7201 EQU  3               ; STATUS B offset

;  no interrupts, no waits
REG1_7201 EQU   0               ; 7201 Register 1 value
;  non-DMA, non-vectored interrupts, priority type 1 (Ra>Rb>Ta>Tb)
REG2_7201 EQU   14H             ; 7201 Register 2 value
;  8 bits/char, no CRC, receiver enabled
REG3_7201 EQU   0C1H            ; 7201 Register 3 value
;  clock/16, 1.5 stop bit, no parity
REG4_7201 EQU   48H             ; 7201 Register 4 value
;  DTR low (active), 8 bits/char, transmitter enabled, RTS low, no CRC
REG5_7201 EQU   0EAH            ; 7201 Register 5 value

SEG_8253 EQU    0E002H          ; Segment for 8253 timer
SETA_8253 EQU   0               ; Speed for port A
SETB_8253 EQU   1               ; Speed for port B
CTRL_8253 EQU   3               ; Control for 8253 timer

SEG_8259 EQU    0E000H          ; Segment for 8259 int. controller
CW1_8259 EQU    0               ; Offset for 8259 register 1
CW2_8259 EQU    1               ; Offset for 8259 register 2

; external variables used:
; drives - # of disk drives on system
; flags - global flags as per flginfo structure defined in pcdefs
; trans - global transmission parameters, trinfo struct defined in pcdefs
; portval - pointer to current portinfo structure (currently either port1
;    or port2)
; port1, port2 - portinfo structures for the corresponding ports

; global variables defined in this module:
; xofsnt, xofrcv - tell whether we saw or sent an xoff.
; setktab - keyword table for redefining keys (should contain a 0 if
;    not implemented)
; setkhlp - help for setktab.

DATAS   segment public 'datas'
        extrn   drives:byte, flags:byte, trans:byte
        extrn   portval:word, port1:byte, port2:byte

setktab db      0
setkhlp db      CR,LF,'Set Key not supported on VICTOR/SIRIUS Kermit$'
sh_key_str db   'Set Key not supported on VICTOR/SIRIUS Kermit'
sh_key_len dw   45
machnam db      'VICTOR/SIRIUS$'
clrlin  db      CR              ; clears full line with next line...
clreol  db      ESC,'K$'        ; clears from cursor to end of line
home    db      ESC,'H$'        ; homes cursor
clrscr  db      ESC,'E$'        ; clears screen and homes cursor
delstr  db      BS,BS,'  ',BS,BS,'$' ; Delete string - why 2???
clr_25  db      ESC,'j',ESC,'x1',ESC,'Y8 ',ESC,'l',ESC,'k',ESC,'y1$'
        ;clr_25 does the entire operation of clearing the mode line
start_25 db     ESC,'j',ESC,'x1',ESC,'Y8 ',ESC,'p$'
        ; start_25 enables line 25 and moves to the start in reverse video
end_25  db      ESC,'q',ESC,'k',ESC,'y1$'
        ; end_25 turns off reverse video and line 25 and returns the cursor
mov_pfx db      ESC,'Y$'        ; prefix for moves
set_inv db      ESC,'p$'        ; set to inverse video
reset_inv db    ESC,'q$'        ; reset inverse video
modem   mdminfo <DATAA_7201,STATA_7201,SETA_8253,0FDH,2,61H,104H>
savsci  dw      ?               ; Save for serial port interrupt vector.
savscs  dw      ?               ; Ditto.
portin  db      FALSE           ; Has comm port been initialized.
xofsnt  db      FALSE           ; Say if we sent an XOFF.
xofrcv  db      FALSE           ; Say if we received an XOFF.
erms20  db      CR,LF,'?Warning: System has no disk drives$'
badbd   db      CR,LF,'Unimplemented baud rate$'
tmp     db      ?,'$'
temp1   dw      ?               ; Temporary storage.
getbd_flag db   FALSE,FALSE     ; Tell if we have been called once
init_flag db    FALSE,FALSE     ; Tell if we have initialized 7201 channel

ontab   db      02H             ; Two entries.
        db      03H,'OFF$'      ; Should be alphabetized.
        dw      00H
        db      02H,'ON$'
        dw      01H

comptab db      08H
        db      01H,'1$'
        dw      01H
        db      01H,'2$'
        dw      00H
        db      01H,'A$'
        dw      01H
        db      01H,'B$'
        dw      00H
        db      04H,'COM1$'
        dw      01H
        db      04H,'COM2$'
        dw      00H
        db      03H,'TTY$'
        dw      01H
        db      03H,'UL1$'
        dw      00H

; this table is indexed by the baud rate definitions given in
; pcdefs.  Unsupported baud rates should contain FF.
;  This number is determined by 78125/(baud rate) (decimal values)

bddat   label   word
        dw      6B5H            ; 45.5 baud
        dw      61BH            ; 50 baud
        dw      412H            ; 75 baud
        dw      2C6H            ; 110 baud
        dw      245H            ; 134.5 baud
        dw      209H            ; 150 baud
        dw      104H            ; 300 baud
        dw      82H             ; 600 baud
        dw      41H             ; 1200 baud
        dw      2BH             ; 1800 baud
        dw      27H             ; 2000 baud
        dw      21H             ; 2400 baud
        dw      10H             ; 4800 baud
        dw      8H              ; 9600 baud
        dw      4H              ; 19200 baud
        dw      2H              ; 38400 baud

; variables for serial interrupt handler

source  db      BUFSIZ DUP(?)   ; Buffer for data from port.
srcpnt  dw      0               ; Pointer in buffer (DI).
count   dw      0               ; Number of chars in int buffer.
savesi  dw      0               ; Save SI register here.

DATAS   ends

CODE    segment public
        extrn   comnd:near, dopar:near, defkey:near, sleep:near
        extrn   lclyini:near
        assume  cs:code,ds:datas

; local initialization

LCLINI  proc    near
        mov     flags.vtflg,0   ; no terminal emulation [jrd]
        mov     bx,offset init_flag
        cmp     flags.comflg,1  ; Using Com 1?
        je      lclini0         ; Yes
        add     bx,1            ; This is for Com 2
lclini0:
        cmp     byte ptr [bx],FALSE ; already inited?
        jne     lclini1         ; Yes
        call    init_7201       ; Just need to init this thing...
        mov     byte ptr [bx],TRUE ; Now we have
lclini1:
        call    lclyini         ; init term part too
        ret
LCLINI  endp

; This is a local routine to reinitialize the 7201 controller for
; the currently selected port to be sure that it is in the correct
; state...

INIT_7201 proc  near
        push    es
        push    bx
        cli                     ; no interrupts for a flash
        mov     bx,SEG_7201     ; Point at the 7201
        mov     es,bx
        mov     bx,modem.mdstat
        mov     byte ptr es:[bx],1
        mov     byte ptr es:[bx],18H ; Software reset of current port
        push    ax              ; Kill time for at least (4) 2.5 MHz
        pop     ax              ; clock cycles (8 processor cycles)
        mov     bx,STATA_7201   ; This one must be in port A
        mov     byte ptr es:[bx],2 ; Register 2 must be first one set
        mov     byte ptr es:[bx],REG2_7201
        mov     bx,modem.mdstat ; Rest are for proper port
        mov     byte ptr es:[bx],4 ; Register 4 must be second one
        mov     byte ptr es:[bx],REG4_7201
        mov     byte ptr es:[bx],1 ; Rest are in any order
        mov     al,REG1_7201    ; This one may need to be fudged
        cmp     portin,FALSE    ; Is port on and running?
        je      init_a
        or      al,18H          ; Turn on receive interrupts
init_a:
        mov     es:[bx],al
        mov     byte ptr es:[bx],REG1_7201
        mov     byte ptr es:[bx],3
        mov     byte ptr es:[bx],REG3_7201
        mov     byte ptr es:[bx],5
        mov     byte ptr es:[bx],REG5_7201
        mov     byte ptr es:[bx],10H ; Clear external/status interrupts
        mov     byte ptr es:[bx],30H ; Clear special receive cond. int.
        mov     byte ptr es:[bx],38H ; Set to end of interrupt
        sti                     ; interrupts ok again
        pop     bx
        pop     es
        ret
INIT_7201 endp

; this is called by Kermit initialization.  It checks the
; number of disks on the system, sets the drives variable
; appropriately.  Returns normally.
;  Since VICTOR doesn't provide any simple way to get the actual
;  number of drives, we will use the number that DOS thinks we have.

DODISK  proc    near
        mov     ah,GCURDSK              ; Get current disk
        int     DOS
        mov     dl,al                   ; Want to reselect that one
        mov     ah,SELDSK
        int     DOS                     ; AL now has how many drives
        cmp     al,1                    ; Make sure there's at least 1
        jl      dodsk0
        mov     drives,al               ; Remember how many.
        ret
dodsk0: mov     ah,PRSTR                ; Print a warning message.
        mov     dx,offset erms20        ; I'm not sure if things will
        int     DOS                     ; work with no disks?!?!?!
        mov     drives,0                ; Say there aren't any drives.
        ret
DODISK  endp

; Show the definition of a key.  Since it isn't really necessary to redefine
; keys for the VICTOR/SIRIUS (assuming that KEYGEN is available), this isn't
; implemented, and the string returned to the calling sequence merely says so.
; Returns a string to print in AX, length of same in CX.
; Returns normally.

SHOWKEY proc    near
        mov     ax,offset sh_key_str
        mov     cx,sh_key_len
        ret
SHOWKEY endp

; Clear the input buffer. This throws away all the characters in the
; serial interrupt buffer.  This is particularly important when
; talking to servers, since NAKs can accumulate in the buffer.
; Returns normally.

CLRBUF  proc    near
        cli
        mov     ax,offset source
        mov     srcpnt,ax
        mov     savesi,ax
        mov     count,0
        sti
        ret
CLRBUF  endp

; Clear to the end of the current line.  Returns normally.

CLEARL  proc    near
        mov     dx,offset clreol
        mov     ah,PRSTR
        int     DOS
        ret
CLEARL  endp

; Put the char in AH to the serial port.  This assumes the
; port has been initialized.  Should honor xon/xoff.  Skip returns on
; success, returns normally if the character cannot be written.

OUTCHR  proc    near
        mov     bp,portval
        cmp     ds:[bp].floflg,0 ; Are we doing flow control.
        je      outch2          ; No, just continue.
        mov     cl,trans.rtime  ; receive timeout interval
        mov     ch,0
outch1:
        cmp     xofrcv,TRUE     ; Are we being held?
        jne     outch2          ; No - it's OK to go on.
        mov     al,1            ; else sleep for a second
        call    sleep
        loop    outch1          ; and try it again
        mov     xofrcv,FALSE    ; timed out, force it off and fall thru.
outch2:
        push    es
        push    bx
        xor     cx,cx
        mov     al,ah           ; Parity routine works on AL.
        call    dopar           ; Set parity appropriately.
        mov     ah,al           ; Don't overwrite character with status.
        mov     bx,SEG_7201     ; point at 7201
        mov     es,bx
        mov     bx,modem.mdstat
outch3:
        mov     al,es:[bx]
        test    al,4            ; ready?
        jnz     outch4          ; yes
        loop    outch3
        jmp     outch5          ; Timeout
outch4:
        mov     al,ah           ; Now send it out
        mov     bx,modem.mddat
        mov     es:[bx],al
        pop     bx
        pop     es
        jmp     rskp
outch5:
        pop     bx
        pop     es
        ret
OUTCHR  endp

; This routine blanks the screen.  Returns normally.

CMBLNK  proc    near
        mov     dx,offset clrscr
        mov     ah,PRSTR
        int     DOS
        ret
CMBLNK  endp

; Locate: homes the cursor.  Returns normally.

LOCATE  proc    near
        mov     dx,offset home
        mov     ah,PRSTR
        int     DOS
        ret
LOCATE  endp

; write a line in inverse video at the bottom of the screen...
; the line is passed in dx, terminated by a dollar sign.  Returns normally.

PUTMOD  proc    near
        push    si              ; better save this
        push    dx              ; preserve message
        mov     dx,offset start_25 ; to set up for write to 25
        mov     ah,PRSTR
        int     DOS
        mov     ah,DCONIO       ; output a char at a time
        pop     si              ; get back message
        cld                     ; better increment
putmod1:
        lodsb                   ; get byte
        cmp     al,'$'          ; is it end of string?
        je      putmod2
        mov     dl,al
        int     DOS
        jmp     putmod1
putmod2:
        mov     dx,offset end_25 ; back to normal
        mov     ah,PRSTR
        int     DOS
        pop     si              ; and restore it
        ret
PUTMOD  endp

; clear the mode line written by putmod.  Returns normally.

CLRMOD  proc    near
        mov     dx,offset clr_25 ; to clear line 25
        mov     ah,PRSTR
        int     DOS
        ret
CLRMOD  endp

; put a help message on the screen.  This one uses reverse video...
; pass the message in ax, terminated by a null.  Returns normally.

PUTHLP  proc    near
        push    si
        push    ax              ; save this for a flash
;       mov     dx,offset set_inv ; set inverse video
;       mov     ah,PRSTR
;       int     DOS
        mov     ah,DCONIO       ; don't check anything...
        pop     si              ; This used to be AX
        cld                     ; better increment on strings
puthlp1:
        lodsb                   ; get byte
        cmp     al,0            ; is it null (null-terminated string)
        je      puthlp2
        mov     dl,al
        int     DOS
        jmp     puthlp1
puthlp2:
        mov     dl,13           ; want a crlf
        int     DOS
        mov     dl,10
        int     DOS
;       mov     dx,offset reset_inv ; reset inverse video
;       mov     ah,PRSTR
;       int     DOS
        pop     si
        ret
PUTHLP  endp

; Set the baud rate for the current port, based on the value
; in the portinfo structure.  Returns normally.

DOBAUD  proc    near
        push    es              ; Better save this I think
        mov     bp,portval
        mov     temp1,ax        ; Don't overwrite previous rate.
        mov     ax,ds:[bp].baud ; Check if new rate is valid.
        mov     tmp,2
        mul     tmp             ; Get index into baud table.
        mov     bx,offset bddat ; Start of table.
        add     bx,ax
        mov     ax,[bx]         ; The data to output to port.
        cmp     ax,0FFH         ; Unimplemented baud rate.
        jne     dobd0
        mov     ax,temp1        ; Get back orginal value.
        mov     ds:[bp].baud,ax ; Leave baud rate as is.
        mov     ah,PRSTR
        mov     dx,offset badbd ; Give an error message.
        int     DOS
        pop     es
        ret
dobd0:  mov     temp1,ax        ; Remember value to output.
        mov     bx,SEG_8253     ; Point at 8253 timer
        mov     es,bx
        mov     bx,CTRL_8253    ; Set up function first
        mov     ax,modem.mdcom  ; Set up control byte
        ror     al,1            ; Need port number in high bits
        ror     al,1
        and     al,0C0H         ; Keep only top 2 bits
        add     al,36H          ; Set both, Mode 3, binary
        mov     es:[bx],al
        mov     bx,modem.mdcom  ; Where to write the rate
        mov     ax,temp1        ; Get divider back
        mov     es:[bx],al
        mov     es:[bx],ah      ; Rate divider is written
        pop     es
        ret
DOBAUD  endp

; Get the current baud rate from the serial card and set it
; in the portinfo structure for the current port.  Returns normally.
; This is used during initialization.  Unfortunately, since the VICTOR-9000
; doesn't have a baud rate register, we will just use this to set to
; a default value the first time it is called for each port, and not
; do anything on subsequent calls.

GETBAUD proc    near
        push    ax              ; save some regs. [jrd]
        push    bx              ; [jrd]
        mov     bx,offset getbd_flag
        cmp     flags.comflg,1  ; Port 1?
        je      getb1           ; Yes
        inc     bx              ; Port 2 now...
getb1:
        cmp     byte ptr [bx],FALSE ; Already done?
        jne     getb2           ; Yes
        mov     byte ptr [bx],TRUE ; It'll soon be done
        mov     bx,portval
        mov     [bx].baud,DEF_BAUD
        mov     ax,DEF_BAUD
        call    dobaud          ; to be sure
getb2:  pop     bx              ; restore regs [jrd]
        pop     ax              ; [jrd]
        ret
GETBAUD endp

; Skip returns if no character available at port,
; otherwise returns with char in al, # of chars in buffer in dx.

PRTCHR  proc    near
        call chkxon             ; see if we need to xon
        cmp count,0
        jnz prtch2
        jmp rskp                ; No data - check console.
prtch2:
        mov si,savesi
        cld                     ; better increment strings
        lodsb                   ; get a byte
        cmp si,offset source + bufsiz   ; bigger than buffer?
        jb prtch1               ; no, keep going
        mov si,offset source    ; yes, wrap around
prtch1:
        dec count
        mov savesi,si
        mov dx,count            ; return # of chars in buffer
        ret
PRTCHR  endp

; local routine to see if we have to transmit an xon

CHKXON  proc    near
        push    bx
        mov     bx,portval
        cmp     [bx].floflg,0   ; doing flow control?
        je      chkxo1          ; no, do nothing.
        cmp     xofsnt,FALSE    ; have we sent an xoff?
        je      chkxo1          ; no, forget it
        cmp     count,MNTRGL    ; below trigger?
        jae     chkxo1          ; no, forget it
        mov     ax,[bx].flowc   ; ah gets xon
        call    outchr          ; send it
        nop
        nop
        nop                     ; in case it skips
        mov     xofsnt,FALSE    ; remember we've sent the xon.
chkxo1:
        pop     bx              ; restore register
        ret                     ; and return
CHKXON  endp

; Send a break out the current serial port.  Returns normally.

SENDBR  proc    near
        push    bx
        push    ax
        cli                     ; no interrupts for a flash
        mov     bx,SEG_7201     ; Point at 7201
        mov     es,bx
        mov     bx,modem.mdstat
        mov     byte ptr es:[bx],1 ; Register 1
        mov     al,REG1_7201
        and     al,0E7H         ; Turn off interrupts
        mov     es:[bx],al
        mov     byte ptr es:[bx],5 ; Register 5
        mov     al,REG5_7201
        or      al,10H          ; Set break bit
        mov     es:[bx],al
        sti                     ; interrupts back on
        mov     ax,250          ; This is a 250 msec break
                                ; A long break is 3500 msec
        call    wait_msec       ; kill time
        cli                     ; no interrupts please
        mov     byte ptr es:[bx],5 ; Register 5
        mov     byte ptr es:[bx],REG5_7201
        mov     byte ptr es:[bx],1 ; Register 1
        mov     al,REG1_7201
        or      al,18H          ; Turn on interrupts
        mov     es:[bx],al
        sti                     ; interrupts back on
        pop ax
        pop bx
        ret                     ; And return.
SENDBR  endp

; Wait for the # of milliseconds in ax.  The delay is set for a 5 MHz
; clock rate.  Actual delay for ax=1 is 1.007 msec, plus 1.005 msec
; for each increment in ax.

WAIT_MSEC proc  near
        push    cx              ; 10 cycles
        mov     cx,ax           ; 2 cycles
wait_msec1:
        push    cx              ; 10 cycles
        mov     cx,294          ; 4 cycles
wait_msec2:
        loop    wait_msec2      ; 5+17*(CX-1) cycles
        pop     cx              ; 8 cycles
        loop    wait_msec1      ; 17 cycles if jump, 5 cycles if no jump
        pop     cx              ; 8 cycles
        ret
WAIT_MSEC endp

; Position the cursor according to contents of DX:
; DH contains row, DL contains column.  Returns normally.

POSCUR  proc    near
        push    dx              ; save this
        mov     dx,offset mov_pfx ; move prefix string
        mov     ah,PRSTR
        int     DOS
        pop     dx
        push    dx
        mov     dl,dh
        add     dl,' '          ; this is the row
        mov     ah,DCONIO       ; no checking please
        int     DOS
        pop     dx
        add     dl,' '          ; this is the column
        int     DOS
        ret
POSCUR  endp

; Delete a character from the terminal.  This works by printing
; backspaces and spaces.  Returns normally.

DODEL   proc    near
        mov     dx,offset delstr ; Erase weird character.
        mov     ah,PRSTR
        int     DOS
        ret
DODEL   endp

; Move the cursor to the left margin, then clear to end of line.
; Returns normally.

CTLU    proc    near
        mov     dx,offset clrlin
        mov     ah,PRSTR
        int     DOS
        ret
CTLU    endp

; set the current port.

COMS    proc    near
        mov     dx,offset comptab
        mov     bx,0
        mov     ah,CMKEY
        call    comnd
         jmp    r
        push    bx
        mov     ah,CMCFM
        call    comnd           ; Get a confirm.
         jmp    comx            ;  Didn't get a confirm.
         nop
        pop     bx
        mov     flags.comflg,bl ; Set the comm port flag.
        cmp     flags.comflg,1  ; Using Com 1?
        jne     coms0           ; Nope.
        mov     ax,offset port1
        mov     portval,ax
        mov     modem.mddat,DATAA_7201 ; Set COM1 defaults.
        mov     modem.mdstat,STATA_7201
        mov     modem.mdcom,SETA_8253
        cmp     init_flag,FALSE ; already inited?
        jne     coms_1e
        call    init_7201       ; To be sure
        mov     init_flag,TRUE  ; we have now
coms_1e:
        ret
coms0:
        mov     ax,offset port2
        mov     portval,ax
        mov     modem.mddat,DATAB_7201 ; Set COM2 defaults.
        mov     modem.mdstat,STATB_7201
        mov     modem.mdcom,SETB_8253
        cmp     init_flag+1,FALSE ; already inited?
        jne     coms_2e
        call    init_7201       ; To be sure
        mov     init_flag+1,TRUE ; we have now
coms_2e:
        ret
comx:
        pop     bx
        ret
COMS    endp

; Set heath emulation on/off.

VTS     proc    near
        mov     dx,offset ontab
        mov     bx,0
        mov     ah,CMKEY
        call    comnd
         jmp    r
        push    bx
        mov     ah,CMCFM
        call    comnd           ; Get a confirm.
         jmp    vt0             ;  Didn't get a confirm.
         nop
        pop     bx
        mov     flags.vtflg,bl  ; Set the VT52 emulation flag.
        ret
vt0:
        pop     bx
        ret
VTS     endp

VTSTAT  PROC    NEAR    ; For Status display [jrd]
        ret             ; no emulator status to display
VTSTAT  ENDP

; initialization for using serial port.  This routine performs
; any initialization necessary for using the serial port, including
; setting up interrupt routines, setting buffer pointers, etc.
; Doing this twice in a row should be harmless (this version checks
; a flag and returns if initialization has already been done).
; SERRST below should restore any interrupt vectors that this changes.
; Returns normally.

SERINI  proc    near
        push    es
        cmp     portin,FALSE    ; Did we initialize port already?
        jne     serin0          ; Yes, so just leave.
        cli                     ; Disable interrupts
        xor     ax,ax           ; Address low memory
        mov     es,ax
        mov     bx,modem.mdintv
        mov     ax,es:[bx]
        mov     savsci,ax
        mov     ax,offset serint ; Point at our routine
        mov     es:[bx],ax
        add     bx,2            ; Now for CS value
        mov     ax,es:[bx]
        mov     savscs,ax
        mov     es:[bx],cs
        mov     portin,TRUE     ; Note that we are initialized
        call    clrbuf          ; Clear input buffer.
        mov     ax,SEG_8259     ; Point at 8259 interrupt controller
        mov     es,ax
        mov     bx,CW2_8259     ; Control word 2
        mov     al,es:[bx]
        and     al,modem.mden   ; Enable INT1 (all from 7201)
        mov     es:[bx],al      ; Save it
        mov     bx,CW1_8259     ; Control word 1
        mov     al,modem.mdmeoi ; Clear any outstanding requests
        mov     es:[bx],al
        mov     bx,SEG_7201     ; Point at 7201 serial controller
        mov     es,bx
        mov     bx,modem.mdstat
        mov     byte ptr es:[bx],1 ; Register 1
        mov     al,REG1_7201
        or      al,18H          ; Turn on receive interrupts
        mov     es:[bx],al
        mov     byte ptr es:[bx],10H ; Clear external/status interrupts
        mov     byte ptr es:[bx],30H ; Clear special receive cond. int.
        mov     byte ptr es:[bx],38H ; Set to end of interrupt
        sti                     ; Allow interrupts
serin0:
        pop es
        ret                     ; We're done.
SERINI  endp

; Reset the serial port.  This is the opposite of serini.  Calling
; this twice without intervening calls to serini should be harmless.
; Returns normally.

SERRST  proc    near
        push    es              ; preserve this
        cmp     portin,FALSE    ; Reset already?
        je      srst0           ; Yes, just leave.
        cli                     ; Disable interrupts
        mov     bx,SEG_8259     ; Point at 8259 interrupt controller
        mov     es,bx
        mov     bx,CW2_8259
        mov     al,es:[bx]
        or      al,modem.mddis  ; Turn off INT1
        mov     es:[bx],al
        xor     bx,bx           ; Address low memory
        mov     es,bx
        mov     bx,modem.mdintv ; Restore the serial card int vector
        mov     ax,savsci
        mov     es:[bx],ax
        add     bx,2            ; Restore CS too.
        mov     ax,savscs
        mov     es:[bx],ax
        mov     bx,SEG_7201     ; Point at 7201 serial controller
        mov     es,bx
        mov     bx,modem.mdstat
        mov     byte ptr es:[bx],1 ; Register 1
        mov     al,REG1_7201
        and     al,0E7H         ; Turn off receiver interrupts
        mov     es:[bx],al
        mov     portin,FALSE    ; Reset flag.
        sti
srst0:
        pop es
        ret                     ; All done.
SERRST  endp

; serial port interrupt routine.  This is not accessible outside this
; module, handles serial port receiver interrupts.

SERINT  proc  near
        push    ax
        push    bx
        push    cx
        push    di
        push    bp
        push    ds
        push    es
        cld
        mov     ax,seg datas
        mov     ds,ax           ; address data segment
        mov     bx,SEG_7201     ; point at 7201
        mov     es,bx
        mov     bx,modem.mdstat
        mov     al,es:[bx]      ; get status
        test    al,1            ; anything there?
        jnz     srint9
        jmp     retint
srint9:
        mov     bx,modem.mddat
        mov     al,es:[bx]      ; get data byte
        or      al,al
        jnz     srint8
        jmp     retint          ; Ignore nulls
srint8:
        mov     ah,al
        and     ah,7FH          ; strip parity temporarily
;  if we ignore rubouts, we also ignore ASCII 255!?
;       cmp     ah,7FH          ; Ignore rubouts, too.
;       jz      retint
        mov     bp,portval
        cmp     ds:[bp].floflg,0 ; Doing flow control?
        je      srint2          ; Nope.
        mov     bx,ds:[bp].flowc ; Flow control char (BH = XON, BL = XOFF).
        cmp     al,bl           ; Is it an XOFF?
        jne     srint1          ; Nope, go on.
        mov     xofrcv,TRUE     ; Set the flag.
        jmp     retint
srint1:
        cmp     al,bh           ; Get an XON?
        jne     srint2          ; No, go on.
        mov     xofrcv,FALSE    ; Clear our flag.
        jmp     retint
srint2:
        cmp     count,BUFSIZ    ; buffer full?
        jge     srint3a         ; nothing to do then, nowhere to put it
        mov     bx,ds
        mov     es,bx           ; Need right segment
        mov     di,srcpnt
        cld                     ; better increment on strings
        stosb
        cmp     di,offset source + bufsiz
        jb      srint3          ; not past end...
        mov     di,offset source ; wrap buffer around
srint3:
        mov     srcpnt,di       ; save pointer
        inc     count
        cmp     count,MNTRGH    ; Past the high trigger point?
        jbe     retint          ; No, we're within our limit.
srint3a:
        cmp     ds:[bp].floflg,0 ; Doing flow control?
        je      retint          ; No, do nothing.
        cmp     xofsnt,TRUE     ; Have we sent an XOFF?
        je      retint          ; Yes.
        mov     bx,ds:[bp].flowc ; Flow control char (BH = XON, BL = XOFF).
        mov     ah,bl           ; Get the XOFF.
        call    outchr          ; Send it.
        nop
        nop
        nop                     ; ignore failure.
        mov     xofsnt,TRUE     ; Remember we sent it.
        jmp     retint
retint:
        mov     bx,SEG_8259     ; point at 8259
        mov     es,bx
        mov     bx,CW1_8259
        mov     al,modem.mdmeoi ; Clear interrupt
        mov     es:[bx],al
        mov     bx,SEG_7201     ; point at 7201 again
        mov     es,bx
        mov     bx,modem.mdstat
        mov     byte ptr es:[bx],38H ; Notify 7201 of end of interrupt
        sti
        pop     es
        pop     ds
        pop     bp
        pop     di
        pop     cx
        pop     bx
        pop     ax
intret:
        iret
SERINT  endp

; Produce a beep.  Returns normally.

BEEP    proc    near
        mov     dl,BELL
        mov     ah,DCONIO       ; No checks, just do it
        int     DOS
        ret
BEEP    endp

; put the number in ax into the buffer pointed to by di.  Di is updated

NOUT    proc    near
        mov     dx,0            ; high order is always 0.
        mov     bx,10
        div     bx              ; divide to get digit
        push    dx              ; save remainder digit
        or      ax,ax           ; test quotient
        jz      nout1           ; zero, no more of number
        call    nout            ; else call for rest of number
nout1:  pop     ax              ; get digit back
        add     al,'0'          ; make printable
        stosb                   ; drop it off
        ret                     ; and return
NOUT    endp

; Jumping to this location is like retskp.  It assumes the instruction
;   after the call is a jmp addr.

RSKP    PROC    NEAR
        pop     bp
        add     bp,3
        push    bp
        ret
RSKP    ENDP

; Jumping here is the same as a ret.

R       PROC    NEAR
        ret
R       ENDP

CODE    ends

        end
