;			===================
;			X  I  O  S  -  8  6
;			===================

;			Concurrent  CP/M-86
;			eXtended I/O System
;			      for the
;			IBM Personal Computer

;			Copyright (c) 1983
;			Digital Research, Inc.
;			Box 579, Pacific Grove
;			California, 93950

;	This XIOS is presented as an example
;	hardware interface the CCP/M-86 operating system.

;	Permission is hereby granted to use or
;	abstract the following program in  the
;	implementation of Concurrent CP/M-86,
;	CP/M, MP/M or CP/Net for the 8086 or 8088
;	micro-processor.

title 'Example CCP/M-86 XIOS'

;************************************************************************
;*									*
;*			XIOS ORGANIZATION				*
;*									*
;************************************************************************

;	This XIOS is organized into the several major sections which
;	begin with the section name centered and underlined.
;	The following Table of Contents lists the major sections,
;	and most of the sub-sections.

;	Section Name					Page
;	  Sub Section

;	CCP/M Formats					6
;	  SYSDAT Format					6
;	  CCP/M System Call Equates			6
;	  Process Descriptor Format			7
;	  Console Control Block Format			8
;	  Memory Descriptor Format			9

;	XIOS Equates					11
;	  XIOS Flag Assignments				11
;	  ASCII Codes					11

;	XIOS HEADER					13
;	AND CCP/M INTERFACE
;	  Header					13
;	  CCP/M Interface				14
;	  IO_POLL					16
;	  POLL						16

;	INTERRUPT HANDLERS				17
;	  IBM PC Interrtupt Structure			17
;	  IBM PC Keyboard Ports				17
;	  8259 Equates					17
;	  8253-5 Equates				18
;	  I_TICK					18
;	  I_KEYBOARD					21
;	  I_DISK					24
;	  I_UNEXPECTED					26

;	CHARACTER I/O					28
;	  IBM PC Screen Equates				28
;	  IBM PC Parallel Port Equates			28
;	  6845 CRT Controller Equates			28
;	  IO_CONST					29
;	  IO_CONIN					29
;	  IO_CONOUT					33
;	  Special Character Output Routines		36
;	  Escape Sequence Routines			37
;	  Console Output Subroutines			46
;	  IO_SWITCH					49
;	  IO_STATLINE					50
;	  IO_LISTST					54
;	  IO_LIST					54
;	  IO_AUXIN					55
;	  IO_AUXOUT					55

;	Character I/O Data				56
;	  IO_CONIN Data					56
;	  Keyboard Translation Tables			57
;	  Special Output Character Tables		60
;	  Escape Sequence Tables			60
;	  Screen Structures				61
;	  Console Control Blocks (CCBs)			63
;	  Status Line Data				64
;	  List Control Blocks (LCBs)			65

;	Disk I/O					66
;	  IBM PC Disk Equates				66
;	  8272 FDC Controller Equates			66
;	  8237 DMA Controller Equates			67
;	  CCP/M Disk I/O Equates			68
;	  IO_SELDSK					69
;	  IO_READ					69
;	  IO_WRITE					71
;	  IO_FLUSHBUF					79

;	Disk I/O Data					80

;	INIT:						84

;	Miscellaneous Routines				93
;	  PRINT_MSG:					93
;	  RESET:					93


;	The XIOS functions invoked by the CCP/M kernel through
;	the JMP to ENTRY:, are denoted by a row of equal signs
;	above and below the function label.  For example the IO_CONIN
;	label has the form:

;	;========
;	io_conin:
;	;========

;	Internal XIOS functions that are called from various
;	parts of the XIOS are of the form:

;	;-------------------
;	set_physical_cursor:
;	;-------------------

;	Interrupt routines are similar to the latter but begin
;	with the string "i_", for example:

;	;----------
;	i_keyboard:
;	;----------

;	Subroutines local to a specific function are underlined:

;	action_key:
;	;----------


; 	Register usage for XIOS "io_" functions and INIT:

;	Entry:	AL = function # (at ENTRY: label)
;		CX = entry parameter
;		DX = entry parameter
;		DS = SYSDAT (at ENTRY: and INIT:)
;		ES = User Data Area (UDA) of currently running process

;	Exit:	AX = return
;		BX = AX (in exit)
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

;	Far calls to the Supervisor from the XIOS:
;		CL = function
;		CH = 0
;		DX = parameter
;		DS = data segment of parameter if DX is an address
;		ES = UDA of currently running process

;	Far jumps to the Dispatcher made from XIOS interrupt routines:
;		All registers the same as on entry to the interrupt
;		routine, except CS and IP which are changed by the JMPF.
;		(the FLAGS, CS, IP are on the stack
;		by virtue of the INT instruction)

;	DEV_SETFLAG calls to the Supervisor from XIOS interrupt routines:
;		CL = DEV_SETFLAG function number
;		CH = 0
;		DX = flag number to set
;		DS = SYSDAT
;		ES = don't care (must be restored at end of the
;			interrupt service routine, however)

;	Notes: The DEV_SETFLAG call and the jump to the dispatcher are
;		the only legal operating system "entry" points
;		for interrupt routines.

;		Changes have been made in the
;		register conventions from
;		the CP/M-86 BIOS and the MP/M-86 XIOS.


;	The XIOS must be assembled using the following memory model:
;
;	8080 model:
;	-----------
;	The code and data segments are the same,
;	mixed code and data.  The code segment
;	is org'd at 0C00H relative to the system
;	data area.
;
;	high	+-----------------+\
;		|  system tables  | |
;		+-----------------+ |
;		|  XIOS (C and D) |  >  system data
;		+-----------------+ |
;		|  SYSDAT         | |
;		+-----------------+x
;		|  system code    | >	system code
;	low	+-----------------+/


;	Notes on coding style:

;	Indentation of the code is used to emphasize branching
;	after conditional jumps.  Lower case is used for all code
;	and variables for easier reading.  Code labels, equates
;	and variable names are in upper case within comments to
;	distinquish them from the english.

;	Equates and data variables are placed in proximity to where
;	they are used.	Having the code and data intermixed requires
;	the use of equates and ORG statements throughout the XIOS.

;	The XIOS may be broken up into smaller files with the use
;	of the ASM86 "include" statement.  All of the data variables
;	may be placed in one separate data segment and the extra
;	equates and ORGs removed.

eject
;			CCP/M Formats
;			-------------

;************************************************************************
;*									*
;*			SYSDAT Format					*
;*									*
;************************************************************************

DSEG
ORG	0
supmod		rw	2		;internal entry point to SUP
	org	038h
dispatcher	rw	2		;interrupt routines may exit with
	org	040h			;a JMPF here
osseg		rw	1		;beginning paragraph of O.S.
	org	044h
endseg		rw	1		;1st paragraph after O.S.
	org	04bh
sys_disk	rb	1		;system disk number
	org	050h
temp_disk	rb	1		;temporary disk number
	org	058h
mdul		rw	1		;root of unused memory descriptors
mfl		rw	1		;root of memory free list
	org	068h
rlr		rw	1		;Ready List Root, 1st PD is
	org	072h			;running process
thrdrt		rw	1		;Thread Root, list of active
qlr		rw	1		;processes
	org	078h
version		rw	1		;address of version string
					;relative to SUP code segment
bvernum		rw	1		;BDOS version number
osvernum	rw	1		;O.S. version number
tod_day		rw	1		;binary days since January 1, 1978
tod_hour	rb	1		;BCD
tod_min		rb	1		;BCD
tod_sec		rb	1		;BCD
	org	088h
open_vec	rw	1		;16 bit vector of drives with
					;open files - used by status line
					;routine

;************************************************************************
;*									*
;*	CCP/M System Calls Used by this XIOS				*
;*									*
;************************************************************************

					;process control functions:
p_delay		equ	141		;delay specified number of ticks
p_dispatch	equ	142		;let other another process run
p_pdadr		equ	156		;get double word pointer of process
					;descriptor
p_term		equ	143		;terminate process

					;device control functions:
dev_poll	equ	131		;poll device
dev_waitflag	equ	132		;wait for flag to be set
dev_setflag	equ	133		;set a specified flag

					;Time function:
t_seconds	equ	155		;get date,hours,minutes,seconds


;************************************************************************
;*									*
;*		Process Descriptor Format				*
;*									*
;************************************************************************

;	The Process Descriptor (PD) along with the
;	associated User Data Area (UDA), describe
;	the current state of a Process under CCP/M-86.
;	The process descriptor is always within the System
;	Data Segment.

;	Process Descriptor:

;   +-----+-----+-----+-----+-----+-----+-----+-----+
; 00|   link    |  thread   |stat |prior|   flag    |
;   +-----+-----+-----+-----+-----+-----+-----+-----+
; 08|                     name                      |
;   +-----+-----+-----+-----+-----+-----+-----+-----+
; 10|    uda    |disk | user|       reserved        |
;   +-----+-----+-----+-----+-----+-----+-----+-----+
; 18|               resereved           |  parent   |
;   +-----+-----+-----+-----+-----+-----+-----+-----+
; 20|cons |     reserved    |list |    reserved     |
;   +-----+-----+-----+-----+-----+-----+-----+-----+
; 28|                   reserved                    |
;   +-----+-----+-----+-----+-----+-----+-----+-----+

;	link	- used for placement into System Lists
;	thread	- link field for Thread List
;	stat	- current process activity
;	prior	- priority
;	flag	- process state flags
;	name	- name of process
;	uda	- segment address of user data area
;	disk	- current default disk
;	user	- current default user number
;	mem	- pointer to MD list of memory owned
;		  by this process
;	parent	- process that created this process
;	cons	- default console device (doesn't imply ownership)
;	list	- default list device (doesn't imply ownership)

p_link		equ	word ptr 0
p_thread	equ	word ptr p_link + word
p_stat		equ	byte ptr p_thread + word
p_prior		equ	byte ptr p_stat + byte
p_flag		equ	word ptr p_prior + byte
p_name		equ	byte ptr p_flag + word    
p_uda		equ	word ptr p_name + 8
p_disk		equ	byte ptr p_uda + word
p_user		equ	byte ptr p_disk + byte
p_mem		equ	word ptr p_user + 3
p_parent	equ	word ptr p_mem + 8
p_cons		equ	byte ptr p_parent + word
p_list		equ	byte ptr p_cons + 4

pd_len		equ	30H

;************************************************************************
;*									*
;*		Console Control Block Format				*
;*									*
;************************************************************************
;*
;*	The CCB is used by the system to control the virtual
;*	consoles.  There must be one correctly initialiazed CCB
;*	in the XIOS per virtual	console.
;*
;*	+---------+---------+---------+---------+
;*  00	|      owner        |      reserved	|
;*	+---------+---------+---------+---------+
;*  04	|                reserved               |
;*	+---------+---------+---------+---------+
;*  08	|  mimic  |      reserved               |
;*	+---------+---------+---------+---------+
;*  0C	|      reserved     |       state       |
;*	+---------+---------+---------+---------+
;*  10  |     maxbufsiz     |     reserved      |
;*	+---------+---------+---------+---------+
;*  14  |               reserved                |
;*      +---------+---------+---------+---------+
;*  18  |               reserved                |
;*      +---------+---------+---------+---------+
;*  1C  |               reserved                |
;*      +---------+---------+---------+---------+
;*  20  |               reserved                |
;*      +---------+---------+---------+---------+
;*  24  |               reserved                |
;*      +---------+---------+---------+---------+
;*  28  |               reserved                |
;*      +---------+---------+---------+---------+
;*
;*	owner	- current owner of device
;*		  if 0, no owner
;*	mimic	- list dev that mimics us.
;*		  0ffh means no mimic device
;*	state	- current state of virtual console
;*	maxbufsiz - maximum file size for buffered mode

c_owner		equ	word ptr 00h
c_mimic		equ	byte ptr 08h
c_state		equ	word ptr 0Eh
c_maxbufsiz	equ	word ptr 10h
ccblen		equ	2ch

;CCB state flags

csm_buffered	equ	00001h
csm_background	equ	00002h
csm_purging	equ	00004h
csm_noswitch	equ	00008h
csm_ctrlS	equ	00080h
csm_ctrlO	equ	00100h
csm_ctrlP	equ	00200h

;************************************************************************
;*									*
;*		Memory Descriptor Format				*
;*									*
;************************************************************************

;*	GENCCPM creates an MD per memory partition specified,
;*	links them together and roots the list at the Memory Free
;*	list location in SYSDAT.
;*	The XIOS in the INIT: routine can trim this list to
;*	be in the bounds of actual memory

;*	+---------+---------+---------+---------+
;*  04	|       link        |       start       |
;*	+---------+---------+---------+---------+
;*  08	|      length       |      reserved     |
;*	+---------+---------+---------+---------+
;*  0C  |      reserved     |
;*      +-------------------+

;*	link	- offset of next MD, 0 if end of list
;*	start	- beginning paragraph of free memory
;*	length	- size of partition in paragraphs

md_link		equ	word ptr 0
md_start	equ	word ptr md_link + word
md_length	equ	word ptr md_start + word
mdlen		equ	md_length + 4

eject
;			XIOS Equates
;			---- -------

true		equ	0ffffh
false		equ	0

;************************************************************************
;*									*
;*			XIOS Flag Assignments	 			*
;*									*
;************************************************************************

;	Definition of flag table used by by CCP/M and this XIOS

					;CCP/M flags:
					;0 - is reserved
tick_flag	equ	1		;tick flag number
sec_flag	equ	2		;seconds flag number
min_flag	equ	3		;minutes flag number

					;XIOS flags:
fdc_flag	equ	4		;disk operation flag
key_flag	equ	5		;key available flag
bell_flag	equ	6
last_flag	equ	bell_flag

;************************************************************************
;*									*
;*			ASCII Codes					*
;*									*
;************************************************************************

nul	equ	00h
soh	equ	01h
stx	equ	02h
etx	equ	03h
eot	equ	04h
enq	equ	05h
ack	equ	06h
bel	equ	07h
bs	equ	08h
ht	equ	09h
lf	equ	0ah
vt	equ	0bh
ff	equ	0ch
cr	equ	0dh
so	equ	0eh
shi	equ	0fh
dle	equ	10h
dc1	equ	11h
dc2	equ	12h
dc3	equ	13h
dc4	equ	14h
nak	equ	15h
syn	equ	16h
etb	equ	17h
can	equ	18h
em	equ	19h
subb	equ	1ah
esc	equ	1bh
fs	equ	1ch
gs	equ	1dh
rds	equ	1eh
us	equ	1fh
del	equ	7fh

xon	equ	dc1
xoff	equ	dc3

eject
;		XIOS HEADER AND CCP/M INTERFACE
;		-------------------------------

;************************************************************************
;*									*
;*			XIOS Header 					*
;*									*
;************************************************************************

CSEG
ORG	0C00h				;8080 model XIOS base
					;CCP/M-86 entry points to XIOS:
	jmp	init			;called once at system boot
	jmp	entry			;subsequent calls go through here

num_vir_cons	equ	4		;number of virtual consoles

sysdat		dw	0
supervisor_o	rw	1
supervisor_s	rw	1
supervisor	equ	dword ptr supervisor_o

SL1	equ	offset $
DSEG
ORG	SL1	

ticks_per_second	equ	60

tick		db	false		;set by I_TICK
ticks_sec	db	ticks_per_second
door		db	false		;used if door open interrupt
					;is available to XIOS
		rb	2
nvcns		db	num_vir_cons	;4 virtual consoles
nccb		db	4		;total number of CCBs
nlcb		db	2		;2 list devices

ccb		dw	offset ccb_tab	;pointer to the first CCB
lcb		dw	offset lcb_tab	;pointer to the first LCB

					;disk parameter header offsets
dph_tbl		dw	offset dphA	;A
		dw	offset dphB	;B
		dw	0,0,0,0,0,0,0	;C-I
		dw	0,0,0		;J-L
dph_m_adr	dw	dphM,0,0,0	;M-P

;	If Mdisk memory is not present, INIT: 0's DPH_M_ADR.
;	We put this DPHM in now so GENCCPM will perform
;	DPH fixups for us.
					

genccpm_buf	dw	screens_size	;GENCCPM will alloc screens_size
					;# of paragraphs and put the
					;segment address of the buffer in
					;the variable GENCCPM_BUF

trans_table	dw	key_table	;allow a transient to change
					;the keyboard mapping
debug		db	false		;allow CP/M-86 to be used
					;for debugging, see INIT:

;************************************************************************
;*									*
;*			CCP/M INTERFACE					*
;*									*
;************************************************************************

SL2	equ	offset $
CSEG
ORG	SL2

;=====
;=====
entry:					;arrive here from JMP at
;=====					;03H in XIOS code segment
;=====

;	entry:	AL = function number
;		CX, DX parameters
;	exit:	AX = BX = return
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

;	Note: no alteration of stack is allowed during entry except
;	for the return address caused by the "CALL FUNCTION_TABLE[BX]"
;	instruction.

	cld				;set the direction flag
	xor ah,ah
	cmp al,xios_funcs
	jae ill_func
	  shl al,1			;multiply by 2
	  mov bx,ax			;put in pointer register
	  call function_table[bx]	;no range checking needed
	  mov bx,ax			;only called by O.S.

ill_func:
	retf				;return to O.S. kernel

SL3	equ	offset $
DSEG
ORG	SL3

xios_funcs	equ	14

function_table:
	dw	io_const		; 0 console status
	dw	io_conin		; 1 console input
	dw	io_conout		; 2 console output
	dw	io_listst		; 3 list status
	dw	io_list			; 4 list output
	dw	io_auxin		; 5 auxillary input
	dw	io_auxout		; 6 auxillary out
	dw	io_switch		; 7 switch screen
	dw	io_statline		; 8 update or print new status
	dw	io_seldsk		; 9 select disk
	dw	io_read			;10 read logical sector
	dw	io_write		;11 write logical sector
	dw	io_flushbuf		;12 flush buffers
	dw	io_poll			;13 poll device

SL4	equ	offset $
CSEG
ORG	SL4

;-------
setflag:
;-------
;	entry:	DL = flag number
;	exit:	AX = BX = return

	mov cl,dev_setflag
	jmps supif

;--------
waitflag:
;--------
;	entry:	DL = flag number
;	exit:	AX = BX = return

	mov cl,dev_waitflag
	;jmps supif

;-----
supif:
;-----
;	entry:	CL = function number
;		CH = 0
;		DX parameter
;		ES = user data area
;	exit:	AX = BX = return
;		CX = error code from O.S.
;		ES = UDA or return value

	xor ch,ch			;ensure CH is 0
	callf supervisor
	ret

;=======
io_poll:
;=======
;	entry:	DL =	device #
;	exit:	AL =	0 if not ready, 0FFH if ready
;		BX =	device# * 2
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

;	IO_POLL is called from the dispatcher after a process makes
;	a DEV_POLL call to the O.S.

	xor bx,bx
	mov bl,dl
	shl bx,1
	jmp poll_table[bx]

;----
poll:					;poll device
;----
;	entry:	DL = device number
;	exit:	AL = 0ffh device ready
;		AL = 0 if not ready
;		BX = device# * 2
	
	call io_poll			;check hardware first
	test ax,ax ! jz p_os		;AX=0 if not ready
	  ret				;BX=device# * 2 set by io_poll
p_os:					;DL=device#
	push bx				;save device# * 2
	mov cl,dev_poll			;give up the CPU resource
	call supif
	pop bx				;device# * 2
	ret

SL5	equ	offset $
DSEG
ORG	SL5

poll_table	dw	listst		;each of these functions must
		dw	listst		;preserve BX and DX
		dw	0,0,0,0		;replace with other poll device
					;routines address, when needed.
eject
;			Interrupt Handlers
;			------------------

;************************************************************************
;*									*
;*			IBM PC Interrupt Structure			*
;*									*
;************************************************************************

s_step_int		equ	01h		;interrupt numbers
nmi_int			equ	02h
one_byte_int		equ	03h
tick_int		equ	08h
key_int			equ	09h
disk_int		equ	0Eh
equip_int		equ	11H
mem_size_int		equ	12H
rom_rs232_int		equ	14h		;see init for debugging use
rom_printer_int		equ	17h		;	"
os_int			equ	224		;normal CCP/M-86 entry
debug_int		equ	225		;debugger entry to O.S.

iv_s_step		equ	s_step_int*4	;interrupt vector offsets
iv_nmi			equ	nmi_int*4
iv_one_byte		equ	one_byte_int*4
iv_tick			equ	tick_int*4
iv_key			equ	key_int*4
iv_disk			equ	disk_int*4
iv_rs232		equ	rom_rs232_int*4
iv_debug		equ	debug_int*4

;************************************************************************
;*									*
;*		IBM PC Keyboard Ports					*
;*									*
;************************************************************************

kbd_data	equ	060h		;input port for the key board data
kbd_control	equ	061h		;control port for the key board

;************************************************************************
;*									*
;*	8259 Programmable Interrupt Controller Commands			*
;*			and Ports					*
;*									*
;************************************************************************

pic_even_port		equ	020h	;port 0
pic_odd_port		equ	021h	;bit 0 is A0 of 8259 instructions

pic_nseoi		equ	020h	;non specific end of interupt

disk_channel		equ	06h
keyboard_channel	equ	01h
timer_channel		equ	00h

;************************************************************************
;*									*
;*	      8253-5 Counter Timer Port and Data Equates		*
;*									*
;************************************************************************

timer_0_reg		equ	040h		;first counter timer port
timer_1_reg		equ	041h
timer_2_reg		equ	042h
timer_cmd_reg		equ	043h		;command port

timer_60_hz		equ	19886		;constant for 60.001 hz tick
timer_1000_hz		equ	533h		;constant for 1000 hz tone

bell_cmd		equ	10110110b	;timer 2 lsb,msb binary
bell_on			equ	03h
bell_off		equ	0fch

port_a			equ	060h
port_b			equ	061h
port_c			equ	062h

;************************************************************************
;*									*
;*				I_TICK					*
;*									*
;************************************************************************

;	The following routine gets control on a timer interrupt,
;	from the 8253-5. 


SL6	equ	offset $
CSEG
ORG	SL6

;------
i_tick:
;------
;	Entry:	Currently running process's register state
;	Exit:	All registers preserved

	push ds				;use one level of user stack
	mov ds,sysdat
	cmp tcnt,0			;keep entry count to allow
	jnz t_noswitch			;debugging under DDT86
	  mov i_tick_ss,ss
	  mov i_tick_sp,sp
	  mov ss,sysdat
	  mov sp,offset i_tick_stack
t_noswitch:
	inc t_cnt

	inc int_cnt			;interrupt counter
	sti

	push ax ! push bx ! push cx
	push dx ! push bp ! push di
	push si ! push es

;	Bells on the IBM PC must turn on a speaker for a given duration.
;	A "bell cycle" must have an off period so we can distinquish
;	it from the next bell generated.  A process sending a bell
;	to IO_CONOUT, waits for a DEV_SETFLAG operation from the I_TICK
;	routine that signals the end of the bell.
;
;	Handling bells sent to background processes presents some
;	problems.  If we generate bells sent to a background
;	console, the bells would not pertain to the displayed image.
;	If we keep a bell count per console, and produce the stacked up
;	bells when we switch in a background virtual console
;	the bells would be out of "sync" with the
;	the displayed image.  Stopping a background process because
;	it produced a bell would defeat the purpose of virtual screens.
;	Therefore, bells sent to background consoles are ignored.
;
;	Note: the screen structures are defined in the SERIAL IO section.

	cmp bell_ticks,0		;in bell cycle if non 0
	  jz check_motor_off		;BELL_TICKS is set by IO_CONOUT
	  dec bell_ticks
	  jnz check_bell_off
	    mov dx,bell_flag
	    call setflag		;wake up process that generated
	    jmps check_motor_off	;the bell, ES doesn't need to
check_bell_off:				;be the UDA on DEV_SETFLAG calls
	    cmp bell_ticks,1		;turn bell on last tick of cycle
	    jne check_motor_off
	      in al,port_b
	      and al,bell_off
	      out port_b,al

check_motor_off:
	cmp disk_busy,true		;leave motor on while disk
	je check_seconds		;operation is underway ...
					;helpful for debuging disk routines
	  cmp motor_off_counter,0	;motor is off if 0
	  je check_seconds		;motor on routine sets counter to
	    dec motor_off_counter	;0ffh
	    jnz check_seconds		;not timed out yet
	      mov dx,fdc_port
	      mov al,fdc_on		;do not reset the FDC
	      mov motor_flags,al	;deselect the motors
	      out dx,al			;turn it off
					;only one can be on at a time
check_seconds:
	dec tick_counter		;has 1 second elapsed ?
	jnz no_second_flag
	  mov tick_counter,ticks_per_second	;yes - set
	  mov dx,sec_flag		;the second flag
	  call setflag			;DS=SYSDAT but ES does not
					;need to be UDA on DEV_SETFLAG
no_second_flag:
	cmp tick,false			;only set the tick flag
	je i_tick_exit			;when the tick variable in
	  mov dx,tick_flag		;the XIOS header is true
	  call setflag			;DS must = SYSDAT but
					;ES does not need = UDA
					;on DEV_SETFLAG
i_tick_exit:
	pop es ! pop si ! pop di
	pop bp ! pop dx ! pop cx
	pop bx				;AX still on stack

	cli				;stop reentrancy

	mov al,pic_nseoi		;signal end of interrupt
	out pic_even_port,al		;to 8259

	pop ax

	dec t_cnt
	jnz no_stack_restore
	  mov ss,i_tick_ss
	  mov sp,i_tick_sp

no_stack_restore:
	dec int_cnt
	pop ds				;restore DS of interrupted process
	jz it_disp
	  iret
it_disp:
	jmpf cs:dword ptr dispatcher	;go run the next ready process

;************************************************************************
;*									*
;*			I_TICK Data					*
;*									*
;************************************************************************

SL7	equ	offset $
DSEG
ORG SL7

bell_ticks	db	0
tick_counter	db	ticks_per_second	;counter for tick interupts
int_cnt		db	0

org (offset $ + 1) and 0fffeh

		dw	0CCCCh,0CCCCH,0CCCCH
		dw	0CCCCh,0CCCCH,0CCCCH
		dw	0CCCCh,0CCCCH,0CCCCH
		dw	0CCCCh,0CCCCH,0CCCCH
		dw	0CCCCh,0CCCCH,0CCCCH

		dw	0CCCCh,0CCCCH,0CCCCH
		dw	0CCCCh,0CCCCH,0CCCCH
		dw	0CCCCh,0CCCCH,0CCCCH
		dw	0CCCCh,0CCCCH,0CCCCH
		dw	0CCCCh,0CCCCH,0CCCCH

		dw	0CCCCh,0CCCCH,0CCCCH
		dw	0CCCCh,0CCCCH,0CCCCH
		dw	0CCCCh,0CCCCH,0CCCCH
		dw	0CCCCh,0CCCCH,0CCCCH
		dw	0CCCCh,0CCCCH,0CCCCH

i_tick_stack	rw	0

i_tick_ss	rw	1
i_tick_sp	rw	1
t_cnt		db	0

;************************************************************************
;*									*
;*			I_KEYBOARD					*
;*									*
;************************************************************************

;	The following routine gets control after a keyboard interrupt.
;	Interrupts are generated on the IBM PC when a key is depressed
;	or released.

SL8	equ	offset $
CSEG
ORG	SL8

;----------
i_keyboard:
;----------
;	Entry:	Currently running process's register state
;	Exit:	All registers preserved

	push ds ! mov ds,sysdat		;use one level of user stack
	mov i_keyboard_ss,ss		;use data segment, keep
	mov i_keyboard_sp,sp		;XIOS code and data separate
	mov ss,sysdat
	mov sp,offset i_keyboard_stack	;keyboard interrupt stack
	
	inc int_cnt			;keep other processes from
	sti				;running

	push ax ! push bx ! push cx
	push dx ! push bp ! push di
	push si ! push es

	in al,kbd_data			;get the scan code
	mov dl,al

	mov al,kb_cnt			;# chars in buffer
	cmp al,kblen			;if it is full throw away
	je ik_full			;scan code, PIN rings the
					;bell when VINQ gets full.
	  inc kb_cnt			;we have one more char in buf
	  mov ax,kb_in			;ptr to next char
	  mov bx,ax
	  mov byte ptr [bx],dl		;put the scan code in buffer
	  inc bx			;next free byte in buffer
	  cmp bx,kblen+offset kb_buf	;wrap around ?
	  jne ik_ptr_ok
	    mov bx,offset kb_buf	;back to beginning of buffer
ik_ptr_ok:
	  mov kb_in,bx			;save new pointer

ik_full:
	cmp kb_wait,false		;only call DEV_SETFLAG when
	je ik_no_flag			;a process is waiting for
	  mov dx,key_flag		;keyboard input
	  call setflag			;process doesn't wake up
	  mov kb_wait,false		;until dispatch occurs

ik_no_flag:
	in al,kbd_control		;get the current control port
	mov ah,al			;save the current state
	or al,080h			;reset the key board

	pop es ! pop si ! pop di
	pop bp ! pop dx ! pop cx
	pop bx				;AX still on stack

	out kbd_control,al		;send to control port
	mov al,ah
	out kbd_control,al		;set keyboard back to normal state

	cli				;keep this interrupt handler
					;from being reentered
	mov al,pic_nseoi		;signal end of interrupt
	out pic_even_port,al		;to 8259

	pop ax

	mov ss,i_keyboard_ss
	mov sp,i_keyboard_sp

	dec int_cnt
	pop ds
	jz ik_disp
	  iret
ik_disp:
	jmpf cs:dword ptr dispatcher

;************************************************************************
;*									*
;*			I_KEYBOARD Data					*
;*									*
;************************************************************************

SL9	equ	offset $
DSEG
ORG	SL9

;	Keyboard interrupt routine stack

org	(offset $ + 1) and 0fffeh

		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH

		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH

		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH

i_keyboard_stack	rw	0

i_keyboard_ss	dw	0		;keyboard interrupt
i_keyboard_sp	dw	0		;handler

;	Keyboard input buffer, filled by I_KEYBOARD:, read by
;	IO_CONIN:

kblen		equ	32		;length of character buffer

kb_buf		rb	kblen		;char buffer
kb_out		dw	kb_buf		;pts to next char to read
kb_in		dw	kb_buf		;pts to next free char buffer
kb_cnt		db	0		;# of chars in buffer
kb_wait		db	false		;is any process waiting for
					;keyboard input

;************************************************************************
;*									*
;*			I_DISK						*
;*									*
;************************************************************************

;	The following routine gets control from an interrupt from the
;	FDC.  The current state is saved and DEV_SETFLAG is called.

SL10	equ	offset $
CSEG
ORG	SL10

;------
i_disk:
;------
;	Entry:	Currently running process's register state
;	Exit:	All registers preserved

	push ds ! mov ds,sysdat
	mov disk_ss,ss			;save the stack segment
	mov disk_sp,sp			;and stack pointer
	mov ss,sysdat			;set up interrupt stack
	mov sp,offset i_disk_stack

	inc int_cnt
	sti

	push ax ! push bx ! push cx
	push dx ! push bp ! push di
	push si ! push es

	mov dx,fdc_flag			;DEV_SETFLAG call must have
	call setflag			;DS = SYSDAT, but ES
					;does not need to be set to UDA
					;on flag set calls
	pop es ! pop si ! pop di
	pop bp ! pop dx ! pop cx
	pop bx				;AX still on stack

	cli				;keep this interrupt
					;handler from being
					;reentered
	mov al,pic_nseoi		;signal end of interrupt
	out pic_even_port,al		;to 8259

	pop ax	

	mov ss,disk_ss			;restore user's stack
	mov sp,disk_sp
	dec int_cnt
	pop ds
	jz id_disp
	  iret
id_disp:
	jmpf cs:dword ptr dispatcher

;************************************************************************
;*									*
;*			I_DISK Data					*
;*									*
;************************************************************************

SL11	equ	offset $
DSEG
ORG	SL11

;	Stack switch area for disk interrupt

org	(offset $ + 1) and 0fffeh

		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH

		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH

		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH
		dw	0CCCCH,0CCCCH,0CCCCH

i_disk_stack	rs	0
disk_ss		dw	0
disk_sp		dw	0

;************************************************************************
;*									*
;*			I_UNEXPECTED					*
;*									*
;************************************************************************

SL12	equ	offset $
CSEG
ORG	SL12

;------------
i_unexpected:			;unknown interrupts go here
;------------

;	Display on the console the interrupt vector number that caused
;	the CPU to be here.  We will terminate the process that caused
;	the unknown interrupt, even if its keep flag is on.

	cmp debug,true
	jne u_notd		;break to CP/M-86
	  int 3			;when debugging
u_notd:

	mov ax,sysdat
	mov ds,ax
	mov es,ax		;ES,DS = SYSDAT SEGMENT
	mov di,offset unex_msg_pd
	mov si,rlr		;PD offset
	test si,si
	jz iu_nopd
	  lea si,p_name[si]
	  mov di,offset unex_msg_pd
	  mov cx,4
	  rep movsw		;get PD name into message string

iu_nopd:
	  mov si,offset unex_msg
	  call print_msg	;print unexpected interrupt message

	  mov bx,rlr		;terminate the running process
	  test bx,bx		;if no process is running
	  jnz iu_abort		;halt the machine
	    hlt			;this should only happen if
				;kernel image has been corrupted
iu_abort:
	  mov p_flag[bx],0	;force termination to succeed
	  mov es,p_uda[bx]	;set up UDA
	  mov cx,p_term
	  mov dx,0ffffh
	  jmp supif		;process is terminated, interrupts 
				;are forced on when next process to
				;run is brought into context

;************************************************************************
;*									*
;*	       		I_UNEXPECTED Data				*
;*									*
;************************************************************************

SL13	equ	offset $
DSEG
ORG	SL13

unex_msg	db	cr,lf,'Terminating '
unex_msg_pd	db	'        '
		db	': unexpected interrupt',cr,lf,0

eject
;			Character I/O
;			-------------

;************************************************************************
;*									*
;*			IBM PC Screen Equates				*
;*									*
;************************************************************************

rows_per_screen		equ	24
columns_per_screen	equ	80
screen_siz		equ	rows_per_screen * columns_per_screen
						;in words

screens_size		equ	((2 * screen_siz + 15)/16) * num_vir_cons
						;storage for all the screens
						;in paragraphs
bw_video_seg		equ	0B000h		;segment address of
						;start of video ram
bw_video_status_line	equ	screen_siz * 2
						;byte offset of status line

;************************************************************************
;*									*
;*		IBM PC Parallel Port Equates				*
;*									*
;************************************************************************

list_ports	dw	03bch		;BW card parallel port
		dw	0378h		;0378h parallel port card as shipped

;************************************************************************
;*									*
;*	       6845 CRT Controller Port and Command Equates		*
;*									*
;************************************************************************

;	The IBM PC's monochrome memory mapped video display begins
;	at paragraph 0B000H.  It represents a screen 80 X 25.
;	Each video character requires a word value, the low byte
;	is the ASCII code (characters codes > 128 are also displayed)
;	and the high byte is an attribute byte.  The 25th line
;	is reserved by this XIOS as a status line.

bw_card			equ	003b4h

video_on		equ	00029h
video_off		equ	00021h

cursor_start		equ	10
cursor_end		equ	11
display_start_hi	equ	12
display_start_low	equ	13
cursor_hi		equ	14
cursor_low		equ	15

;************************************************************************
;*									*
;*			IO_CONST					*
;*									*
;************************************************************************

SL14	equ	offset $
CSEG
ORG SL14

;========
io_const:
;========
;	entry:	DL console number
;	exit:	AL = 0ffh if ready
;		AL = 0 if not
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

	xor ax,ax
	ret

;************************************************************************
;*									*
;*			IO_CONIN					*
;*									*
;************************************************************************

;========
io_conin: 
;========
;	entry:	DL console number
;	exit:	AH = 0 and AL = character data
;		AH = 0ffh and AL screen to switch to
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

	pushf ! cli
	cmp kb_cnt,0
	jne ci_haveone
ci_wait:
	  mov kb_wait,true
	  popf
	  mov dx,key_flag		;get the flag number
	  call waitflag			;wait for a scan code
	  jmps ci_haveone2
ci_haveone:
	popf
ci_haveone2:
	dec kb_cnt
	mov ax,kb_out
	mov bx,ax
	mov al,[bx]			;scan code in AL
	inc bx
	cmp bx,offset kb_buf+kblen
	jne ci_ptr_ok
	  mov bx,offset kb_buf
ci_ptr_ok:
	mov kb_out,bx
	push es				;save UDA
	push ds ! pop es		;ES=SYSDAT
	call scan_trans			;returns AH=0 and AL=char to
	pop es				;return to process
					;else AH=0ffh - check for
					;switch screen
	test ah,ah			;AH=0 then done
	jz conin_done
					;AH=0ffh and AL=0 if released
					;key, AL=special key otherwise
	  cmp al,0f9h			;0F0h-0F9h are switch screen
	  ja io_conin			;codes
	    cmp al,0f0h
	    jb io_conin			;implement check for programable
					;function keys here
	      and al,0fh		;switch screen range 0-9
	      ;jmp conin_done
conin_done:
	ret

scan_trans:
;----------
;	entry:	AL=scan code
;	exit:	AH=0 if ASCII and AL=ASCII code
;		AH=0FFH if AL is special code

;	Test for CTRL, ALT already being down and DEL being current
;	scan code.  If yes, call the ROM reset routine.

	mov scan_code,al		;save scan code
	cmp al,83 ! jnz mask_release	;is scan code the DEL key ?
	  test down_bits,ctrl_bit	;yes $ is control down ?
	  jz mask_release
	    test down_bits,alt_bit	;yes $ is alt down ?
	    jz mask_release
	      jmp reset			;yes alt and ctrl are down
 
mask_release:				;parity is on when key is released
	and al,7fh			;turn off key release bit
	cmp al,83 ! jle valid_key	;test for non$existent key
	  jmp no_key

;	Test for CTRL, SHIFT, ALT, NUMBLOCK, CAPSLOCK
;	keys.  The scan codes for these keys are in action_key_table.
;	and are refered to as action keys in the comments below.
;	The bit position in AH corresponds with what kind of key is
;	found, see equates below.
;	The DOWN_BITS byte has bits on for action keys that are currently
;	being held down by the operator.  The TOGGLE_BITS byte is similar
;	but for keys that have toggle action, NUM LOCK, CAPS LOCK.
;	Note, there is one only one set of DOWN_BITS, whereas
;	the TOGGLE_BITS are kept on a per virtual console basis in the
;	screen structures.

valid_key:				;AL=scan code without parity
	mov si,foreground_ss		;foreground screen structure
	mov di,offset action_key_table	;look for CTRL,SHIFT,ALT,NUMLOCK
	xor cx,cx			;CAPSLOCK
	mov cl,num_action_keys		;get count
	repnz scasb			;look for the byte	
	jnz not_action_key
	  dec di			;backup to matched action key
	  sub di,offset action_key_table
					;DI= 0 relative index
	  mov ah,offset action_key_masks[di]
					;get the corresponding bit
	  jmp action_key		;pattern

not_action_key:
	mov al,scan_code
	or al,al ! jns key_make		;sign is on if key just released
	  jmp no_key			;ignore release condition on
					;non action type key
key_make:
	mov bx,offset key_table		;translation table for single keys
	test down_bits,ctrl_bit		;was control key already down ?
	jz test_for_keypad		;no - try numlock
	  mov bx,offset control_table	;yes - point to control key table
	  jmps translate

test_for_keypad:
	cmp al,71 ! jb test_for_shift	;test for keypad
	  test ss_mode[si],ssm_numlock	;from keypad, numlock on ?
	  jz test_for_shift
	    test down_bits,shft_bit
	    jnz translate		;use key_table if numlock,shifted
	    mov bx,offset shift_table
	    jmps translate		;use shift table if numlock

test_for_shift:
	test down_bits,shft_bit
	jz translate			;no $ return key from key_table
	  mov bx,offset shift_table	;yes $ get key from shift_table

translate:
	xlat bx				;look up the key
	test al,80h			;is it special ?
	jnz special_key			;yes then done
	  test ss_mode[si],ssm_capslock	;test for caps lock
	  jz test_for_alt
	    cmp al,'z'			;yes
	    ja test_for_alt		;not alphabetic
	      cmp al,'a'		;is it lower case ?
	      jae do_case_change	;yes, switch case
	        cmp al,'Z'
	        ja test_for_alt		;not alphabetic
	          cmp al,'A'		;test for upper case
	          jb test_for_alt	;not alphabetic
do_case_change:
	xor al,020h			;switch the case
test_for_alt:
	and al,07fh			;mask off the sign bit from table
	test down_bits,alt_bit		;is alt key currently down ?
	jz printable			;no
	  or al,080h			;yes $ turn on the msb for alt key
printable:
	xor ah,ah			;signal printable char
	jmps scan_done

no_key:
	mov al,0			;AH=FF, AL=0 force IO_CONIN: 
					;to get another scan code
special_key:
	mov ah,0ffh 

scan_done:
	ret

action_key:
;----------
;	Scan code in AL indicates an action key.  AH is
;	the bit mask for the action key.
;	Live action keys are those that must remain pressed down
;	to have	an effect, i.e., CTRL, SHIFT, ALT.  The state of live
;	action keys are bits stored in DOWN_BITS.
;	Toggle action keys are those that switch back and forth in
;	function each time they are depressed, i.e., NUMLOCK, CAPSLOCK.
;	The state of the toggle action keys are bits stored in the
;	SS_MODE byte of the foreground screen structure.

;	entry:	AL = scan code, one of CTRL, SHIFT,
;		ALT, NUMLOCK, CAPSLOCK with parity masked off
;		AH = bit mask
;		SI = foreground screen structure
;	exit:	jumps to NO_KEY since no printable
;		character results

					;ALT is last live key in table
	cmp al,alt			;see if live action: ALT,SHIFT,CTRL
	mov al,scan_code		;get scan code with parity
	ja toggle_action		;no - it is a toggle type key
	  test al,80H			;pressed or released ?
	  jz action_make		;no parity bit - key is down
	    not ah			;parity bit on - key is released
	    and down_bits,ah		;clear the bit for this live
	    jmps action_key_done	;action key
action_make:
	  or down_bits,ah		;set bit for this live action key
	  jmps action_key_done

toggle_action:				;NUMLOCK or CAPSLOCK
	test al,80H			;depressed or released ?
	jnz action_key_done		;ignore release of toggle
	  xor ss_mode[si],ah		;it is toggle: reverse the state
	  call update_status
	  ;jmps action_key_done

action_key_done:
	jmps	no_key


;************************************************************************
;*									*
;*			IO_CONOUT					*
;*									*
;************************************************************************

;=========
io_conout:
;=========
;	entry:	CL = character to output
;		DL = device number
;	exit:	None
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

;	While in IO_CONOUT routines:
;		AL = character to output
;		AH = device number
;		BX = screen structure

	call ioo_conout
	ret

ioo_conout:
	cmp dl,nvcns
	jb co_ok
	  ret				;support of character devices
					;which are not virtual consoles
					;can go here ...
co_ok:
					;NOTE: PIN will not call
					;IO_SWITCH, while in
					;IO_CONOUT and we are
					;the foreground or requested
					;screen.
	mov al,cl
	mov ah,dl			;AH = virtual console #
	xor dh,dh			;get screen structure for
	mov bx,dx			;this console
	shl bx,1			;word index
	mov bx,screen_struct_addrs[bx]
	jmp ss_escape[bx]		;escape handler if in the middle
					;of and escape sequence else
					;jump to CO_NO_ESCAPE

co_no_escape:				;look for CR,LF,backspace,ESC
	mov di,offset special_char_tab	;and jump to special handler
	mov si,offset special_func_tab	
	call co_lookup
	jcxz co_simple_char
	  ret				;all done if special handling
					;or expanding and escape
					;sequence

co_simple_char:				;simple character output

;	Put character in screen and update cursor position
;	BX = screen structure
;	AL = character

	cmp al,bel			;is it a bell ?
	jne no_bell
	  cmp bx,foreground_ss		;bells to background consoles
	  jne ignore_bell		;are ignored
	    cmp bell_ticks,0		;are we generating a bell
	    jne ignore_bell		;already ?
	      in al,port_b		;and then off for 1/60
	      or al,bell_on
	      out port_b,al
	      mov bell_ticks,7		;a bell is on 6/60 of a sec
	      mov dx,bell_flag		;wait for tick interrupt
	      call waitflag		;(I_TICK:) to call DEV_SETFLAG
					;when bell is done

ignore_bell:				;NOTE: PIN will not call
	ret				;IO_SWITCH, while in
					;IO_CONOUT and we are
					;the foreground or requested
					;screen.
no_bell:
	mov di,ss_cursor[bx]		;cursor byte offset
	push es
	call set_up_es

co_back:
	push ax				;save virtual console number
	mov ah,ss_attribute[bx]
	stosw				;update data and atrribute
	pop ax
	pop es
					;DI points at next data
					;and attribute word
	cmp ss_column[bx],columns_per_screen-1
	jb inc_col
	  test ss_mode[bx],ssm_no_wrap
	  jnz co_nowrap
	    call carriage_return
	    jmp line_feed
co_nowrap:
	    ret				;don't move cursor
					;when at end of line
inc_col:
	jmp r_right			;move cursor right


co_look_up:
;----------
;	entry:	BX = address of screen structure
;		AH = device#
;		AL = character to scan for
;		DI = ptr to lookup table, first byte is length
;		SI = table of functions to jump to if char found in
;			lookup table
;	exit:	CX = 0ffffh if special character found
;			and special function has been performed
;		CX = 0 if char not found, no special handling is performed
;		AX,BX preserved if not special function

	mov bp,es ! mov dx,ds		;use registers instead of
	mov es,dx			;stack for speed
	xor cx,cx ! mov cl,[di]		;length of lookup
	mov dx,cx			;save the length
	inc di
	repne scasb			;is it in table ?
	mov es,bp			;restore UDA
	je lu_func			;CX=0 if not found
	  ret

lu_func:
	  inc cx			;iteration we matched on
	  sub dx,cx			;function number
	  shl dx,1			;jump to special function
	  add si,dx
	  call word ptr [si]
	  xor cx,cx ! dec cx		;return CX=0FFFFH
	  ret
			
;************************************************************************
;*									*
;*		Special Output Character Routines			*
;*									*
;************************************************************************

;---------------
carriage_return:
;---------------
;	entry:	BX = screen structure
;	exit:	BX preserved

	xor dx,dx
	xchg ss_column[bx],dl		;set column to 0
	shl dx,1			;back up cursor
	sub ss_cursor[bx],dx
cr_done:
	jmp set_physical_cursor

;---------
line_feed:
;---------
;	entry:	BX = screen structure
;	exit:	BX preserved

	cmp ss_row[bx],rows_per_screen-1
	je lf_scroll
	  jmp d_down			;no-just move cursor down
lf_scroll:				;yes-scroll screen up
	  xor dl,dl			;start row
	  mov dh,rows_per_screen-1	;end row
	  call scroll_up
	  jmp erase_line

;----------
back_space:
;----------
;	entry:	BX = screen structure
;	exit:	BX preserved

	mov di,ss_cursor[bx]
	test di,di			;at home position, return
        jz bs_ret
	  cmp ss_column[bx],0		;if column 0, go up to previous line
	  je bs_row
            dec ss_column[bx]
	    jmps bs_set_cursor
bs_row:
	  test ss_mode[bx],ssm_no_wrap	;don't back up to previous row
	  jnz bs_ret			;if in NOWRAP mode
	    dec ss_row[bx]
	    mov ss_column[bx],columns_per_screen - 1
bs_set_cursor:
	    sub di,2
	    mov ss_cursor[bx],di
	    jmp set_physical_cursor
bs_ret:
	ret

;************************************************************************
;*									*
;*			Escape Sequence Routines			*
;*									*
;************************************************************************

escape:					;expand escape sequences
;------
;	entry:	BX = screen_structure address for this device console
;		AL = current byte in escape sequence
;	exit:	ss_escape = address of where to continue
;				escape sequence or 0 if done

	mov ss_escape[bx],offset escape1
	ret				;back to co_look_up

escape1:				;first char of escape sequence
	mov ss_escape[bx],offset co_no_escape
					;assume done with escape
	mov di,offset esc_tab1		;if more chars are needed
	mov si,offset esc_func_tab1	;in the escape sequence, the
	jmp co_look_up			;escape field is set by the
					;handler - see X_AND_Y: 
;------------
erase_screen:				;ESC E
;------------
;	entry:	BX = screen structure whose screen data to blank
;		AH = virtual console #
;	exit:	AX,CX,DI are used
;		BX = screen structure

	push ax ! push es
	call set_up_es 
	mov cx,screen_siz
	xor di,di
	mov ax,0720h
	rep stosw
	pop es ! pop ax
	;jmps home

;----
home:					;ESC H
;----
;	entry:	BX = screen structure
;	exit:	BX preserved

	xor dx,dx
	mov ss_cursor[bx],dx
	mov ss_row[bx],dl
	mov ss_column[bx],dl
	jmp set_physical_cursor

;-----
right:					;ESC C
;-----
;	entry:	BX = screen structure
;	exit:	BX preserved

	cmp ss_column[bx],columns_per_screen-1
	jb r_right
	  ret
r_right:
	  add ss_cursor[bx],2
	  inc ss_column[bx]
	  jmp set_physical_cursor

;----
left:					;ESC D
;----
;	entry:	BX = screen structure
;	exit:	BX preserved

	cmp ss_column[bx],0
	ja l_left
	  ret
l_left:
	  sub ss_cursor[bx],2
	  dec ss_column[bx]
	  jmp set_physical_cursor

;----
down:					;ESC B
;----
;	entry:	BX = screen structure
;	exit:	BX preserved

	cmp ss_row[bx],rows_per_screen-1
	jb d_down
	  ret
d_down:
	  add ss_cursor[bx],columns_per_screen*2
	  inc ss_row[bx]
	  jmp set_physical_cursor

;--
up:					;ESC A
;--
;	entry:	BX = screen structure
;	exit:	BX preserved

	cmp ss_row[bx],0
	ja d_up
	  ret
d_up:
	  sub ss_cursor[bx],columns_per_screen*2
	  dec ss_row[bx]
	  jmp set_physical_cursor

;--------------
up_with_scroll:				;ESC I
;--------------
;	entry:	BX = screen structure
;	exit:	BX preserved

	cmp ss_row[bx],0		;if not the top line
	ja up				;just do a cursor up
	  xor dl,dl			;start row
	  mov dh,rows_per_screen-1	;ending row
	  call scroll_down		;down one row
	  call erase_line		;blank out current row
	  ret				;don't move the cursor

;------
report:
;------
	ret

;----
save:					;ESC j
;----
;	entry:	BX = screen structure
;	exit:	BX preserved
;		AX destroyed

	mov ax,ss_xy[bx]
	mov ss_old_xy[bx],ax
	mov ax,ss_cursor[bx]
	mov ss_oldcursor[bx],ax
	ret

;-------
restore:				;ESC k
;-------
;	entry:	BX = screen structure
;	exit:	AX,BX preserved

	mov cx,ss_oldxy[bx]
	mov ss_xy[bx],cx
	mov cx,ss_oldcursor[bx]
	mov ss_cursor[bx],cx
	jmp set_physical_cursor

;-------
x_and_y:				;ESC Y
;-------
;	entry:	BX = screen structure
;	exit:	BX preserved

	mov ss_escape[bx],offset xy_row	;wait for row
	ret
xy_row:
	sub al,32			;make row relative to 0
	cmp al,rows_per_screen-1
	jbe row_ok
	  mov ss_escape[bx],offset xy_err
row_ok:
	mov ss_row[bx],al
	mov ss_escape[bx],offset xy_col	;wait for column
	ret
xy_col:
	sub al,32			;make column relative to 0
	cmp al,columns_per_screen-1
	jbe xy_set_col
	  mov al,columns_per_screen-1
xy_set_col:
	mov ss_column[bx],al
	call compute_cursor
	call set_physical_cursor
xy_err:
	mov ss_escape[bx],offset co_no_escape
	ret

;-----------
erase_begin:				;ESC b
;-----------
;	entry:	BX = screen structure
;		AH = virtual screen number
;	exit:	AX,CX,DI are used
;		BX = screen structure

	push es
	call set_up_es

	mov ax,ss_cursor[bx]		;cursor byte offset
	shr ax,1			;make word offset
	inc ax				;make relative to 1
	mov cx,ax			;number of words to erase
	mov ax,0720h			;7 = normal attribute
					;20 = ASCII space
	xor di,di			;start at beginning
	rep stosw			;of display
	pop es
	ret
	
;---------
erase_end:				;ESC j
;---------
;	entry:	BX = screen structure
;		AH = virtual screen number
;	exit:	AX,CX,DI are used
;		BX = screen structure

	push es
	call set_up_es

	mov ax,ss_cursor[bx]		;cursor byte offset
	mov di,ax			;starting offset to erase
	shr ax,1			;make word offset
	mov cx,screen_siz		;screen size in words
	sub cx,ax			;less 0 relative cursor position
	mov ax,0720h			;7 = normal attribute
					;20 = ASCII space
	rep stosw
	pop es
	ret

;----------
erase_line:				;ESC l
;----------
;	entry:	BX = screen structure
;	exit:	BX preserved

	push es
	call set_up_es

	xor ax,ax
	mov al,ss_row[bx]		;current row
	mov cx,columns_per_screen	;words to move
	mul cx				;word offset of current row
	shl ax,1			;byte offset
	mov di,ax			;destination
	mov ax,0720h			;7 = normal attribute
					;20 = ASCII space
	rep stosw
	pop es
	ret

;---------
erase_bol:				;ESC o
;---------				;erase from beginning
;	entry:	BX = screen structure	;of line
;	exit:	BX preserved

	push es
	call set_up_es

	xor ax,ax
	mov al,ss_row[bx]		;current row
	mov cx,columns_per_screen*2
	mul cx				;byte offset of row
	mov di,ax	
	xor cx,cx
	mov cl,ss_column[bx]		;words to blank
	inc cx				;include cursor
	mov ax,0720h			;7 = normal attribute
					;20 = ASCII space
	rep stosw
	pop es
	ret

;---------
erase_eol:				;ESC K
;---------				;erase to end of line
;	entry:	BX = screen structure
;	exit:	BX preserved

	push es
	call set_up_es

	mov di,ss_cursor[bx]
	mov cx,columns_per_screen
	sub cl,ss_column[bx]		;words to blank
	mov ax,0720h			;7 = normal attribute
					;20 = ASCII space
	rep stosw
	pop es
	ret

;-----------
insert_line:				;ESC L
;-----------
;	entry:	BX = screen structure
;	exit:	BX preserved

	push ax				;save virtual console number
	mov dl,ss_row[bx]		;start row
	mov dh,rows_per_screen-1	;ending row
	call scroll_down		;move screen down one row
	call erase_line			;blank out the line
	xor ax,ax			;save virtual console number
	xchg al,ss_column[bx]		;set cursor to beginning
	shl ax,1			;of line
	sub ss_cursor[bx],ax
	pop ax
	jmp set_physical_cursor

;-----------
delete_line:				;ESC M
;-----------
;	entry:	BX = screen structure
;	exit:	BX preserved

	push ax				;save virtual console number
	mov dl,ss_row[bx]		;move screen up one line
	mov dh,rows_per_screen-1	;to present row
	call scroll_up
	push ss_xy[bx]			;save row and column
	mov ss_row[bx],rows_per_screen-1
	call erase_line			;erase last line
	pop ss_xy[bx]			;restore row and column
	pop ax				;restore virtual console number
	jmp carriage_return		;put cursor at beginning of line

;-----------
delete_char:				;ESC N
;-----------
;	entry:	BX = screen structure
;	exit:	BX preserved

	push es
	call set_up_es
	mov di,ss_cursor[bx]
	mov si,di
	add si,2

	xor cx,cx
	mov cl,columns_per_screen-1
	sub cl,ss_column[bx]

	push ds ! push es ! pop ds
	rep movsw
	mov 0[di],0720H			;put a blank in last column
	pop ds ! pop es
	ret

;-------------
enter_reverse:				;ESC p
;-------------
;	entry:	BX = screen structure
;	exit:	BX preserved

;	video attribute byte has the following structure:
;
;     -- high nibble ---       --- low nibble ----
;	7      6   5   4         3       2   1   0
;     blink   background       bright   foreground
;	        color                     color
;
;	So we must swap the colors and keep the blink and bright
;	bits unchanged.
			
	test ss_mode[bx],ssm_reverse	;already in reverse video ?
	jnz rev_done
	  or ss_mode[bx],ssm_reverse	;turn on reverse video mode bit
	  ;jmps rev_swap

rev_swap:
;--------
	mov al,ss_attribute[bx]
	mov ah,al			;save blink and bright bits
	and al,077h			;mask off blink and bright bits
	mov cl,4
	ror al,cl			;swap nibbles and colors
	and ah,088h			;get just the blink and bright bits
	or al,ah			;put them together again
	mov ss_attribute[bx],al		;save the new attribute
rev_done:
	ret

;------------
exit_reverse:				;ESC q
;------------
;	entry:	BX = screen structure
;	exit:	BX preserved

	test ss_mode[bx],ssm_reverse
	jz rev_done
	  and ss_mode[bx],not ssm_reverse	;turn off reverse mode bit
	  jmps rev_swap

;-----------
enter_blink:				;ESC s
;-----------
;	entry:	BX = screen structure
;	exit:	BX preserved

	or ss_attribute[bx],080h
	ret

;----------
exit_blink:				;ESC t
;----------
;	entry:	BX = screen structure
;	exit:	BX preserved

	and ss_attribute[bx],not (080h)
	ret

;------------
enter_bright:				;ESC r
;------------
;	entry:	BX = screen structure
;	exit:	BX preserved

	or ss_attribute[bx],08h
	ret

;-----------
exit_bright:				;ESC u
;-----------
;	entry:	BX = screen structure
;	exit:	BX preserved

	and ss_attribute[bx],not (08h)
	ret

;-------------
enable_cursor:				;ESC e
;-------------
;	entry:	BX = screen structure
;	exit:	BX preserved

	and ss_mode[bx],not ssm_no_cursor	;used by IO_SWITCH:
	mov ah,0bh			;on cursor byte
	jmps cursor_on_off

;-------------
disable_cursor:				;ESC f
;-------------
;	entry:	BX = screen structure
;	exit:	BX preserved

	or ss_mode[bx],ssm_no_cursor	;used by IO_SWITCH:
	mov ah,26h			;off cursor byte

cursor_on_off:
	mov dx,bw_card			;get the control register
	mov al,cursor_start		;register to update
	out dx,al
	inc dx
	mov al,ah			;turn off/on cursor
	out dx,al
	ret

;-----------
enable_wrap:			;ESC v
;-----------
;	entry:	BX = screen structure
;	exit:	nothing preserved since IO_STATLINE is
;		called

	and ss_mode[bx],not ssm_no_wrap
	jmp update_status

;------------
disable_wrap:			;ESC w
;------------
;	entry:	BX = screen structure
;	exit:	nothing preserved since IO_STATLINE is
;		called

	or ss_mode[bx],ssm_no_wrap
	jmp update_status

;************************************************************************
;*									*
;*		Console Output Sub-Routines				*
;*									*
;************************************************************************

scroll_up:				;scroll up in range given in
;---------				;DX
;	entry:  AH = virtual console number
;		BX = screen structure
;		DL = start row, 0 relative
;		DH = ending row, 0 relative
;	exit:	AX,BX,ES preserved

	push ax ! push es
	call set_up_es
	mov cx,dx			;copy of start,ending rows

	xor ax,ax			;set up SI,DI
	mov al,dl			;AX = beginning row
	mov dx,columns_per_screen*2
	mul dx				;byte offset of beginning row
	mov di,ax			;first destination word
	add ax,columns_per_screen*2	;first word to copy
	mov si,ax
					;set up CX
	mov ax,cx			;AL=start,AH=end
	sub ah,al			;end always > start
	xor al,al
	xchg al,ah			;AX=number or rows to move
	
	mov dx,columns_per_screen
	mul dx				;words to move up
	mov cx,ax			;get count
	push ds ! push es ! pop ds
	rep movsw
	pop ds ! pop es ! pop ax
	ret

scroll_down:				;scroll down in range given
;-----------				;in DX
;	entry:  AH = virtual console number
;		BX = screen structure
;		DL = start row, 0 relative
;		DH = ending row, 0 relative
;	exit:	AX,BX,ES preserved

	push ax ! push es
	call set_up_es
	mov cx,dx			;copy of start,ending rows
					;set up SI,DI
	xor ax,ax
	mov al,dh			;AX=ending row
	inc al				;next row after end row
	mov dx,columns_per_screen*2
	mul dx				;byte offset of next row
	sub ax,2			;last word of ending row
	mov di,ax			;first destination word
	sub ax,columns_per_screen*2	;first word to copy
	mov si,ax
	std				;auto-decrement SI,DI
					;set up CX
	mov ax,cx			;AL=start,AH=ending 
	sub ah,al			;end always > start
	xor al,al
	xchg al,ah			;# rows to move
	mov dx,columns_per_screen
	mul dx				;words to move down
	mov cx,ax			;get count
	push ds ! push es ! pop ds
	rep movsw
	pop ds ! pop es ! pop ax
	cld			  	;zero direction flag !
	ret

compute_cursor:			;update SS_CURSOR field from
;--------------			;SS_ROW and SS_COLUMN fields
;	entry:	BX = screen structure address
;		AH = virtual console device #
;		ES = segment of video
;	exit:	CX,DX used
;		AX,BX preserved

	push ax				;save virtual console number
	xor ax,ax
	mov al,ss_row[bx]		;row and column
	mov cx,columns_per_screen*2
	mul cx
	xor cx,cx
	mov cl,ss_column[bx]
	shl cx,1
	add ax,cx
	mov ss_cursor[bx],ax		;word offset in screen array
	pop ax				;restore virtual console number
	ret

set_physical_cursor:
;-------------------
;	If the screen is the foreground screen then set the
;	cursor register in the 6845 to SS_CURSOR[BX]
;
;	entry:	BX = screen structure address
;		AH = virtual console device #
;		ES = segment of video
;	exit:	AX,CX,DX used

	cmp ah,foreground_screen
	jne sp_ret
	  mov cx,ss_cursor[bx]		;byte offset of cursor
	  shr cx,1			;make a word value
	  mov dx,bw_card		;get the control register
	  mov al,cursor_low		;register to update
	  out dx,al
	  inc dx ! mov al,cl
	  out dx,al
	  dec dx ! mov al,cursor_hi
	  out dx,al
	  inc dx ! mov al,ch
	  out dx,al
sp_ret:
	ret

set_up_es:				;set up ES for subsequent
;---------				;string operation
;	entry:	BX = screen structure
;		AH = virtual console number
;	exit:	ES = physical video RAM segment if
;			foreground, else ES =
;			virtual console RAM for this screen
;		CX = destroyed

	mov cx,bw_video_seg
	mov es,cx
	cmp ah,foreground_screen
	je set_r
	  mov cx,ss_screen_seg[bx]
	  mov es,cx
set_r:
	ret

;************************************************************************
;*									*
;*			Switch Screen					*
;*									*
;************************************************************************

;=========
io_switch:
;=========
;	entry:	DL = Screen to switch to
;	exit:	None
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

;	PIN process does range checking of DL

	push es				;save UDA

	xor ax,ax ! mov al,dl
	mov bl,dl ! mov cx,ccblen
	mul cx ! add ax,ccb		;compute new foreground CCB
	mov foreground_ccb,ax

	xchg bl,foreground_screen	;save destination screen
					;and get screen to save
	xor bh,bh			;index into screen structure array
	shl bx,1			;word pointer
	mov bx,screen_struct_addrs[bx]	;get pointer to old envionrment
	mov cx,screen_siz		;in words
	mov es,ss_screen_seg[bx]	;destination segment
	xor si,si ! mov di,si
	mov ax,bw_video_seg
	push ds				;save DS
	mov ds,ax			;source segment
	rep movsw			;do the copy
	pop ds				;restore DS

	mov bl,foreground_screen

	xor bh,bh			;requested one
	shl bx,1
	mov bx,screen_struct_addrs[bx]
	mov foreground_ss,bx		;new foreground screen SS address
	xor si,si ! mov di,si
	mov ax,bw_video_seg
	mov es,ax
	mov cx,screen_siz		;in words
	push ds
	mov ds,ss_screen_seg[bx] 
	rep movsw
	pop ds ! pop es			;restore UDA
	mov ah,foreground_screen	;param to set_physical_cursor
	push ax				;save AH = foreground screen
	test ss_mode[bx],ssm_no_cursor	;turn on/off cursor ?
	jnz sw_no_cursor
	  call enable_cursor
	  jmps sw_done
sw_no_cursor:
	  call disable_cursor
sw_done:
	pop ax				;restore AH = foreground screen
	jmp set_physical_cursor
	ret

;************************************************************************
;*									*
;*			Status Line Routine				*
;*									*
;************************************************************************

;-------------
update_status:			;XIOS call for normal status update
;-------------
	xor cx,cx

;===========
io_statline:
;===========
;	entry:	CX = 0 regular status update
;		else DX:CX->string to display
;		CX = 0ffffh stop display of special status
;		line, and start normal status line update
;	exit:	AX<>0 if we couldn't print
;		the requested status line: i.e., another
;		process is already updating the status
;		line, or a special status line is being
;		displayed.
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

 	push cx ! push dx
s_wait:				;status line is not reentrant code
	mov al,true
	xchg al,sts_locked
	cmp al,true
	jne s_own
	  mov cl,p_delay		;wait one tick
	  mov dx,1
	  call supif
	  je s_wait			;try again
s_own:
	pop dx ! pop cx
	push es				;save UDA
	test cx,cx			;CX=0 normal status update
	jz s_norm
	  cmp cx,0ffffh
	  jne s_special
	    mov sts_special,false
	    jmps s_norm_ok
s_special:
	  cmp sts_special,true
	  jne s_special_ok		;is status line busy with
	    mov ax,0ffffh		;an earlier special display ?
	    jmp s_exit

s_special_ok:
	  mov sts_special,true
	  push ds			;save SYSDAT
	  mov ds,dx
	  mov si,cx
	  jmp s_prt
s_norm:
	cmp sts_special,true		;if special line is currently
	jne s_norm_ok			;being displayed skip normal
	  mov ax,0ffffh
	  jmp s_exit			;update
s_norm_ok:
	mov dx,ds ! mov es,dx
	mov di,offset status_msg_start
	mov al,' '
	mov cx,columns_per_screen
	rep stosb			;blank out local status string

	mov si,offset constr		;put in 'Console='
	mov di,offset smsg_con
	mov cx,length constr
	rep movsb

	mov si,offset prnstr		;put in 'Printer='
	mov di,offset smsg_prn
	mov cx,length prnstr
	rep movsb

	mov al,foreground_screen
	add al,'0'
	mov smsg_cnum,al		;set console number

	mov bx,foreground_ccb		;get process which owns the
	mov si,c_owner[bx]		;foreground console
	test si,si ! jz s_nopd		;make sure its non-zero
	  mov al,p_list[si]
	  add al,'0' ! mov smsg_pnum,al
	lea si,p_name[si]		;offset of process name
	mov di,offset smsg_pd
	mov cx,4 ! rep movsw

s_nopd:
s_ctrlS:
	mov ax,c_state[bx]
	test ax,csm_ctrlS ! jz s_ctrlO
	  mov s_msgctrlS,'S^' ! jmps s_ctrlP
s_ctrlO:
	test ax,csm_ctrlO ! jz s_ctrlP	;ctrl S and ctrl O are mutally
	  mov s_msgctrlS,'O^'		;exclusive print one of ^S or ^O
s_ctrlP:
	test ax,csm_ctrlP ! jz s_mode
	  mov s_msgctrlP,'P^'
	  mov dl,'=' ! mov dh,c_mimic[bx]
	  add dh,'0' ! mov smsg_ctrlP_num,dx
s_mode:
	mov di,offset s_msgmode
	mov cx,length dynstr

	mov si,offset nosstr			;test for noswitch
	test ax,csm_noswitch ! jnz s_movmode
	  mov si,offset dynstr
	  test ax,csm_buffered ! jz s_movmode	;lsb bit=0: dynamic
	    mov si,offset bufstr		;lsb bit=1:buffered
	    test ax,csm_purging ! jz s_movmode	;check for purging
	      mov si,offset purstr

s_movmode:
	rep movsb

s_getopenvec:
					;display a letter for each drive
					;with open files
	mov cx,16			;loop count - 16 bits to check
	mov al,'A'			;A through P
	mov ah,6			;count of drive letters displayed
	mov dx,open_vec			;BDOS sets this vector in SYSDAT
	mov di,offset smsg_openvec

open_nxt:
	shr dx,1			;lowest bit is A drive, highest is P
	jnc s_open_nxt			;no carry then no open files
	  stosb				;store letter, incr DI
	  dec ah 			;count letters displayed, 6 max
	  jz s_capslock			;used up drive display field
s_open_nxt:
	inc al				;next letter
	loop open_nxt			;tested all 16 drive bits ?

s_capslock:
	mov bx,foreground_ss
	mov al,ss_mode[bx]
	test al,ssm_capslock ! jz s_numlock
	  mov si,offset capstr
	  mov di,offset smsg_capslock
	  mov cx,length capstr
	  rep movsb

s_numlock:
	test al,ssm_numlock ! jz s_wrap
	  mov si,offset numstr
	  mov di,offset smsg_numlock
	  mov cx,length numstr
	  rep movsb

s_wrap:					;BX=foreground_ss
	test al,ssm_no_wrap		;if bit is set leave WRAP
	jnz s_display			;field blank
	  mov si,offset wrpstr
	  mov di,offset smsg_wrap
	  mov cx,length wrpstr
	  rep movsb

s_display:
	mov si,offset status_msg_start
	push ds				;save SYSDAT
s_prt:
	mov di,bw_video_status_line
	mov ax,bw_video_seg
	mov es,ax
	mov ah,09h			;underlined and enhanced attribute
	mov cx,columns_per_screen
	
s_dloop:
	lodsb				;get character
	stosw				;display character and attribute
	loop s_dloop
	pop ds				;restore SYSDAT
	xor ax,ax
s_exit:
	pop es				;restore UDA
	mov sts_locked,false
	ret

;************************************************************************
;*									*
;*			List Device Routines				*
;*									*
;************************************************************************

;=========
io_listst:				;list devices are on poll table
;=========
;	entry:	DL =	device
;	exit:	AL =	0 if not ready
;			0ffh if ready
;		BX =	device# * 2
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

	xor dh,dh ! shl dx,1
	mov bx,dx			;device # * 2
	;jmps listst

listst:					;called from IO_POLL:
;------					;and IO_LISTST: above
;	entry:	BX = device# * 2
;		DL = device#
;	exit:	AL = 0 if not ready
;			0ffh if ready
;		BX = device# * 2
;		DL presereved

	push dx				;save device #
	xor cx,cx			;CX=0
	mov dx,list_ports[bx]		;port for list device 0
					;parallel status
	inc dx ! in al,dx		;DX=status register, get status
	test al,80h
	jz par_ret
	  dec cx			;ready: CX=0ffffh

par_ret:
	mov ax,cx ! pop dx
	ret	

;=======
io_list:
;=======
;	entry:	CL = character
;		DL = device
;	exit:	character sent
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

	push cx ! push dx		;save the character and device
	call poll			;wait for device to be ready
	pop dx ! pop cx			;device and character
					;BX=device# * 2
	mov al,cl			;AL = character
	mov dx,list_ports[bx]		;get the port for this device
	test dx,dx			;0 if no such device
	jz lst_done
	  out dx,al			;send character
	  inc dx ! inc dx		;reset it
	  mov al,0dh			;printer strobe on
	  out dx,al
	  mov al,0ch			;printer strobe off 
	  out dx,al
lst_done:
	ret

;************************************************************************
;*									*
;*			IO_AUXIN and IO_AUXOUT				*
;*									*
;************************************************************************

;========
io_auxin:
;========
;	entry:	none
;	exit:	AL = character input
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

	ret

;=========
io_auxout:
;=========
;	entry:	CL character to output
;	exit:	none
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

	ret

eject
;			Character I/O Data
;			------------------

;************************************************************************
;*									*
;*			IO_CONIN DATA					*
;*									*
;************************************************************************

SL15	EQU	offset $
DSEG
ORG	SL15

scan_code	db	0		;code returned by keyboard

down_bits	db	0		;bit vector of
					;action keys currently pressed
					;bits representing toggle keys-
					;numlock and capslock-are
					;kept in the screen structures
;	Action key scan codes

ctrl		equ	29		;live action keys
shft_left	equ	42
shft_right	equ	54
alt		equ	56		;keys greater than ALT are
capslock	equ	58		;toggle action keys
numlock		equ	69

;	Bit masks for special function keys,
;	these values are masked into the DOWN_BITS bytes.

ctrl_bit	equ	001h
shft_bit	equ	002h
alt_bit		equ	008h

num_action_keys	equ	6		;number of entries in table
					;below
action_key_table:
	db	ctrl			;action key look up table
	db	shft_right
	db	shft_left
	db	alt
	db	numlock
	db	capslock
		
action_key_masks:			;look up table for mask bits
	db	ctrl_bit		;to use in DOWN_BITS or
	db	shft_bit		;the SS_MODE byte in the
	db	shft_bit		;foreground screen structure
	db	alt_bit
	db	ssm_numlock		;see screen structure definition
	db	ssm_capslock		; "

pfk_id_table	db	';<=>?@abcdghikmopqrs'

;************************************************************************
;*									*
;*		Keyboard Translation Tables				*
;*									*
;************************************************************************

;	Three keyboard translation tables follow.  The IBM PC returns
;	a "scan code" from the keyboard which is used as an index into
;	the following tables.
;	KEY_TABLE contains ASCII for keys that have no other keys held down
;	simultaneously.  The SHIFT_TABLE is for keys depressed when
;	the shift, capslocks, or numslock keys are also down
;	The CTRL_TABLE is for keys depressed when the CTRL
;	is down.

;	0FFH in the translation table designates an illegal key code.
;	The most significant bit is set for keys that are programmable
;	or are fixed function keys, and also for the switch screen keys.

key_table:
	;	translation		keyboard scan code
	;	-----------		------------------

	db	0ffH			;0 - doesn't exist
	db	esc			;1
	db	'1234567890-='		;2-13 (1st row)
	db	bs,ht			;14-15 (backspace, horizontal tab)
	db	'qwertyuiop[]'		;16-27 (2nd row)
	db	cr, 0ffh		;28-29 (carriage return)
	db	'asdfghjkl;''`'		;30-41 (3rd row)
	db	0ffh			;42 (left shift)
	db	'\zxcvbnm,./'		;43-53 (4th row)
	db	0ffh			;54 (right shift)
	db	'*'			;55
	db	0ffh			;56 (alt)
	db	' '			;57 (space bar)
	db	0ffh			;58 (caps lock)

					;-function keys-
					;-programmable-
	db	80h,81h,82h,83h		;59-62 (fucntion keys f1,f2,f3,f4)
	db	84h,85h,86h,87h		;63-66 (function keys f5,f6,f7,f8)
	db	88h,89h			;67-68 (function keys f9,f10)

	db	0ffh			;69 (num lock)
	db	dc3			;70 (scroll lock)

					;- key pad -
					;-programmable-
	db	8ah			;71 (home)
	db	8bh			;72 (up arrow)
	db	8ch			;73 (Pg Up)
	db	'-'			;74
	db	8dh			;75 (left arrow)
	db	0ffh			;76
	db	8eh			;77 (right arror)
	db	'+'			;78
	db	8fh			;79 (end)
	db	90h			;80 (down arrow)
	db	91h			;81 (Pg Dn)
	db	92h			;82 (Ins)
	db	7fh			;83 (Del)

shift_table:
	;	translation		keyboard scan code
	;	-----------		------------------

	db	0ffH			;0 - doesn't exist
	db	esc			;1
	db	'!@#$%^&*()_+'		;2-13 (1st row)
	db	bs,ff			;14-15 (backspace, form feed)
	db	'QWERTYUIOP{}'		;16-27 (2nd row)
	db	cr			;28 (carriage return)
	db	0ffh			;29 (ctrl)
	db	'ASDFGHJKL:"~'		;30-41 (3rd row)
	db	0ffh			;42 (left shift)
	db	'|ZXCVBNM<>?'		;43-53 (4th row)
	db	0ffh			;54 (right shift)
	db	dle			;55 (Prt Sc - ^P)
	db	0ffh			;56 (Alt)
	db	' '			;57 (space bar)
	db	0ffh			;58 (Caps Lock)

					;-function keys-
					;-return fixed strings-
	db	203,204,205,206		;59-62 (f1,f2,f3,f4)
	db	207,208,209,210		;63-67 (f5,f6,f7,f8)
	db	211,212			;67-68 (f9,f10)
	db	0ffh			;69 (Num Lock)
	db	dc1			;70 (Scroll Lock - ^Q)

					;-key pad-
	db	'789-456+1230.'		;71-83 (ascii values on keys)

control_table:
	;	translation		keyboard scan code
	;	-----------		------------------

	db	0ffh			;0 (no such key code)
	db	ESC			;1
	db	0ffh			;2
	db	NUL			;3 (ctrl @)
	db	0ffh,0ffh,0ffh		;4-6
	db	RDS			;7 (ctrl ^)
	db	0ffh,0ffh,0ffh,0ffh	;8-11
	db	US			;12 (ctrl _)
	db	0ffh			;13
	db	DEL			;14 (left arrow)
	db	0ffh			;15
	db	DC1,ETB,ENQ,DC2		;16-19 (ctrl q,w,e,r)
	db	DC4,EM,NAK,HT		;20-23 (ctrl t,y,u,i)
	db	SHI,DLE,ESC,GS		;24-27 (ctrl o,p,[,])
	db	cr			;28 (ctrl carriage return)
	db	0ffh			;29
	db	SOH,DC3,EOT,ACK		;30-33 (ctrl a,s,d,f)
	db	BEL,BS,LF,VT		;34-37 (ctrl g,h,j,k)
	db	FF			;38 (ctrl l)
	db	0ffh,0ffh,0ffh,0ffh	;39-42
	db	FS,SUBB,CAN,ETX		;43-47 (ctrl \,z,x,c)
	db	SYN,STX,SO,CR		;47-50 (ctrl v,b,n,m)
	db	0ffh,0ffh,0ffh,0ffh	;51-54
	db	DLE			;55 (Prt Sc = ctrl P)
	db	0ffh,' ',0ffh		;56-58

					;-function keys
	db	193,194,195,196		;59-62 (f1,f2,f3,f4)
	db	197,198,199,200		;63-66 (f5,f6,f7,f8)
	db	201,202			;66-68 (f9,f10)
	db	0ffh			;69
	db	ETX			;70 break (^scroll lock = ctrl c)

					;-key pad-
					;-returns fixed escape sequences-
	db	247,248,249,0ffh	;7,8,9,-
	db	244,245,246,0ffh	;4,5,6,+
	db	241,242,243		;1,2,3
	db	240,0ffh		;0,.

;************************************************************************
;*									*
;*		Special Output Characters Tables			*
;*									*
;************************************************************************

special_char_tab	db	4		;number of special chars
			db	cr,lf,bs,esc	;the special chars

special_func_tab	dw	carriage_return	;the special char routines
			dw	line_feed
			dw	back_space
			dw	escape

;************************************************************************
;*									*
;*		Escape Sequence Tables					*
;*									*
;************************************************************************

esc_tab1	db	28		;number of escape sequences
						;supported
		db	'H','C','D','B','A'	;the second char
		db	'I','j','k','Y','E'	;in the escape
		db	'b','J','l','o','K'	;sequence
		db	'L','M','N','p','q'
		db	'r','u','s','t','e'
		db	'f','v','w'
						;the escape sequence
						;routines
esc_func_tab1	dw	home		;H	
		dw	right		;C
		dw	left		;D
		dw	down		;B
		dw	up		;A

		dw	up_with_scroll	;I
		dw	save		;j
		dw	restore		;k
		dw	x_and_y		;Y - row and column
		dw	erase_screen	;E

		dw	erase_begin	;b
		dw	erase_end	;J
		dw	erase_line	;l
		dw	erase_bol	;o - BOL = begin of line
		dw	erase_eol	;K - EOL = end of line

		dw	insert_line	;L
		dw	delete_line	;M
		dw	delete_char	;N
		dw	enter_reverse	;p
		dw	exit_reverse	;q

		dw	enter_bright	;r
		dw	exit_bright	;u
		dw	enter_blink	;s
		dw	exit_blink	;t
		dw	enable_cursor	;e

		dw	disable_cursor	;f
		dw	enable_wrap	;v
		dw	disable_wrap	;w

;************************************************************************
;*									*
;*			Screen Structures				*
;*									*
;************************************************************************

;	Each virtual console has a structure of the following
;	format associated with it.  (SS = Screen Structure)
;	The data in this structure is dependent on the type of screen
;	supported and any escape sequence handling in IO_CONOUT.
;	Note: SS_CURSOR is the byte offset of the cursor relative
;	to the screen segment.  SS_ROW, SS_COLUMN are relative to 0
;	and are	word offsets, i.e., if SS_ROW is 0 and SS_COLUMN is 1,
;	they refer to bytes 2 and 3 in the video RAM or a background
;	screen's data area.

ss_cursor	equ	word ptr 0			;points at data/attrib
ss_oldcursor	equ	word ptr ss_cursor + word
ss_escape	equ	word ptr ss_oldcursor + word	;escape routine to return to
ss_screen_seg	equ	word ptr ss_escape + word	;data for screen image
ss_xy		equ	word ptr ss_screen_seg + word	;overlay row, col
ss_row		equ	byte ptr ss_xy			;current row
ss_column	equ	byte ptr ss_row + byte		;current col
ss_oldxy	equ	word ptr ss_xy + word		;overlay old row, col
ss_oldrow	equ	byte ptr ss_oldxy		;old row
ss_oldcolumn	equ	byte ptr ss_oldxy + byte	;old col
ss_attribute	equ	byte ptr ss_oldxy + word
ss_mode		equ	byte ptr ss_attribute + byte
ss_len		equ	ss_mode + byte

;	ss_mode byte bit values

ssm_reverse	equ	00000001B	;1 = if reverse video
					;0 = in normal
ssm_no_wrap	equ	00000010B	;0 = wrap at EOL, 1 = discard
ssm_no_cursor	equ	00000100B	;0 = cursor, 1 = cursor
ssm_log		equ	00001000B	;0 = normal, 1 = data log
ssm_numlock	equ	00010000B	;1 = numlock on
ssm_capslock	equ	00100000B	;1 = capslock on

screen_structures	rb	0

ss0		dw	0,0	;cursor, old cursor
		dw	offset co_no_escape
				;initially not in an escape sequence
		dw	0	;screen_seg - set by INIT:
		db	0,0	;row,column
		db	0,0	;old row, old column
		db	07h	;attribute
		db	0	;mode - initialize wrap,cursor,reverse off,
				;no data logging

ss1		dw	0,0	;cursor, old cursor
		dw	offset co_no_escape
				;initially not in an escape sequence
		dw	0	;screen_seg - set by INIT:
		db	0,0	;row,column
		db	0,0	;old row, old column
		db	07h	;attribute
		db	0	;mode - initialize wrap,cursor,reverse off,
				;no data logging

ss2		dw	0,0	;cursor, old cursor
		dw	offset co_no_escape
				;initially not in an escape sequence
		dw	0	;screen_seg - set by INIT:
		db	0,0	;row,column
		db	0,0	;old row, old column
		db	07h	;attribute
		db	0	;mode - initialize wrap,cursor,reverse off
				;no data logging

ss3		dw	0,0	;cursor, old cursor
		dw	offset co_no_escape
				;initially not in an escape sequence
		dw	0	;screen_seg - set by INIT:
		db	0,0	;row,column
		db	0,0	;old row, old column
		db	07h	;attribute
		db	0	;mode - initialize wrap,cursor,reverse off
				;no data logging

screen_struct_addrs	dw	offset ss0
			dw	offset ss1
			dw	offset ss2
			dw	offset ss3

;************************************************************************
;*									*
;*		Console Control Blocks					*
;*									*
;************************************************************************

foreground_screen	db	0		;console 0 is initial
foreground_ccb		dw	offset ccb0	;foreground console
foreground_ss		dw	offset ss0

ccb_tab		rb	0
ccb0		dw	0		;owner
		dw	0,0,0
		db	0ffh, 0ffh	;mimic, msource
		db	0
		db	0		;virtual console number
		dw	0
		dw	0		;foreground and dynamic
					;be the foreground console
		dw	10h		;max buffer file size
                dw      0,0,0,0,0,0
                dw      0,0,0,0,0,0
		dw	0

ccb1		dw	0		;owner
		dw	0,0,0
		db	0ffh, 0ffh	;mimic, msource
		db	0
		db	1		;virtual console number
		dw	0
		dw	csm_background	;background and dynamic
		dw	10h		;max buffer file size
                dw      0,0,0,0,0,0
                dw      0,0,0,0,0,0
		dw	0

ccb2		dw	0		;owner
		dw	0,0,0
		db	0ffh, 0ffh	;mimic, msource
		db	0
		db	2		;virtual console number
		dw	0
		dw	csm_background	;background and dynamic
		dw	10h		;max buffer file size
                dw      0,0,0,0,0,0
                dw      0,0,0,0,0,0
		dw	0

ccb3		dw	0		;owner
		dw	0,0,0
		db	0ffh, 0ffh	;mimic, msource
		db	0
		db	3		;virtual console number
		dw	0
		dw	csm_background	;background and dynamic
		dw	10h		;max buffer file size
                dw      0,0,0,0,0,0
                dw      0,0,0,0,0,0
		dw	0

					;non virtual console CCB
;ccb4		dw	0		;would look like this:
;		dw	0,0,0
;		db	0
;		db	0
;		db	0
;		dw	0
;		dw	0
;		dw	0
;               dw      0,0,0,0,0,0
;               dw      0,0,0,0,0,0
;		dw	0

;************************************************************************
;*									*
;*			Status Line Data				*
;*									*
;************************************************************************

sts_locked	db	false	;semaphore for status line code
sts_special	db	false	;is a special status line being displayed ?

;	The format of the status line is:

;Console=1 Buffered GENCCPM  ABCDEF ^S^P=0 Printer=2 CapsLock NumLock Wrap
;                                   ^O

status_msg_start	rb	0
smsg_con		rb	8		;0-7 (Console=)
smsg_cnum		rb	1		;8
			db	' '		;9
smsg_mode		rb	8		;10-17
			db	' '		;18
smsg_pd			rb	8		;19-26
			db	' '		;27
smsg_openvec		rb	6		;28-33
			db	' '		;34
smsg_ctrlS		rw	1		;35-36
smsg_ctrlO		rw	0		;overlays ctrlS
			db	' '		;37
smsg_ctrlP		rw	1		;38-39
smsg_ctrlP_num		rw	1		;40-41
			db	' '		;42
smsg_prn		rb	8		;43-50 (Printer=)
smsg_pnum		rb	1		;51
			db	' '		;52
smsg_capslock		rb	8		;53-60
			db	' '		;61
smsg_numlock		rb	7		;62-68
			db	' '		;69
smsg_wrap		rb	4		;70-73
			rb	6		;74-79

;	String constants for status line

dynstr			db	'Dynamic '	;these
bufstr			db	'Buffered'	;messages must
purstr			db	'Purging '	;be the same length
nosstr			db	'NoSwitch'

constr			db	'Console='
prnstr			db	'Printer='
capstr			db	'CapsLock'
numstr			db	'NumLock'
wrpstr			db	'Wrap'


;************************************************************************
;*									*
;*			List Control Blocks				*
;*									*
;************************************************************************

lcb_tab		rb	0
lcb0		dw	0,0,0,0		;each LCB is 10 bytes long
		db	0
		db	0ffh		;msource
lcb1		dw	0,0,0,0
		db	0
		db	0ffh		;msource

eject
;			DISK I/O
;			--------

;************************************************************************
;*									*
;*			IBM PC Disk Equates				*
;*									*
;************************************************************************

;	The following equates are set to the size of a double density,
;	single sided 5 & 1/4" floppy.

bytes_per_sector	equ	512
sectors_per_track	equ	8		;1 to 8
bytes_per_track		equ	sectors_per_track * bytes_per_sector
tracks_per_disk		equ	40		;0 to 39
bytes_per_disk		equ	tracks_per_disk * bytes_per_track

;	Memory disk support, drive M: is a RAM disk.

m_disk_segment	equ 	0C000h	;segment base of mdisk RAM on IBM PC

;************************************************************************
;*									*
;*		Intel 8272 FDC Equates					*
;*									*
;************************************************************************

fdc_stat		equ	03f4h		;status port for the disk controller
fdc_data		equ	fdc_stat+1	;data port for the disk controller
fdc_port		equ	03f2h		;all bits clear on channel reset

		;7 6 5 4 3 2 1 0
		;| | | | | | \_/
		;| | | | | |  |
		;| | | | | |  drive select 00=a,01=b,10=c,11=d
		;| | | | | fdc reset*
		;| | | | int & dmarq enable
		;d c b a motor on

fdc_on		equ	00001100b	;mask to keep the 8272 unreset
fdc_no_motor	equ	11111100b	;mask for no motors

fdc_read_cmd	equ	01100110b	;mfm, skip deleted data, read
fdc_write_cmd	equ	01000101b	;mfm, write
fdc_format_cmd	equ	01001101b	;mfm, format
fdc_seek_cmd	equ	00001111b	;seek
fdc_recal_cmd	equ	00000111b	;home to track 0
fdc_si_cmd	equ	00001000b	;sense interupt status
fdc_spec_cmd	equ	00000011b	;specify

fdc_ready	equ	10000000b	;mask for transfer ready

fdc_spec_1	equ	11001111b	;srt=0c, hd unload=0f first specify byte
fdc_spec_2	equ	00000011b	;hd load=1, mode=DMA second specify byte

f_bytes		equ	2		;magic number for 512 bytes per sector
f_sectors	equ	8		;sectors per track
f_gap		equ	03ah		;magic number for format gap
f_filler	equ	0e5h		;fill character

r_bytes		equ	2		;magic number for 512 bytes
r_sectors	equ	8
r_gap		equ	02ah
r_dtl		equ	0ffh

;************************************************************************
;*									*
;*		8237 DMA Controller Port and Commands			*
;*									*
;************************************************************************

dma_c0_address	equ	000h		;8237 channel 0 address		rw
dma_c0_count	equ	001h		;8237 channel 0 transfer count	rw
dma_c1_address	equ	002h		;8237 channel 1 address		rw
dma_c1_count	equ	003h		;8237 channel 1 transfer count	rw
dma_c2_address	equ	004h		;8237 channel 2 address		rw
dma_c2_count	equ	005h		;8237 channel 2 transfer count	rw
dma_c3_address	equ	006h		;8237 channel 3 address		rw
dma_c3_count	equ	007h		;8237 channel 3 transfer count	rw
dma_stat_reg	equ	008h		;8237 status register		ro
dma_cmd_reg	equ	dma_stat_reg	;8237 command register		wo
dma_requ_reg	equ	dma_stat_reg+1	;8237 software dma request	wo
dma_bmsk_reg	equ	dma_stat_reg+2	;8237 binary channel mask	wo
dma_mode_reg	equ	dma_stat_reg+3	;8237 mode register		wo
dma_cbpf	equ	dma_stat_reg+4	;8237 clear byte pointer f/f	wo
dma_temp_reg	equ	dma_stat_reg+5	;8237 temporary register	ro
dma_clear	equ	dma_stat_reg+5	;8237 master clear		wo
dma_mask_reg	equ	dma_stat_reg+7	;8237 linear channel mask	wo

dma_page_c1	equ	080h		;a16 to a20 for channel 1
dma_page_fdc	equ	081h		;a16 to a20 for channel 2
dma_page_c3	equ	082h		;a16 to a20 for channel 3

;	The following labels define single mode, address increment
;	auto-initialization disable, read or write using channel 2

dma_mode_write_fdc	equ	01001010b
dma_mode_read_fdc	equ	01000110b

dma_bmsk_fdc		equ	00000010b	;binary channel mask for disk

;	DMA channel assignments

;channel 0 dynamic memory refresh
;channel 1
;channel 2 floppy disk controller
;channel 3

;************************************************************************
;*									*
;*		CCP/M Disk I/O Equates					*
;*									*
;************************************************************************

;	Equates for parameter passing for read and write
;	requests from the BDOS.

;	At the disk read and write function entries,
;	all disk I/O parameters are on the stack.
;	The stack at these entries appears as
;	follows:
;
;           +-------+-------+
;       +14 |  DRV  | MCNT  |   Drive and Multi sector count
;           +-------+-------+
;       +12 |     TRACK     |   Track number
;           +-------+-------+
;       +10 |    SECTOR     |   Physical sector number
;           +-------+-------+
;        +8 |    DMA_SEG    |   DMA segment 
;           +-------+-------+
;        +6 |    DMA_OFF    |   DMA offset
;           +-------+-------+
;        +4 |    RET_SEG    |   BDOS return segment
;           +-------+-------+
;        +2 |    RET_OFF    |   BDOS return offset
;           +-------+-------+
;      SP+0 |    RET_ADR    |   Return address to XIOS ENTRY routine
;           +-------+-------+
;
;	These parameters may be indexed and modifided
;	directly on the stack by the XIOS read and write rotines
;	They will be removed by the BDOS when the XIOS completes
;	the read/write function and returns to the BDOS.

drive	equ	byte ptr 14[bp]
mcnt	equ	byte ptr 15[bp]
track	equ	word ptr 12[bp]
sector	equ	word ptr 10[bp]
dma_seg	equ	word ptr 8[bp]
dma_off	equ	word ptr 6[bp]

;	Some equtes in the Disk Parameter Header (DPH)
;	and the Disk Parameter Block.

xlt	equ	0	;translation table offset in DPH
dpb	equ	8	;disk parameter block offset in DPH
spt	equ	0	;sectors per track offset in DPB
psh	equ	15	;physical shift factor offset in DPB

;************************************************************************
;*									*
;*				IO_SELDSK				*
;*									*
;************************************************************************

SL16	equ	offset $
CSEG
ORG	SL16

;=========
io_seldsk:	    ; Function 7:  Select Disk
;=========
;	entry:	CL = disk to be selected
;		DL = 00h if disk has not been previously selected
;		   = 01h if disk has been previously selected
;	exit:	AX = 0 if illegal disk
;		   = offset of DPH relative from
;			XIOS Data Segment
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

	xor bx,bx			;get ready for error
	cmp cl,15			;is it a valid drive ?
	ja sel_ret			;if not just exit
	  mov bl,cl
	  shl bx,1			;index into the DPH's
	  mov ax,dph_tbl[bx]		;get DPH address
sel_ret:
	ret

;************************************************************************
;*									*
;*				IO_READ					*
;*									*
;************************************************************************

;=======
io_read:	    ; Function 11: Read sector
;=======
; Reads the sector on the current disk, track and
; sector into the current DMA buffer.

;	entry:	parameters on stack
;	exit:	AL = 00	if no error occured
;		AL = 01 if an error occured
;		AL = 0ffh if density change detected
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

	mov bp,sp
	mov al,drive
	xor ah,ah			;index into the physical driver
	mov si,ax
	shl si,1
	jmp read_tbl[si]		;jump to physical driver read routine

read_m_disk:	
;-----------
	call mdisk_calc			;calculate byte address;
	push es				;save UDA
	les di,dword ptr dmaoff 	;load destination DMA offset
					;and segment
	xor si,si			;setup source DMA address
	push ds				;save current DS
	mov ds,bx			;load pointer to sector in memory
	rep movsw			;execute move of 128 bytes....
	pop ds				;then restore user DS register
	pop es				;restore UDA
	xor ax,ax			;return with good return code
	ret

read_sd_floppy:
;--------------
	mov dma_mode_storage,dma_mode_read_fdc
	mov flp_rw_command,fdc_read_cmd
					;set up for read before going
	jmp flp_io			;to common read/write code

mdisk_calc:
;----------
;	exit:	BX = sector paragraph address
;		CX = length in words to transfer
;	Assume MDISK DPB describes a disk with a physical
;	sector size of 128, 8 sectors to a 1K track.
;	Avoid deblocking by setting the logical sector size (128)
;	equal to the physical sector size.

	mov bx,track			;pickup track number
	mov cl,3			;times eight for relative sector
					;number
	shl bx,cl
	mov cx,sector			;plus sector
	add bx,cx			;gives relative sector number
	mov cl,3			;times eight for paragraph of
					;sector start
	shl bx,cl
	add bx,m_disk_segment		;plus base address of disk in memory
	mov al,mcnt
	xor ah,ah			;multiply by 64, length of sector
	mov cl,6			;in words
	shl ax,cl			;length * multi sector count
	mov cx,ax
	cld
	ret

;************************************************************************
;*									*
;*				IO_WRITE				*
;*									*
;************************************************************************

;========
io_write:		; Function 12: Write disk
;========
; Write the sector in the current Dma buffer to the current disk on the current
; track in the current sector.

;	entry:	CL = 0 - Defered Writes
;		CL = 1 - non-defered writes
;		CL = 2 - def-wrt 1st sect unalloc blk
;	exit:	AL = 00H if no error occured
;		AL = 01H if error occured
;		AL = 02H if read only disk
;		AL = 0ffh if density change detected
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call
	
	mov bp,sp
	mov al,drive
	xor ah,ah			;index into the physical driver
	mov si,ax
	shl si,1
	jmp write_tbl[si]		;jump to physical driver write routine

write_m_disk:
;------------
	call mdisk_calc			;calculate byte address
	push es				;save UDA
	mov es,bx			;setup destination DMA segment
	xor di,di			;destination offset
	push ds				;save user segment register
	lds si,dword ptr dmaoff 	;load source DMA offset
					;and segment
	rep movsw			;move from user to disk in memory
	pop ds				;restore user segment pointer
	pop es				;restore UDA
	xor ax,ax			;return no error
	ret

write_sd_floppy:
;---------------
	mov dma_mode_storage,dma_mode_write_fdc
	mov flp_rw_command,fdc_write_cmd
	;jmps flp_io			;set up for write before
					;going to common read/write code

flp_io:
;------
;	entry:	parameters on stack
;	exit:	zero flag set if successful

;	Common code for floppy read and write
;	Note the parameters on the stack, SECTOR, TRACK are
;	0 relative, and MCNT, SECTORS_PER_TRACK are relative to 1

	mov disk_busy,true		;keep I_TICK from turning off motor
	mov ax,sectors_per_track	;max sectors - starting sector =
	sub ax,sector			;sectors left on track
	xor bh,bh
	mov bl,mcnt			;multi sector count is byte variable
	cmp ax,bx			;sectors left on track, sectors
					;requested 
	jbe flp_track			;transfer to end of track 
	  mov al,mcnt			;transfer to before end of track

flp_track:
	sub mcnt,al			;AL = # sectors to R/W on this
					;iteration
					;through FLP_IO,
	mov track_mcnt,al		;sectors to R/W on current track 
				
					;check for 64K page overlap
	mov ah,al			;shl al,8 (* 256)
	xor al,al
	shl ah,1			;* 512
	push ax				;how many bytes to R/W on cur trk

	mov ax,dma_seg			;compute new 20 bit DMA addr
	mov bx,dma_off
	call comp_dma			;returns AX = low 16 bits of 20 bit adr
	not ax				;how many bytes left in this 64K page

	pop bx				;BX=bytes to R/W on current track
	cmp ax,bx			;does this transfer fit in 64K page
	jb flp_end_64K
	  jmp flp_pg_ok

flp_end_64K:
					;read to end of 64K page and then
					;read spanning sector locally
	  mov al,ah			;how many sectors fit in this page ?
	  xor ah,ah			;bytes left in 64K page
	  shr ax,1			;divided by 512
	  test ax,ax			;if 0, no sectors fit
	  jz flp_rwlocal		;in the rest of this 64K page

	    mov num_sec,al		;sectors that fit in end 64K page
	    sub track_mcnt,al		;track_mcnt always > AL
	    call flp_phys_io		;read/write them, DMA already computed
	    jz flp_end64k_ok
	      jmp flp_ret		;zero flag is reset for error return

flp_end64K_ok:
	      xor ax,ax
	      mov al,num_sec		;compute new DMA offset
	      add sector,ax		;still on the same track
	      xchg ah,al		;shl ax,8 (* 256)
	      shl ah,1			;* bytes_per_sector (512)
	      add dma_off,ax		;64K wrap around is legal
	    
flp_rwlocal:				;read into XIOS data segment
					;the spanning
	    mov bx,offset local_buf	;sector
	    mov ax,ds			;compute 20 bit local DMA addr
	    call comp_dma
	    cmp flp_rw_command,fdc_read_cmd
					;reading or writing ?
	    je flp_local
	      mov si,dma_off		;get the sector to write from local
	      mov di,offset local_buf	;buffer
	      push es ! push ds ! pop es
	      mov ax,dma_seg ! mov ds,ax
	      mov cx,bytes_per_sector/2
	      rep movsw
	      pop es ! mov ds,sysdat
	      mov dma_off,si		;update DMA offset

flp_local:
	    mov num_sec,1		;read/write one sector
	    call flp_phys_io		;XIOS data cannot span 64K page
	    jnz flp_ret			;return error
	      inc sector ! dec track_mcnt

	      cmp flp_rw_command,fdc_read_cmd
	      jne flp_local_done	;reading or writing ?
	        mov di,dma_off		;move the sector to user's area
	        mov si,offset local_buf	;if reading
	        push es ! mov es,dma_seg
	        mov cx,bytes_per_sector/2
	        rep movsw
	        mov dma_off,di		;update DMA offset
	        pop es

flp_local_done:
	      mov ax,dma_seg		;compute new 20 bit DMA addr
	      mov bx,dma_off		;for next FDC read/write
	      call comp_dma

flp_pg_ok:				;read will not cross 64K boundary
	mov al,track_mcnt		;could be 0 if we just read locally
	test al,al ! jz nxt_track
	  mov num_sec,al		;read the rest from this track
	  call flp_phys_io		;DMA is already computed
	  jnz flp_ret			;return error, zero flag reset
	    xor ax,ax
	    mov ah,num_sec		;shl num_sec,8 (* 256)
	    shl ah,1			;* 512
	    add dma_off,ax

nxt_track:
	xor al,al
	cmp mcnt,al
	jz flp_ret			;sucessful return with zero flag on
	  inc track
	  mov sector,0
	  jmp flp_io			;more IO to do on next track
 
flp_ret:
	mov disk_busy,false
	ret

comp_dma:	;Compute 20 bit address from offset, segment
;--------

;	entry:	AX =	segment
;		BX =	offset
;	exit:	AX =	low 16 bits
;		CH =	highest 4 bits of address, always less then 16 -
;			no megabyte wrap around
;
;		The XIOS variables DMA_LOW16 and DMA_HIGH4 are
;		set by this routine.  These variables are transferred
;		to the floppy disk controller by the routine DMA_SET_UP.

	mov cl,4 ! rol ax,cl		;make paragraphs into bytes
	mov ch,al ! and al,0f0h		;save high 4 bits, 0 low 4 bits
	add ax,bx			;add byte offset
	adc ch,0 ! and ch,0fh		;add in the carry, page is less than
	mov dma_low16,ax		;16
	mov dma_high4,ch
	ret

flp_phys_io:
;-----------
;	entry:	num_sec = number of sectors to read in this
;		operation
;		disk parameters on the stack
;	exit:	zero flag set if ok

;	Perform physical actions to read/write floppy diskette

	mov d_a_number,r_bytes
	mov d_a_eot,r_sectors
	mov d_a_gpl,r_gap
	mov d_a_dtl,r_dtl
	call motor_on			;make sure motor is on
	mov recals,recal_max
recal_loop:
	mov retries,retry_max
retry_loop:
	call seek
	call dma_setup
	call fdc_read_write
	jz phys_ret			;if errors 
	  dec retries			;attempt retries
	  jnz retry_loop
	    call recal			;if retries fail
	    dec recals			;recalabrate the drive
	    jnz recal_loop
	      or al,1			;give up and return error
phys_ret:
	ret

motor_on:
;--------
;	entry:	none
;	exit:	none

;	Turn on the motor if it is off.

	mov motor_off_counter,0ffh	;set for I_TICK timeout 
	mov al,010h			;pick up a bit for motor a
	mov cl,drive			;fetch the binary drive code
	shl al,cl			;make it a bit for motor x
	mov ah,motor_flags		;fetch the motor bits
	test ah,al			;check to see if its on
	jnz motor_on_done		;yes then leap
	  or al,fdc_on			;mask in the no reset,enable interupt
	  or al,drive			;mask in the drive
	  mov motor_flags,al		;save for later
	  mov dx,fdc_port		;point to the port number
	  out dx,al			;select & motor on
	  cmp flp_rw_command,fdc_read_cmd
	  je motor_on_done
	    mov dx,ticks_per_second/8	;wait for motor on 1/8 second
	    mov cl,p_delay		;when writing
	    call supif	

					;When initially debugging
;	  xor cx,cx			;do not use the P_DELAY function
;m_wait:				;until I_TICK is implemented;
;	  loop m_wait			;use a delay loop like this.

motor_on_done:
	ret

recal:
;-----
;	entry:	none
;	exit:	none

;	Move the head to home on the selected disk drive.

	mov disk_arguments,2		;specify number of arguments
	mov d_a_command,fdc_recal_cmd
	mov al,drive			;get current disk
	mov d_a_drive,al		;set up the command block
	call fdc_command_put		;go send the command block
	mov dx,fdc_flag
	call waitflag			;wait for FDC interrupt
rc_end:
	jmp sense_interrupt		;required after RECAL command
	;ret

seek:
;----
;	entry:	TRACK to seek to on the stack relative to BP
;	exit:	none

	mov disk_arguments,3		;request 3 byte command transfer
	mov d_a_command,fdc_seek_cmd
	xor bl,bl			;select head 0
	mov ax,track			;get track off stack

					;code for double sided diskettes
					;would go here ...
;	cmp al,40			;test for off side 1
;	jb side_ok			;if single sided then done
;	  mov bl,1			;else specify head 1
;	  mov ah,79			;compute track on second side
;	  sub ah,al
;	  xchg ah,al
					
side_ok:
	mov d_a_head,bl			;set up the head
	add bl,bl			;multiply by 2
	add bl,bl			;multiply by 4
	mov d_a_cylinder,al		;set up the track number
	or bl,drive			;bits 0,1 are drive number
	mov d_a_drive,bl		;set up disk number
	mov ax,sector			;get sector off stack
	inc ax				;increment it
	mov d_a_record,al		;and set it up
	add al,num_sec			;compute new end of track
	dec al
	mov d_a_eot,al			;put last sector in params
	call fdc_command_put		;send the command block
	mov dx,fdc_flag
	call waitflag			;wait for FDC interrupt

st_end:
	jmp sense_interrupt		;required after SEEK command
	;ret

dma_setup:
;---------
;	entry:	DMA_MODE_STORAGE, DMA_LOW16, DMA_HIGH4 set up
;	exit:   none

;	Set the DMA device up for a read/write operation.
;	The current DMA command word must in DMA_MODE_STORAGE.
;	DMA_LOW16 and DMA_HIGH4 are the twenty bit starting address.
;	The read/write operation cannot cross a physical 64K boundary.

	out dma_cbpf,al			;reset the byte pointer

	mov al,dma_mode_storage		;get the mode byte
	out dma_mode_reg,al		;set the mode
	mov ax,dma_low16		;low 16 bits of 20 bit DMA address
	out dma_c2_address,al		;send low 8 bits
	mov al,ah
	out dma_c2_address,al		;send next 8 bits
	mov al,dma_high4		;high 4 bits of 20 bit DMA address
	out dma_page_fdc,al
	xor ax,ax
	mov ah,num_sec			;shl num_sec,8 (* 256)
	shl ah,1			;*512
	dec ax				;0 relative
	out dma_c2_count,al		;set up the low byte
	mov al,ah			;get the low byte
	out dma_c2_count,al		;and the high byte
	mov al,dma_bmsk_fdc		;get the binary channel mask
	out dma_bmsk_reg,al		;enable the disk channel
	ret

fdc_read_write:
;--------------
;	entry:	DMA device set up, head positioned on correct
;		track
;	exit:	zero flag set if successful

;	Send read or write command to 8272 FDC

	mov disk_arguments,9		;9 byte command for read or write
	mov al,flp_rw_command		;get read or write command
	mov d_a_command,al		;put it in the command string
	call fdc_command_put		;send the command to the FDC
	mov dx,fdc_flag
	call waitflag			;waiô foò FDÃ interrupt
	mov disk_results,7		;7 byte result transfer
	call fdc_status_get		;get the result bytes
	test d_r_st0,0C0H		;test status register 0
	ret				;return zero flag set on success

sense_interrupt:
;---------------
;	Sense interrupt command on the FDC.  It is called
;	after a recal or a seek to test for seek complete.

	mov disk_arguments,1		;only one byte of command
	mov d_a_command,fdc_si_cmd	;sense interrupt commmand
	call fdc_command_put		;send the command to the FDC
	mov disk_results,2		;2 bytes are returned
	call fdc_status_get		;get the 2 result bytes
	ret

fdc_command_put:
;---------------
;	entry:	DISK_ARGUMENT array set up
;	exit:	none

;	Send the command block in the DISK_ARGUMENTS table to
;	the 8272 FDC.
;	The number of commands to write to the FDC is the
;	first item in the table.

	mov dx,fdc_stat			;point to the i/o port
	mov si,offset disk_arguments	;point to the table of arguments
	cld				;make sure we go forward
	lodsb				;get the length of the arguments table
	mov cl,al			;get it into the count register
	sub ch,ch			;zero the high byte

fdc_command_loop:
	in al,dx			;get the current control byte
	test al,fdc_ready		;if not ok to send next byte
	jz fdc_command_loop		;then loop waiting
	  inc dx			;point at the data port
	  lodsb				;else get the byte to send
	  out dx,al			;send it
	  dec dx			;point back at the status port
	  loop fdc_command_loop		;if not last byte then loop
	  ret				;else were all done

fdc_status_get:
;--------------
;	entry:	number of results in 1st byte of DISK_RESULTS array
;	exit:	none

;	Get the status information from the 8272
;	FDC and place them in the table at DISK_RESULTS.
;	The first byte in the table is the number of results
;	to read from the FDC

	push es				;save UDA
	mov dx,fdc_stat			;point at the status port
	mov ax,ds			;get our data segment
	mov es,ax			;into the extra segment
	mov di,offset disk_results + 1	;point to where to put the data
	cld 				;make sure we go forward
	mov cl,disk_results		;fetch the number of expected results
	sub ch,ch			;zero the high byte

fdc_status_loop:
	in al,dx			;get the current control byte
	test al,fdc_ready		;if not ok to read next byte
	jz fdc_status_loop		;then loop waiting
	  inc dx			;point at the data port
	  in al,dx			;get the byte
	  stosb				;put it in the structure
	  dec dx			;point back at the status port
	  loop fdc_status_loop		;if not last then loop
	pop es				;restore UDA
	ret				;else return

;************************************************************************
;*									*
;*			IO_FLUSHBUF					*
;*									*
;************************************************************************

;===========
io_flushbuf:			;Flush Buffer
;===========
;	entry:	None
;	exit:	AL = 00h if no error occurs
;		   = 01h if error occurs
;		   = 02h if read/only disk
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call


	xor al,al	;no need to flush buffer with
	ret		;no blocking/deblocking in XIOS

;************************************************************************
;*									*
;*			Disk I/O Data					*
;*									*
;************************************************************************

SL17	equ	offset $
DSEG
ORG	SL17
	
retry_max	equ		5
recal_max	equ		10
retries		db		0
recals		db		0

;	The following tables are used to issue commands to,
;	and read results from the 8272 FDC.  The first entry
;	in each table is the number of bytes to send or receive
;	from the device

disk_arguments	db	0		;number of arguments to send
d_a_command	db	0		;command read/write
d_a_drive	db	0		;drive select & head select
d_a_cylinder	db	0		;cylinder to read/write
d_a_head	db	0		;head
d_a_record	db	0		;sector
d_a_number	db	2		;magic number for 512 bytes/sector 
d_a_eot		db	sectors_per_track	;end of track sector number
d_a_gpl		db	02ah		;inter sector gap length
d_a_dtl		db	0ffh		;data length

disk_results	db	0		;number of bytes to read
d_r_st0		db	0		;status byte 0
d_r_st1		db	0		;status byte 1
d_r_st2		db	0		;status byte 2
d_r_cylinder	db	0		;cylinder we are on now
d_r_head	db	0
d_r_record	db	0
d_r_number	db	0		;number of sectors read

f_a_bytes	db	0
f_a_sectors	db	0
f_a_gap		db	0
f_a_filler	db	0

dma_mode_storage	db	dma_mode_read_fdc	;current DMA mode
flp_rw_command		db	fdc_read_cmd		;reading or writing ?

num_sec			db	1		;num sectors to read/write
						;in one call to FDC

track_mcnt		rb	1		;multi sector count on
						;current track
dma_low16		rw	1		;20 bit address storage
dma_high4		rb	1

motor_flags		db	0		;last state of the motor bits
disk_busy		db	false
motor_off_counter	db	0		;time out counter to
						;turn off motor

;	Sector buffer used by read/write routines when requested
;	multi sector I/O operation crosses a 64K page boundary.
;	This buffer cannot cross 64K page boundary on the IBM PC.

local_buf	rb	bytes_per_sector

;	Jump table for disk I/O read and write routines.
;	Expand for hard disks and other types of floppies.

read_tbl	dw	offset read_sd_floppy
		dw	offset read_sd_floppy
		dw	0,0,0,0		;C,D,E,F
		dw	0,0,0,0		;G,H,I,J
		dw	0,0		;K,L
		dw	offset read_m_disk
		dw	0,0,0		;N,O,P

write_tbl	dw	offset write_sd_floppy
		dw	offset write_sd_floppy
		dw	0,0,0,0		;C,D,E,F
		dw	0,0,0,0		;G,H,I,J
		dw	0,0		;K,L
		dw	offset write_m_disk
		dw	0,0,0		;N,O,P

;	Disk parameter headers

				;Floppy A:
dph_A	dw	xlt0,0000h	;translate table
	dw	0000h,0000h	;scratch area
	dw	dpb_flp		;dsk parm block
	dw	0ffffh		;check
	dw	0ffffh		;alloc vectors
	dw	0ffffh		;dir buff cntrl	blk
	dw	0ffffh		;data buff cntrl blk
	dw	0ffffh		;hash table segment

				;Floppy B:
dph_B	dw	xlt1,0000h	;translate table
	dw	0000h,0000h	;scratch area
	dw	dpb_flp		;dsk parm block
	dw	0ffffh		;check
	dw	0ffffh		;alloc vectors
	dw	0ffffh		;dir buff cntrl	blk
	dw	0ffffh		;data buff cntrl blk
	dw	0ffffh		;hash table segment

				;Mdisk:
dph_M	dw	0000h,0000h	;translate table
	dw	0000h,0000h	;scratch area
	dw	dpb_m		;dsk parm block
	dw	0000h		;check
	dw	0ffffh		;alloc vectors
	dw	0ffffh		;dir buff cntrl	blk
	dw	0		;data buff cntrl blk
	dw	0		;hash table segment

;	Disk Parameter Blocks

				;For both floppy drives
dpb_flp		dw	8	;sectors per track
		db	3	;block shift
		db	7	;block mask
		db	0	;extnt mask
		dw	155	;disk size in 1k blocks
				;less offset track(s)
		dw	63	;directory max
		db	11000000b	;alloc0
		db	0	;alloc1
		dw	16	;check size
		dw	1	;offset
		db	2	;phys sec shift
		db	3	;phys sec mask

				;For Mdisk:
dpb_M		dw	8	;sectors per track
		db	3	;block shift
		db	7	;block mask
		db	0	;extnt mask
dpb_m_dsm	dw	191	;max disk size in 1k blocks
				;less offset track(s)
				;reset set by INIT: after memory test,
				;set max here so GENCCPM
				;can reserve the Allocation Vector
		dw	63	;directory max
		db	11000000b	;alloc0
		db	0	;alloc1
		dw	0	;check size
		dw	0	;offset
		db	0	;phys sec shift
		db	0	;phys sec mask

xlt0	equ	0		;no translate table
xlt1	equ	xlt0		;no translate table

eject
;				Init
;				----

;************************************************************************
;*									*
;*		      XIOS Initialization				*
;*									*
;************************************************************************

;	The following routine is used to initialize any required
;	data areas and alter any peripheral chip programming when
;	starting up CCP/M-86.  This code is called once from the
;	SUP(ERVISOR) after calling the SUP has called the RTM,
;	RTM, CIO, MEM, BDOS initialization routines and before the
;	SUP has created the RSP processes.
;	This code can be placed in an XIOS data area if the XIOS is
;	8080 model (mixed code and data).  Usually, overlaying the
;	initialization code with a data area is done after the XIOS
;	has been debugged.

SL18	equ	offset $
CSEG
ORG	SL18

;====
;====
init:					;arrive here from the JMP
;====					;at 0 in XIOS code segment
;====

	cli ! cld			;Supervisor restores DS,ES and int
					;224 after on INIT call 
	push es				;save the UDA

;	Memory initialization

	int mem_size_int		;get memory size in K bytes
	push ax				;save the memory size
	push ds ! pop es		;ES=SYSDAT
	mov di,offset sign_mem		;fill in sign on message
	mov bl,100
	div bl
	add al,030h			;put in hundreds digit
	stosb
	mov al,ah			;get remainder
	xor ah,ah			;make sure we don't overflow
	mov bl,10
	div bl
	add al,030h			;put in tens digit
	stosb
	mov al,ah			;get remainder
	add al,030h
	stosb				;put in ones digit
	pop ax				;restore memory size in K bytes

;	Trim the Memory Free List to the actual memory size.
;	This code checks the MFL to be in the bounds of
;	the address found by the INT MEMORY_SIZE call.
;	The first Memory Descriptor and all those following
;	MDs that represent partitions that extend past
;	the end of memory, are placed on the Memory Descriptor
;	Unused List.  The O.S. uses MDs from the
;	MDUL when memory is allocated to processes.

;	This code can be expanded to adjust the MD_LENGTH
;	field of the MD that overlaps the end of memory,
;	instead of just placing it on the MDUL and potentially
;	wasting some memory.  However, if the partitions
;	are created by GENCCPM with starting addresses
;	that correspond to the RAM card options there is no
;	waste.  For the IBM PC, partitions starting
;	at 64K and every 32K boundary thereafter will be
;	trimmed by the following code with no wasted RAM.

	mov cl,6			;make number of K into
	shl ax,cl			;number of paragraphs
	mov cx,ax
	mov bx,offset mfl - md_link
next_mfl:
	mov si,bx			;save previous link
	mov bx,md_link[bx]
	test bx,bx			;GENCCPM sorts memory partitions
	jz mfl_done			;in accending order of starting
	  mov ax,md_start[bx]		;paragraph.  Assume memory is
	  add ax,md_length[bx]		;contiguous from 0 on IBM PC.
	  cmp ax,cx			;AX=end of partition, CX=
	  jbe next_mfl			;end of memory
	    mov md_link[si],0		;terminate MFL
	    mov si,bx			;save beginning of severed list
next_mdul:				;recycle MDs from MFL
	    mov di,bx			;save last link
	    mov bx,md_link[bx]		;to Memory Descriptor
	    test bx,bx			;Unused List
	    jnz next_mdul		;look for end
	      mov ax,mdul		;save MDUL list
	      mov mdul,si		;attach trimmed MDs to MDUL
	      mov md_link[di],ax	;re-attach original MDUL to
mfl_done:				;new end of MDUL

;	Initialize the Memory Disk

test_for_mdisk:
	mov dx,m_disk_segment		;DX is base of 64K Mdisk segments
	push ds				;save SYSDAT
	mov cx,3			;try three contiguous 64K areas

next64K:
	mov es,dx ! mov ds,dx		;ES=DS=mdisk area
	mov si,0fffeh			;last word of 64K segment
	mov di,si
	mov ax,0aa55h			;bit pattern to write
	mov bx,ax			;keep for compare
	stosw				;write it
	lodsw				;read it
	cmp ax,bx
	jne m_test_done			;not equal-then no more memory
	  push cx			;set Mdisk to E5's, blank disk
	  mov ax,0E5E5h			;parity of memory at C0000H is not
          xor di,di			;set by IBM PC ROM
	  mov cx,8000H			;32K words = 64K bytes
	  rep stosw
	  pop cx			;restore loop count
	  add dx,1000H			;try next 64K
	  loop next64k

m_test_done:
	pop ds				;restore DS
	cmp cx,3			;if equal no mdisk
	jne m_exists
	  mov dph_m_adr,0		;IO_SELDSK will return
	  jmps m_disk_done		;an error on Mdisk select

m_exists:
	  mov sign_m_disk,cr		;print mdisk message
	  cmp cx,2			;select to work
	  jne more_than_64K
            mov sign_md_size1,'46'	;64 - low byte high byte storage
	    mov dpb_m_dsm,63		;number of 1K blocks - 1
	    jmps mdisk_done

more_than_64K:
	    cmp cx,1
	    jne more_than_128K
	      mov sign_md_size0,'1'
	      mov sign_md_size1,'82'
	      mov dpb_m_dsm,127		;number of 1K blocks - 1
	      jmps mdisk_done

more_than_128K:				;it is 192K
		mov sign_md_size0,'1'
		mov sign_md_size1,'29'	;low byte high byte
		mov dpb_m_dsm,191	;number of 1K blocks - 1
	        ;jmps m_disk_done

m_disk_done:

;	Disk I/O Initialization

;	Initialize DPH Hash Table Segment Entry.
;	Commented out since we let GENCCPM perform this
;	initialization.  Use this code if you build your own
;	Buffer Control Blocks, Hash Table Segment, Allocation
;	and Check Sum Vectors.

;	mov cx,16
;inith0:	push cx
;	dec cl
;	call io_seldsk			;get disk param header offset
;	or bx,bx ! jz inith1		;if not 0, BX = DPH
;	    mov	ax,18[bx]		;AX = Hash Tbl	Offset
;	    or ax,ax ! jz inith1	;if 0, No Hash Tbl
;		mov cl,4
;		shr ax,cl
;		mov dx,ds
;		add ax,dx		;AX = Hash Tbl Seg
;		mov 18[bx],ax
;inith1:
;	pop cx
;	loop inith0
;
;	Initialize data BCB segment addresses
;	all drives share the same set of data buffers
;
;	xor cl,cl			;get disk param header address
;	call io_seldsk			;BX=DPH address for drive A:
;	mov si,16[bx]			;SI=data BCB root address
;	mov si,[si]
;initd1:
;	mov ax,10[si]			;AX=BCB buffer offset
;	mov cl,4
;	shr ax,cl
;	mov dx,ds
;	add ax,dx
;	mov 10[si],ax			;AX=BCB buffer segment
;	mov si,12[si]			;SI=next BCB
;	or si,si ! jnz initd1		;0 if end of linked list
;	
;end of disk init

set_up_interrupts:
	mov al,10111100b		;enable diskette, keyboard,
	out pic_odd_port,al		;timer interrrupt
					;and mask off the rest

	mov disk_arguments,3		;send specify command to the
	mov d_a_command,fdc_spec_cmd	;FDC (Intel 8272 or NEC 765)
	mov d_a_drive,0dfh		;step rate=3ms, head unload=240ms
	mov d_a_cylinder,02h		;head load=2ms, DMA mode true
	call fdc_command_put		;head unload and head load times
					;are meaningless on mini floppies
					;where the head is loaded
					;when the motor is turned on
	mov ax,timer_60_hz
	out timer_0_reg,al
	xchg ah,al
	out timer_0_reg,al
					
	mov al,bell_cmd			;set up the bell frequency
	out timer_cmd_reg,al		;send the command
	mov ax,timer_1000_hz		;get the constant
	out timer_2_reg,al
	xchg ah,al
	out timer_2_reg,al

;	Paint the software interrupt vectors.

;	When debugging leave the single step, one byte, debugger and
;	serial interrupt vectors pointing at DDT86 and CP/M-86.

;	To debug CCP/M-86 under CP/M-86, DDT86 or SID86 must be
;	able to perform console input, console input status and 
;	console output through the CP/M-86 BIOS.
;	This is usually accomplished by BIOS through a serial port.
;	Often the BIOS routines AUXIN and AUXOUT, are used for
;	serial device support.  If the BIOS supports the IOBYTE,
;	it may be possible to redirect the CP/M console to a
;	serial port using the STAT transient.

;	When debugging is finished, all interrupts unused
;	by the XIOS should be initialized in XIOS INIT to
;	point at the XIOS I_UNEXPECTED interrupt handler routine.

	xor dx,dx
	cmp debug,true			;debug flag: see HEADER
	jne no_save
	  push ds ! pop es		;ES=SYSDAT
	  mov ds,dx			;DS=0
	  mov bx,2			;count of 2 words
	  mov cx,bx
	  mov di,offset iv_save		;local save area
	  mov si,iv_sstep		;single step interrupt
	  rep movsw
	  mov cx,bx			;CX=2
	  mov si,iv_one_byte		;one byte INT instruction
	  rep movsw
	  mov cx,bx
	  mov si,iv_rs232		;serial port
	  rep movsw
	  mov cx,bx			;CX=2
	  mov si,iv_debug		;debugger interrupt
	  rep movsw

no_save:				;paint all the interrupt vectors
	  mov ds,dx			;to point at I_UNEXPECTED:
	  mov es,dx			;ES=0, DS=0	
	  mov word ptr .0,offset i_unexpected
	  mov word ptr .2,cs
	  mov cx,255*2
	  mov si,0 ! mov di,4
	  rep movsw

int_offsets:				;interrupt vectors used by XIOS
	  mov ax,offset i_tick		;DS=0,ES=0
	  mov .iv_tick,ax		;tick interrupt
	  mov ax,offset i_keyboard
	  mov .iv_key,ax
	  mov ax,offset i_disk
	  mov .iv_disk,ax

	  mov ds,cs:sysdat		;in debug environment ?
	  cmp debug,true
	  jne no_restore
					;DS=SYSDAT,ES=0
	    mov cx,bx			;BX=2
	    mov si,offset iv_save	;local save area
	    mov di,offset iv_s_step	;single step interrupt
	    rep movsw
	    mov cx,bx			;CX=2
	    mov di,offset iv_one_byte	;one byte INT instruction
	    rep movsw
	    mov cx,bx			;CX=2
	    mov di,offset iv_rs232	;serial port
	    rep movsw
	    mov cx,bx
	    mov di,offset iv_debug	;debugger interrupt
	    rep movsw

no_restore:
	push ds
	pop es				;ES=SYSDAT

init_parallel:
	mov dx,3bch			;BW parallel port
	add dx,2
	mov al,08h			;printer init byte
	out dx,al
	mov ax,1000h			;delay count

parallel_wait:
	dec ax
	jnz parallel_wait
	mov al,0Ch			;printer strobe off
	out dx,al

set_up_video:	
	mov dx,bw_card			;get the video chip port
	mov si,offset bw_table		;initialization commands
	mov cx,length bw_table		;how many commands
	call video_init			;send commands to port
					;color board is similar ...

;	Set up the virtual screen structures (one per virtual console)
;	and blank their screen save areas.

	mov dx,genccpm_buf		;paragraph address of buffer
					;space allocated by GENCCPM.
					;this area is not part of the
					;CCPM.SYS file, and is located
					;with GENCCPM allocated disk
					;data buffers at the end of
					;the system image.
	xor ah,ah			;CX = screen we are initializing
					;and param to erase_screen
	mov bx,offset screen_structures

init_screen_structures:
	mov ss_screen_seg[bx],dx	;RAM storage segment for this virtual
					;console
	push ax ! push dx		;save screen #

;	test ah,ah			;to keep the LOADER messages,
;	jz no_erase			;don't erase screen 0
	  call erase_screen
;no_erase:
	pop dx ! pop ax			;screen #
	add dx,(screen_siz*2 + 15)/16	;next virtual screen storage area
	add bx,ss_len			;next screen_structure
	inc ah ! cmp ah,nvcns		;do for each virtual console
	jb init_screen_structures
					
	mov si,offset sign_on
	call print_msg
	pop es				;restore the UDA
	sti
	retf				;initializaiton done

video_init:
;----------
;	entry:	DX = video chip port
;		SI = table of commands to send to the port
;		CX = number of commands to send
;	exit:	none

	xor bl,bl

video_init_l:
	mov al,bl ! inc bl
	out dx,al ! inc dx
	lodsb !	out dx,al
	dec dx !
	loop video_init_l

	add dx,4
	mov al,video_on
	out dx,al
	ret

;************************************************************************
;*									*
;*			INIT Data					*
;*									*
;************************************************************************

SL19	equ	offset $
DSEG
ORG	SL19

bw_table	db	61h,50h,52h,0fh,19h,06h,19h,19h,02h,0dh,0bh,0ch,0,0,0,0

iv_save		rw	10		;space for 5 interrupt vectors

sign_on	rb	0	
		db 'Example XIOS Version of '
		db '4/15/83'
		db cr,lf,cr,lf
		db 'Hardware Supported :',cr,lf
		db '                   Diskette(s) :   '
sign_d		db '2',cr,lf
		db '      Parallel Printer Port(s) :   '
sign_sp		db '1',cr,lf
		db '              Main Memory (Kb) : '
sign_mem	db '   ',cr,lf
sign_mdisk	db 0					;end msg here
		db '                   M:Disk (Kb) : '	;unless mdisk
sign_md_size0	rb 1					;set to 64,128,192
sign_md_size1	rw 1
		db cr,lf,0

eject
;			Miscellaneous Routines
;			----------------------

;************************************************************************
;*									*
;*				RESET					*
;*									*
;************************************************************************

SL20	equ	offset $
CSEG
ORG	SL20

;---------
print_msg:
;---------
;	entry:	SI = address of string to print until 0 byte
;			on foreground console
;	exit:	none

	mov dl,foreground_screen
p_msg_l:
	mov cl,[si]
	test cl,cl
	jz p_done
	  push dx ! push si
	  call io_conout
	  pop si ! pop dx
	  inc si
	  jmps p_msg_l
p_done:
	ret

;-----
reset:
;-----
;	Reset function - reboot from floppy

	  mov ax,40H
	  mov ds,ax			;ROM data segment
	  mov reset_flag,1234H		;parameter to reset routine
	  jmpf rom_reset		;don't perform diagnostics

CSEG	0ffffh				;address in ROM monitor
ORG	0				;of reset routine

rom_reset:

DSEG	0
ORG	72h

reset_flag	rw	1	
