        name msxv90
; File MSXV90.ASM
; Last modification: 30 July 1986

; 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
; Add some register save/restores here and there.
; Add global procedures ihosts and ihostr to handle host initialization
; when packets are to be sent or received by us,resp. 24 March 1986
; Add global procedure dtrlow to force DTR and RTS low in support of Kermit
; command Hangup.   B.G.Peterson 10 April 1986
; Add support of serial port settings through use of IOCTL DOS function
; code from Andreas Stumpf (ZRZS@DS0RUS1I), merged by B.G.Peterson 10 April 86
; Moved VTS and VTSTAT routines to MSYxxx.ASM where the terminal emulation
; is done anyway. B.G.Peterson 10 April 1986
; Last update 28 April 1986
; 30 July 1986 Corrected IHOSTS and IHOSTR to prevent sending null byte if
;  no flow control.
;  Modified SERHNG so it turns DTR and RTS off for 3 seconds and then back
;  on again so a new connection can be made easily.
;  Modified SERRST to wait until transmitter is empty before turning off
;  port.
;  Modified port controller access so that if opening it using the standard
;  Victor drivers fails, it will just go direct to the hardware. [bgp]

        public  serini, serrst, clrbuf, outchr, coms, 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
        public  ihosts, ihostr, dtrlow                          ; [jrd]

        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
ENABLE_INT EQU  18H             ; Mask to turn on interrupts (OR)
;   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
BREAK_ON EQU    10H             ; Mask to turn on break bit (OR)
DTR_RTS_OFF EQU 7DH             ; Mask to turn off DTR and RTS (AND)

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
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$'
hngmsg  db      CR,LF,' The phone should have hung up.',CR,LF,'$' ; [jrd]
hnghlp  db      CR,LF,' The modem control lines DTR and RTS for the current'
        db      ' port are forced low (off)'
        db      CR,LF,' to hang up the phone.  Normally, Kermit leaves them'
        db      ' high (on) when it exits.'
        db      CR,LF,' They will return high after about 3 seconds.'
        db      CR,LF,'$'                                       ; [jrd]
tmp     db      ?,'$'
temp1   dw      ?               ; Temporary storage.

ontab   db      02H             ; Two entries.
        mkeyw   'OFF',00H       ; must be alphabetized
        mkeyw   'ON',01H

comptab db      0AH
        mkeyw   '1',01H
        mkeyw   '2',00H
        mkeyw   'A',01H
        mkeyw   'B',00H
        mkeyw   'COM1',01H
        mkeyw   'COM2',00H
        mkeyw   'SERIALA',01H
        mkeyw   'SERIALB',00H
        mkeyw   'TTY',01H
        mkeyw   'UL1',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      6B4H            ; 45.5 baud
        dw      61AH            ; 50 baud
        dw      411H            ; 75 baud
        dw      2C6H            ; 110 baud
        dw      244H            ; 134.5 baud
        dw      208H            ; 150 baud
        dw      104H            ; 300 baud
        dw      82H             ; 600 baud
        dw      41H             ; 1200 baud
        dw      2BH             ; 1800 baud
        dw      26H             ; 2000 baud
        dw      20H             ; 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.

; variables for accessing portinfo from serial drivers using IOCTL function
;
; Structure is defined according to "Systems Programmers Toolkit II",
; Appendix A.       Structure contains baud rate and values of control registers
; 0 through 7.

pval    struc
type    dw      11H             ; port access
status  dw      (?)
blocktype dw    0               ; serial
baudr   dw      (?)             ; baud rate to set or get
CR0     db      (?)
CR1     db      (?)
CR2A    db      (?)
CR2B    db      (?)
CR3     db      (?)
CR4     db      (?)
CR5     db      (?)
CR6     db      (?)
CR7     db      (?)
pval    ends

erms41  db      CR,LF,'?Warning:  Cannot open com port'
        db      CR,LF,'  Going direct to serial controller hardware...$' ; [bgp]
rdbuf   db      20 dup (?)      ; input buffer

plength equ     17              ; length of pval structure
oldpval pval    <,,,41H,,,,,,,,,> ; default to 1200 baud
newpval pval    <,,,41H,,,,,,,,,> ; value comes from bdtab above... [bgp]

prttab  dw      com2,com1       ; 0=com2, 1=com1 in flags.comflg
com1    db      'SERIALA',0     ; name string for device
com2    db      'SERIALB',0

IOread  equ     2               ; read status block
IOwrite equ     3               ; write status block

prthnd  dw      0               ; handle for accessing port

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,TTHEATH ; BIOS does HEATH, use as default
        cmp     flags.comflg,1  ; using port 1?
        jne     lclini2         ; no...
        mov     ax,offset port1
        mov     portval,ax
        mov     modem.mddat,DATAA_7201 ; set COM1 values
        mov     modem.mdstat,STATA_7201
        mov     modem.mdcom,SETA_8253
        jmp     lclini0
lclini2:                        ; using port2
        mov     ax,offset port2
        mov     portval,ax
        mov     modem.mddat,DATAB_7201 ; set COM2 values
        mov     modem.mdstat,STATB_7201
        mov     modem.mdcom,SETB_8253
lclini0:
        call    opnprt          ; get file handle and init port
        call    lclyini         ; init term part too
        ret
LCLINI  endp

; procedure to get a file handle for the port.  if it fails ask the user
; for some predefined handle (3 is the usual value) (Andreas Stumpf)
; 30 July 1986 If it fails, just warn the user and go direct to the
;  hardware [bgp]

OPNPRT  proc    near
        cmp     prthnd,0        ; is one open?
        jle     opnprta         ; no...
        mov     bx,prthnd       ; better close this
        mov     ah,CLOSE2       ; to be sure they don't accumulate
        int     DOS
        mov     prthnd,0        ; done...
opnprta:
        mov     al,flags.comflg
        mov     ah,0
        mov     si,ax
        shl     si,1            ; double index
        mov     dx,prttab[si]
        mov     ah,OPEN2
        mov     al,2
        int     DOS             ; open port on handle
        jnc     opnprt2
        mov     ah,PRSTR        ; it didn't like the string...
        mov     dx,offset erms41
        int     DOS
        mov     prthnd,-1       ; no port is open! [bgp]
        push    es              ; better save this
        mov     bx,SEG_7201     ; point at controller
        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 four 2.5 MHz cylces
        pop     ax              ; 8 processor cycles
        mov     bx,STATA_7201   ; First one is always port A
        mov     byte ptr es:[bx],2 ; must be first one set
        mov     byte ptr es:[bx],REG2_7201
        mov     bx,modem.mdstat
        mov     byte ptr es:[bx],4 ; must be second
        mov     byte ptr es:[bx],REG4_7201
        mov     byte ptr es:[bx],1 ; rest any order
        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
        pop     es
        call    getbaud         ; use this to be sure right value is used
        call    dobaud          ; better set baud rate to the correct value too
        jmp     opnprt2a        ; all init done... [bgp]
opnprt2:
        mov     prthnd,ax       ; call succeeded - save handle
        mov     bx,ax
        mov     ah,IOCTL
        mov     al,IOread       ; get old values
        mov     cx,plength
        mov     dx,offset oldpval ; place for old values
        int     DOS
        mov     ah,IOCTL
        mov     al,IOread
        mov     dx,offset newpval ; one to work on
        int     DOS

; set registers to something neat

        cli                     ; avoid interrupts here
        mov     bx,offset newpval
        mov     [bx].CR1,REG1_7201
        mov     [bx].CR2A,REG2_7201
        mov     [bx].CR3,REG3_7201
        mov     [bx].CR4,REG4_7201
        mov     [bx].CR5,REG5_7201
        mov     bx,prthnd       ; get handle
        mov     ah,IOCTL
        mov     al,IOwrite      ; set new values
        int     DOS
opnprt2a:
        push    es              ; save this for a flash
        mov     bx,SEG_7201
        mov     es,bx
        mov     bx,modem.mdstat
        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 interupt
        pop     es              ; back again
        sti                     ; interrupts are okay again
        ret
OPNPRT  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
        mov     si,ax
        mov     ah,DCONIO       ; don't check anything...
        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
        pop     si
        ret
PUTHLP  endp

; Set the baud rate for the current port, based on the value
; in the portinfo structure.      Returns normally.      Method of setting using
; IOCTL thanks to Andreas Stumpf

DOBAUD  proc    near
        push    ax              ; these too [jrd]
        push    bx
        push    cx
        push    dx
        mov     bx,portval
        mov     bx,[bx].baud
        cmp     bx,0
        jl      unk_baud        ; out of range
        cmp     bx,BAUDSIZ
        jge     unk_baud        ; out of range
        shl     bx,1            ; get index into table
        mov     ax,offset bddat ; start of table
        add     bx,ax
        mov     ax,[bx]         ; get divider
        cmp     ax,0FFH         ; unimplemented baud rate?
        je      unk_baud        ; that's right
        mov     bx,offset newpval
        mov     [bx].baudr,ax   ; set it in structure no matter what [bgp]
        cmp     prthnd,0        ; anything open?
        jge     dobaud0         ; yes, do it nice
        push    es              ; better save this...
        push    ax              ; save divider
        mov     bx,SEG_8253     ; point at 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
        pop     ax              ; get divider back
        mov     es:[bx],al
        mov     es:[bx],ah      ; done
        pop     es
        jmp     dobaud1
dobaud0:                        ; [bgp]
        mov     ah,IOCTL
        mov     al,IOwrite
        mov     bx,prthnd       ; set the poor thing
        mov     cx,plength
        mov     dx,offset newpval
        int     DOS
dobaud1:
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        ret
unk_baud:
        mov     ah,PRSTR
        mov     dx,offset badbd ; Give an error message.
        int     DOS
        pop     dx              ; restore regs [jrd]
        pop     cx
        pop     bx
        pop     ax
        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.       The method of getting the baud
; rate directly from the port handler is thanks to Andreas Stumpf.
; Note that this assumes that the thing has a defined baud rate to
; start with from the initialization and the opnprt has been called on
; the current port so that the values in newpval are defined.

GETBAUD proc    near
        push    ax
        push    bx
        push    cx
        push    dx
        cmp     prthnd,0        ; opened?
        jne     go_gb           ; yes, get the rate
        call    opnprt          ; no, open it
go_gb:
        mov     bx,offset newpval
        mov     ax,[bx].baudr
        mov     bx,offset bddat+(BAUDSIZ-1)*2
        mov     cx,BAUDSIZ      ; length of baudtable
loop_gb:
        cmp     ax,[bx]
        je      have_gb
        dec     bx
        dec     bx
        dec     cx
        jnz     loop_gb
have_gb:
        dec     cx              ; value is one greater than desired
        mov     bx,portval      ; cx=-1 means unrecognized (dropped off bottom)
        mov     [bx].baud,cx
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        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

; IHOSTS - Initialize the host by sending XON, or equivalent, and enter the
; cycle of clear input buffer, wait 1 second, test if buffer empty then exit
; else repeat cycle. Requires that the port be initialized before hand.
; Ihosts is used by the local send-file routine just after initializing
; the serial port.
; 22 March 1986 [jrd]
; 30 July 1986 Avoid sending nulls if no flow control [bgp]

IHOSTS  proc    near
        push    ax              ; save the registers
        push    bx
        push    cx
        push    dx
        mov     bx,portval      ; port indicator
        mov     ax,[bx].flowc   ; put Go-ahead flow control char in ah
        or      ah,ah           ; check for null char
        jz      ihosts1         ; z=null, don't send it
        call    outchr          ; send it (release Host's output queue)
         nop                    ; outchr can do skip return
         nop
         nop
ihosts1:call    clrbuf          ; clear out interrupt buffer
        mov     ax,1            ; sleep for 1 second
        call    sleep           ; procedure sleep is in msscom.asm
        call    prtchr          ; check for char at port
         jmp    ihosts1  ; have a char in al, repeat wait/read cycle
         nop                    ; prtchr does skip return on empty buffer
        pop     dx              ; empty buffer. we are done here.
        pop     cx
        pop     bx
        pop     ax
        ret
IHOSTS  endp

; IHOSTR - initialize the remote host for our reception of a file by
; sending the flow-on character (XON typically) to release any held
; data. Called by receive-file code just after initializing the serial
; port.  22 March 1986 [jrd]
; 30 July 1986 Avoid sending null if no flow control [bgp]

IHOSTR  proc    near
        push    ax              ; save regs
        push    bx
        push    cx
        mov     bx,portval      ; port indicator
        mov     ax,[bx].flowc   ; put Go-ahead flow control char in ah
        or      ah,ah           ; check for null char
        jz      ihostr1         ; z=null, don't send it
        call    outchr          ; send it (release Host's output queue)
         nop                    ; outchr can do skip return
         nop
         nop
ihostr1:pop     cx
        pop     bx
        pop     ax
        ret
IHOSTR  endp

; Global proc to hang up the phone by making DTR and RTS low.

DTRLOW  proc    near
        mov ah,cmtxt            ; allow text to be able to display help
        mov bx,offset rdbuf     ; dummy buffer
        mov dx,offset hnghlp    ; help message
        call comnd              ; get a confirm
         jmp r
        call serhng             ; drop DTR and RTS
        mov ah,PRSTR            ; give a nice message
        mov dx,offset hngmsg
        int dos
        jmp rskp
DTRLOW  endp

; SERHNG us used to hang up the phone.  This resets the port (by calling
; serrst), and then forces DTR and RTS low to terminate the connection.
; 12 April 1986 [bgp]
; 30 July 1986 Turn them back on again after 3 seconds so new connection can
;  be made [bgp]

SERHNG  proc    near
        call    serrst          ; reset the port to be sure
        push    dx
        push    cx
        push    bx
        push    ax
        cmp     prthnd,0        ; nice method open? [bgp]
        jge     serhng0         ; yes
        push    es              ; better save this
        mov     bx,SEG_7201     ; point at controller
        mov     es,bx
        mov     bx,modem.mdstat
        cli                     ; no interrupts please
        mov     byte ptr es:[bx],5 ; register 5
        mov     byte ptr es:[bx],REG5_7201 and DTR_RTS_OFF
        sti                     ; interrupts ok again
        mov     ax,3            ; sleep for 3 seconds
        call    sleep
        mov     bx,SEG_7201
        mov     es,bx
        mov     bx,modem.mdstat
        cli                     ; no interrupts please
        mov     byte ptr es:[bx],5
        mov     byte ptr es:[bx],REG5_7201
        sti                     ; interrupts ok again
        pop     es
        jmp     serhng1         ; [bgp]
serhng0:
        mov     bx,offset newpval
        mov     [bx].CR5,REG5_7201 and DTR_RTS_OFF
        mov     dx,bx           ; where the stuff is
        mov     cx,plength      ; how long it is
        mov     bx,prthnd       ; get handle
        mov     ah,IOCTL
        mov     al,IOwrite      ; set new values
        cli                     ; no interrups please
        int     DOS
        sti                     ; allow interrupts for a while [bgp]
        mov     ax,3            ; sleep for 3 seconds
        call    sleep
        mov     bx,offset newpval ; turn them back on again...
        mov     [bx].CR5,REG5_7201
        mov     dx,bx
        mov     cx,plength
        mov     bx,prthnd
        mov     ah,IOCTL
        mov     al,IOwrite
        cli                     ; no interrupts again
        int     DOS
        sti                     ; interrupts are okay again
serhng1:
        pop     ax
        pop     bx
        pop     cx
        pop     dx
        ret
SERHNG  endp

; Send a break out the current serial port.     Returns normally.
; Changed to use IOCTL function 12 April 1986 [bgp]
; 30 July 1986 Direct to hardware if not opened right [bgp]

SENDBR  proc    near
        push    es
        push    dx
        push    cx
        push    bx
        push    ax
        cmp     prthnd,0        ; open ok? [bgp]
        jge     sendbr0         ; yes, do it nice
        mov     bx,SEG_7201     ; point at 7201
        mov     es,bx
        mov     bx,modem.mdstat
        cli                     ; no interrupts please
        mov     byte ptr es:[bx],1 ; register 1
        mov     byte ptr es:[bx],REG1_7201
        mov     byte ptr es:[bx],5
        mov     byte ptr es:[bx],REG5_7201 or BREAK_ON
        sti                     ; interrupts ok again
        mov     ax,250          ; wait 250 msec
        call    wait_msec
        cli                     ; no interrupts
        mov     byte ptr es:[bx],5
        mov     byte ptr es:[bx],REG5_7201
        mov     byte ptr es:[bx],1
        mov     byte ptr es:[bx],REG1_7201 or ENABLE_INT
        sti                     ; interrupts ok again
        jmp     sendbr1         ; [bgp]
sendbr0:
        mov     bx,offset newpval
        mov     [bx].CR1,REG1_7201 ; to disable interrupts
        mov     [bx].CR5,REG5_7201 or BREAK_ON
        mov     dx,bx           ; where the stuff is
        mov     cx,plength      ; how much there is
        mov     bx,prthnd       ; get handle
        mov     ah,IOCTL
        mov     al,IOwrite      ; set new values
        cli                     ; avoid interrupts here
        int     DOS
        mov     bx,SEG_7201     ; have to explicitly do register 1
        mov     es,bx           ; IOCTL doesn't seem to touch it
        mov     bx,modem.mdstat
        mov     byte ptr es:[bx],1 ; Register 1
        mov     byte ptr es:[bx],REG1_7201
        sti                     ; interrupts back on
        mov     ax,250          ; This is a 250 msec break
                                ; A long break is 3500 msec
        call    wait_msec       ; kill time
        mov     bx,offset newpval
        mov     [bx].CR5,REG5_7201
        mov     bx,prthnd
        mov     ah,IOCTL
        mov     al,IOwrite
        cli                     ; no interrupts please
        int     DOS
        mov     bx,SEG_7201     ; Point at 7201 serial controller
        mov     es,bx
        mov     bx,modem.mdstat
        mov     byte ptr es:[bx],1 ; Register 1 must be done explicitly
        mov     byte ptr es:[bx],REG1_7201 or ENABLE_INT
        sti                     ; interrupts are okay again
sendbr1:
        pop     ax
        pop     bx
        pop     cx
        pop     dx
        pop     es
        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    ax
        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
        push    dx
        add     dl,' '          ; this is the column
        int     DOS
        pop     dx
        pop     ax
        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
        call    opnprt          ; open the handle
        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
        call    opnprt
        ret
comx:
        pop     bx
        ret
COMS    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.     Modified to IOCTL function 12 April 1986 [bgp]

SERINI  proc    near
        push    es
        push    dx
        push    cx
        push    bx
        push    ax
        cmp     portin,FALSE    ; Did we initialize port already?
        je      serin0
        jmp     serin1          ; Yes, just leave
serin0:
        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
        mov     ax,offset source
        mov     srcpnt,ax
        mov     savesi,ax
        mov     count,0
        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

;  Note that access to the serial controller here is only to register 1 which
;  must be done explicitly anyway, so there is no reason to care about
;  whether a port is open [bgp]

        mov     bx,SEG_7201     ; Point at 7201 serial controller
        mov     es,bx
        mov     bx,modem.mdstat
        mov     byte ptr es:[bx],1 ; Register 1 must be done explicitly
        mov     byte ptr es:[bx],REG1_7201 or ENABLE_INT
        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
serin1:
        pop     ax
        pop     bx
        pop     cx
        pop     dx
        pop     es              ; We're done.
        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.     Modified to use IOCTL function 12 April 1986 [bgp]
; 30 July 1986 Make it go direct to hardware if handle open failed [bgp]
;   Also, don't reset the port until the transmit buffer is empty

SERRST  proc    near
        push    es              ; preserve this
        push    dx
        push    cx
        push    bx
        push    ax
        cmp     portin,FALSE    ; Reset already?
        je      srst0           ; Yes, just leave.
        mov     bx,SEG_7201     ; point at 7201 [bgp]
        mov     es,bx
        mov     bx,modem.mdstat
sersta:
        cli
        mov     byte ptr es:[bx],1 ; want status register 1
        mov     al,es:[bx]      ; get status
        sti
        test    al,1            ; all sent? (transmitter and shift reg. empty)
        jz      sersta          ; no, wait...
        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

; As in SERINI, the only access to the serial controller is to register 1
; which must be done explicitly anyway, so we don't care about the handle [bgp]

        mov     bx,SEG_7201     ; Point at 7201 serial controller
        mov     es,bx
        mov     bx,modem.mdstat
        mov     byte ptr es:[bx],1 ; Register 1 has to be done explicitly
        mov     byte ptr es:[bx],REG1_7201
        mov     portin,FALSE    ; Reset flag.
        sti
srst0:
        pop     ax
        pop     bx
        pop     cx
        pop     dx
        pop     es              ; All done.
        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

