; CN8XIC.A86
; I/O for ICL PC.
; STARTSYSDEP ; This is so:
;               ;     PIP LISTING=86KERMIT.LST[WSSTARTSYSDEP^ZQENDSYSDEP^Z]
;               ; will work.
;
; **************************************************************************
;
; This is the i/o support module for the Honeywell MicroSystem Executive
; Running Concurrent CP/M (COS-86, FTOS)
;
;  Mark J. Hewitt  University of Birmingham, UK   August 1985
;
;  Port selection is provided between the V24 and TELEX ports.  It is
;  impractical to extend this to the PRINTER port because both the PRINTER
;  and KEYBOARD are routed through the same interrupt vector, and the XIOS
;  code explicitly enables interrupts.  This means that if I hijack the
;  OS interrupt with what I think is non-reentrant but safe code, and pass
;  all the keyboard characters to the OS, when it returns, interrupts are
;  enabled, and havoc follows.  There are three solutions to this, for the
;  brave of heart:
;
;    a) Patch the STI instruction out of the OS image at runtime.
;    b) Write the Kermit interrupt service routine to be fully re-entrant.
;    c) Do not bother to provide support for the PRINTER port.
;
;  I've chosen solution (c).
;
;  The V.24 port uses the full V.24 standard, and it is therefore necessary
;  to loop back the baud rate clock.  This appears on pin 16 of the D-type
;  connector.  This should be connected to pins 15 and 17 for normal async.
;  operation.
;
;  A further limitation is that only one Kermit can be run at once, even
;  though it would appear that two Kermits could be run concurrently to
;  the two ports.  This is because the ports share the same interrupt vector.
;
; **************************************************************************
;
 CSEG $

; Port base definitions

comm equ 08000h  ; Telex and V24 port i/o base    (7201)
bgen equ 0E000h  ; Baud Rate Generator            (8253-5)
ictrl equ 0E400h  ; Interrupt Controller           (8259A)

; And the I/O ports themselves

bgcmd equ bgen+6  ; Baud rate generator command port

iccmd equ ictrl+0  ; Interrupt controller command port
icmask equ ictrl+2  ; Interrupt controller mask register port

tlxcmd equ comm+4  ; Telex command port
tlxbg equ bgen+4  ; Baud rate countdown value for telex port
tlxio equ comm+0  ; Telex data io port

v24cmd equ comm+6  ; V24 command port
v24bg equ bgen+2  ; Baud rate countdown value for v24 port
v24io equ comm+2  ; V24 data io port
;
; Port selection
;
ptlx equ 0  ; Telex port selected
pv24 equ 1  ; V.24 port selected

;
; Interrupt vectors in page 0
;
ivcomm equ 0100h  ; 0:x interrupt vector for Telex and V.24
;
; Interrupt masks
;
imcomm equ 01h  ; mask for Telex/V.24 interrupts
imnet equ 02h  ; mask for network interrupt
imfdc equ 04h  ; mask for floppy disc controller interrupt
imbus equ 08h  ; mask for expansion bus interrupt
imwdc equ 10h  ; mask for winchester disc controller int.
imxxx equ 20h  ; not used
imfrl equ 40h  ; mask for frame reference latch interrupt
imprt equ 80h  ; mask for printer and keyboard interrupts
;
;  Baud rate generator command words
;
tlxbsel equ 0B6h  ; select telex baud rate register
v24bsel equ 76h  ; select V.24 baud rate register
;
;  Interrupt controller commands
;
iceoi equ 20h  ; end of interrupt
;
;  I/O register bits
;
;  For communications (Telex and V.24) ports
;
ccreg0 equ 00h  ; Control instruction - select register 0
ccreg1 equ 01h  ; Control instruction - select register 1
ccreg2 equ 02h  ; Control instruction - select register 2
ccreg3 equ 03h  ; Control instruction - select register 3
ccreg4 equ 04h  ; Control instruction - select register 4
ccreg5 equ 05h  ; Control instruction - select register 5
ccreg6 equ 06h  ; Control instruction - select register 6
ccreg7 equ 07h  ; Control instruction - select register 7

c0null equ 00h  ; Register 0 - null command
c0abort equ 08h  ; Register 0 - send abort
c0resi equ 10h  ; Register 0 - reset ext. status ints.
c0chrst equ 18h  ; Register 0 - channel reset
c0eninc equ 20h  ; Register 0 - enable int. on next character
c0rpti equ 28h  ; Register 0 - reset pending tx int./DMA req.
c0errst equ 30h  ; Register 0 - error reset
c0eoi equ 38h  ; Register 0 - end of interrupt
c0rxcrc equ 40h  ; Register 0 - reset rx CRC checker
c0txcrc equ 80h  ; Register 0 - reset tx CRC generator
c0ricrc equ 0C0h  ; Register 0 - reset idle/CRC latch

c1stien equ 01h  ; Register 1 - external/status int enable
c1txien equ 02h  ; Register 1 - transmitter interrupt enable
c1cav equ 03h  ; Register 1 - condition affects vector
c1noi equ 00h  ; Register 1 - no rx or DMA interrupts
c1i1st equ 08h  ; Register 1 - int. on 1st received character
c1iall equ 10h  ; Register 1 - int. on all received characters
c1ialp equ 18h  ; Register 1 - int on all rx'd chars, no parity
c1wrxtx equ 20h  ; Register 1 - WAIT on rx/tx
c1txbcm equ 40h  ; Register 1 - TX byte count mode enbable
c1wten equ 80h  ; Register 1 - WAIT function enable
;
;  and some useful abbreviations
;
c1norm equ c1ialp
;
c2dma0 equ 00h  ; Register 2 - No DMA
c2dma1 equ 01h  ; Register 2 - DMA mode 1
c2dma2 equ 02h  ; Register 2 - DMA mode 2
c2dma3 equ 03h  ; Register 2 - DMA mode 3
c2pri equ 04h  ; Register 2 - Set DMA priority
c2ack0 equ 00h  ; Register 2 - Int. Ack. mode 0 (NV,D432)
c2ack1 equ 08h  ; Register 2 - Int. Ack. mode 1 (NV, D432)
c2ack2 equ 10h  ; Register 2 - Int. Ack. mode 2 (NV, D210)
c2ack4 equ 20h  ; Register 2 - Int. Ack. mode 4 (8085 master)
c2ack5 equ 28h  ; Register 2 - Int. Ack. mode 5 (8085 slave)
c2ack6 equ 30h  ; Register 2 - Int. Ack. mode 6 (8086)
c2ack7 equ 38h  ; Register 2 - Int. Ack. mode 7 (8085/8259A slave)
c2rxim equ 40h  ; Register 2 - rx interrupt mask
c2syncb equ 80h  ; Register 2 - pin 10 ~RTSB or ~SYNCB

c3rxen equ 01h  ; Register 3 - receive enable
c3scli equ 02h  ; Register 3 - sync character load inhibit
c3asm equ 04h  ; Register 3 - address search mode
c3rxcrc equ 08h  ; Register 3 - receiver CRC enable
c3hunt equ 10h  ; Register 3 - enter hunt phase
c3aen equ 20h  ; Register 3 - auto enables on DCD/CTS
c3r5bit equ 00h  ; Register 3 - 5 bit data
c3r6bit equ 40h  ; Register 3 - 6 bit data
c3r7bit equ 80h  ; Register 3 - 7 bit data
c3r8bit equ 0C0h  ; Register 3 - 8 bit data
;
;  and some useful abbreviations
;
c3norm equ c3rxen+c3r8bit
;
c4pen equ 01h  ; Register 4 - parity enable
c4ep equ 02h  ; Register 4 - even parity
c41stp equ 04h  ; Register 4 - 1 stop bit
c415stp equ 08h  ; Register 4 - 1.5 stop bits
c42stp equ 0C0h  ; Register 4 - 2 stop bits
c48syn equ 00h  ; Register 4 - 8 bit internal sync (monosync)
c416syn equ 10h  ; Register 4 - 16 bit internal sync (bisync)
c4sdlc equ 20h  ; Register 4 - SDLC
c4exts equ 30h  ; Register 4 - External sync
c41clk equ 00h  ; Register 4 - 1x clock rate
c416clk equ 40h  ; Register 4 - 16x clock rate
c432clk equ 80h  ; Register 4 - 32x clock rate
c464clk equ 0C0h  ; Register 4 - 64x clock rate
;
;  and some useful abbreviations
;
c4norm equ c41stp+c416clk
;
c5txcrc equ 01h  ; Register 5 - transmitter CRC enable
c5rts equ 02h  ; Register 5 - request to send
c5poly equ 04h  ; Register 5 - CRC polynomial select
c5txen equ 08h  ; Register 5 - transmitter enable
c5sbrk equ 10h  ; Register 5 - send break
c5t5bit equ 00h  ; Register 5 - transmit 5 bit data
c5t6bit equ 20h  ; Register 5 - transmit 6 bit data
c5t7bit equ 40h  ; Register 5 - transmit 7 bit data
c5t8bit equ 60h  ; Register 5 - transmit 8 bit data
c5dtr equ 80h  ; Register 5 - data terminal ready
;
;  and some useful abbreviations
;
c5norm equ c5rts+c5txen+c5t8bit+c5dtr
;
cs0rxr equ 01h  ; Status register 0 - received char ready
cs0ip equ 02h  ; Status register 0 - interrupt pending
cs0tbe equ 04h  ; Status register 0 - tx buffer empty
cs0dcd equ 08h  ; Status register 0 - data carrier detect
cs0sync equ 10h  ; Status register 0 - sync status
cs0cts equ 20h  ; Status register 0 - clear to send
cs0idle equ 40h  ; Status register 0 - idle CRC latch status
cs0brk equ 80h  ; Status register 0 - break detect

cs1sent equ 01h  ; Status register 1 - all sent
cs1sdlc equ 0Eh  ; Status register 1 - SDLC residue code
cs1pe equ 10h  ; Status register 1 - parity error
cs1oe equ 20h  ; Status register 1 - overrun error
cs1fe equ 40h  ; Status register 1 - framing error
cs1eosf equ 80h  ; Status register 1 - end of SDLC frame
;
; System Calls
;
p_dispatch equ 8Eh ; Reschedule in Concurrent CP/M
f_errmode equ 2dh ; Set BDOS error mode
p_pdadr  equ 9Ch ; Get current process's descriptor (PD)
s_sysdat equ 9Ah ; Get address of system data segment
p_termcpm equ 0 ; return to Concurrent CP/M
c_wrtstr equ 9 ; write a string to console
;
;  Process management equates
;
pnoff equ 8  ; offset of process name into PD
pnlen equ 8  ; length of process name in PD
pcns equ 20h  ; offset to process console in PD
thrdrt equ 72h  ; Offset to thread list root in system data
thread equ 2  ; Offset to thread list pointer in PD
;
; Clock rate *10 for timing loops ;[19g]
;
clckrt equ 80   ;[19g]  8.0 Mhz
;
;  Maximum number of examinations of output port to be ready before
;  rescheduling.
;
outlmt equ 1000h

;
;  The executable code starts here
;
;
; ===========================================================================
;
;                        INITIALISATION ROUTINES
;
; ===========================================================================
;
; INTERFACE ROUTINE SERINI - Initialisation code
;
serini: cmp mninit, true ; Ensure that we only initialise once
 je  serin2
 mov mninit, true
;
;  Now check that only one invokation of Kermit exists, and abort if we
;  were not there first - too many frogs spoil the pond!
;
 call setname  ; set my own process name
 call onlychk  ; ensure we are the only Kermit
;
;  Initialise the screen
;
 call toansi  ; configure the screen in ANSI mode
 call clrscr  ; clear the screen in ANSI mode.
;
;  Disable I/O interrupts, and save the old interrupt mask.
;  CGL - removed
; mov dx, icmask ; read the current interrupt mask
; in al, dx
; mov oldmsk, al ; and save it
; or al, imcomm ; mask off i/o interrupts
; out dx, al  ; and reprogram interrupt controller
;
;  Save the system i/o interrupt vectors
;
; mov ax, ds  ; save the data segment in code segment
; mov cs:mndseg, ax ;   for use by interrupt handler

; mov ax, 0  ; point to zero page and save both the
; mov es, ax  ;   system's i/o interrupt vectors
; mov ax,es:.ivcomm+0 ;     for the V.24/Telex channel
; mov vscoff, ax
; mov ax, es:.ivcomm+2
; mov vscseg, ax
;
;  Configure the default port
;
; CGL - removed
; mov ax, 0  ; point to zero page and set the interrupt
; mov es, ax  ;   vector for the V.24/Telex channel to my
    ;     interrupt service routine
; mov ax, offset isr ; set offset address
; mov es:.ivcomm+0, ax
; mov ax, cs  ; set segment address
; mov es:.ivcomm+2, ax
;
; call setmode  ; set UART mode for current port
; call setbaud  ; set the baud rate for the current port
; call mnflush  ; flush and enable the current port
; call inton  ; turn interrupts on for current port
;
;  set BDOS error mode
;
 mov cl, f_errmode
 mov dl, 0FEh ; return and display mode
 int bdos

serin2: ret   ; initialisation over
;
; INTERNAL ROUTlNE SETNAME - set the name of my process
;                            This is to ensure that all invokations of
;                            Kermit have the same name, and thus we can
;                            make certain that only one is running.
;
setname:mov cl, p_pdadr ; get the address of my process descriptor
 int bdos
 mov pd_seg, es ; and save it
 mov pd_off, ax
 add bx, pnoff ; offset into PD of process name field
 mov si, offset myname
 mov di, bx
 mov cx, pnlen ; length of process name
 cld
 rep movsb  ; move the process name
 ret
;
;  INTERNAL ROUTINE ONLYCHK - ensure that the current process is the only
;                             incarnation running.  Only return if we are
;                             alone (In space, no-one can hear you scream)
;
onlychk:pushf   ; this must be done with interrupts off
 cli
 mov cl, s_sysdat ; get address of system data segment
 int bdos
 mov bx, es:word ptr thrdrt[bx] ; address of root of thread list

;
;  Loop through the thread list, looking for processes with the same name
;  and differently addressed process dcescriptors to the current one
;
 cld
oc001: push bx
 push es
 mov si, offset myname ; compare the names
 add bx, pnoff  ; point at name on thread list
 mov di, bx
 mov cl, pnlen
 repz cmpsb   ; perform the comparison
 pop es   ; restore regs - does not alter flags
 pop bx
 jz oc002   ; may be myself
oc003: mov bx, es:word ptr thread[bx] ; next process on thread list
 cmp bx, 0   ; null terminated thread list
 jne oc001
 popf
 ret    ; return through here if we are alone

oc002: cmp bx, pd_off  ; check if we have found ourselves
 jz oc003   ; we have - this is OK!

 mov dx, offset frogXn ; another kermit exists - abort
 mov cl, c_wrtstr  ; ... prettily
 mov al, es:byte ptr pcns[bx] ; the console of other Kermit
 popf    ; restore interrupt status
 add al, '0'
 mov okcons, al
 int bdos
oc004: mov cl, p_termcpm  ; and exit
 int bdos
 jmp oc004   ; just in case

 DSEG $  ; Data used for process management

pd_seg rw 1  ; segment containing current process descriptor
pd_off rw 1  ; offset of current process descriptor
myname db 'Lineuser' ; Name that current process will be known by
frogXn db 'Another Lineuser is running on console '
okcons db 1  ; console of other kermit
 db cr, lf, '$'

 CSEG $

;
; INTERFACE ROUTINE SERFIN - restore environment (as far as possible)
;                            to that which existed before we played with it
;
serfin: cmp mninit, true ; only deinitialise if necessary
 jmp  serfn2  ; CGL
 mov mninit, false
;
;  Disable i/o interrupt while we reset the vectors
;
 mov dx, icmask ; get present interrupt mask
 in al, dx  ; and turn off all i/o interrupts
 or al, imcomm ;   from the V.24/Telex channel
 out dx, al  ; reprogram the interrupt controller
;
;  Reset the i/o interrupt vectors
;
 mov ax, 0  ; point at page 0 and reset the int. vectors
 mov es, ax
 mov ax, vscoff ;   for the V.24/Telex port
 mov es:.ivcomm+0, ax
 mov ax, vscseg
 mov es:.ivcomm+2, ax
;
; turn interrupts back on (or off...)
;
 mov al, oldmsk ; restore original interrupt mask
 out dx, al
;
;  Reset screen modes
;
 call clrscr  ; be tidy - clear the screen
 call toft  ; reset screen to FT mode

serfn2: ret   ; deinitialisation over

;
; INTERNAL ROUTINE TOANSI - configure screen in ANSI mode
;
toansi: ret ; CGL
 mov dx, offset ansion
 call tmsg
 ret
;
;  INTERNAL ROUTINE TOFT - configure screen in FT mode
;
toft: ret ; CGL
 mov dx, offset fton
 call tmsg
 ret
;
; INTERNAL ROUTINE SETMODE - set the operating mode for current port's UART.
;                            Port number in cport,
;                            Current UART command port in ccmdp.
;
setmode: ret ; CGL
 push ax
 push dx  ; we'll need this

 mov dx, ccmdp ; current command port for UART

 cmp cport, ptlx ; is it the Telex port?
 je smcomm
 cmp cport, pv24 ; is it the V.24 port?
 jne setmo2  ; must be an error - just return for now

smcomm:    ; set UART modes for the Telex/V.24 port
 mov al, c0chrst ; reset the port
 out dx, al
 mov al, c0resi+ccreg4 ; select register 4
 out dx, al
 mov al, c4norm ; 16x Clock, 1 stop bit, no parity
 out dx, al
 mov al, c0resi+ccreg3 ; Select register 3
 out dx, al
 mov al, c3norm ; 8 bits/character, RX enable
 out dx, al
 mov al, c0resi+ccreg5 ; select register 5
 out dx, al
 mov al, c5norm ; 8 bits/character, TX enable RTS and DTR
 out dx, al
 mov al, c0resi+ccreg1 ; select register 1
 out dx, al
 mov al, c1norm ; Interrupt enable
 out dx, al

setmo2: pop dx  ; modes now set, restore regs. and return
 pop ax
 ret

;
;  INTERNAL ROUTINE SETBAUD - set the baud rate of a current port.
;                             port number in cport.
;                             timer countdown table offset in cbaud.
;
setbaud: ret ; CGL
 push bx  ; we'll be using this
 push dx  ; and this
 push ax  ; and this too

 mov al, bdtab ; check that rate is legal
 dec al  ; pick up number of valid rates from BDTAB
 cmp cbaud, al ;  0 <= cbaud <= [bdtab]-1
 ja setbd2  ; just return if not legal

 mov bx, offset bdtct ; get timer value
 mov al, cbaud  ; from timer countdown table
 mov ah, 0
 add al, al  ; word offset
 add bx, ax  ; bx now points to correct value

 mov dx, bgcmd ; dx is now baud rate generator command port

 cmp cport, ptlx ; is it the telex port?
 je sbtlx
 cmp cport, pv24 ; is it the v24 port?
 jne setbd2  ; just return if not

 mov al, v24bsel ; set baud rate for v24 port
 out dx, al
 mov dx, v24bg
 jmp setbd3

sbtlx: mov al, tlxbsel ; set baud rate for telex port
 out dx, al
 mov dx, tlxbg

setbd3: mov ax, [bx] ; set the countdown value
 out dx, al
 mov al, ah
 out dx, al

setbd2: pop ax  ; baud rate set, retore regs. and return
 pop dx
 pop bx
 ret

;
;  INTERNAL ROUTINE MNFLUSH - enable and flush current port.
;                             Port in cport.
;
mnflush: ret ; CGL
 push ax  ; preserve registers
 push dx

 mov dx, ciop ; current io port
 in al, dx  ; flush the port
 in al, dx
 in al, dx
 mov dx, ccmdp ; reset any pending interrupts
 mov al, c0errst
 out dx, al
 mov al, c0resi
 out dx, al

 pop dx  ; port flushed, retore regs. and return
 pop ax
 ret

;
; INTERNAL ROUTINE INTON - enable interrupts for the selected port
;                          Port number in cport.
;                          Ensure that the port selected is enabled, and
;                          that all other ports are as the system would
;                          wish them!
inton: ret ; CGL
 push ax
 push dx

 mov dx, icmask
 mov al, oldmsk ; Disable i/o interrupts from the V24/Telex
 or al, imcomm ;   channel.
 out dx, al

 cmp cport, ptlx ; is it the Telex port?
 je ietlx
 cmp cport, pv24 ; is it the V.24 port?
 jne inton2  ; must be an error - just return for now

 mov dx, tlxcmd ; disable ints from Telex port
 jmp iecomm

ietlx: mov dx, v24cmd ; disable ints from V.24 port

iecomm: mov al, c0resi+ccreg1
 out dx, al
 mov al, c1norm and not c1ialp
 out dx, al

 mov dx, icmask
 and al, not imcomm ; enable Telex and V.24 interrupts
 out dx, al

inton2: pop dx
 pop ax  ; interrupts now enabled - restore regs.
 ret   ;   and return

 DSEG $   ; Data used by initialisation/deinitialisation

mninit db false  ; flag set when initialised
oldmsk rb 1  ; Old interrupt mask
;
;  Screen mode control strings
;
ansion db esc, '[$' ; enter ANSI mode
fton db esc, 'Q$' ; re-enter FT mode
;
;  Current port status
;
cport db ptlx  ; current port number - default to TELEX
cbaud db 8  ; current baud rate - default to 4800
ciop dw tlxio  ; current i/o port - default to TELEX
ccmdp dw tlxcmd  ; current command/status port - default TELEX
;
;  Storage for system interrupt vectors
;
vscoff rw 1  ; offset for system v.24/telex int. vector
vscseg rw 1  ; seg. address for system v.24/telex int. vec
;
;  Baud rate timer countdown table
;
bdtct dw 769  ;    50 baud, code  0, +0.03% error
 dw 513  ;    75             1  -0.04%
 dw 350  ;   110             2  -0.10%
 dw 256  ;   150             3  +0.16%
 dw 128  ;   300             4  +0.16%
 dw  64  ;   600             5  +0.16%
 dw  32  ;  1200             6  +0.16%
 dw  16  ;  2400             7  +0.16%
 dw   8  ;  4800             8  +0.16%
 dw   4  ;  9600             9  +0.16%
 dw   2  ; 19200            10  +0.16%

 CSEG $

; ===========================================================================
;
;                             SET COMMANDS
;
; ===========================================================================
;
;  INTERFACE ROUTINE BDSET - set baud rate for current port (cport).
;                            save current baud rate in cbaud.
;
bdset: mov dx, offset bdtab ; table of valid baud rates
 mov bx, offset bdhlp ; help information for SET BAUD
 mov ah, cmkey  ; Command parser - KEYWORD lookup
 call comnd
   jmp r   ; error return
 mov settmp, bx  ; Normal return - save value
 mov ah, cmcfm  ; Command parser - CONFIRM
 call comnd
   jmp r
 mov bx, settmp
 mov cbaud, bl  ; save the baud rate
 call setbaud   ; and set it for the current port
 jmp rskp   ; end of parsing SET BAUD command

 DSEG $

settmp rw 1   ; temporary storage for baud rate

 CSEG $
;
;  INTERFACE ROUTINE PRTSET - set the current port.
;
prtset: jmp  rskp ; CGL
 mov dx, offset potab ; table of valid port names
 mov bx, offset pohlp ; help information for SET PORT
 mov ah, cmkey  ; Command parser - KEYWORD lookup
 call comnd
   jmp r   ; error return

 mov settmp, bx  ; Normal return - save value

 mov ah, cmcfm  ; Command parser - CONFIRM
 call comnd
   jmp r
;
;  Now we can do the work - first preset a few registers
;
 mov bx, settmp  ; restore port number
 mov dx, ccmdp  ; current command port
 mov al, c0resi+ccreg1 ; and command to select register 1
;
;  establish which port we are to use
;
 cmp bl, ptlx  ; is it the Telex port?
 je settlx
 cmp bl, pv24  ; is it the V.24 port?
 je setv24
 jmp r   ; must've been an error
;
;  Set the current port to be the V.24 connector
;
setv24: out dx, al   ; disable interrupts from current port
 mov al, c1norm and not c1ialp
 out dx, al
 mov ciop, v24io  ; Set V.24 port
 mov ccmdp, v24cmd
 jmp prtdoit
;
;  Set the current port to be the Telex port
;
settlx: out dx, al   ; disable interrupts from current port
 mov al, c1norm and not c1ialp
 out dx, al
 mov ciop, tlxio  ; Set Telex port
 mov ccmdp, tlxcmd
;
; and actually configure it
;
prtdoit:mov cport, bl  ; save the current port
 call setmode   ; configure the selected UART
 call setbaud   ; set the port's baud rate
 call mnflush   ; flush it
 call inton   ; and enable interrupts for it
 jmp rskp   ; end of parsing SET PORT command
;
;  Data required by the SET commands
;
 DSEG $    ; SET command data
;
;  Baud rate table
;
bdtab db 11   ; number of entries
 db 3, '110$'  ; size of entry, and the keyword$
 dw 02   ; value returned
 db 3, '150$'
 dw 03
 db 4, '1200$'
 dw 06
 db 5, '19200$'
 dw 10
 db 4, '2400$'
 dw 07
 db 3, '300$'
 dw 04
 db 4, '4800$'
 dw 8
 db 2, '50$'
 dw 00
 db 3, '600$'
 dw 05
 db 2, '75$'
 dw 01
 db 4, '9600$'
 dw 09
;
;  Help table for baud rate setting
;
bdhlp db cr, lf, '    50     75    110    150    300    600'
 db cr, lf, '  1200   2400   4800   9600  19200'
 db '$'
;
;  Port table
;
potab db 2
 db 5, 'AUX  $'
 dw ptlx
 db 3, 'AUX$'
 dw pv24
;
;  Help table for port selection
;
pohlp db cr, lf, 'AUX        $'

 CSEG $

; ===========================================================================
;
;                              SHOW COMMANDS
;
; ===========================================================================

;
;  INTERFACE ROUTINE SHOBD - display the currently set baud rate within
;                            the SHOW command.
;
shobd: mov dx, offset bdst ;Baud rate string.
 call tcrmsg
 mov al, cbaud ;Print the keyword corresponding to the
 mov bx, offset bdtab; current value of mnbaud.
 call tabprt
 ret

;
;  INTERFACE ROUTINE SHOPRT - display the currently selected communication
;                             port within the SHOW command.
;
shoprt: mov dx, offset prtst ; Port name string
 call tcrmsg
 mov al, cport  ; current port code
 mov bx, offset potab ; and print the corresponding
 call tabprt   ;   textual description
 mov dx, offset prtst2
 call tmsg
 ret

 DSEG $

prtst db 'Communicating via $'
prtst2 db ' port$'

 CSEG $

; ===========================================================================
;
;                                I/O ROUTINES
;
; ===========================================================================
;
;  INTERNAL ROUTINE ISR - Interrupt service routine for Printer, Keyboard,
;                         Telex and V.24 ports.
;
isr: cli    ; disable intrerupts
 mov cs:mnax, ax  ; save ax - we will need a register
 mov ax, sp
 mov cs:mnsp, ax  ; save current stack pointer
 mov ax, ss
 mov cs:mnsseg, ax  ; Save current stack segment
 mov ax, cs:mndseg  ; Switch to my stack
 mov ss, ax
 mov sp, offset mnstk
 push ds   ; Save registers
 push es
 push bp
 push di
 push si
 push dx
 push cx
 push bx
 mov ds, ax   ; set our data segment address
;
;  That's the housekeeping out of the way - now we can start
;
 mov dx, ccmdp  ; see if char. ready at default port
 in al, dx
 test al, cs0rxr  ; is there a character for us?
 jz iprt3   ; no - clear interrupt, and return

iprt2: mov dx, ciop  ; fetch the character
 in al, dx
 call iproc   ; process the character in AL

iprt3: mov dx, iccmd  ; signal end of interrupt to
 mov al, iceoi  ;   interrupt controller
 out dx, al

 mov dx, tlxcmd  ; Clear interrupt status at telex/v.24
 mov al, c0eoi  ;   channel.
 out dx, al   ; note we use the Telex (A) channel

 pop bx   ; restore registers
 pop cx
 pop dx
 pop si
 pop di
 pop bp
 pop es
 pop ds
 mov ax, cs:mnsp  ; restore interrupt stack
 mov sp, ax
 mov ax, cs:mnsseg  ; restore original stack segment
 mov ss, ax
 mov ax, cs:mnax  ; restore original AX
 iret    ; all over - return

;
;  CSEG data required by interrupt service routine
;
mnax dw 0  ; temp. copy of AX
mnsp dw 0  ; interrupt stack pointer
mnsseg dw 0  ; interrupt stack segment
mndseg dw 0  ; location of our data segment

;
; INTERNAL ROUTINE IPROC - process incoming character from Rx interrupt
;                          Character in AL
;

iproc:
 call cglin
 cmp cglfin, true
 jne ipr1a
 ret
ipr1a:
 cmp floctl, floxon ;are we doing flow-control ? [19a] start
 jne ipr2b  ;no - go on
 cmp al, xoff ;is it an XOFF?
 jne ipr2a  ;no - go on
 mov xofrcv, true ;set the flag
 ret

ipr2a: cmp al, xon  ;an XON?
 jne ipr2b  ;no
 mov xofrcv, false ;clear the flag
 ret   ;    [19a] end

ipr2b: cmp mnchrn,mnchnd ;Is the buffer full?
 je iperr  ;If so, take care of the error.
 inc mnchrn  ;Increment the character count.
 mov bx,mnchip ;Get the buffer input pointer.
 inc bx  ;Increment it.
 cmp bx,offset mnchrs+mnchnd ;Past the end?
 jb ipro3
 mov bx, offset mnchrs ;Yes, point to the start again.
ipro3: mov mnchip,bx ;Save the pointer.
 mov [bx],al  ;Put the character in the buffer.
 cmp floctl, floxon ;do flow-control?  [19a] start
 je ipro4  ;If yes jump
 ret

ipro4: cmp xofsnt, true ;Have we sent an XOFF
 jnz ipro5
 ret   ;return if we have

ipro5: cmp mnchrn, mntrg2 ;Past the High trigger point?
 ja ipro6  ;yes - jump
 ret

ipro6: mov al, xoff
 call prtout  ;send an XOFF
 mov xofsnt, true ;set the flag
 ret   ;    [19a] End

iperr: ret   ; just return on error for now

;
;  INTERFACE ROUTINE CFIBF - Clear serial port input buffer
;
cfibf: mov mnchrn, 0 ;Say no characters in the buffer.
 mov mnchip, OFFSET mnchrs-1+mnchnd ;Reset input pointer.
 mov mnchop, OFFSET mnchrs-1+mnchnd ;Reset output pointer.
 ret

;
;  INTERFACE ROUTINE PRTOUT - send character in AL to current port.
;
prtout:
   call dopar
   push dx
   push cx
   mov cx, outlmt
prtou2:
   call outwait
   loop prtou2
   nop
   call outchr
   pop cx
   pop dx
   ret

;   mov cl,al
; prout2:
;   mov al,19
;   call xios
;   or al,al
;   jnz prout3
;   call dispatch
;   jmp prout2
; prout3:
;  mov al,6
;  call xios
;  pop cx
;  pop dx
;  ret
; INTERNAL ROUTINE XIOS (CGL)
;
; INTERNAL ROUTINE OUTWAIT - test if port ready for next char to be sent.
;                            returns RSKP if ready.
;
outwait:
 cmp floctl, floxon
 jne outwt1
 cmp xofrcv, true
 je outwt3
outwt1: push ax
 mov al, 19
 push bx
 call xios
 pop bx
 or al,al
 jnz outwt4
 pop ax
outwt3: call dispatch
 ret
outwt4: pop ax
 jmp rskp
;
;  INTERNAL ROUTINE OUTCHR - send data to a port
;
outchr:
 mov cl,al
 mov al, 6
 push bx
 call xios
 pop bx
 ret

;
; INTERFACE ROUTINE INSTAT - determine if there is any data to receive.
;
instat:
 cmp mnchrn, 0
 jne inst2
; call dispatch
 mov cglfin, false
inst1:
 cmp cglfin, true
 je inst3
 push bx
 call iproc
 pop bx
 jmp inst1
inst2:
       jmp rskp
inst3: cmp mnchrn, 0
       jne inst2
       ret

;
;  INTERFACE ROUTINE INCHR - read a character from a port
;
inchr:
 push bx
 cli ;   Disable interrupts while were are playing.
 dec mnchrn  ;Decrement the number of chars in the buffer.
 mov bx,mnchop ;Get the pointer into the buffer.
 inc bx  ;Increment to the next char.
 cmp bx,offset mnchrs+mnchnd ;Past the end?
 jb inchr2
 mov bx, offset mnchrs ;If so wrap around to the start.
inchr2: mov mnchop,bx ;Save the updated pointer.
 mov al,[bx]  ;Get the character.
 sti  ;  All done, we can restore interrupts.
 pop bx
 cmp parflg,parnon ;[par] no parity?
 je inchr3  ;[par] yup, don't bother stripping
 and al,7fh  ;[par] checking parity, strip off
inchr3: cmp floctl, floxon ;do flow-control?  [19a] start
 je inchr4  ;If yes jump
 ret
inchr4: cmp xofsnt, true ;Have we sent an XOFF
 je inchr5  ;Jump if yes
 ret
inchr5: cmp mnchrn, mntrg1 ;Under the low trigger point?
 jb inchr6  ;yes - jump
 ret
inchr6: push ax  ;save current character
 mov al, xon
 call prtout  ;send an XON
 mov xofsnt, false ;turn off the flag
 pop ax  ;get back character
 ret   ;    [19a] end
cglin:
   push bx
   mov al,18
   call xios
   or al,al
   jz cgli2
   mov al,5
   call xios
   pop bx
   ret
cgli2:
   mov cglfin, true
   pop bx
   ret   ;    [19a] end
; internals CGL
get_sys_ptrs_:
push es
push ds
mov cl,09ch
int 224
pop ds
mov bx,ax
mov bx,es:010h[bx]
mov uda_segment,bx
mov pda_offset,ax
mov ax,es
mov pda_segment,ax
push ds
mov cl,09ah
int 224
pop ds
mov sysdat_offset,ax
mov ax,es
mov sysdat_segment,ax
pop es
mov ax,offset data_pointer
ret
xios:
mov bx,sysdat_segment
or bx,bx
jz do_get_sys_ptrs
push ds ! push es
push bx
mov bx,uda_segment
mov es,bx
mov bx,sysdat_offset
pop ds
callf dword ptr 28h[bx]
pop es ! pop ds
ret
do_get_sys_ptrs:
push ax  ! push cx ! push dx
call get_sys_ptrs_
pop dx ! pop cx ! pop ax
jmp xios
data_pointer:
sysdat_segment  dw 0
sysdat_offset dw 0
pda_segment dw 0
pda_offset dw 0
uda_segment dw 0
;
;  INTERFACE ROUTINE PRTBRK - Send a BREAK sequence to the default port
;
prtbrk: ret ; CGL
 mov dx, ccmdp  ; current command port
 cmp cport, ptlx  ; is it TELEX port?
 je brkc
 cmp cport, pv24  ; is it V.24 port?
 jne brka   ; must be an error - just return

brkc:
 mov al, c0resi+ccreg5 ; break to telex/v24 ports
 out dx, al   ; select register 5
 mov al, c5norm+c5sbrk ; 8 bits, TX enable, Break, RTS & DTR
 out dx, al
 mov ax, 275   ; for 275 mS
 call mswait
 mov al, c0resi+ccreg5 ; select register 5
 out dx, al
 mov al, c5norm  ; 8 bits, TX enable, RTS & DTR
 out dx, al
 ret

brka: ret

 DSEG $
;
;  Input character queue
;
mnchnd equ 512  ;Size of circular buffer.
mnchrs rb mnchnd  ;Circular character buffer for input.
mnchip dw mnchrs-1+mnchnd ;Input pointer into character buffer.
mnchop dw mnchrs-1+mnchnd ;Output pointer into character buffer.
mnchrn dw 0  ;Number of chars in the buffer.

mntrg1 equ 128  ;[19a] Low trigger point for Auto XON/XOFF
mntrg2 equ 384  ;[19a] High trigger point for Auto XON/XOFF

floctl db 1  ;[19a] If floctl=floxon do Auto XON/XOFF logic
xofsnt db 0  ;[19a] set if XOFF was sent
xofrcv db 0  ;[19a] set if XOFF was recieved
cglfin db 0  ; set if no more chars on aux input
;
;  a small stack for interrupt handling
;
 rw 64  ;Interrupt stack  ;[28e]
mnstk dw 0  ;bottom of stack  ;[28e]

 CSEG $

; ===========================================================================
;
;                             UTILITY ROUTINES
;
; ===========================================================================
;
; INTERNAL ROUTINE MSWAIT - Delay for AL milliseconds
;
mswait:    ;    [34] start
 mov cx,5*clckrt ; inner loop count for 1 millisec.
mswai1:
 sub cx,1  ;** inner loop takes 20 clock cycles
 jnz mswai1  ;**
 dec ax  ; outer loop counter
 jnz mswait  ; wait another millisecond
 ret   ;    [34] end

;
; INTERNAL ROUTINE DISPATCH: Reschedule current process
;
dispatch:
 push ax
 push bx
 push cx
 mov cl, p_dispatch
 int bdos
 pop cx
 pop bx
 pop ax
 ret



; ===========================================================================
;
;                        SCREEN CONTROL ROUTINES
;
; ===========================================================================
;
; INTERFACE ROUTINE POSCUR - positions cursor to row and col (each 1 byte)
;                            pointed to by dx.
;

poscur: mov bx, dx  ;Do ANSI cursor positioning.
 mov al, [bx] ;Get row value
 add al, ' '
 mov byte ptr anspos+2, al
 mov al, 1[bx] ;Do same for column value
 add al, ' '
 mov byte ptr anspos+3, al
 mov dx, offset anspos ;Print cursor positioning string.
 call tmsg
 ret

;
; INTERFACE ROUTINE CLRSCR - homes cursor and clears screen.
;

clrscr: mov dx, offset anscls
 call tmsg
 ret
;
; INTERFACE ROUTINE CLRLIN - clears line.
;

clrlin: mov dl, cr  ;Go to beginning of line
 call bout
;
; ...FALL THROUGH
;
;  INTERFACE ROUTINE CLREOL - clear to end of line
;

clreol: mov dx, offset ansclr ;Clear from cursor to end of line
 call tmsg
 ret

;
; INTERFACE ROUTINE REVON - turns on reverse video display
;

revon: mov dx, offset ansron
 call tmsg
 ret

;
; INTERFACE ROUTINE REVOFF - turns off reverse video display
;

revoff: mov dx, offset ansrof
 call tmsg
 ret
;
; INTERFACE ROUTINE BLDON - turns on bold (highlighted) display
;

bldon: mov dx, offset ansbon
 call tmsg
 ret
;
; INTERFACE ROUTINE BLDOFF - turns off bold (highlighted) display
;

bldoff: mov dx, offset ansbof
 call tmsg
 ret


 DSEG $

anspos db esc,'=xy$' ;Position cursor to row and column
anscls db esc, '+$' ;Home cursor and clear screen
ansclr db esc, 'T$' ;Clear from cursor to end of line
ansron db esc, '[4ZZ$' ;Turn on reverse video
ansrof db esc, '[0ZZ$' ;Turn off reverse video
ansbon db esc, '[8ZZ$' ; Bold on
ansbof db esc, '[0ZZ$' ; Bold off

 CSEG $
;
; INTERFACE ROUTINE DOTAB - do tab expansion if necessary
;
dotab: jmp rskp  ; assume h/w does it for now

;
;  Assorted textual constants required as part of the machine interface
;
 DSEG $

delstr  db 10O,'$'  ;Delete string.
system db '  ICL Personal Computer                Concurrent$'

 CSEG $
;
; ENDSYSDEP
;
