	PAGE 59, 132

	TITLE MSXIBM -- Machine dependent module for IBM-PC, except Terminal

; Update 4 Jan 86

IF1
 %OUT >> Starting pass 1
ELSE
 %OUT >> Starting pass 2
ENDIF

    PUBLIC SerIni_PC, SerRst_PC, ClrBuf_PC, OutChr_PC, Coms_PC
;    PUBLIC VTS_PC
    PUBLIC DoDel_PC, CtlU_PC, CmBlnk_PC, Locate_PC, PrtChr_PC, DoBaud_PC
    PUBLIC ClearL_PC, DoDisk_PC, GetBaud_PC, Beep_PC
    PUBLIC PutHlp_PC, PutMod_PC, PutErr_PC, ClrMod_PC, PosCur_PC
    PUBLIC SendBr_PC, SetKTab_PC, SetKHlp_PC, LclIni_PC, ShowKey_PC
    PUBLIC Which_card, Drop_DTR_PC, Which_page, Beam_Off, Beam_On
    PUBLIC Set_up_video_card_pointers, CRT_Mode

    INCLUDE msdefs.h

false	EQU 0
true	EQU 1
Screen	EQU 10h			; BIOS screen call

XOFF_trigger_count EQU BufSiz*2 / 3 ; High point = 2/3 of buffer full

Mode_control_port EQU 3D8h	; CRT mode control port
Video_enable EQU 8		; Display enable flag

Number_of_rows EQU 24		; Length in rows
Number_of_columns EQU 80	; Width in characters
Highest_row EQU Number_of_rows-1; Highest row number
Highest_column EQU Number_of_columns-1 ; Highest column number
Number_of_rows_PC EQU 25	; Length in rows including mode line

; constants used by serial port handler

BRKBIT	EQU 40h			; Send-break bit
TIMER	EQU 40h			; Use to issue short beep
PORT_B	EQU 61h			; Port B address
MCONF	EQU 11h			; Machine configuration
KEYB	EQU 16h
BIOS	EQU 10h

MDMDAT1	EQU 03F8h		; Address of modem port 1 data register
MDMSTS1	EQU 03FDh		; Address of modem port	1 status register
MDMCOM1	EQU 03FBh		; Address of modem port 1 command register

MDMDAT2	EQU 02F8h		; Port 2 address register
MDMSTS2	EQU 02FDh		; Port 2 status register
MDMCOM2	EQU 02FBh		; Port 2 command register

MDMINP	EQU 1			; Input ready bit

MDMINTV	EQU 0030h		; Address of modem port interrupt vector
MDINTV2 EQU 002Ch		; Address for port 2

MDMINTO	EQU 0EFh		; Mask to enable interrupt for modem port
MDINTO2 EQU 0F7h		; Enable interrupt level 3

MDMINTC	EQU 010h		; Bit to set to disable interrupts for modem
MDINTC2 EQU 008h		; Disable IRQ3

INTCONT	EQU 0021h		; Address of 8259 interrupt controller ICW2-3
INTCON1	EQU 0020h		; Address of 8259 ICW1

EOICOM	EQU 0064h		; End of interrupt
EOICOM2	EQU 0063h		; End of interrupt for COM2


; 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, PC_Subtype:BYTE
    EXTRN portval:word, port1:byte, port2:byte
    EXTRN Count:WORD, XofSnt:BYTE, Force_mono:BYTE, Force_card:BYTE
    EXTRN Source:BYTE, SrcPnt:WORD, PC_Type:BYTE

    PUBLIC Video_page_addresses

Video_page_addresses LABEL WORD
	DW 0B800h		; Page zero
	DW 0B900h		; Page one

SetKTab_PC DB 12
	mkeyw	'BACKSPACE',0eh
	mkeyw	'F1',3bh
	mkeyw	'F2',3ch
	mkeyw	'F3',3dh
	mkeyw	'F4',3eh
	mkeyw	'F5',3fh
	mkeyw	'F6',40h
	mkeyw	'F7',41h
	mkeyw	'F8',42h
	mkeyw	'F9',43h
	mkeyw	'F10',44h
	mkeyw	'SCAN',-1

SetKHlp_PC DB cr,lf,'Keyname: backspace, f1, ... f10, or "SCAN" followed by '
	DB 'decimal scan code$'
brkval	DB 0			; What to send for a break
brkadr	DW 0			; Where to send it
modem	mdminfo	<MDMDAT1,MDMSTS1,MDMCOM1,MDMINTO,MDMINTC,EOICOM,MDMINTV>
erms20	DB '? Warning:  System has no disk drives', Cr, Lf, '$'
erms40	DB '? Warning:  Unrecognized baud rate', Cr, Lf, '$'
badbd	DB 'Unimplemented baud rate', Cr, Lf, '$'
No_memory DB '? Not enough memory for program to run (screen management)'
	DB Cr, Lf, '$'
crlf	DB cr,lf,'$'
delstr  DB BS,' ',BS,'$'	; Delete string. [21d]
clrlin  DB cr,'$'		; Clear line (just the cr part)
savsci	DW ?			; Save for serial port interrupt vector. [14]
savscs	DW ?			; Ditto.  [14]
savbr1	DW ?			; "Break" interrupt vector. [25]
savbr2	DW ?			; Ditto. [25]
portin	DB 0			; Has comm port been initialized. [21c]
xofrcv	DB 0			; Say if we received an XOFF
tmp	DB ?,'$'
temp	DW 0
temp1	DW ?			; Temporary storage
temp2	DW ?			; Temporary storage

CRT_Mode DB 7			; Crt mode for video card
Which_page DB 0			; Video page number
Screen_pointer DW 0B000h	; Pointer to real screen memory

Beam_status DB ?		; Place to hold CRT status reg value

ontab	DB 02H			; Two entries
	DB 03H,'OFF$'		; Should be alphabetized.  [19a]
	DW 00H
	DB 02H,'ON$'
	DW 01H

comptab	DB 04H
	DB 01H,'1$'
	DW 01H
	DB 01H,'2$'
	DW 00H
	DB 04H,'COM1$'
	DW 01H
	DB 04H,'COM2$'
	DW 00H

; this table is indexed by the baud rate definitions given in
; pcdefs.  Unsupported baud rates should contain FF

bddat	label	word
	DW 0FFH			; 45.5 baud  -- Not supported
	DW 900H			; 50 baud
	DW 600H			; 75 baud
	DW 417H			; 110 baud
	DW 359H			; 134.5 baud
	DW 300H			; 150 baud
	DW 180H			; 300 baud
	DW 0C0H			; 600 baud
	DW 60H			; 1200 baud
	DW 40H			; 1800 baud
	DW 3AH			; 2000 baud
	DW 30H			; 2400 baud
	DW 18H			; 4800 baud
	DW 0CH			; 9600 baud
	DW 06h			; 19200 baud
	DW 03h			; 38400 baud
	DW 05h			; 23040 baud

; variables for serial interrupt handler

savesi	DW 0			; Save SI register here
telflg	DB 0			; Are we acting as a terminal
mst	DW 0			; Modem status address
mdat	DW 0			; Modem data address
mdeoi	DB 0			; End-of-Interrupt value

rbtrn	DB 7fH			; rubout

shkbuf	DB 300 dup (?)		; room for definition
shkmsg	DB '  Scan code: '
shkmln	EQU $-shkmsg
shkms1	DB cr,lf,'  Definition: '
shkm1ln	EQU $-shkms1

DataS	ENDS

Code	SEGMENT	PUBLIC

    EXTRN Comnd:near, dopar:near, defkey:near, gss:near
    EXTRN Set_up_dynamic_memory:NEAR, Exit2:NEAR, NoInt:NEAR, OkInt:NEAR
    EXTRN Write_to_standard_output:NEAR

    ASSUME cs:Code, ds:DataS

Which_card DB 0			; 0 for mono, 1 for color, 2 for EGA

; local initialization

LclIni_PC	proc	near

	call Set_up_dynamic_memory ; Try to get memory
	 jnc LCL_0

	mov ah, PrStr		; Code to type message
	mov dx, OFFSET No_memory ; Complain about lack of memory
	int Dos			; Display it

	call Exit2		; Bomb out also
	 nop
	 nop
	 nop

	ret			; Return to main program

LCL_0:	mov	ax,0eH		; scan code for arrow key
	mov	si,offset rbtrn	; translate to rubout
	mov	cx,1		; one char translation
	call	defkey
	mov brkval,BRKBIT	; What to send for a break
	mov ax,modem.mdcom	; Where to send it
	mov brkadr,ax

	call Set_up_video_card_pointers
	jmp Go_to_page_zero

LclIni_PC	endp


Set_up_video_card_pointers PROC

	mov ah, 15		; Code to read current video mode
	int Screen		; Read it

	mov CRT_Mode, al	; Save crt mode

	sub ah, ah		; Set flag for mono
	mov Which_page, ah	; Zero the page number

	cmp Force_card, 0FFh	; Are we forcing some card type?
	 je SUV_0		;  No, keep checking

	mov ah, Force_card	; Pick up forced value
	mov Which_card, ah	; Save it
	ret			; That's it

SUV_0:	cmp al, 7		; Is it really color?
	 je SUV_1		;  Ok, it is monochrome

	mov ah, 1		; Flag for color card

SUV_1:	mov Which_card, ah	; Remember which card we are using
	cmp ah, 1		; Did we just decide that this is a color card?
	 je Check_for_EGA	; See if we are on the Enhanced card

	ret			; That's it

Set_up_video_card_pointers ENDP


; See if we are using the Enhanced Graphics Adapter

Check_for_EGA PROC

	cmp PC_Type, 0		; We won't even bother without PC-DOS
	 jne CEG_Done		;  Not running PC-DOS, assume not EGA

	cmp PC_Subtype, 0	; Are we on a normal PC?
	 je CEG_PC		;  Yes

	cmp PC_Subtype, 1	; Are we on an XT?
	 je CEG_XT		;  Yes

	cmp PC_Subtype, 4	; ... or a Portable (same motherboard as XT)?
	 je CEG_XT		;  Yes

	cmp PC_Subtype, 3	; Are we on an AT?
	 je CEG_AT		;  Yes

;	jmp CEG_Done		; No way of knowing about EGA, assume CGA
	jmp CEG_Assume_EGA	; Try default of EGA for non-PC type machines

				; What this really means is that we don't
				;  assume working BIOS support for our "normal"
				;  complicated color screen update
				; This certainly speeds up the screen on
				;  liquid crystal (and other ...) displays

; Regular PC -- give it a shot!

CEG_PC:	call NoInt		; No ints during this
	in al, 61h		; This port allows access to port 60h
	mov bl, al		; Save the original value from port 61h
	or al, 80h		; Turn on the bit making port 60h be switches
	out 61h, al		; Set this up
	in al, 60h		; Read system board switch bank 1
	xchg al, bl		; Save the switch settings, get back original
				;  value from port 61h
	out 61h, al		; Restore original value
	call OkInt		; Interrupts are permitted again
	mov al, bl		; Get back the switch settings
	jmp SHORT CEG_Common_code ; Join common code

; PC/XT -- have to use a slightly different method

CEG_XT:	call NoInt		; No interrupts during this stuff
	in al, 61h		; This port controls how we read port 62h
	mov bl, al		; Save the original value from port 61h
	or al, 40h		; Force this bit on so we read the high half
	out 61h, al		; Set this up
	in al, 62h		; Read the high half of SW 1
	xchg al, bl		; Save the switch settings, get back original
				;  value from port 61h
	out 61h, al		; Restore original value
	call OkInt		; Interrupts are permitted again
	mov al, bl		; Get back the switch settings
	jmp SHORT CEG_Common_code ; Join common code

; PC AT, look into CMOS battery powered RAM for configuration info

CEG_AT:	mov al, 14h		; CMOS register holding equip flags
	out 70h, al		; Tell CMOS chip which register we want
	in al, 71h		; Read the equip flags

    PUBLIC CEG_Common_code

CEG_Common_code:
	test al, 30h		; Are either of these switches set?
	 jnz CEG_Done		;  Not EGA

CEG_Assume_EGA:
	mov Which_card, 2	; Code for EGA

CEG_Done: ret			; Go home

Check_for_EGA ENDP


    PUBLIC Switch_video_pages

Switch_video_pages:
	mov ax, 0B000h		; Address of monochrome card
	cmp Which_card, 0	; Monochrome card?
	 je SVP_Set		;  Yes

	mov ax, 0B800h		; Address of color card
	cmp Which_card, 2	; EGA?
	 je SVP_Set		;  Yes

; True color card, really switch pages

	mov al, 1		; Get a one
	sub al, Which_page	; 1 minus 0 = 1, 1 minus 1 = 0
	mov Which_page, al	; Store for next time
	mov ah, 5		; Code to switch video pages
	int Screen		; Tell ROM BIOS to switch pages now

	mov bl, Which_page	; Get number of page we are on
	sub bh, bh		; Zero top half of word
	shl bx, 1		; Double for word offset
	mov ax, Video_page_addresses[bx] ; Address of the screen memory

SVP_Set:
	mov Screen_pointer, ax	; Set up this pointer
	ret			; Done here

    PUBLIC Go_to_page_zero

Go_to_page_zero:

	sub al, al		; Get a zero
	mov Which_page, al	; Store it as the page we are on
	mov ah, 5		; Code to switch video pages
	int Screen		; Tell ROM BIOS to switch pages now

	mov ax, 0B000h		; Address of monochrome card's only page
	cmp Which_card, 0	; Mono card?
	 je PG0_Mono		;  Monochrome

	mov ax, 0B800h		; Addr of color card page zero (maybe EGA)

PG0_Mono:
	mov Screen_pointer, ax	; Set up pointer
	ret

    PUBLIC Blank_current_page

Blank_current_page:
	mov ax, 600h		; Code to blank entire window
	mov bh, 7		; Normal video attribute
	sub cx, cx		; Make a zero as upper left corner
	mov dx, (100h * (Number_of_rows_PC - 1) ) + Highest_column
				; Use real lower right corner of screen
	int Screen		; Ask BIOS to blank it for us
	ret			; Done here


; Turn video beam off and on to prevent screen snow on color card

Beam_Off PROC

	cmp CRT_Mode, 7		; BW?
	 je BOF_1		;  Yes

	push es			; Save reg
	sub ax, ax		; Make a zero
	mov es, ax		; Address low memory
	mov al, es:[465h]	; Pick up DOS's value for this
	mov Beam_status, al	; Save for later
	and al, NOT Video_enable ; Mask off the video bit
	mov dx, Mode_control_port ; Address of CRT control port
	out dx, al		; Turn off the video now
	pop es			; Restore saved reg

BOF_1:	ret

Beam_Off ENDP

Beam_On PROC

	cmp CRT_Mode, 7		; BW?
	 je BON_1		;  Yes

	mov al, Beam_status	; Pick up saved beam state
	mov dx, Mode_control_port ; Address of CRT control port
	out dx, al		; Restore the old state

BON_1:	ret

Beam_On ENDP


; this is called by Kermit initialization.  It checks the
; number of disks on the system, sets the drives variable
; appropriately.  Returns normally

DoDisk_PC	PROC	NEAR
	int mconf			; Get equipment configuration
	mov ah,al			; Store AL value for a bit
	and al,01H			; First, look at bit 0
	jz dodsk0			; No disk drives -- forget it
	mov al,ah			; Get back original value
	mov cl,6			; Shift over bits 6 and 7
	shr al,cl			; To positions 0 and 1
	inc al				; Want 1 thru 4 (not 0 thru 3)
	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 only a cassette
	mov drives,0			; Say there aren't any drives
	ret
DoDisk_PC	ENDP

; show the definition of a key.  The terminal argument block (which contains
; the address and length of the definition tables) is passed in ax
; Returns a string to print in AX, length of same in CX
; Returns normally
ShowKey_PC	proc	near
	push	es
	push	ax		; save the ptr
	mov	bx,ds
	mov	es,bx		; address data segment
	cld
showk1:	xor	ah,ah
	int	keyb		; read a char
	push	ax		; save the character
	call	gss		; get shift state
	pop	bx
	mov	ah,al		; shift state to ah
	mov	al,bh		; scan code to al
	push	ax		; remember scan code
	mov	di,offset shkbuf
	mov	si,offset shkmsg
	mov	cx,shkmln
	rep	movsb		; copy in initial message
	call	nout		; write out scan code
	mov	si,offset shkms1
	mov	cx,shkm1ln	; second message
	rep	movsb
	pop	ax		; get scan code back
	pop	bx		; and terminal arg block
	mov	cx,[bx].klen	; and length
	jcxz	showk2		; no table, not defined
	push	di		; remember output ptr
	mov	di,[bx].ktab	; get key table
	repne	scasw		; search for a definition for this
	mov	si,di		; remember result ptr
	pop	di		; get output ptr back
	jne	showk2		; not defined, forget it
	sub	si,[bx].ktab	; compute offset from beginning
	sub	si,2		; minus 2 for pre-increment
	add	si,[bx].krpl	; get index into replacement table
	mov	si,[si]		; pick up replacement
	mov	cl,[si]		; get length
	mov	ch,0
	inc	si
	rep	movsb		; copy into buffer
showk2:	mov	ax,offset shkbuf ; this is buffer
	mov	cx,di
	sub	cx,ax		; length
	pop	es
	ret			; and return
ShowKey_PC	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_PC	PROC	NEAR
	call NoInt		; No interrupts during this
	mov ax,offset source
	mov srcpnt,ax
	mov savesi,ax
	mov count,0
	call OkInt
	ret
ClrBuf_PC	ENDP

; Clear to the end of the current line.  Returns normally

ClearL_PC	PROC	NEAR
	mov ah,3		; Clear to end of line
	mov bh,0
	int bios		; Get current cursor position
	mov cx,dx
	mov dl,79
	mov ah,7
	mov al,0
	mov bh,7
	int bios
	ret
ClearL_PC	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_PC:
	push cx			; Save initial cx
	mov bp,portval
	cmp ds:[bp].floflg,0	; Are we doing flow control
	 je outch2		;  No, just continue

	sti			; Make sure interrupts are ok
	mov cx, 4		; Perform the inner loop this many times

outcha:	push cx			; Save outer loop counter
	xor cx, cx		; Clear inner loop counter

outch1:	cmp xofrcv, true	; Are we being held?
	 jne outchb		;  No - it's OK to go on

	loop outch1		; Held, try for a while (inner loop)

	pop cx			; Restore outer loop counter
	loop outcha		; Restart inner loop

	mov xofrcv, false	; Timed out, force it off and fall thru
	jmp SHORT outch2	; Join common code

outchb:	pop cx			; Flush saved outer loop counter

outch2:	push dx			; Save register
	sub 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 dx,modem.mdstat	; Get port status
outch3:	in al,dx
	test al,20H		; Transmitter ready?
	jnz outch4		; Yes
	loop outch3
	 jmp outch5		; Timeout
outch4:	mov al,ah		; Now send it out
	mov dx,modem.mddat
	out dx,al
	pop dx
	pop cx
	jmp rskp
outch5:	pop dx
	pop cx
	ret

; This routine blanks the screen.  Returns normally

CmBlnk_PC	PROC	NEAR		; This is stolen from the IBM example
	mov cx,0
	mov dx,184FH
	mov bh,7
	mov ax,600H
	int bios
	ret
CmBlnk_PC  ENDP

; Locate_PC: homes the cursor.  Returns normally

Locate_PC  PROC	NEAR
	mov dx,0		; Go to top left corner of screen
	jmp PosCur_PC
Locate_PC  ENDP

; Write a line in inverse video (or Blue-on-White) at the
;	bottom of the screen...  Ptr to the line is passed in dx,
;	line is terminated by a $.  Returns normally

PutMod_PC PROC

	mov bh,70h		; Inverse video
	cmp Force_mono, 0	; Mono forced?
	 jne PMD_1		;  Yes

	cmp Which_card, 0	; Monochrome card?
	 je PMD_1		;  Yes

	mov bh, 71h		; Use Blue on White on color card

PMD_1:	jmp Mode_common		; Use common code

PutMod_PC ENDP

; Write a line in inverse video or Red-on-White at the
;	bottom of the screen...  Ptr to line is passed in dx,
;	line is terminated by a $.  Returns normally

PutErr_PC PROC

	mov bh,70h		; Inverse video
	cmp Force_mono, 0	; Mono forced?
	 jne PER_1		;  Yes

	cmp Which_card, 0	; Monochrome card?
	 je PER_1		;  Yes

	mov bh, 74h		; Use Red on White on color card

PER_1:	jmp Mode_common		; Join common code

PutErr_PC ENDP

; Common code for PutMod_PC and PutErr_PC

Mode_common PROC

	push	dx		; preserve message
	mov	cx,1800h
	mov	dx,184fh
	mov	ax,600h		; scroll to clear the line
	int	bios		; Do it
	mov	dx,1800h	; now address line 24
	call	PosCur_PC
	pop	dx		; get message back
	call Write_to_standard_output ; Use standard routine to do this
;	mov	ah,prstr
;	int	dos		; write it out
	ret			; and return

Mode_common ENDP

; clear the mode line written by PutMod_PC.  Returns normally
ClrMod_PC	proc	near
	mov	cx,1800h
	mov	dx,184fh
	mov	ax,600h
	mov	bh,7h
	int	bios
	ret
ClrMod_PC	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_PC	proc	near
	push	ax		; preserve this
	cld			; Forwards
	mov	si,ax		; point to it
	mov	dh,0		; init counter
puthl1:	lodsb			; get a byte
	cmp	al,lf		; linefeed?
	jne	puthl2		; no, keep going
	inc	dh		; count it
	jmp	puthl1		; and keep looping
puthl2:	cmp	al,0		; end of string?
	jne	puthl1		; no, keep going
	mov	ax,600h		; scroll to clear window
	xor	cx,cx		; from top left
	mov	dl,4fh		; to bottom right of needed piece
	mov	bh, 70h		; Inverse video
	cmp	Force_mono, 0	; Mono forced?
	 jne	 PHP_1		;  Yes

	cmp	Which_card, 0	; Monochrome card?
	 je	 PHP_1		;  Yes

	mov	bh, 1Eh		; Use Yellow on Blue on color card

PHP_1:	int	bios
	call	Locate_PC	; home cursor
	pop	si		; point to string again
puthl3:	lodsb			; get a byte
	cmp	al,0		; end of string?
	je	puthl4		; yes, stop
	mov	ah,14
	int	bios		; else write to screen
	jmp	puthl3		; and keep going
puthl4:	call	ClrMod_PC		; Erase any mode line already there
	mov	dx,24 * 100H	; go to last line
	jmp	PosCur_PC		; position and return
PutHlp_PC	endp

%OUT >> About half way through source file

; Set the baud rate for the current port, based on the value
; in the portinfo structure.  Returns normally

DoBaud_PC	PROC	NEAR
	mov bp,portval
	mov temp1,ax		; Don't overwrite previous rate. [25]
	mov ax,ds:[bp].baud	; Check if new rate is valid. [25]
	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
	ret
dobd0:	mov temp1,ax		; Remember value to output. [25]
	mov dx,modem.mdcom	; LCR -- Initialize baud rate. [19b]
	in al,dx
	mov bl,al
	or ax,80H
	out dx,al
	mov dx,modem.mddat	; [19b]
	mov ax,temp1
	out dx,al
	inc dx
	mov al,ah
	out dx,al
	mov dx,modem.mdcom	; [19b]
	mov al,bl
	out dx,al
	ret
DoBaud_PC	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

GetBaud_PC	PROC	NEAR
	mov dx,modem.mdcom	; Get current Line Control Register value
	in al,dx
	mov bl,al		; Save it
	or ax,80H		; Turn on to access baud rate generator
	out dx,al
	mov dx,modem.mddat	; Divisor latch
	inc dx
	in al,dx		; Get hi order byte
	mov ah,al		; Save here
	dec dx
	in al,dx		; Get lo order byte
	push ax
	mov dx,modem.mdcom
	mov al,bl		; Restore old value
	out dx,al
	pop ax
	cmp ax,0FFFFH		; Who knows what this is
	je getb2
	mov bx,offset bddat	; Find rate's offset into table
	mov cl,0		; Keep track of index
getb0:	cmp ax,[bx]
	je getb1
	inc cl
	cmp cl,baudsiz		; At the end of the list
	jge getb2
	add bx,2
	jmp getb0
getb1:	mov ch,0
	mov bp,portval
	mov ds:[bp].baud,cx	; Set baud rate
	ret
getb2:	mov ah,prstr
	mov dx,offset erms40
	int dos
	ret
GetBaud_PC	ENDP

; skip returns if no character available at port,
; otherwise returns with char in al, # of chars in buffer in dx
PrtChr_PC  PROC	NEAR
	cld			; Forwards
	call chkxon		; see if we need to xon
	cmp count,0
	jnz prtch2
	jmp rskp		; No data - check console
prtch2:	mov si,savesi
	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_PC  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, skip all this
	cmp	xofsnt,false	; have we sent an xoff?
	je	chkxo1		; no, forget it
	cmp	count,XOFF_trigger_count ; below trigger?
	jae	chkxo1		; no, forget it
	mov	ax,[bx].flowc	; ah gets xon
	call	OutChr_PC	; 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_PC PROC
	push cx
	push dx
	push ax
	xor cx,cx		; Clear loop counter
	mov dx,brkadr		; Port address.  [19b]
	in al,dx		; Get current setting
	or al,brkval		; Set send-break bit(s)
	out dx,al		; Start the break
	push ax
	mov ax,275		; # of ms to wait
	call pcwait		; hold break for desired interval
	pop ax
	xor al,brkval		; Clear send-break bit(s)
	out dx,al		; Stop the break
	pop ax
	pop dx
	pop cx
	ret			; And return
SENDBR_PC ENDP

; Wait for the # of milliseconds in ax
; Thanks to Bernie Eiben for this one
pcwait	proc	near
	mov	cx,240		; inner loop counter for 1 millisecond
	cmp PC_Subtype, 3	; Is this an AT?
	 jne pcwai1		;  No

	mov cx, 4*240		; It is an AT, use a much higher value

pcwai1:	sub	cx,1		; inner loop takes 20 clock cycles
	jnz	pcwai1
	dec	ax		; outer loop counter
	jnz	pcwait		; wait another millisecond
	ret
pcwait	endp

; Position the cursor according to contents of DX:
; DH contains row, DL contains column.  Returns normally

PosCur_PC	PROC	NEAR
	mov ah,2		; Position cursor
	mov bh,0
	int bios
	ret
PosCur_PC	ENDP

; Delete a character from the terminal.  This works by printing
; backspaces and spaces.  Returns normally

DoDel_PC	PROC	NEAR
	mov ah,prstr
	mov dx,offset delstr	; Erase weird character
	int dos		
	ret
DoDel_PC	ENDP

; Move the cursor to the left margin, then clear to end of line
; Returns normally

CtlU_PC	PROC	NEAR
	mov ah,prstr
	mov dx,offset clrlin
	int dos
	call ClearL_PC
	ret
CtlU_PC	ENDP

; set the current port

Coms_PC	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,MDMDAT1	; Set COM1 defaults
	mov modem.mdstat,MDMSTS1
	mov modem.mdcom,MDMCOM1
	mov modem.mddis,MDMINTC
	mov modem.mden,MDMINTO
	mov modem.mdmeoi,EOICOM
	mov modem.mdintv,MDMINTV
	mov brkadr,MDMCOM1
	ret
coms0:	mov ax,offset port2
	mov portval,ax
	mov modem.mddat,MDMDAT2	; Set COM2 defaults
	mov modem.mdstat,MDMSTS2
	mov modem.mdcom,MDMCOM2
	mov modem.mddis,MDINTC2
	mov modem.mden,MDINTO2
	mov modem.mdmeoi,EOICOM2
	mov modem.mdintv,MDINTV2
	mov brkadr,MDMCOM2
	ret
comx:	pop bx
	ret
Coms_PC	ENDP

; Set heath emulation on/off

VTS_PC	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_PC	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_PC below should restore any interrupt vectors that this changes
; Returns normally

SerIni_PC PROC

	push es
	cmp portin, 0		; Did we initialize port already? [21c]
	 jne serin0		;  Yes, so just leave. [21c]

	call NoInt		; Disable interrupts

	xor ax,ax		; Address low memory
	mov es,ax

	mov bx,modem.mdintv	; Save serial card interrupt vector. [19b]
	mov ax,es:[bx]
	mov savsci,ax

	mov ax,offset SerInt	; And point it to my routine
	mov es:[bx],ax

	add bx,2		; Save CS register too. [19b]

	mov ax,es:[bx]
	mov savscs,ax
	mov es:[bx],cs

	mov portin,1		; Remember port has been initialized

	call ClrBuf_PC		; Clear input buffer

	mov ax,modem.mdstat
	mov mst,ax		; Use this address for status

	mov ax,modem.mddat
	mov mdat,ax		; Use this address for data

	mov al,modem.mdmeoi
	mov mdeoi,al		; Use to signify end-of-interrupt

	in al,21H		; Set up 8259 interrupt controller
	and al,modem.mden	; Enable INT3 or INT4
	out 21H,al

	mov dx,modem.mdcom	; Set up the serial card
	mov al,3
	out dx,al

	mov dl,0F9h
	mov al,1		; Set up interrupt enable register
	out dx,al

	mov dl,0FCh		; Enable interrupts from serial card
	mov al,0Bh
	out dx,al

	call OkInt		; Allow interrupts

	mov dl,0F8h
	in al,dx

serin0:	pop es
	ret

SerIni_PC ENDP


; Reset the serial port.  This is the opposite of SerIni_PC.  Calling
;  this twice without intervening calls to SerIni_PC should be harmless.
; Returns normally.

SerRst_PC PROC

	push es			; Preserve this
	cmp portin,0		; Reset already?
	 je srst1		;  Yes, just leave

	call NoInt		; Disable interrupts

	mov dx, 03FCh		; Disable modem interrupts, assume port 1
	cmp flags.comflg, 1	; Is it port 1?
	 je srst0		;  Yes

	mov dh, 2		; Port 2

srst0:	mov al, 3
	out dx,al

	in al, 21h		; Interrupt controller

	or al, modem.mddis	; Inhibit IRQ3 or IRQ4
	out 21H, 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 portin, 0		; Reset flag

	call OkInt

srst1:	pop es
	ret			; All done

SerRst_PC ENDP


Drop_DTR_PC PROC

	push ax			; Save regs
	push dx

	mov dx, 03FCh		; Port for COM1 modem control register
	cmp flags.comflg, 1	; Using port 1 ?
	 je DTR_0		;  Yes, continue

	mov dh, 2		; Using COM2

DTR_0:	sub al, al		; Clear this register
	out dx,al		; Drop DTR now

	pop dx			; Restore regs
	pop ax
	ret			; Done here

Drop_DTR_PC ENDP


;   Serial port interrupt routine

SERINT  PROC FAR

; Save some registers ...
	push dx
	push ds
	push ax
	push es

	mov ax, SEG DataS	; Address data segment
	mov ds, ax		; Set up ds
	mov es, ax		;  and es

	mov dx, mst		; Line Status Register (LSR), for port 1 or 2
	in al, dx

	test al, mdminp		; Data available?
	 jz Return_2		;  Nope

	mov dx, mdat		; Get the data
	in al,dx

	cmp Count, BufSiz	; Room left in buffer?
	 jae Return_2		;  No room left, don't overstore!!

; Save more registers ...
	push bx
	push di
	push bp

	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 SHORT RetInt

srint1:	cmp al, bh		; Get an XON?
	 jne srint2		;  No, go on

	mov xofrcv, false	; Clear our flag
;	jmp SHORT RetInt

RetInt:

; Restore some saved registers ...
	pop bp
	pop di
	pop bx

Return_2:
	sti			; Allow ints now
	mov al, mdeoi		; Send End-of-Interrupt ...
	out intcon1, al		;  to 8259

; Restore other saved registers ...
	pop es
	pop ax
	pop ds
	pop dx

	iret			; Return from interrupt

srint2:	cld			; Store forwards
	mov di, srcpnt		; Where to store it
	stosb			; Store it

	cmp di, OFFSET Source + BufSiz
	 jb srint3		;  Not past end ...

	mov di, OFFSET Source	; Wrap buffer around

srint3:	inc count		; Bump the count
	mov srcpnt, di		; Store the updated pointer

	cmp ds:[bp].floflg, 0	; Doing flow control?
	 je RetInt		;  No, just leave

	cmp count, XOFF_trigger_count ; Past the high trigger point?
	 jbe RetInt		;  No, we're within our limit

	cmp xofsnt, true	; Have we sent an XOFF?
	 je RetInt		;  Yes

	mov ah,bl		; Get the XOFF
	push cx			; OutChr_PC clobbers cx, we're not allowed to
	call OutChr_PC		; Send it
	 nop
	 nop
	 nop
	pop cx			; Restore cx

	mov xofsnt,true		; Remember we sent it
	jmp SHORT RetInt	; Branch upwards into common code

SERINT	ENDP


; Produce a short beep.  The PC DOS bell is long enough to cause a loss
; of data at the port.  Returns normally

Beep_PC	PROC	NEAR
	mov al,10110110B	; Gen a short beep (long one losses data.)
	out timer+3,al		; Code snarfed from Technical Reference
	mov ax,533H
	out timer+2,al
	mov al,ah
	out timer+2,al
	in al,port_b
	mov ah,al
	or al,03
	out port_b,al
	sub cx,cx
	mov bl,1
beep0:  loop beep0
	dec bl	
	jnz beep0
	mov al,ah
	out port_b,al
	ret
Beep_PC	ENDP

; put the number in ax into the buffer pointed to by di.  Di is updated
nout	proc	near
	cld			; Forwards
	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

