;	Figure 8-10
;
;	Revision History
;	This file contains two fixes not shown in the Programmer's Handbook
;	for CP/M.
;	Line 3828 has been changed from STA MOB$Maximum$Busy to
;				        STA MOB$Character
;	A new line has been inserted after line 9270,
;		LXI	H,Disk$Control$5
;	Also note that the 300 or so lines of code inadvertently not
;	printed in the book (see bottom of page 261, top of 262) is
;	present in this file.
;
;	AJL 08/29/83.
;
;********************************************************
;*							*
;*	       Enhanced BIOS Listing			*
;*							*
;********************************************************
;
;	This is a skeletal example of an enhanced BIOS.
;	As such it includes fragments of the standard BIOS
;	shown as Figure 6-4, but only in outline so as to
;	avoid cluttering up the enhancements with the
;	supporting substructure. Many of the original
;	comment blocks have been abbreviated or deleted
;	entirely.
;
;<---- NOTE : 	The line numbers at the left are included purely
;		to allow reference to the code from the text.
;		There are deliberately induced discontinuities
;		in the numbers to allow space for expansion.
;
VERSION		EQU	'00'	;Equates used in the sign on message
MONTH		EQU	'02'
DAY		EQU	'26'
YEAR		EQU	'83'
;
;************************************************************************
;*									*
;*	This BIOS is for a computer system with the following		*
;*	hardware configuration :					*
;*									*
;*		- 8080 CPU						*
;*		- 64K Bytes of RAM					*
;*		- 3 Serial I/O Ports (Using Signetics 2651) for :	*
;*		       Console, Communications and List        		*
;*		- Two 5 1/4" Mini Floppy  double-sided,	double-		*
;*		  density drives. These drives use 512-byte sectors.	*
;*		  These are used as logical disks A: and B:.		*
;*		  Full track buffering is supported.			*
;*		- Two 8" standard Diskette Drives (128-byte sectors)	*
;*		  These are used as logical disks C: and D:.		*
;*		- A memory-based disk (M-DISK) is supported.		*
;*									*
;*		  Two intelligent disk controllers are used, one for	*
;*		  each diskette type. These controllers access memory	*
;*		  directly, both to read the details of the 		*
;*		  operations they are to perform and also to read	*
;*		  and write data from and to the diskettes.		*
;*									*
;*									*
;************************************************************************

	
;	Equates for characters in the ASCII character set.
;
XON	EQU	11H	;Re-enables transmission of data
XOFF	EQU	13H	;Disables transmission of data
ETX	EQU	03H	;End of Transmission
ACK	EQU	06H	;Acknowledge
CR	EQU	0DH	;Carriage Return
LF	EQU	0AH	;Line Feed
TAB	EQU	09H	;Horizontal Tab
BELL	EQU	07H	;Sound terminal's Bell
;
;
;	Equates for defining memory size and the base address and
;	length of the system components.
;
Memory$Size	EQU	64	;Number of Kbytes of RAM
;
;	The BIOS Length must be determined by inspection.
;	Comment out the ORG BIOS$Entry line below by changing the first
;	character to a semi-colon, (this will make the Assembler start 
;	the BIOS at location 0), then assemble the BIOS and round up to 
;	the nearest 100H the address displayed on the console at the end 
;	of the assembly.
;
BIOS$Length	EQU	2500H	;<-- Revised to an approximate value
				;    to reflect enhancements
;
CCP$Length	EQU	0800H	;Constant
BDOS$Length	EQU	0E00H	;Constant
;
Overall$Length	EQU	(CCP$Length + BDOS$Length + BIOS$Length + 1023) / 1024
;
CCP$Entry	EQU	(Memory$Size - Overall$Length) * 1024
BDOS$Entry	EQU	CCP$Entry + CCP$Length + 6	
BIOS$Entry	EQU	CCP$Entry + CCP$Length + BDOS$Length
;
BDOS		EQU	0005H	;BDOS entry point (used for making
				;  system reset requests)
;
;#
;	ORG	BIOS$Entry	;Assemble code at BIOS address
;
;	BIOS Jump Vector
;
	JMP	BOOT	;Cold Boot - entered from CP/M Bootstrap Loader
Warm$Boot$Entry:	;  Labelled so that the initialization code can
			;  put the Warm Boot entry address down in location
			;  0001H and 0002H of the base page.
	JMP	WBOOT	;Warm Boot - entered by jumping to location 0000H
			;  Reloads the CCP which could have been
			;  overwritten by previous program in Transient
			;  Program area.
	JMP	CONST	;Console Status - returns A = 0FFH if there is a
			;  Console Keyboard character waiting.
	JMP	CONIN	;Console Input - returns the next console keyboard
			;  Character in A.
	JMP	CONOUT	;Console Output - outputs the character in C to
			;  the console device.
	JMP	LIST	;List Output - outputs the character in C to the
			;  list device.
	JMP	AUXOUT	;Auxiliary Output - outputs the character in C to the 
			;  logical auxiliary device.
	JMP	AUXIN	;Auxiliary Input - returns the next input character from
			;  the logical auxiliary device in A.
	JMP	HOME	;Homes the currently selected disk to Track 0.
	JMP	SELDSK	;Selects the disk drive specified in register C and
			;  returns the address of the Disk Parameter Header
	JMP	SETTRK	;Sets the track for the next read or write operation
			;  from the BC register pair.
	JMP	SETSEC	;Sets the sector for the next read or write operation
			;  from the A register.
	JMP	SETDMA	;Sets the Direct Memory Address (Disk Read/Write)
			;  address for the next read or write operation
			;  from the DE register pair.
	JMP	READ	;Reads the previously specified Track and Sector from
			;  the selected disk into the DMA address.
	JMP	WRITE	;Writes the previously specified Track and Sector onto
			;  the selected disk from the DMA address.
	JMP	LISTST	;Returns A = 0FFH if the list device(s) are
			;  logically ready to accept another output byte.
	JMP	SECTRAN	;Translates a logical sector into a physical one.
;
;	Additional "Private" BIOS Entry Points
;
	JMP	AUXIST	;Returns A = 0FFH if there is input data for
			;  the logical auxiliary device
	JMP	AUXOST	;Returns A = 0FFH if the auxiliary device(s) are
			;  logically ready to accept another output byte.
	JMP	Specific$CIO$Initialization
			;Initializations character device whose device
			;  number is in register A on entry
	JMP	Set$Watchdog
			;Sets up Watchdog timer to CALL address specified
			;  in HL, after BC clock ticks have elapsed
	JMP	CB$Get$Address
			;Configuration Block Get Address
			;  Returns address in HL of data element whose
			;  code number is specified in C
;
;#
;	Long Term Configuration Block
;
Long$Term$CB:
;
;
;	Public Files (files in user 0 being accessible from all
;	other user numbers), is enabled when this flag is set
;	non-zero.
;
CB$Public$Files:	DB	0	;Default is OFF
;
;
;	The Forced Input pointer is initialized to point to the
;	following string of characters. These are injected into 
;	the console input stream on system startup.
;
CB$Startup:		DB	'SUBMIT STARTUP',LF,0,0,0,0,0,0
;
;	Logical to Physical Device Re-direction
;
;		Each logical device has a 16-bit word associated
;		with it. Each bit in the word is assigned to a
;		specific PHYSICAL device. For INPUT, only one bit
;		can be set - and input will be read from the 
;		corresponding physical device. OUTPUT can be
;		directed to several devices, so more than one
;		bit can be set.
;
;		The following EQUates are used to indicate
;		specific Physical devices.
;
;			1111 11             )
;			5432 1098 7654 3210 )<- Device Number
Device$0	EQU	0000$0000$0000$0001B
Device$1	EQU	0000$0000$0000$0010B
Device$2	EQU	0000$0000$0000$0100B
;
;		The following words are tested by the logical
;		device drivers in order to transfer control
;		to the appropriate physical device drivers
;
CB$Console$Input:	DW	Device$0
CB$Console$Output:	DW	Device$0
;
CB$Auxiliary$Input:	DW	Device$1
CB$Auxiliary$Output:	DW	Device$1
;
CB$List$Input:		DW	Device$2
CB$List$Output:		DW	Device$2
;
;		The table below relates specific bits in the
;		redirection words above to specific Device
;		Tables used by the physical drivers
;
CB$Device$Table$Addresses:
	DW	DT$0
	DW	DT$1
	DW	DT$2
	DW	0,0,0,0,0,0,0,0,0,0,0,0,0	;Unassigned
;
;
;	Device Initialization Byte Streams
;
;	These initialization streams are output during the device
;	initialization phase, or on request whenever the Baud rate
;	needs to be changed. They are defined in the Long Term
;	Configuration Block so as to "freeze" their contents from one
;	system startup until the next.
;
;	The address of each stream is contained in each Device Table.
;
;	The stream format is :
;
;		DB	xx		;Port number (00H terminates)
;		DB	nn		;Number of bytes to output to port
;		DB	vv,vv,vv..	;Values to be output
;
D0$Initialize$Stream:		;Example data for an 8251A chip
	DB	0EDH			;Port number for 8251A
	DB	6			;Number of bytes
	DB	0,0,0			;Dummy bytes to get chip ready
	DB	0100$0010B		;Reset and raise DTR
	DB	01$10$11$10B		;1 stop, no parity, 8 bits/char,
					;  Divide down of 16
	DB	0010$0101B		;RTS high, enable Tx/Rx
				;Example data for an 8253 chip
	DB	0DFH			;Port number for 8253 Mode
	DB	1			;Number of Bytes to output
	DB	01$11$011$0B		;Select :
					;	Counter 1
					;	Load LS Byte first
					;	Mode 3, Binary Count
	DB	0DEH			;Port number for counter
	DB	2			;Number of Bytes to output
D0$Baud$Rate$Constant:			;Label used by utilities
	DW	0007H			;9600 Baud (based on 16x divider)
	DB	0			;Port number of 00 terminates stream

D1$Initialize$Stream:		;Example data for an 8251A chip
	DB	0DDH			;Port number for 8251A
	DB	6			;Number of bytes
	DB	0,0,0			;Dummy bytes to get chip ready
	DB	0100$0010B		;Reset and raise DTR
	DB	01$10$11$10B		;1 stop, no parity, 8 bits/char,
					;  Divide down of 16
	DB	0010$0101B		;RTS high, enable Tx/Rx


	DB	0DFH			;Port number for 8253 Mode
	DB	1			;Number of Bytes to output
	DB	10$11$011$0B		;Select :
					;	Counter 2
					;	Load LS Byte first
					;	Mode 3, Binary Count
	DB	0DEH			;Port number for counter
	DB	2			;Number of Bytes to output
D1$Baud$Rate$Constant:
	DW	0038H			;1200 Baud (based on 16x divider)
	DB	0			;Port number of 00 terminates stream

D2$Initialize$Stream:		;Example data for an 8251A chip
	DB	0DDH			;Port number for 8251A
	DB	6			;Number of bytes
	DB	0,0,0			;Dummy bytes to get chip ready
	DB	0100$0010B		;Reset and raise DTR
	DB	01$10$11$10B		;1 stop, no parity, 8 bits/char,
					;  Divide down of 16
	DB	0010$0101B		;RTS high, enable Tx/Rx


	DB	0DFH			;Port number for 8253 Mode
	DB	1			;Number of Bytes to output
	DB	11$11$011$0B		;Select :
					;	Counter 3
					;	Load LS Byte first
					;	Mode 3, Binary Count
	DB	0DEH			;Port number for counter
	DB	2			;Number of Bytes to output
D2$Baud$Rate$Constant:
	DW	0038H			;1200 Baud (based on 16x divider)
	DB	0			;Port number of 00 terminates stream

;	
;	This following table is used to determine the maximum
;	value for each character position in the ASCII time 
;	value above (except the ":"). Note - this table is present
;	in the Long Term Configuration Block so that the clock can
;	be set "permanently" to either 12 or 24 hour format.
;
;	NOTE : The table is processed backwards - to correspond 
;	with the ASCII time.
;	Each character represent the value for the corresponding 
;	character in the ASCII time at which a Carry-and-Reset-to-zero
;	should occur.
;
	DB	0		;"Terminator"
CB$12$24$Clock:
	DB	'34'		;Change to '23' for a 12-hour clock
	DB	0FFH		;"Skip" character
	DB	'6:'		;Maximum minutes are 59
	DB	0FFH		;"Skip" character
	DB	'6:'		;Maximum seconds are 59
Update$Time$End:		;Used when updating the time
;
;
;	Variables for the Real Time Clock and Watchdog
;	timer.
;
RTC$Ticks$per$Second	DB	60	;Number of Real Time Clock
					;  ticks per elapsed second
RTC$Tick$Count		DB	60	;Residual count before next
					;  second will elapse
RTC$Watchdog$Count	DW	0	;Watchdog timer tick count
					;(0 = no watchdog timer set)
RTC$Watchdog$Address	DW	0	;Address to which control
					;  will be transferred if the
					;  watchdog count hits 0

;
;	Function Key Table
;
;	This table consists of a series of entries, each one having the
;	following structure :
;
;		DB	Second character of sequence emitted by
;			terminal's function key
;	(	DB	Third character of sequence - NOTE : this is	  )
;	(		field will not be present if the source code	  )
;	(		has been configured to accept only two characters )
;	(		in function key sequences.			  )
;	(		NOTE : Adjust the EQUates for :			  )
;	(			Function$Key$Length			  )
;	(			Three$Character$Function		  )
;
;		DB	A character string to be forced into the console
;			input stream when the corresponding function key
;			is pressed. The last byte of this string must be
;			00H in order to terminate the forced input.
;
Function$Key$Lead	EQU	1BH	;Signals Function Key Sequence
Function$Key$Length	EQU	3	;Number of characters in Function
					;  key input sequence (NOTE : this
					;  can only be 3 or 2 characters).

;
					;The logic associated with Function
					;  key recognition is made easier with
					;  the following EQUate
Three$Character$Function	EQU	Function$Key$Length - 2
				;Three$Character$Function will be TRUE if the
				;  function keys emit a three character
				;  sequence, FALSE if they emit a two character
				;  sequence.

;	Each entry in the table must be the same length, as defined by :
;
CB$Function$Key$Entry$Size	EQU	16 + 1 + Function$Key$Length - 1
;					^    ^                         ^
;				        |    |                         |
;		Maximum length of substitute |		Lead character is not
;		string                       |          in table entry
;		      			     For the terminating 00H
;
;	The last entry in the table is marked by a 00-byte.
;
;	The example values shown below are for a VT-100 terminal.
;
CB$Function$Key$Table:
;                        123456789.1234  5  6 7 <- Use to check length
	DB	'O','P','Function Key 1',LF,0,0
	DB	'O','Q','Function Key 2',LF,0,0
	DB	'O','R','Function Key 3',LF,0,0
	DB	'O','S','Function Key 4',LF,0,0
;
;                        123456789.1
	DB	'[','A','Up Arrow',LF,0,0,0,0,0,0,0,0
	DB	'[','B','Down Arrow',LF,0,0,0,0,0,0
	DB	'[','C','Right Arrow',LF,0,0,0,0,0
	DB	'[','D','Left Arrow',LF,0,0,0,0,0,0

	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0	;Spare entries
	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	
	DB	0FFH,0FFH	;Terminator for utility that pre-programs
				;  function key sequence
;
;
;	Console Output Escape Sequence Control Table
;
;	This table is referenced after a Function$Key$Lead character
;	has been detected in the CONOUT routine. The next character
;	to be output to the console is compared to the first byte
;	in each 3-byte table entry. If a match is found, then control
;	is transferred to the address following the byte that matched
;
CONOUT$Escape$Table:
	DB	't'		;Read current time
	DW	CONOUT$Time
	DB	'd'		;Read current date
	DW	CONOUT$Date
	DB	'u'		;Set current time
	DW	CONOUT$Set$Time
	DB	'e'		;Set current date
	DW	CONOUT$Set$Date

	DB	0		;Terminator
;
Long$Term$CB$End:
;
;#
;
;	Interrupt Vector 
;
;	Control is transferred here by the Programmable Interrupt
;	Controller - an Intel 8259A.
;
;	NOTE : The Interrupt Controller chip requires that the
;		interrupt vector table start on a paragraph
;		boundary. This is achieved by the following ORG line
	ORG	($ AND 0FFE0H) + 20H
Interrupt$Vector:
			;Interrupt Number
	JMP	RTC$Interrupt		;0 - Clock
	DB	0				;Skip a byte
	JMP	Character$Interrupt	;1 - Character I/O
	DB	0
	JMP	Ghost$Interrupt		;2 - Not used
	DB	0
	JMP	Ghost$Interrupt		;3 - Not used
	DB	0
	JMP	Ghost$Interrupt		;4 - Not used
	DB	0
	JMP	Ghost$Interrupt		;5 - Not used
	DB	0
	JMP	Ghost$Interrupt		;6 - Not used
	DB	0
	JMP	Ghost$Interrupt		;7 - Not used
;
;#

;	Device Port Numbers and other Equates
;
CIO$Base$Port	EQU	80H		;Base Port number

D0$Base$Port	EQU	CIO$Base$Port		;Device 0
D0$Data$Port	EQU	D0$Base$Port
D0$Status$Port	EQU	D0$Base$Port + 1
D0$Mode$Port	EQU	D0$Base$Port + 2
D0$Command$Port	EQU	D0$Base$Port + 3
;

D1$Base$Port	EQU	CIO$Base$Port + 4 	;Device 1
D1$Data$Port	EQU	D1$Base$Port
D1$Status$Port	EQU	D1$Base$Port + 1
D1$Mode$Port	EQU	D1$Base$Port + 2
D1$Command$Port	EQU	D1$Base$Port + 3

D2$Base$Port	EQU	CIO$Base$Port + 8	;Device 2
D2$Data$Port	EQU	D2$Base$Port
D2$Status$Port	EQU	D2$Base$Port + 1
D2$Mode$Port	EQU	D2$Base$Port + 2
D2$Command$Port	EQU	D2$Base$Port + 3

D$Mode$Value$1	EQU	01$00$11$10B
				;1 stop bit, no parity
				;8 bits, Async 16x rate
D$Mode$Value$2	EQU	00$11$1100B
				;Tx/Rx on internal clock
				;9600 Baud
D$Command$Value	EQU	00$100111B
				;Normal mode
				;Enable Tx/Rx
				;RTS and DTR active
D$Error		EQU	0011$1000B
D$Error$Reset	EQU	00$110111B
				;Same as command value plus error reset
D$Output$Ready	EQU	0000$0001B
D$Input$Ready	EQU	0000$0010B
D$DTR$High	EQU	1000$0000B	;Note : this is actually the
					;       Data Set Ready pin
					;       on the chip. It is connected
					;       to the DTR pin on the cable
D$Raise$RTS	EQU	00$1$00111B	;Raise RTS, Tx/Rx Enable
D$Drop$RTS	EQU	00$0$00111B	;Drop RTS, Tx/Rx Enable
;
;
;	Interrupt Controller Ports (Intel 8259A)
;
;	Note : these equates are placed here so that they
;		follow the  definition of the Interrupt Vector
;		and thus avoid 'P' (Phase) errors in ASM.
;
IC$OCW1$Port	EQU	0D9H	;Operational Control Word 1
IC$OCW2$Port	EQU	0D8H	;Operational Control Word 2 
IC$OCW3$Port	EQU	0D8H	;Operational Control Word 3
IC$ICW1$Port	EQU	0D8H	;Initialization Control Word 1
IC$ICW2$Port	EQU	0D9H	;Initialization Control Word 2
;
IC$EOI		EQU	20H	;Non-specific End of Interrupt
;
IC$ICW1		EQU	(Interrupt$Vector AND 1110$0000B) + 000$10110B
				;Sets the A7-A5 bits of the interrupt
				;  vector address plus :
				;	Edge triggered
				;	4 byte interval
				;	Single 8259 in system
				;	No ICW4 needed
IC$ICW2		EQU	Interrupt$Vector SHR 8
				;Address bits A15 - A8 of the interrupt
				;  vector address. Note the interrupt
				;  vector is the first structure in
				;  the Long Term Configuration Block

IC$OCW1		EQU	1111$1100B	;Interrupt Mask
				;Interrupt 0 (Clock) enabled
				;Interrupt 1 (Character Input) enabled
;
;#
;
;
Display$Message:	;Displays the specified message on the console.
			;On entry, HL points to a stream of bytes to be
			;output. A 00H-byte terminates the message.
	MOV	A,M		;Get next message byte
	ORA	A		;Check if terminator
	RZ			;Yes, return to caller
	MOV	C,A		;Prepare for output
	PUSH	H		;Save message pointer
	CALL	CONOUT		;Goto main console output routine
	POP	H		;Recover message pointer
	INX	H		;Move to next byte of message
	JMP	Display$Message	;Loop until complete message output
;
;#
;
Enter$CPM:	;This routine is entered either from the Cold or Warm
		;Boot code. It sets up the JMP instructions in the
		;Base Page, and also sets the high-level disk driver's
		;Input/Output Address (also known as the DMA address).
;
	MVI	A,JMP		;Get Machine Code for JMP
	STA	0000H		;Setup JMP at location 0000H
	STA	0005H		;and at location 0005H
;
	LXI	H,Warm$Boot$Entry	;Get BIOS Vector address
	SHLD	0001H		;Put address at location 0001H

	LXI	H,BDOS$Entry	;Get BDOS entry point address
	SHLD	6		;Put address at location 0005H
;
	LXI	B,80H		;Set Disk I/O Address to default
	CALL	SETDMA		;Use normal BIOS routine
;
	EI			;Ensure interrupts are enabled
	LDA	Default$Disk	;Handover current Default Disk to
	MOV	C,A		;Console Command Processor
	JMP	CCP$Entry	;Transfer to CCP
;
;#
;
;	Device Table Equates
;	The drivers use an Device Table for each
;	Physical Device they service. The EQUates that follow
;	are used to access the various fields within the
;	Device Table.
;
;			Port Numbers and Status Bits
DT$Status$Port		EQU	0	;Device Status Port number
DT$Data$Port		EQU	DT$Status$Port+1
					;Device Data Port number
DT$Output$Ready		EQU	DT$DataPort+1
					;Output ready status mask
DT$Input$Ready		EQU	DT$Output$Ready+1
					;Input ready status mask
DT$DTR$Ready		EQU	DT$Input$Ready+1
					;DTR ready to send mask
DT$Reset$Int$Port	EQU	DT$DTR$Ready+1
					;Port number used to reset an
					;  interrupt
DT$Reset$Int$Value	EQU	DT$Reset$Int$Port+1
					;Value output to reset interrupt
DT$Detect$Error$Port	EQU	DT$Reset$Int$Value+1
					;Port number for error detect
DT$Detect$Error$Value	EQU	DT$Detect$Error$Port+1
					;Mask for detecting error (parity etc.)
DT$Reset$Error$Port	EQU	DT$Detect$Error$Value+1
					;Output to port to reset error
DT$Reset$Error$Value	EQU	DT$Reset$Error$Port+1
					;Value to output to reset error
DT$RTS$Control$Port	EQU	DT$Reset$Error$Value+1
					;Control port for lowering RTS
DT$Drop$RTS$Value	EQU	DT$RTS$Control$Port+1
					;Value, when output, to drop RTS
DT$Raise$RTS$Value	EQU	DT$Drop$RTS$Value+1
					;Value, when output, to raise RTS
;
;		Device Logical Status (incl. Protocols)
DT$Status		EQU	DT$Raise$RTS$Value+1
					;Status Bits
DT$Output$Suspend	EQU	0000$0001B	;Output suspended pending
						;  Protocol action
DT$Input$Suspend	EQU	0000$0010B	;Input suspended until
						;  buffer empties
DT$Output$DTR		EQU	0000$0100B	;Output uses DTR high to send
DT$Output$Xon		EQU	0000$1000B	;Output uses Xon/Xoff
DT$Output$Etx		EQU	0001$0000B	;Output uses Etx/Ack
DT$Output$Timeout	EQU	0010$0000B	;Output uses Timeout
DT$Input$RTS		EQU	0100$0000B	;Input uses RTS high to receive
DT$Input$Xon		EQU	1000$0000B	;Input uses Xon/Xoff
;
DT$Status$2		EQU	DT$Status+1	;Secondary Status Byte
DT$Fake$Typeahead	EQU	0000$0001B	;Requests Input$Status to
						;return "Data Ready" when
						;Control Characters are in
						;input buffer
;
DT$Etx$Count		EQU	DT$Status$2+1
					;No. of chars sent in Etx protocol
DT$Etx$Message$Length	EQU	DT$Etx$Count+2
					;Specified message length
;
;			Input Buffer values
DT$Buffer$Base		EQU	DT$Etx$Message$Length+2
					;Address of Input Buffer
DT$Put$Offset		EQU	DT$Buffer$Base+2
					;Offset for Putting chars into buffer
DT$Get$Offset		EQU	DT$Put$Offset+1
					;Offset for Getting chars from buffer
DT$Buffer$Length$Mask	EQU	DT$Get$Offset+1
					;Length of buffer - 1
					;Note : Buffer length must always be
					;  a binary number; e.g. 32, 64 or 128
					;This mask then becomes :
					;  32 ->  31 (0001$1111B)
					;  64 ->  63 (0011$1111B)
					; 128 -> 127 (0111$1111B)
					;After the Get/Put offset has been
					;incremented it is ANDed with the mask
					;to reset it to zero when the end of
					;the buffer has been reached.
DT$Character$Count	EQU	DT$Buffer$Length$Mask+1
					;Count of the number of characters
					;  currently in the buffer
DT$Stop$Input$Count	EQU	DT$Character$Count+1
					;Stop input when the count reaches
					;  this value
DT$Resume$Input$Count	EQU	DT$Stop$Input$Count+1
					;Resume input when the count reaches
					;  this value
DT$Control$Count	EQU	DT$Resume$Input$Count+1
					;Count of the number of control
					;  characters in the buffer
DT$Function$Delay	EQU	DT$Control$Count+1
					;Number of clock ticks to delay to
					;  allow all characters after function
					;  key lead-in to arrive
DT$Initialize$Stream	EQU	DT$Function$Delay+1
					;Address of byte stream necessary to
					;  initialize this device

;#
;
;	Device Tables
;
DT$0:
	DB	D0$Status$Port	;Status Port (8251A chip)
	DB	D0$Data$Port	;Data Port
	DB	D$Output$Ready	;Output Data Ready
	DB	D$Input$Ready	;Input Data Ready
	DB	D$DTR$High	;DTR ready to send
	DB	IC$OCW2$Port	;Reset Interrupt Port (00H is a unused port)
	DB	IC$EOI		;Reset Interrupt Value (Non-specific EOI)
	DB	D0$Status$Port	;Detect Error Port
	DB	D$Error		;Mask : Framing, Overrun, Parity errors
	DB	D0$Command$Port	;Reset Error Port
	DB	D$Error$Reset	;Reset Error : RTS high, Reset, Tx/Rx enable
	DB	D0$Command$Port	;Drop/Raise RTS Port
	DB	D$Drop$RTS	;Drop RTS Value (keep Tx & Rx enabled)
	DB	D$Raise$RTS	;Raise RTS Value (keep Tx & Rx enabled)
	DB	DT$Input$Xon + DT$Input$RTS	;Protocol and Status
	DB	0		;Status #2
	DW	1024		;Etx/Ack Message Count
	DW	1024		;Etx/Ack Message Length
	DW	D0$Buffer	;Input Buffer
	DB	0		;Put Offset into buffer
	DB	0		;Get Offset into buffer
	DB	D0$Buffer$Length -1 ;Buffer length mask
	DB	0		;Count of Character in buffer
	DB	D0$Buffer$Length - 5 ;Stop input when count hits this value
	DB	D0$Buffer$Length / 2 ;Resume input when count hits this value
	DB	0		;Count of Control Characters in buffer
	DB	6		;Number of 16.66ms ticks to allow Function
				;  Key sequence to arrive (approx. 90ms)
	DW	D0$Initialize$Stream	;Address of initialization stream
;
DT$1:
	DB	D1$Status$Port	;Status Port (8251A chip)
	DB	D1$Data$Port	;Data Port
	DB	D$Output$Ready	;Output Data Ready
	DB	D$Input$Ready	;Input Data Ready
	DB	D$DTR$High	;DTR ready to send
	DB	IC$OCW2$Port		;Reset Interrupt Port (00H is a unused port)
	DB	IC$EOI		;Reset Interrupt Value (Non-specific EOI)
	DB	D1$Status$Port	;Detect Error Port
	DB	D$Error		;Mask : Framing, Overrun, Parity errors
	DB	D1$Command$Port	;Reset Error Port
	DB	D$Error$Reset	;Reset Error : RTS high, Reset, Tx/Rx enable
	DB	D1$Command$Port	;Drop/Raise RTS Port
	DB	D$Drop$RTS	;Drop RTS Value (keep Tx & Rx enabled)
	DB	D$Raise$RTS	;Raise RTS Value (keep Tx & Rx enabled)
	DB	DT$Input$Xon + DT$Input$RTS	;Protocol and Status
	DB	0		;Status #2
	DW	1024		;Etx/Ack Message Count
	DW	1024		;Etx/Ack Message Length
	DW	D1$Buffer	;Input Buffer
	DB	0		;Put Offset into buffer
	DB	0		;Get Offset into buffer
	DB	D1$Buffer$Length -1 ;Buffer length mask
	DB	0		;Count of Character in buffer
	DB	D1$Buffer$Length - 5 ;Stop input when count hits this value
	DB	D1$Buffer$Length / 2 ;Resume input when count hits this value
	DB	0		;Count of Control Characters in buffer
	DB	6		;Number of 16.66ms ticks to allow Function
				;  Key sequence to arrive (approx. 90ms)
	DW	D1$Initialize$Stream	;Address of initialization stream
;
;
DT$2:
	DB	D2$Status$Port	;Status Port (8251A chip)
	DB	D2$Data$Port	;Data Port
	DB	D$Output$Ready	;Output Data Ready
	DB	D$Input$Ready	;Input Data Ready
	DB	D$DTR$High	;DTR ready to send
	DB	IC$OCW2$Port	;Reset Interrupt Port (00H is a unused port)
	DB	IC$EOI		;Reset Interrupt Value (Non-specific EOI)
	DB	D2$Status$Port	;Detect Error Port
	DB	D$Error		;Mask : Framing, Overrun, Parity errors
	DB	D2$Command$Port	;Reset Error Port
	DB	D$Error$Reset	;Reset Error : RTS high, Reset, Tx/Rx enable
	DB	D2$Command$Port	;Drop/Raise RTS Port
	DB	D$Drop$RTS	;Drop RTS Value (keep Tx & Rx enabled)
	DB	D$Raise$RTS	;Raise RTS Value (keep Tx & Rx enabled)
	DB	DT$Input$Xon + DT$Input$RTS	;Protocol and Status
	DB	0		;Status #2
	DW	1024		;Etx/Ack Message Count
	DW	1024		;Etx/Ack Message Length
	DW	D2$Buffer	;Input Buffer
	DB	0		;Put Offset into buffer
	DB	0		;Get Offset into buffer
	DB	D2$Buffer$Length -1 ;Buffer length mask
	DB	0		;Count of Character in buffer
	DB	D2$Buffer$Length - 5 ;Stop input when count hits this value
	DB	D2$Buffer$Length / 2 ;Resume input when count hits this value
	DB	0		;Count of Control Characters in buffer
	DB	6		;Number of 16.66ms ticks to allow Function
				;  Key sequence to arrive (approx. 90ms)
	DW	D2$Initialize$Stream	;Address of initialization stream
;
;#
;	General Character I/O Device Initialization
;
;	This routine will be CALLed from the main CP/M
;	initialization code.
;
;	It makes repeated calls to the Specific Character I/O
;	Device initialization routine.
;
General$CIO$Initialization:
	XRA	A		;Set Device Number (used to access the
				;  table of Device Table Addresses in the
				;  Configuration Block)
	MOV	C,A		;Match to externally callable interface
GCI$Next$Device:
	CALL	Specific$CIO$Initialization	;Initialize the device
	INR	A		;Move to next device
	CPI	16		;Check if all possible devices (0-15)
	RZ			;  have been initialized
	JMP	GCI$Next$Device
;
;#
;
;	Specific Character I/O Initialization
;
;	This routine outputs the specified byte values to the specified
;	ports as controlled by the Initialization Streams in the
;	Configuration Block. Each Device Table contains a pointer to
;	these streams. The Device Table itself is select according
;	to the Device NUMBER - this is an entry parameter for this
;	routine.
;	This routine will be CALLed either from the General Device
;	Initialization routine above, or directly by a BIOS call from
;	a system utility executing in the TPA.
;
;	Entry Parameters
;
;		C = Device Number
;
;	Exit Parameters
;
;		A = Device Number (preserved)
;
;===========================
Specific$CIO$Initialization:		;<=== BIOS Entry Point (Private)
;===========================
	MOV	A,C		;Get Device Number
	PUSH	PSW		;Preserve Device Number
	ADD	A		;Make Device Number into word pointer
	MOV	C,A
	MVI	B,0		;Make into a Word
	LXI	H,CB$Device$Table$Addresses	;Get table base
	DAD	B		;HL -> Device Table Address
	MOV	E,M		;Get LS Byte
	INX	H
	MOV	D,M		;Get MS Byte : DE -> Device Table

	MOV	A,D		;Check if Device Table address = 0
	ORA	E
	JZ	SCI$Exit	;Yes, device table non-existent

	LXI	H,DT$Initialize$Stream
	DAD	D		;HL -> Initialization Stream Address
	MOV	E,M		;Get LS Byte
	INX	H
	MOV	D,M		;Get MS Byte
	XCHG			;HL -> Initialization Stream itself
	CALL	Output$Byte$Stream	;Output byte stream to various
					;  ports
;
SCI$Exit:
	POP	PSW		;Recover user's Device Number in C
	RET
;
;#
;	Output Byte Stream
;
;	This routine outputs initialization bytes to port
;	numbers. The byte stream has the following format
;
;		DB	ppH	Port number
;		DB	nn	Number of bytes to output
;		DB	vvH,vvH...   Bytes to be output
;		:
;		:	Repeated
;		:
;		DB	00H	Port number of 0 terminates
;
;	Entry Parameters
;
;		HL -> Byte Stream
;
Output$Byte$Stream:
OBS$Loop:
	MOV	A,M		;Get Port Number
	ORA	A		;Check if 00H (terminator)
	RZ			;Exit if at end of stream
	STA	OBS$Port	;Store in port number below
	INX	H		;HL -> Count of bytes
	MOV	C,M		;Get count
	INX	H		;HL -> First initialization byte
;
OBS$Next$Byte:
	MOV	A,M		;Get next byte
	INX	H		;HL -> next data byte (or port number)

	DB	OUT
OBS$Port:
	DB	0		;<- Setup in instruction above
	DCR	C		;Count down on byte counter
	JNZ	OBS$Next$Byte	;Output next data byte
	JMP	OBS$Loop	;Go back for next port number
;
;#
;	CONST - Console Status
;
;	This routine checks both the Forced Input pointer and
;	the Character Count for the appropriate input buffer.
;	The A register is set to indicate whether or not there
;	is data waiting.
;
;	Entry Parameters : None.
;
;	Exit Parameters
;
;		A = 000H if there is no data waiting
;		A = 0FFH if there is data waiting
;
;==========================
CONST:					;<=== BIOS Entry Point (Standard)
;==========================
	LHLD	CB$Console$Input	;Get redirection word
	LXI	D,CB$Device$Table$Addresses
	CALL	Select$Device$Table	;Get Device Table address
	JMP	Get$Input$Status	;Get status from input device
					;  and return to caller
;#
;
;	CONIN - Console Input
;
;	This routine returns the next character for the console input
;	stream. Depending on the circumstances, this can be a character
;	from the console input buffer, or from a previous stored string
;	of characters to be "forced" into the input stream.
;
;	The "forced input" can come from any previously stored character
;	string in memory. It is used to inject the current time and date
;	or a string associated with a Function Key into the console
;	stream. On system startup, a string of "SUBMIT STARTUP" is
;	forced into the console input stream to provide a mechanism
;	
;	Normal ("unforced") input comes from whichever physical device
;	is specified in the Console Input redirection word (see the
;	Configuration Block).
;
CONIN$Delay$Elapsed:	DB	0	;Flag used during Function Key
					;  processing to indicate that
					;  a predetermined delay has
					;  elapsed
;
;==========================
CONIN:					;<=== BIOS Entry Point (Standard)
;==========================
	LHLD	CB$Forced$Input		;Get the Forced Input pointer
	MOV	A,M			;Get the next character of input
	ORA	A			;Check if a null
	JZ	CONIN$No$FI		;Yes, no forced input
	INX	H			;Yes, update the pointer
	SHLD	CB$Forced$Input		;  and store it back
	RET
;
CONIN$No$FI				;No forced input
	LHLD	CB$Console$Input	;Get redirection word
	LXI	D,CB$Device$Table$Addresses
	CALL	Select$Device$Table	;Get Device Table address
	CALL	Get$Input$Character	;Get next character from input device
	
				;Function Key Processing
	CPI	Function$Key$Lead	;Check if first character of Function
					;  key sequence (normally an ESCape)
	RNZ				;Return to BIOS caller if not
	PUSH	PSW			;Save lead in character
	LXI	H,DT$Function$Delay	;Get delay time constant for
					;  delay while waiting for subsequent
					;  characters of function key sequence
					;  to arrive
	DAD	D
	MOV	C,M			;Get delay value
	MVI	B,0			;Make into word value
	XRA	A			;Indicate timer not yet timed out
	STA	CONIN$Delay$Elapsed
	LXI	H,CONIN$Set$Delay$Elapsed ;Address to resume at after delay
	CALL	Set$Watchdog		;Sets up delay based on real time
					;  clock such that control will be 
					;  transferred to specified address
					;  after time interval has elapsed
CONIN$Wait$for$Delay:			;Wait here until delay has elapsed
	LDA	CONIN$Delay$Elapsed	;Check flag set by Watchdog routine
	ORA	A
	JZ	CONIN$Wait$for$Delay

CONIN$Check$for$Function:
	LXI	H,DT$Character$Count	;Now check if the remaining characters
					;  of the sequence have been input
	DAD	D
	MOV	A,M			;Get count of characters in buffer
	CPI	Function$Key$Length - 1
	JNC	CONIN$Check$Function	;Enough characters in buffer for
					;  possible Function Key sequence
	POP	PSW			;Insufficient characters in buffer
					;  to be a Function Key, so return
					;  to caller with Lead character
	RET

;
;	The following routine is CALLed by the Watchdog routine
;	when the specified delay has elapsed.
;
CONIN$Set$Delay$Elapsed:
	MVI	A,0FFH			;Indicate Watchdog timer timed out
	STA	CONIN$Delay$Elapsed
	RET				;Return to Watchdog routine
;
;
CONIN$Check$Function:
	LXI	H,DT$Get$Offset		;Save the current "Get Pointer"
	DAD	D			;  into the buffer
	MOV	A,M			;Get the pointer
	PUSH	PSW			;Save pointer on the stack

	LXI	H,DT$Get$Offset		;Check the second (and possibly third
	CALL	Get$Address$in$Buffer	;  character in the sequence)
	MOV	B,M			;Get the second character

	IF	Three$Character$Function
	PUSH	B			;Save for later use
	LXI	H,DT$Get$Offset		;Retreive the third character
	CALL	Get$Address$in$Buffer
	POP	B			;Recover second character
	MOV	C,M			;Now BC = Char2, Char 3
	ENDIF

	PUSH	D			;Save Device Table pointer
	LXI	H,CB$Function$Key$Table - CB$Function$Key$Entry$Size
					;Get pointer to function key table
					;  in Configuration Block
	LXI	D,CB$Function$Key$Entry$Size	;Get entry size ready for loop
CONIN$Next$Function:
	DAD	D			;Move to next (or first) entry
	MOV	A,M			;Get second character of sequence
	ORA	A			;Check if end of function key table
	JZ	CONIN$Not$Function	;Yes - it is not a function key
	CMP	B			;Compare second characters
	JNZ	CONIN$Next$Function	;No match, so try next entry in table

	IF	Three$Character$Function
	INX	H			;HL -> Third character
	MOV	A,M			;Get third character of sequence
	DCX	H			;Simplify logic for 2 & 3 char seq.
	CMP	C			;Compare third characters
	JNZ	CONIN$Next$Function	;No match, so try next entry in table
	INX	H			;When match found, compensate for
					;  extra decrement
	ENDIF

	INX	H			;HL -> first character of substitute
					;  string of characters (00-byte term)
	SHLD	CB$Forced$Input		;Make the CONIN routine inject the
					;  substitute string into the input
					;  stream
	
					;Now that a Function Sequence has been
					;  identified, the stack must be 
					;  balanced prior to return
	POP	D			;Get the Device Table pointer
	POP	PSW			;Dump the Get Offset value
	POP	PSW			;Dump the Function Sequence Lead char.

	LXI	H,DT$Character$Count	;Downdate the character count
	DAD	D			;  to reflect the characters removed
					;  from the buffer
	MOV	A,M			;Get the count
	SUI	Function$Key$Length -1	;(the lead character has already
	MOV	M,A			;  been deducted)
	JMP	CONIN			;Return to CONIN processing to get
					;  the forced input characters
CONIN$Not$Function:
				;Attempts to recognize a function key sequence
				;  have failed. The Get Offset pointer must be
				;  restored to its previous value so that
				;  those character(s) presumed to be part of
				;  the function sequence are not lost.

	POP	D			;Recover Device Table pointer
	POP	PSW			;Recover previous "Get" offset
	LXI	H,DT$Get$Offset
	DAD	D			;HL -> Get offset in table
	MOV	M,A			;Reset Get offset as it was after
					;  the lead character was detected
	POP	PSW			;Recover lead character
	RET				;Return the lead character to the user
;
;#
;	Console Output
;
;	This routine outputs data characters to the console device(s).
;	It also "traps" ESCape sequences being output to the console,
;	triggering specific actions according to the sequences.
;	A primitive "state-machine" is used to step through ESCape
;	sequence recognition.
;	In addition to outputting the next character to the ALL of the
;	devices currently selected in the Console Output redirection word,
;	it checks to see that output to the selected device has not been
;	suspended by virtue of Xon/Xoff protocol, and that Data Terminal
;	Ready is high if it should be.
;	Once the character has been output, if Etx/Ack protocol is in use,
;	and the specified length of message has been output, an Etx
;	character is output and the device flagged as being suspended.
;
;	Entry Parameters
;
;		C = Character to be output
;
;	CONOUT storage variables
;
CONOUT$Character:	DB	0	;Save area for character to be output

CONOUT$Processor:	DW	CONOUT$Normal	
					;This is the address of the piece of
					;  code that will process the next
					;  character. The default case is
					;  CONOUT$Normal
CONOUT$String$Pointer:	DW	0	;This points to a string (normally
					;  in the Configuration Block) that
					;  is being preset by characters from
					;  the console output stream
CONOUT$String$Length:	DB	0	;This contains the maximum number of
					;  characters to be preset into a
					;  from the console output stream

;
;	*** WARNING ***
;	The Output Error Message routine shares the code in this
;	subroutine. On entry here, the data byte to be output
;	will be on the stack, and the DE registers setup correctly
;	*** WARNING ***
;
CONOUT$OEM$Entry:
	STA	CONOUT$Character	;Save data byte
	JMP	CONOUT$Entry2		;HL already has special bit map
;
;=====================
CONOUT:				;<=== BIOS Entry Point (Standard)
;=====================
	LHLD	CONOUT$Processor	;Get address of processor to handle
					;  the next character to be output
					;(Default is : CONOUT$Normal)
	PCHL				;Transfer control to the processor
;
;
CONOUT$Normal:				;Normal processor for console output
	MOV	A,C			;Check if possible start of ESCAPE
	CPI	Function$Key$Lead	;  sequence
	JZ	CONOUT$Escape$Found	;Perhaps
CONOUT$Forced:
	MOV	A,C			;Forced output entry point
	STA	CONOUT$Character	;Not Escape sequence - Save data byte

	LHLD	CB$Console$Output	;Get console redirection word
;
CONOUT$Entry2:			;<=== Output Error Message entry point
;
	LXI	D,CB$Device$Table$Addresses	;Addresses of Dev. Tables
	PUSH	D				;Put onto stack ready for loop
	PUSH	H

CONOUT$Next$Device:
	POP	H			;Recover Redirection bit map
	POP	D			;Recover Device Table Addresses pointer
	CALL	Select$Device$Table	;Get Device Table in DE
	ORA	A			;Check if a Device has been
					;  selected (i.e. bit map not all zero)
	JZ	CONOUT$Exit		;No, exit
	PUSH	B	;Yes - B..	;Save Redirection bit map
	PUSH	H			;Save Device Table Addresses pointer
CONOUT$Wait:
	CALL	Check$Output$Ready	;Check if device not suspended and
					;  (if appropriate) DTR is high
	JZ	CONOUT$Wait		;No, wait

	DI				;Interrupts off to avoid
					;  involuntary re-entrancy
	LDA	CONOUT$Character	;Recover the data byte
	MOV	C,A			;Ready for output
	CALL	Output$Data$Byte	;Output the data byte
	EI

	CALL	Process$Etx$Protocol	;Deal with Etx/Ack protocol
	JMP	CONOUT$Next$Device	;Loop back for next device

CONOUT$Exit:
	LDA	CONOUT$Character	;Recover data character
	MOV	A,C			;CP/M "Convention"
	RET
;
CONOUT$Escape$Found:			;Possible Escape Sequence
	LXI	H,CONOUT$Process$Escape	;Vector processing of next character
CONOUT$Set$Processor:
	SHLD	CONOUT$Processor	;Set vector address
	RET				;Return to BIOS caller
;#
;
;	Console Output : Escape Sequence Processing
;
CONOUT$Process$Escape:			;Control arrives here with character
					;  after ESCape in C
	LXI	H,CONOUT$Escape$Table	;Get base of recognition table
CONOUT$Next$Entry:
	MOV	A,M			;Check if at end of table
	ORA	A
	JZ	CONOUT$No$Match		;Yes, no match found
	CMP	C			;Compare to data character
	JZ	CONOUT$Match		;They match
	INX	H			;Move to next entry in table
	INX	H
	INX	H
	JMP	CONOUT$Next$Entry	;Go back and check again
;
CONOUT$No$Match:			;No match found, so original
					;  ESCape and following character
					;  must be output
	PUSH	B			;Save character after ESCape
	MVI	C,Function$Key$Lead	;Get ESCape character
	CALL	CONOUT$Forced		;Output to console devices
	POP	B			;Get character after ESCape
	CALL	CONOUT$Forced		;Output it, too
;
CONOUT$Set$Normal:
	LXI	H,CONOUT$Normal		;Set vector back to normal
	JMP	CONOUT$Set$Processor	;  for subsequent characters
;

CONOUT$Match:
	INX	H			;HL -> LS byte of sub-processor
	MOV	E,M			;Get LS byte
	INX	H
	MOV	D,M			;Get MS byte
	XCHG				;HL -> sub-processor
	PCHL				;Goto sub-processor
;
CONOUT$Date:			;Sub-processor to inject current date
				;  into console input stream (using
				;  forced input)
	LXI	H,Date
CONOUT$Set$Forced$Input:
	SHLD	CB$Forced$Input
	RET			;Return to BIOS' caller
;
CONOUT$Time:			;Sub-processor to inject time into
				;  console input stream
	LXI	H,Time$In$ASCII
	JMP	CONOUT$Set$Forced$Input
;
CONOUT$Set$Date:		;Sub-processor to set the date by taking
				;  the next 8 characters of console output
				;  and storing them into the date string
	LXI	H,Time$Date$Flags	;Set flag to indicate that the
	MVI	A,Date$Set		;  Date has been set by program
	ORA	M
	MOV	M,A
	MVI	A,8			;Set character count
	LXI	H,Date			;Set address
	JMP	CONOUT$Set$String$Pointer
;
;
CONOUT$Set$Time:		;Sub-processor to set the time by taking
				;  the next 8 characters of console output
				;  and storing them into the time string
	LXI	H,Time$Date$Flags	;Set flag to indicate that the
	MVI	A,Time$Set		;  Time has been set by program
	ORA	M
	MOV	M,A
	MVI	A,8			;Set character count
	LXI	H,Time$in$ASCII		;Set address
	JMP	CONOUT$Set$String$Pointer
;
CONOUT$Set$String$Pointer:		;HL -> String, A = count
	STA	CONOUT$String$Length	;Save count
	SHLD	CONOUT$String$Pointer	;Save address
	LXI	H,CONOUT$Process$String	;Vector further output
	JMP	CONOUT$Set$Processor
;
CONOUT$Process$String:		;Control arrives here for each character 
				;  in the string in register C. The 
				;  characters are stacked into the 
				;  receiving string until either a 00-byte
				;  is encountered or the specified number
				;  of characters is stacked.
	LHLD	CONOUT$String$Pointer	;Get current address for stacking chars
	MOV	A,C			;Check if current character is 00H
	ORA	A
	JZ	CONOUT$Set$Normal	;Revert to normal processing
	MOV	M,A			;Otherwise, stack character
	INX	H			;Update pointer
	MVI	M,00H			;Stack fail-safe terminator
	SHLD	CONOUT$String$Pointer	;Save updated pointer
	LXI	H,CONOUT$String$Length	;Downdate count
	DCR	M
	JZ	CONOUT$Set$Normal	;Revert to normal processing
					;  if count hits 0
	RET				;Return with output vectored back
					; to CONOUT$Process$String
;
;#
;
;	AUXIST - Auxiliary Input Status
;
;	This routine checks the Character Count in the 
;	appropriate input buffer.
;	The A register is set to indicate whether or not there
;	is data waiting.
;
;	Entry Parameters : None.
;
;	Exit Parameters
;
;		A = 000H if there is no data waiting
;		A = 0FFH if there is data waiting
;
;==========================
AUXIST:				;<=== BIOS Entry Point (Private)
;==========================
	LHLD	CB$Auxiliary$Input		;Get redirection word
	LXI	D,CB$Device$Table$Addresses	;  and table pointer
	CALL	Select$Device$Table	;Get Device Table address
	JMP	Get$Input$Status	;Get status from input device
					;  and return to caller
;
;#
;
;	Auxiliary Output Status
;
;	This routine sets the A register to indicate whether the
;	Auxiliary Device(s) is/are ready to accept output data.
;	As more than one device can be used for Auxiliary output, this
;	routine returns an Boolean AND of all of their statuses.
;
;	Entry Parameters : None
;
;	Exit Parameters
;
;		A = 000H if one or more List devices are not ready
;		A = 0FFH if all List devices are ready
;
;
;======================
AUXOST:				;<=== BIOS Entry Point (Private)
;======================
	LHLD	CB$Auxiliary$Output		;Get list redirection word
	JMP	Get$Composite$Status
;
;#
;
;	AUXIN - Auxiliary Input (Replacement for READER)
;
;	This routine returns the next input character from the
;	appropriate logical auxiliary device.
;
;	Entry Parameters : None.
;
;	Exit Parameters
;
;		A = data character
;
;==========================
AUXIN:				;<=== BIOS Entry Point (Standard)
;==========================
	LHLD	CB$Auxiliary$Input		;Get redirection word
	LXI	D,CB$Device$Table$Addresses	;  and table pointer
	CALL	Select$Device$Table	;Get Device Table address
	JMP	Get$Input$Character	;Get next input character
					;  and return to caller
;
;#
;	Auxiliary Output (Replaces PUNCH)
;
;	This routine outputs a data byte to the Auxiliary device(s).
;	It is similar to CONOUT except that it uses the Watchdog
;	Timer in order to detect if a device stays busy for more
;	than 30 seconds at a time. It outputs a message to the console
;	if this happens.
;
;	Entry Parameters
;
;		C = data byte
;
AUXOUT$Busy$Message:	DB	CR,LF,7,'Auxiliary device not Ready?',CR,LF,0
;
;=====================
AUXOUT:				;<=== BIOS Entry Point (Standard)
;=====================
	LHLD	CB$Auxiliary$Output	;Get Aux. redirection word
	LXI	D,AUXOUT$Busy$Message	;Message to be output if timeout
					;  occurs
	JMP	Multiple$Output$Byte
;
;#
;
;	List Status
;
;	This routine sets the A register to indicate whether the
;	List Device(s) is/are ready to accept output data.
;	As more than one device can be used for List output, this
;	routine returns an Boolean AND of all of their statuses.
;
;	Entry Parameters : None
;
;	Exit Parameters
;
;		A = 000H if one or more List devices are not ready
;		A = 0FFH if all List devices are ready
;
;
;======================
LISTST:					;<=== BIOS Entry Point (Standard)
;======================
	LHLD	CB$List$Output		;Get list redirection word
	JMP	Get$Composite$Status
;
;#
;	List Output
;
;	This routine outputs a data byte to the List device.
;	It is similar to CONOUT except that it uses the Watchdog
;	Timer in order to detect if the printer stays busy for more
;	than 30 seconds at a time. It outputs a message to the console
;	if this happens.
;
;	Entry Parameters
;
;		C = data byte
;
LIST$Busy$Message:	DB	CR,LF,7,'Printer not Ready?',CR,LF,0
;
;=====================
LIST:				;<=== BIOS Entry Point (Standard)
;=====================
	LHLD	CB$List$Output		;Get list redirection word
	LXI	D,LIST$Busy$Message	;Message to be output if timeout
					;  occurs
	JMP	Multiple$Output$Byte
;
;#
;	Request User Choice
;
;	This routine displays an error message, requesting
;	a choice of :
;
;		R - Retry the operation that caused the error.
;		I - Ignore the error and attempt to continue.
;		A - Abort the program and return to CP/M.
;
;	This routine accepts a character from the console,
;	folds it to upper case and returns to the caller
;	with the response in the A register.
;
RUC$Message:
		DB	CR,LF
		DB	'     Enter R - Retry, I - Ignore, A - Abort : ',0
;
;
Request$User$Choice:
	CALL	CONST			;"Gobble" up any type-ahead
	JZ	RUC$Buffer$Empty
	CALL	CONIN
	JMP	Request$User$Choice

RUC$Buffer$Empty:
	LXI	H,RUC$Message		;Display prompt
	CALL	Output$Error$Message

	CALL	CONIN			;Get console character
	CALL	A$To$Upper		;Make upper case for comparisons
	STA	Disk$Action$Confirm	;Save in confirmatory message
	PUSH	PSW			;Save for later

	LXI	H,Disk$Action$Confirm
	CALL	Output$Error$Message

	POP	PSW			;Recover action code
	RET
;
;#
;
;	Output Error Message
;
;	This routine outputs an error message to all the currently
;	selected console devices EXCEPT those being used to receive
;	LIST output as well. This is to avoid "deadly embrace" situations
;	where the printer being busy for too long causes an error message
;	to be output - and console output is being directed to the
;	printer as well....
;
;	This subroutine makes use of most of the CONOUT subroutine.
;	For reasons of memory economy it enters CONOUT using its
;	own private entry point.
;
;	Entry Parameters
;
;		HL -> 00-byte terminated error message
;
Output$Error$Message:
	PUSH	H			;Save message address
	LHLD	CB$Console$Output	;Get Console Redirection bit map
	XCHG
	LHLD	CB$List$Output		;Get List Redirection bit map
					;HL = List, DE = Console
					;Now set to 0 all bits in the Console
					;  bit map that are set to 1 in the
					;  List bit map
	MOV	A,H			;Get MS byte of List
	CMA				;Invert
	ANA	D			;Preserve only bits where 0's were
	MOV	H,A			;Save result
	MOV	A,L			;Repeat for LS byte of List
	CMA
	ANA	E
	MOV	L,A			;HL now has only pure console
					;  devices
	ORA	H			;Ensure that at least one device
	JZ	OEM$Device$Present	;  is selected
	LXI	H,0001H			;Otherwise use default of Device 0
OEM$Device$Present:
OEM$Next$Character:
	POP	D			;Recover message address into DE
	LDAX	D			;Get next byte of message
	INX	D			;Update message pointer
	ORA	A			;Check if end of message
	RZ				;Yes, exit
	PUSH	D			;Save message address for later
	PUSH	H			;Save special bit map
					;Data character is in A
	CALL	CONOUT$OEM$Entry	;Enter shared code
	POP	H			;Recover special bit map
	JMP	OEM$Next$Character
;
;
;
;	Get Composite Status
;
;	This routine sets the A register to indicate whether the
;	Output Device(s) is/are ready to accept output data.
;	As more than one device can be used for output, this
;	routine returns an Boolean AND of all of their statuses.
;
;	Entry Parameters
;
;		HL = I/O Redirection Bit Map for output device(s)
;
;	Exit Parameters
;
;		A = 000H if one or more List devices are not ready
;		A = 0FFH if all List devices are ready
;
GCS$Status:	DB	0	;Composite status of all devices
;
;
Get$Composite$Status:
	MVI	A,0FFH			;Assume all devices are ready
	STA	GCS$Status		;Preset composite status byte

	LXI	D,CB$Device$Table$Addresses	;Addresses of Dev. Tables
	PUSH	D			;Put onto stack ready for loop
	PUSH	H			;Save bit map
GCS$Next$Device:
	POP	H			;Recover Redirection bit map
	POP	D			;Recover Device Table Addresses pointer
	CALL	Select$Device$Table	;Get Device Table in DE
	ORA	A			;Check if a Device has been
					;  selected (i.e. bit map not all zero)
	JZ	GCS$Exit		;No, exit
	PUSH	B	;Yes - B..	;Save Redirection bit map
	PUSH	H			;Save Device Table Addresses pointer
	CALL	Check$Output$Ready	;Check if device ready
	LXI	H,GCS$Status		;AND together with previous devices
	ANA	M			;  status
	MOV	M,A			;Save composite status

	JMP	GCS$Next$Device		;Loop back for next device
;
GCS$Exit:
	LDA	GCS$Status		;Return with composite status
	ORA	A
	RET
;
;#
;
;	Multiple Output Byte
;
;	This routine outputs a data byte to the all of the
;	devices specified in the I/O Redirection Word.
;	It is similar to CONOUT except that it uses the Watchdog
;	Timer in order to detect if any of the devices stays busy for more
;	than 30 seconds at a time. It outputs a message to the console
;	if this happens.
;
;	Entry Parameters
;
;		HL = I/O Redirection Bit Map
;		DE -> Message to be output if timeout occurs
;		C = data byte
;
MOB$Maximum$Busy	EQU	1800	;Number of Clock Ticks (each at
					;  16.666 milliseconds) that the
					;  device might be busy for.
MOB$Character:		DB	0	;Character to be output
MOB$Busy$Message:	DW	0	;Address of message to be
					;  output if timeout occurs
MOB$Need$Message:	DB	0	;Flag used to detect that the 
					;  Watchdog timer timed out
;
Multiple$Output$Byte:
	MOV	A,C			;Get data byte
	STA	MOB$Character		;Save copy (fix: AJL 082983)
	XCHG				;HL -> Timeout message
	SHLD	MOB$Busy$Message	;Save for later use
	XCHG				;HL = Bit map again

	LXI	D,CB$Device$Table$Addresses	;Addresses of Dev. Tables
	PUSH	D			;Save on stack ready for loop
	PUSH	H			;Save I/O Redirection bit map
MOB$Next$Device:
	POP	H			;Recover Redirection bit map
	POP	D			;Recover Device Table Addresses pointer
	CALL	Select$Device$Table	;Get Device Table in DE
	ORA	A			;Check if any device selected
	JZ	MOB$Exit

	PUSH	B	;<- Yes : B	;Save Device Table Addresses pointer
	PUSH	H			;Save Redirection bit map
;
MOB$Start$Watchdog:
	XRA	A				;Reset message needed flag
	STA	MOB$Need$Message
	LXI	B,MOB$Maximum$Busy		;Time delay
	LXI	H,MOB$Not$Ready		;Address to go to
	CALL	Set$Watchdog			;Start timer

MOB$Wait:
	LDA	MOB$Need$Message		;Check if Watchdog timed out
	ORA	A
	JNZ	MOB$Output$Message		;Yes, output warning message
	CALL	Check$Output$Ready		;Check if device ready
	JZ	MOB$Wait			;No, wait
;
	DI					;Interrupts off to avoid
						;  involuntary re-entrancy
	LXI	B,0				;Turn off Watchdog
	CALL	Set$Watchdog			; (HL setting is irrelevant)

	LDA	MOB$Character			;Get data byte
	MOV	C,A
	CALL	Output$Data$Byte		;Output the data byte
	EI
	CALL	Process$Etx$Protocol		;Deal with Etx/Ack protocol
	JMP	MOB$Next$Device
;
MOB$Ignore$Exit:				;Ignore timeout error
	POP	H				;Balance the stack
	POP	D
;
MOB$Exit:
	MOV	A,C				;CP/M "Convention"
	RET
;
MOB$Output$Message:
	LHLD	MOB$Busy$Message		;Display warning message
	CALL	Output$Error$Message		;  on selected console devices
MOB$Request$Choice:
	CALL	Request$User$Choice		;Display message and get
						;  action character
	CPI	'R'				;Retry
	JZ	MOB$Start$Watchdog		;Restart Watchdog and try again
	CPI	'I'				;Ignore
	JZ	MOB$Ignore$Exit
	CPI	'A'				;Abort
	JZ	System$Reset			;  Give BDOS Function 0
	JMP	MOB$Request$Choice
;
MOB$Not$Ready:			;Watchdog timer routine will CALL this
				;  routine if the device is busy
				;  for more than approximately 30 seconds
				;Note - this is an interrupt service routine
	MVI	A,0FFH			;Set request to output message
	STA	MOB$Need$Message
	RET				;Return to the Watchdog routine
;
;#
;	Check Output Ready
;
;	This routine checks to see if the specified device is ready
;	to receive output data.
;	It does so by checking to see if the device has been suspended
;	for protocol reasons and if Data Terminal Ready is low.
;
;	NOTE : This routine does NOT check if the USART itself is ready.
;		This test is done in the Output Data Byte routine itself.
;
;	Entry Parameters
;
;		DE -> Device Table
;
;	Exit Parameters
;
;		A = 000H (Zero-flag set)   : Device not ready
;		A = 0FFH (Zero-flag clear) : Device ready
;
Check$Output$Ready:
	LXI	H,DT$Status		;Get device status
	DAD	D			;HL -> Status byte
	MOV	A,M			;Get status byte
	MOV	B,A			;Take a copy of the status byte
	ANI	DT$Output$Suspend	;Check if output is suspended
	JNZ	COR$Not$Ready		;Yes - indicate not ready

	MVI	A,DT$Output$DTR		;Check if DTR must be high to send
	ANA	B			;Mask with device status from table
	JZ	COR$Ready		;No, device is logically ready

	LXI	H,DT$Status$Port	;Setup to read device status
	DAD	D
	MOV	A,M			;Get status port number
	STA	COR$Status$Port		;Setup instruction below

	DB	IN
COR$Status$Port:
	DB	0		;<-- Setup by instruction above
	MOV	C,A			;Save hardware status

	LXI	H,DT$DTR$Ready		;Yes, setup to check chip status
	DAD	D			;  to see if DTR is high
	MOV	A,M			;Get DTR high status mask
	ANA	C			;Test chip status
	JZ	COR$Not$Ready		;DTR low - indicate not ready
;
COR$Ready:
	MVI	A,0FFH			;Indicate device ready for output
	ORA	A
	RET
;
COR$Not$Ready:				;Indicate device not ready for output
	XRA	A
	RET
;
;#
;
;	Process Etx/Ack Protocol
;
;	This routine maintains Etx/Ack protocol.
;	After a specified number of data characters have been output
;	to the device, an Etx character is output and the device
;	put into Output Suspended state. Only when an incoming
;	Ack character is received (under interrupt control) will
;	output be resumed to the device.
;
;	Entry Parameters
;
;		DE -> Device Table
;
;	Exit Parameters
;
;		Message Count downdated (& reset if necessary)
;
Process$Etx$Protocol:
	LXI	H,DT$Status		;Check if Etx/Ack protocol enabled
	DAD	D
	MOV	A,M
	ANI	DT$Output$Etx
	RZ				;No, so return immediately
	LXI	H,DT$Etx$Count		;Yes, so downdate count
	DAD	D
	PUSH	H			;Save address of count for later
	MOV	C,M			;Get LS byte
	INX	H
	MOV	B,M			;Get MS byte
	DCX	B
	MOV	A,B
	ORA	C			;Check if count now zero
	JNZ	PEP$Save$Count		;No
	LXI	H,DT$Etx$Message$Length	;Yes, reset to message length
	DAD	D
	MOV	C,M			;Get LS byte
	INX	H
	MOV	B,M			;Get MS byte
PEP$Save$Count:
	POP	H			;Recover address of count
	MOV	M,C			;Save count back in table
	INX	H
	MOV	M,B
;
	ORA	A			;Re-establish whether count hit 0
	RNZ				;No - no further processing required
	MVI	C,ETX			;Yes - send Etx to device
	DI				;Avoids involuntary re-entrancy
	CALL	Output$Data$Byte
	EI
	LXI	H,DT$Status		;Flag device as output suspended
	DAD	D
	DI				;Avoid interaction with interrupts
	MOV	A,M			;Get status byte
	ORI	DT$Output$Suspend	;Set bit
	MOV	M,A			;Save back in table
	EI
	RET
;
;#
;
;	Select Device Table
;
;	This routine scans a 16-bit word, and depending on which is the
;	first 1-bit set, selects the corresponding Device Table Address.
;
;	Entry Parameters
;
;		HL = Bit map
;		DE -> Table of Device Table Addresses
;			The first address in the list is CALLed
;			if the least significant bit of the Bit Map is
;			non-zero, and so on.
;
;	Exit Parameters
;
;		BC -> Current entry in Device Table Addresses
;		DE = Selected Device Table address
;		HL = Shifted bit map
;		Non-zero if a 1-bit was found
;		Zero if Bit Map now entirely 0000
;
;	Note : If HL is 0000H on input, then the first entry in the
;	Device Table Addresses will be returned in DE.
;
Select$Device$Table:
	MOV	A,H		;Get most significant byte of bit map
	ORA	L		;Check if HL completely 0
	RZ			;Return indicating no more bits set
	MOV	A,L		;Check if the LS bit is non-zero
	ANI	1
	JNZ	SDT$Bit$Set	;Yes, return corresponding address
	INX	D		;No, update table pointer
	INX	D
	CALL	SHLR		;Shift HL right one bit
	JMP	Select$Device$Table	;Check next bit
SDT$Bit$Set:
	PUSH	H		;Save shifted bit map
	MOV	B,D		;Take copy of table pointer
	MOV	C,E
	XCHG			;HL -> Address in Table
	MOV	E,M
	INX	H
	MOV	D,M		;DE -> Selected Device Table
				;Setup registers in case of another
				;  entry
	POP	H		;Recover shifted bit map
	CALL	SHLR		;Shift bit map right one bit
	INX	B		;Update DT Address Table pointer to
	INX	B		;  entry
	MVI	A,1		;Indicate that a one bit was found
	ORA	A		;  and registers are setup correctly
	RET
;
;#
;
;	Get Input Character
;
;	This routine gets the next input character from the device
;	specified in the Device Table handed over as an input
;	parameter.
;
Get$Input$Character:
	LXI	H,DT$Character$Count	;Check if any characters have 
	DAD	D			;  been stored in the buffer
GIC$Wait:
	EI				;Ensure that incoming chars will
					;  be detected
	MOV	A,M			;Get character count
	ORA	A
	JZ	GIC$Wait		;No characters, so wait
	DCR	M			;Down date character count for
					;  the character about to be
					;  removed from the buffer
	LXI	H,DT$Get$Offset		;Use the "GET" offset to access
	CALL	Get$Address$in$Buffer	;Returns HL -> Character
					;  and with Get Offset updated
	MOV	A,M			;Get the actual data character
	PUSH	PSW			;Save until later

	LXI	H,DT$Character$Count	;Check downdated count of chars in
	DAD	D			;  buffer, checking if input should be
					;  resumed after having been suspended
	MOV	A,M			;Get downdated count
	LXI	H,DT$Resume$Input$Count	;Check if current count matches
	DAD	D			;  buffer empty threshold
	CMP	M
	JNZ	GIC$Check$Control	;Not at threshold, check if control
					;  character input
	LXI	H,DT$Status		;At threshold, check which means
	DAD	D			;  for pausing input are to be used
	DI				;Ensure no interaction from interrupts
					;  while changing the status byte
	MOV	A,M			;Get Status/Protocol byte
	ANI	0FFH AND NOT DT$Input$Suspend	;Clear suspension flag
	MOV	M,A			;Store back updated status
	EI
	PUSH	PSW			;Save for later use
	ANI	DT$Input$RTS		;Check if Clear to Send to be raised
	JZ	GIC$Check$Input$Xon	;No
	LXI	H,DT$RTS$Control$Port	;Yes, get Control Port number
	DAD	D
	MOV	A,M
	STA	GIC$Raise$RTS$Port		;Store in instruction below
	LXI	H,DT$Raise$RTS$Value
	DAD	D
	MOV	A,M			;Get value needed to raise RTS

	DB	OUT
GIC$Raise$RTS$Port:
	DB	0		;<- Setup in instruction above
					;Drop into check for Xon
GIC$Check$Input$Xon:		;Check if Xon/Xoff protocol being used
				;  to temporarily suspend input
	POP	PSW			;Recover Status/Protocol byte
	ANI	DT$Input$Xon		;Check if Xon bit set
	JZ	GIC$Check$Control	;No, see if control char. input
	MVI	C,XON			;Yes, output Xon character
	CALL	Output$Data$Byte

GIC$Check$Control:
	CALL	Check$Control$Char	;Check if it is a Control Character
	JZ	GIC$Not$Control		;No, or it is CR, LF or TAB
	LXI	H,DT$Control$Count	;Yes, down date count of Control
	DAD	D			;  characters in buffer
	DCR	M
GIC$Not$Control:
	POP	PSW			;Recover data character
	RET

;
;#
;
;***************************************************
;*						   *
;*   Interrupt Driven Serial Input/Output Drivers  *
;*						   *
;***************************************************
;
;
;	These drivers are designed to capture incoming characters
;	using hardware-generated interrupts. Output is not
;	interrupt driven as, in general, the user program can
;	always generate output far faster than the hardware can
;	output it - therefore, the output would rapidly go into
;	a "steady state" waiting for the hardware to catch up.
;
;	The input drivers support the following features :
;
;		*  Serial Protocols for controlling output
;			DTR High to Send
;			Xon/Xoff
;			ETX/Ack with variable message length
;		*  Serial Protocols for controlling input
;			RTS high to send
;			Xon/Xoff
;
;	Input data characters are stored in a circular buffer
;	until the user program is ready to accept them. As an option,
;	the drivers can prevent overrun of this input buffer either
;	by using Request To Send low or Xoff to request that input
;	stop.
;
;
;	Character Interrupt Routine
;
;	This routine receives control when the hardware detects an
;	incoming data character. The hardware chips and necessary
;	memory locations will have been setup during the initialization
;	code.
;
;	Because the actual hardware architecture can vary so much,
;	this routine has been written to show the generic processing
;	required. It assumes a "flat" interrupt structure that transfers
;	control to this routine regardless of which device is actually
;	interrupting. It also assumes that once the data character has
;	been input, a specific value must be output to a particular port
;	in order to recondition the hardware ready for the next interrupt.
;#
;
Character$Interrupt:
	PUSH	H		;Save just HL on user's stack
	LXI	H,0		;Get current stack pointer value
	DAD	SP
	SHLD	PI$User$Stack	;Save user's stack
	LXI	SP,PI$Stack	;Switch to local stack
	PUSH	PSW		;Save remaining registers
	PUSH	B
	PUSH	D

			;Now check each device in turn for incoming
			;	data characters
	LXI	D,DT$0		;Device 0
	CALL	Service$Device	;If interrupting, input the data character
				;  and process according to what the
				;  character is and the device's status
	LXI	D,DT$1		;Device 1
	CALL	Service$Device

	LXI	D,DT$2		;Device 2
	CALL	Service$Device

	MVI	A,IC$EOI	;Tell the Interrupt Controller chip
	OUT	IC$OCW2$Port	;  that the interrupt has been serviced
	POP	D		;Restore registers
	POP	B
	POP	PSW
	LHLD	PI$User$Stack	;Switch back to user's stack
	SPHL
	POP	H
	EI			;Re-enable interrupts in the CPU
	RET			;Resume pre-interrupt processing
;
;#
;
;	Service Device
;
;	This routine performs the device interrupt servicing,
;	checking to see if the device described in the specified
;	Device Table (address in DE) is actually interrupting,
;	and if so, inputs the character. Depending on which data character
;	is input, this routine will either stack it in the input buffer
;	(shutting off the input stream if the buffer is nearly full),
;	or will suspend or de-suspend the output to the device.
;
;	Entry Parameters
;
;		DE -> Device Table
;
Service$Device:
	LXI	H,DT$Status$Port		;Check if this device is really
	DAD	D			;  interrupting
	MOV	A,M			;Get status port number
	STA	SD$Status$Port		;Store into instruction below

	DB	IN			;Input Status
SD$Status$Port:
	DB	0	;<-- Setup by instruction above
;
	LXI	H,DT$Input$Ready	;Check if status indicates data ready
	DAD	D
	ANA	M			;Mask with Input Ready value
	RZ				;No, return to Interrupt Service
					;Check if any errors have occurred
	LXI	H,DT$Detect$Error$Port	;Setup to read error status
	DAD	D			;  interrupting
	MOV	A,M			;Get status port number
	STA	SD$Error$Port		;Store into instruction below

	DB	IN			;Input Error Status
SD$Error$Port:
	DB	0	;<-- Setup by instruction above
;
	LXI	H,DT$Detect$Error$Value	;Mask with error bit(s)
	DAD	D
	ANA	M
	JZ	SD$No$Error		;No bit(s) set
	LXI	H,DT$Reset$Error$Port	;Setup to reset error
	DAD	D
	MOV	A,M			;Get Reset Port number
	STA	SD$Reset$Error$Port	;Store in instruction below
	LXI	H,DT$Reset$Error$Value
	DAD	D
	MOV	A,M			;Get reset interrupt value

	DB	OUT
SD$Reset$Error$Port:
	DB	0	;<-- Setup in instruction above

SD$No$Error:
	LXI	H,DT$Data$Port		;Input the data character (this may
	DAD	D			;be garbled if an error occurred)
	MOV	A,M			;Get data port number
	STA	SD$Data$Port		;Store into instruction below

	DB	IN			;Input data character
SD$Data$Port:
	DB	0	;<-- Setup by instruction above

	MOV	B,A			;Take copy of data character above
	LXI	H,DT$Status		;Check if either Xon or Etx protocols
	DAD	D			;  are currently active
	MOV	A,M			;Get protocol byte
	ANI	DT$Output$Xon + DT$Output$Etx
	JZ	SD$No$Protocol		;Neither are active
	ANI	DT$Output$Xon		;Check if Xon/Xoff is active
	JNZ	SD$Check$if$Xon		;Yes, check if Xon char input
					;No, assume Etx/Ack active
	MVI	A,ACK			;Check if input character is ACK
	CMP	B
	JNZ	SD$No$Protocol		;No, process character as data
SD$Output$Desuspend:			;Yes, device now ready
					;  to accept more data, so indicate
					;  output to device is de-suspended.
					;The non-interrupt driven output
					;  routine checks the suspend bit.
	MOV	A,M			;Get Status/Protocol byte again
	ANI	0FFH AND NOT DT$Output$Suspend ;Preserve all bits BUT suspend
	MOV	M,A			;Save back with suspend = 0
	JMP	SD$Exit			;Exit to interrupt service without
					;  saving data character
;
SD$Check$if$Xon:			;Xon/Xoff protocol active, so
					;  if Xoff received, suspend output
					;  if Xon, received, de-suspend output.
					;The non-interrupt driven output
					;  routine checks the suspend bit.
	MVI	A,XON			;Check if XON character input
	CMP	B
	JZ	SD$Output$Desuspend	;Yes, enable output to device
	MVI	A,XOFF			;Check if XOFF character input
	CMP	B
	JNZ	SD$No$Protocol		;No, process character as data
SD$Output$Suspend:			;Device needs pause in output of
					;  data, so indicate output suspended
	MOV	A,M			;Get Status/Protocol byte again
	ORI	DT$Output$Suspend	;Set suspend bit to 1
	MOV	M,A			;Save back in device table
	JMP	SD$Exit			;Exit to interrupt service without
					;  saving the input character.
;
SD$No$Protocol:
	LXI	H,DT$Buffer$Length$Mask	;Check if there is still space
	DAD	D			;  in the input buffer
	MOV	A,M			;Get length - 1
	INR	A			;Update to actual length
	LXI	H,DT$Character$Count	;Get current count of characters
	DAD	D			;  in buffer
	CMP	M			;Check if count = length
	JZ	SD$Buffer$Full		;Yes, output bell character
	PUSH	B			;Save data character
	LXI	H,DT$Put$Offset		;Compute address of character in
					;  input buffer
	CALL	Get$Address$In$Buffer	;HL -> Character position
	POP	B			;Recover input character
	MOV	M,B			;Save character in input buffer
				;Update number of characters in input
				;  buffer, checking if input should
				;  temporarily halted
	LXI	H,DT$Character$Count
	DAD	D
	INR	M			;Update character count
	MOV	A,M			;Get updated count
	LXI	H,DT$Stop$Input$Count	;Check if current count matches
	DAD	D			;  buffer full threshold
	CMP	M
	JNZ	SD$Check$Control	;Not at threshold, check if control
					;  character input
	LXI	H,DT$Status		;At threshold, check which means
	DAD	D			;  for pausing input are to be used
	MOV	A,M			;Get Status/Protocol byte
	ORI	DT$Input$Suspend	;Indicate input is suspended
	MOV	M,A			;Save updated status in table
	PUSH	PSW			;Save for later use
	ANI	DT$Input$RTS		;Check if Clear to Send to be dropped
	JZ	SD$Check$Input$Xon	;No
	LXI	H,DT$RTS$Control$Port	;Yes, get Control Port number
	DAD	D
	MOV	A,M
	STA	SD$Drop$RTS$Port		;Store in instruction below
	LXI	H,DT$Drop$RTS$Value
	DAD	D
	MOV	A,M			;Get value needed to drop RTS

	DB	OUT
SD$Drop$RTS$Port:
	DB	0		;<- Setup in instruction above
					;Drop into Input Xon test
SD$Check$Input$Xon:		;Check if Xon/Xoff protocol being used
				;  to temporarily suspend input
	POP	PSW			;Recover Status/Protocol byte
	ANI	DT$Input$Xon		;Check if Xon bit set
	JZ	SD$Check$Control	;No, see if control char. input
	MVI	C,XOFF			;Yes, output Xoff character
	CALL	Output$Data$Byte	;Output Data Byte
;
SD$Check$Control:		;Check if Control Character (other than
				;  CR, LF or Tab) input, and update
				;  count of control characters in buffer
	CALL	Check$Control$Char	;Check if Control Character
	JZ	SD$Exit			;No it is not a Control Character
	LXI	H,DT$Control$Count
	DAD	D
	INR	M			;Update count of control chars.
;
SD$Exit:			;Reset hardware interrupt system
	LXI	H,DT$Reset$Int$Port
	DAD	D
	MOV	A,M			;Get Reset Port number
	ORA	A			;Check if Port specified
					;  (assumes it will always be NZ)
	RZ				;Bypass reset if no port specified
	STA	SD$Reset$Int$Port	;Store in instruction below
	LXI	H,DT$Reset$Int$Value
	DAD	D
	MOV	A,M			;Get reset interrupt value

	DB	OUT
SD$Reset$Int$Port:
	DB	0	;<-- Setup in instruction above
	RET				;Return to interrupt service routine
;
SD$Buffer$Full:				;Input buffer completely full
	MVI	C,BELL			;Send bell character as desparate
	JMP	Output$Data$Byte	;  measure. Note JMP - return to
					;  caller will be done by subroutine
;
;#
;
;	Get Address in Buffer
;
;	This routine computes the address of the next character to
;	access in a Device Buffer.
;
;	Entry Parameters
;
;		DE -> Appropriate Device Table
;		HL = The offset in the Device Table of either the
;			Get$Offset or the Put$Offset
;
;	Exit Parameters
;
;		DE unchanged
;		HL -> Address in Character Buffer
;
Get$Address$In$Buffer:
	DAD	D			;HL -> Get/Put Offset in Dev. Table
	PUSH	H			;Preserve pointer to table
	MOV	C,M			;Get offset value
	MVI	B,0			;Make into word value
				;Update offset value, resetting to
				;  0 at end of buffer
	MOV	A,C			;Get copy of offset
	INR	A			;Update to next position
	LXI	H,DT$Buffer$Length$Mask
	DAD	D
	ANA	M			;Mask LS bits with length - 1
	POP	H			;Recover pointer to offset in table
	MOV	M,A			;Save new value (set to 0 if nec.)
	LXI	H,DT$Buffer$Base	;Get Base address of input buffer
	DAD	D			;HL -> Address of Buffer in table
	MOV	A,M			;Get LS byte of address
	INX	H			;HL -> MS byte of address
	MOV	H,M			;H = MS byte
	MOV	L,A			;L = LS byte
	DAD	B			;Add on offset to base
	RET

;
;#
;
;	Check Control Character
;
;	This routine checks the character in A to see if it is a
;	Control Character other than CR, LF or TAB. The result is
;	returned in the Z-flag.
;
;	Entry Parameters
;
;		A = Character to be checked
;
;	Exit Parameters
;
;		Zero status if A does not contain a Control Character
;			or if it is CR, LF or TAB
;
;		Non-zero if A contains a Control Character other than
;			CR, LF, or TAB.
Check$Control$Char:
	MVI	A,' ' - 1		;Space is first non-control char.
	CMP	B
	JC	CCC$No			;Not a control character
	MVI	A,CR			;Check if Carriage Return
	CMP	B
	JZ	CCC$No			;Not really a Control Character
	MVI	A,LF			;Check if Line Feed
	CMP	B
	JZ	CCC$No			;Not really a Control Character
	MVI	A,TAB			;Check if Horizontal Tab
	CMP	B
	JZ	CCC$No			;Not really a Control Character
	MVI	A,1			;Indicate a Control Character
	ORA	A
	RET
CCC$No:					;Indicate A does not contain
	XRA	A			;  a Control Character
	RET
;
;#
;
;	Output Data Byte
;
;	This is a simple polled output routine that outputs a single
;	character (in register C on entry) to the device specified in
;	the Device Table defined.
;	Preferably, this routine would have been re-entrant, however
;	it does have to store the port numbers. Therefore, to use it
;	from code executed with interrupts enabled, the instruction
;	sequence must be :
;
;		DI		;Interrupts off
;		CALL	Output$Data$Byte
;		EI		;Interrupts on
;
;	Failure to do so may cause involuntary re-entrancy.
;
;	Entry Parameters
;
;		C = Character to be output
;		DE -> Device Table
;
Output$Data$Byte:
	PUSH	B			;Save registers
	LXI	H,DT$Output$Ready	;Get output ready status mask
	DAD	D
	MOV	B,M
	LXI	H,DT$Status$Port	;Get status port number
	DAD	D
	MOV	A,M
	STA	ODB$Status$Port		;Store in instruction below
ODB$Wait$until$Ready:

	DB	IN			;Read status
ODB$Status$Port:
	DB	0	;<--  Setup in instruction above

	ANA	B			;Check if ready for output
	JZ	ODB$Wait$until$Ready	;No
	LXI	H,DT$Data$Port		;Get Data Port
	DAD	D
	MOV	A,M
	STA	ODB$Data$Port		;Store in instruction below
	MOV	A,C			;Get character to output

	DB	OUT
ODB$Data$Port:
	DB	0	;<--  Setup in instruction above

	POP	B			;Restore registers
	RET
;
;#
;
;
;	Input Status Routine
;
;	This routine returns a value in the A register indicating whether
;	there is one or more data characters waiting in the input buffer.
;	Some products, such as Microsoft BASIC, defeat normal type-ahead
;	by constantly "gobbling" characters in order to see if an incoming
;	Control-S, -Q or -C has been received. In order to preserve
;	type-ahead under these circumstances, the Input Status return
;	can, as an option selected by the user, only return "data waiting"
;	if the input buffer contains a Control-S, -Q or -C. This fools
;	Microsoft BASIC into allowing type ahead.
;
;	Entry Parameters
;
;		DE -> Device Table
;
;	Exit Parameters
;
;		A = 000H if no characters are waiting in the input
;			buffer
;
;
Get$Input$Status:
	LXI	H,DT$Status$2		;Check if Fake mode enabled
	DAD	D			;HL -> status byte in table
	MOV	A,M			;Get status byte
	ANI	DT$Fake$Typeahead	;Isolate status bit
	JZ	GIS$True$Status		;Fake mode disabled
				;
				;Fake mode - only indicates data
				;	ready if control chars in buffer
	LXI	H,DT$Control$Count	;Check if any control characters
	DAD	D			;in the input buffer
	XRA	A			;Cheap 0
	ORA	M			;Set flags according to count
	RZ				;Return indicating zero
GIS$Data$Ready:
	XRA	A			;Cheap 0
	DCR	A			;Set A = 0FFH and flags NZ
	RET				;Return to caller
;
GIS$True$Status:		;
				;True status, based on any characters
				;	ready in input buffer
	LHLD	CB$Forced$Input		;Check if any forced input waiting
	MOV	A,M			;Get next character of forced input
	ORA	A			;Check if non-zero
	JNZ	GIS$Data$Ready		;Yes - indicate data waiting

	LXI	H,DT$Character$Count	;Check if any characters at all
	DAD	D			;present in buffer
	MOV	A,M			;Get character count
	ORA	A
	RZ				;Empty buffer, A = 0, Z-set
	JMP	GIS$Data$Ready
;
;
;#
;
;	Real Time Clock Processing
;
;	Control is transferred to the RTC$Interrupt routine each time
;	the real time clock ticks. The tick count is downdated in order
;	to see if a complete second has elapsed - if so, the ASCII time
;	in the configuration block is updated.
;
;	Each tick, the Watchdog count is downdated to see if control
;	must be "forced" to a previously specified address on return
;	from the RTC interrupt. The Watchdog timer can be used to pull
;	control out of what would otherwise be an infinite loop - such
;	as waiting for the printer to come ready.
;
;
;	Set Watchdog
;
;	This is a non-interrupt level subroutine that simply sets the
;	Watchdog count and address
;
;	Entry Parameters
;
;		BC = Number of clock ticks before watchdog should
;			time out
;		HL = Address to which control will be transferred when
;			Watchdog times out
;
Set$Watchdog:
	DI				;Avoid interference from interrupts
	SHLD	RTC$Watchdog$Address	;Set address
	MOV	H,B
	MOV	L,C
	SHLD	RTC$Watchdog$Count	;Set count
	EI
	RET
;
;	
;#
;
			;Control is received here each time the
			;  Real Time Clock ticks
RTC$Interrupt:
	PUSH	PSW			;Save other registers
	SHLD	PI$User$HL		;Switch to local stack
	LXI	H,0
	DAD	SP			;Get user's stack
	SHLD	PI$User$Stack		;Save it
	LXI	SP,PI$Stack		;Switch to local stack
	PUSH	B
	PUSH	D

	LXI	H,RTC$Tick$Count	;Downdate tick count
	DCR	M
	JNZ	RTC$Check$Watchdog	;Is not at 0 yet
					;One second has elapsed so
	LDA	RTC$Ticks$per$Second	;Reset to original value
	MOV	M,A
					;Update ASCII Real Time Clock
	LXI	D,Time$in$ASCII$End	;DE -> 1 character after ASCII time
	LXI	H,Update$Time$End	;HL -> 1 character after control table
RTC$Update$Digit:
	DCX	D			;Downdate pointer to Time in ASCII
	DCX	H			;Downdate pointer to control table
	MOV	A,M			;Get next control character
	ORA	A			;Check if end of table and therefore
	JZ	RTC$Clock$Updated	;  all digits of clock updated
	JM	RTC$Update$Digit	;Skip over ":" in ASCII time
	LDAX	D			;Get next ASCII time digit
	INR	A			;Update it
	STAX	D			;  and store it back
	CMP	M			;Compare to maximum value
	JNZ	RTC$Clock$Updated	;No carry needed so update complete
	MVI	A,'0'			;Reset digit to ASCII 0
	STAX	D			;  and store back in ASCII time
	JMP	RTC$Update$Digit	;Go back for next digit
;
RTC$Clock$Updated:
RTC$Check$Watchdog:
	LHLD	RTC$Watchdog$Count	;Get current watchdog count
	DCX	H			;Downdate it
	MOV	A,H			;Check if it is now 0FFFFH
	ORA	A
	JM	RTC$Dog$Not$Set		;It must have been 0 beforehand
	ORA	L			;Check if it is now 0
	JNZ	RTC$Dog$NZ		;No - it has not timed out yet

					;Watchdog timed out, so "CALL"
					;  appropriate routine
	LXI	H,RTC$Watchdog$Return	;Setup return address
	PUSH	H			;  ready for RETurn
	LHLD	RTC$Watchdog$Address	;Transfer control as though by CALL
	PCHL
RTC$Watchdog$Return:			;Control will come back here from
					;  the user's Watchdog routine
	JMP	RTC$Dog$Not$Set		;Behave as though Watchdog not active

RTC$Dog$NZ:
	SHLD	RTC$Watchdog$Count	;Save downdated count
RTC$Dog$Not$Set:			;(Leaves count unchanged)
	MVI	A,IC$EOI		;Reset the interrupt controller chip
	OUT	IC$OCW2$Port

	POP	D			;Restore registers from local stack
	POP	B
	LHLD	PI$User$Stack		;Switch back to user's stack
	SPHL
	LHLD	PI$User$HL		;Recover user's registers
	POP	PSW
	EI				;Re-enable interrupts
	RET
;
;#
;
;	Shift HL Right one bit
;
SHLR:
	ORA	A		;Clear Carry
	MOV	A,H		;Get MS Byte
	RAR			;Bit 7 set from previous Carry
				;Bit 0 goes into Carry
	MOV	H,A		;Put shifted MS byte back
	MOV	A,L		;Get LS Byte
	RAR			;Bit 7 = Bit 0 of MS Byte
	MOV	L,A		;Put back into result
	RET

;
;#
;	High Level Diskette Drivers
;
;	These drivers perform the following functions :
;
;	SELDSK	Select a specified disk and return the address of
;		the appropriate Disk Parameter Header.
;	SETTRK	Set the Track number for the next Read or Write.
;	SETSEC	Set the Sector number for the next Read or Write.
;	SETDMA	Set the DMA (Read/Write) address for the next Read or Write.
;	SECTRAN	Translate a logical sector number into a physical
;	HOME	Set the Track to 0 so that the next Read or Write will
;		be on Track 0.
;
;	In addition, the high level drivers are responsible for making
;	the 5 1/4" floppy diskettes that use a 512-byte sector appear
;	to CP/M as though they used a 128-byte sector. They do this
;	by using what is called Blocking/Deblocking code. This
;	Blocking/Deblocking code is described in more detail later in
;	this listing, just prior to the code itself.
;
;
;
;	Disk Parameter Tables
;
;	As discussed in Chapter 3, these describe the physical 
;	characteristics of the disk drives. In this example BIOS,
;	there are two types of disk drives, standard single-sided,
;	single-density 8", and double-sided, double density 5 1/4"
;	mini diskettes.
;
;	The standard 8" diskettes do not need to use the Blocking/
;	Deblocking code but the 5 1/4" drives do. Therefore an additional
;	byte has been prefixed onto the Disk Parameter Block to 
;	tell the disk drivers what each logical disk's physical
;	diskette type is, and whether or not it needs deblocking.
;
;
;	Disk Definition Tables
;
;	These consist of Disk Parameter Headers, with one entry
;	per logical disk driver, and Disk Parameter Blocks with
;	either one Parameter Block per logical disk, or the same
;	Parameter Block for several logical disks.
;
;#
;
Disk$Parameter$Headers:			;Described in Chapter 3
;
			;Logical Disk A: (5 1/4" Diskette)
	DW	Floppy$5$Skewtable	;5 1/4" Skew Table
	DW	0,0,0			;Reserved for CP/M
	DW	Directory$Buffer
	DW	Floppy$5$Parameter$Block
	DW	Disk$A$Workarea
	DW	Disk$A$Allocation$Vector	
;
			;Logical Disk B: (5 1/4" Diskette)
	DW	Floppy$5$Skewtable	;Shares same Skew Table as A:
	DW	0,0,0			;Reserved for CP/M
	DW	Directory$Buffer	;Share same buffer as A:
	DW	Floppy$5$Parameter$Block	;Same DPB as A:
	DW	Disk$B$Workarea			;Private workarea 
	DW	Disk$B$Allocation$Vector	;Private allocation vector 
;
			;Logical Disk C: (8" Floppy)
	DW	Floppy$8$Skewtable	;8" Skew Table
	DW	0,0,0			;Reserved for CP/M
	DW	Directory$Buffer	;Share same buffer as A:
	DW	Floppy$8$Parameter$Block
	DW	Disk$C$Workarea			;Private workarea
	DW	Disk$C$Allocation$Vector	;Private allocation vector
;
			;Logical Disk D: (8" Floppy)
	DW	Floppy$5$Skewtable	;Shares same Skew Table as A:
	DW	0,0,0			;Reserved for CP/M
	DW	Directory$Buffer	;Share same buffer as A:
	DW	Floppy$8$Parameter$Block	;Same DPB as C:
	DW	Disk$D$Workarea			;Private workarea
	DW	Disk$D$Allocation$Vector	;Private allocation vector

			;Logical Disk M: (Memory Disk)
M$Disk$DPH:
	DW	0			;No Skew required
	DW	0,0,0			;Reserved for CP/M
	DW	Directory$Buffer
	DW	M$Disk$Parameter$Block
	DW	0			;Disk cannot be changed - therefore
					;  no workarea is required
	DW	M$Disk$Allocation$Vector
;
;
;	Equates for Disk Parameter Block
;
;	Disk Types
;
Floppy$5	EQU	1	;5 1/4" Mini Floppy
Floppy$8	EQU	2	;8" Floppy (SS SD)
M$Disk		EQU	3	;Memory Disk
;
;	Blocking/Deblocking Indicator
;
Need$Deblocking	EQU	1000$0000B	;Sector size > 128 bytes
;
;#
;
;	Disk Parameter Blocks
;
;	5 1/4" Mini Floppy
;
				;Extra byte prefixed to indicate
				;disk type and blocking required.
	DB	Floppy$5 + Need$Deblocking
				;The parameter block has been amended
				;  to reflect the new layout of one
				;  track per diskette side - rather
				;  than viewing one track as both
				;  sides on a given head position.
				;It has also been adjusted to reflect
				;  one "new" track more being used for
				;  the CP/M image, with the resulting
				;  change in the number of allocation
				;  blocks and the number of reserved
				;  tracks.
Floppy$5$Parameter$Block:
	DW	36		;128-byte Sectors per Track
	DB	4		;Block Shift
	DB	15		;Block Mask
	DB	1		;Extent Mask
	DW	171		;Maximum Allocation Block Number
	DW	127		;Number of Directory Entries - 1
	DB	1100$0000B	;Bit Map for reserving 1 Alloc. Block
	DB	0000$0000B	;  for File Directory
	DW	32		;Disk Changed Workarea size
	DW	3		;Number of Tracks before Directory
;
;
;	Standard 8" Floppy
				;Extra byte prefixed to DPB for
				;this version of the BIOS
	DB	Floppy$8	;Indicates disk type and the fact
				;that no deblocking is required
Floppy$8$Parameter$Block:
	DW	26		;Sectors per Track
	DB	3		;Block Shift
	DB	7		;Block Mask
	DB	0		;Extent Mask
	DW	242		;Maximum Allocation Block Number
	DW	63		;Number of Directory Entries - 1
	DB	1100$0000B	;Bit Map for reserving 2 Alloc. Blocks
	DB	0000$0000B	;  for File Directory
	DW	16		;Disk Changed Workarea size
	DW	2		;Number of Tracks before Directory
;
;	M$Disk
;
				;The M$Disk presumes that 4 x 48K memory
				;  banks are available - the following
				;  table describes the disk as having
				;  8 tracks, two tracks per memory bank
				;  with each track having 192 128-byte
				;  sectors.
				;  The track number divided by 2 will be
				;  used to do bank select.
	DB	M$Disk		;Type is M$Disk - no deblocking
M$Disk$Parameter$Block:
	DW	192		;Sectors per "Track". Each track is
				;  a 24K of memory
	DB	3		;Block Shift (1024 byte Allocation)
	DB	7		;Block Mask
	DB	0		;Extent Mask
	DW	192		;Maximum Allocation Block Number
	DW	63		;Number of Directory Entries -1
	DB	1100$0000B	;Bit Map for reserving 2 Allocation Blocks
	DB	0000$0000B	;  for File Directory
	DW	0		;Disk Cannot be changed - therefore no
				;  work area
	DW	0		;No reserved tracks
;
Number$of$Logical$Disks		EQU	4
;
;#
;
SELDSK:		;Select disk in Register C
			;C = 0 for drive A, 1 for B, etc.
			;Return the address of the appropriate
			;Disk Parameter Header in HL, or 0000H
			;if the Selected Disk does not exist.	
			;
	LXI	H,0		;Assume an error
	MOV	A,C		;Check if requested disk valid

	CPI	'M' - 'A'	;Check if Memory Disk
	JZ	SELDSK$M$Disk	;Yes

	CPI	Number$of$Logical$Disks
	RNC			;Return if > Maximum number of disks
;
	STA	Selected$Disk	;Save Selected Disk number
				;Set up to return DPH address
	MOV	L,A		;Make disk into word value
	MVI	H,0
				;Compute offset down Disk Parameter
				;Header table by multiplying by
				;Parameter Header length (16 bytes)
	DAD	H		;*2
	DAD	H		;*4
	DAD	H		;*8
	DAD	H		;*16
	LXI	D,Disk$Parameter$Headers	;Get Base address
	DAD	D		;DE -> Appropriate DPH
	PUSH	H		;Save DPH address
;
				;Access Disk Parameter Block in order
				;to extract special prefix byte that
				;identifies disk type and whether
				;Deblocking is required
				;			
	LXI	D,10		;Get DPB pointer offset in DPH
	DAD	D		;DE -> DPB address in DPH
	MOV	E,M		;Get DPB address in DE
	INX	H
	MOV	D,M
	XCHG			;DE -> DPB

SELDSK$Set$Disk$Type:
	DCX	H		;DE -> Prefix Byte
	MOV	A,M		;Get Prefix Byte
	ANI	0FH		;Isolate Disk Type
	STA	Selected$Disk$Type	;Save for use in low level driver
	MOV	A,M		;Get another copy of Prefix Byte
	ANI	Need$Deblocking		;Isolate Deblocking flag
	STA	Selected$Disk$Deblock	;Save for use in low level driver
	POP	H		;Recover DPH Pointer
	RET
;
SELDSK$M$Disk:				;M$Disk Selected
	LXI	H,M$Disk$DPH		;Return correct Parameter Header
	JMP	SELDSK$Set$Disk$Type	;Resume normal processing
;
;#
;
;	Set Logical Track for next Read or Write
;
SETTRK:
	MOV	H,B		;Selected Track in BC on entry
	MOV	L,C
	SHLD	Selected$Track	;Save for low level driver
	RET
;
;#
;
;	Set Logical Sector for next Read or Write
;
;
SETSEC:				;Logical sector in C on entry
	MOV	A,C
	STA	Selected$Sector	;Save for low level driver
	RET
;
;#
;
;	Set Disk DMA (Input/Output) Address for next Read or Write
;
DMA$Address:	DW	0		;DMA Address
;
SETDMA:				;Address in BC on entry
	MOV	L,C		;Move to HL to save
	MOV	H,B
	SHLD	DMA$Address	;Save for low level driver
	RET
;
;#
;
;	Translate logical sector number to physical
;
;	Sector Translation Tables
;	These tables are indexed using the logical sector number,
;	and contain the corresponding physical sector number.
;
Floppy$5$Skewtable:	;Each physical sector contains four
			;128-byte sectors.
	;	Physical 128b	Logical 128b       Physical 512-byte
	DB	00,01,02,03	;00,01,02,03		0  )
	DB	16,17,18,19	;04,05,06,07		4  )
	DB	32,33,34,35	;08,09,10,11		8  )
	DB	12,13,14,15	;12,13,14,15		3  ) Head
	DB	28,29,30,31	;16,17,18,19		7  )  0
	DB	08,09,10,11	;20,21,22,23		2  )
	DB	24,25,26,27	;24,25,26,27		6  )
	DB	04,05,06,07	;28,29,30,31		1  )
	DB	20,21,22,23	;32,33,34,35		5  )
;
	DB	36,37,38,39	;36,37,38,39		0  ]
	DB	52,53,54,55	;40,41,42,43		4  ] 
	DB	68,69,70,71	;44,45,46,47		8  ]
	DB	48,49,50,51	;48,49,50,51		3  ] Head
	DB	64,65,66,67	;52,53,54,55		7  ]  1
	DB	44,45,46,47	;56,57,58,59		2  ]
	DB	60,61,62,63	;60,61,62,63		6  ]
	DB	40,41,42,43	;64,65,66,67		1  ]
	DB	56,57,58,59	;68,69,70,71		5  ]
;
;
Floppy$8$Skewtable:	;Standard 8" Driver
	;	01,02,03,04,05,06,07,08,09,10	 Logical Sectors
	DB	01,07,13,19,25,05,11,17,23,03	;Physical Sectors
	;
	;	11,12,13,14,15,16,17,18,19,20	 Logical Sectors	
	DB	09,15,21,02,08,14,20,26,06,12	;Physical Sectors
	;
	;	21,22,23,24,25,26	 Logical Sectors
	DB	18,24,04,10,16,22	;Physical Sectors

;#
;
SECTRAN:			;Translate logical sector into physical
			;On entry, BC = Logical Sector number
			;	   DE -> Appropriate Skew Table
			;
			;on exit, HL = Physical Sector number
	XCHG			;HL -> Skew Table base
	DAD	B		;Add on Logical Sector number
	MOV	L,M		;Get Physical Sector number
	MVI	H,0		;Make into a 16-bit value
	RET
;	
;#
;
;
HOME:			;Home the selected logical disk to track 0.
			;Before doing this, a check must be made to see
			;if the Physical Disk Buffer has information in
			;it that must be written out. This is indicated by
			;a flag, Must$Write$Buffer, that is set in the
			;Deblocking code.
			;
	LDA	Must$Write$Buffer	;Check if Physical Buffer must
	ORA	A			;  be written out to disk
	JNZ	HOME$No$Write
	STA	Data$In$Disk$Buffer	;No, so indicate that buffer
					;  is now unoccupied.
HOME$No$Write:
	MVI	C,0			;Set to track 0 (logically -
	CALL	SETTRK			;  no actual disk operation occurs)
	RET

;
;#
;	Data written to or read from the mini-floppy drive is transferred
;	via a Physical Buffer that is one complete track in length,
;	9 * 512 bytes. It is declared at the end of the BIOS, and has
;	some small amount of initialization code "hidden" in it.
;
;	The Blocking/Deblocking code attempts to minimise the amount
;	of actual disk I/O by storing the disk and  track 
;	currently residing in the Physical Buffer. 
;	If a read request occurs of a 128-byte CP/M 'sector' 
;	that already is in the Physical Buffer, no disk access occurs.
;	If a write request occurs, if the 128-byte CP/M 'sector'
;	is already in the Physical Buffer, no disk access will occur,
;	UNLESS the BDOS indicates that it is writing to the directory.
;	Directory writes cause an immediate write to disk of the entire
;	track in the Physical Buffer.
;
;
Allocation$Block$Size	EQU	2048
Physical$Sec$Per$Track	EQU	9	;Adjusted to reflect a "new"
					;  track is only one side of the
					;  disk.
Physical$Sector$Size	EQU	512	;This is the actual sector size
					;  for the 5 1/4" mini-floppy diskettes.
					;The 8" diskettes and Memory Disk
					;  use 128-byte sectors.
					;Declare the Physical Disk Buffer for the
					;5 1/4" Diskettes
CPM$Sec$Per$Physical	EQU	Physical$Sector$Size/128
CPM$Sec$Per$Track	EQU	CPM$Sec$Per$Physical*Physical$Sec$Per$Track
Bytes$Per$Track		EQU	Physical$Sec$Per$Track*Physical$Sector$Size
Sector$Mask		EQU	CPM$Sec$Per$Physical-1
Sector$Bit$Shift	EQU	2		;LOG2(CPM$Sec$Per$Physical)
;
			;These are the values handed over by the BDOS
			;  when it calls the WRITE operation.
			;The allocated/unallocated indicates whether the
			;  BDOS wishes to write to an unallocated Allocation
			;  block (it only indicates this for the first
			;  128-byte sector write), or to an Allocation block
			;  that has already been allocated to a file.
			;The BDOS also indicates if it wishes to write to 
			;  the File Directory.
			;
Write$Allocated		EQU	0
Write$Directory		EQU	1
Write$Unallocated	EQU	2	;<== Ignored for track buffering
;
Write$Type:		DB	0	;Contains the type of write as
					;  indicated by the BDOS.
;
;
In$Buffer$Dk$Trk:		;Variables for Physical sector currently
				;  in Disk$Buffer in memory
In$Buffer$Disk: 	DB	0	;) These are moved and compared
In$Buffer$Track: 	DW	0	;) as a group, so do not alter
					; these lines.
In$Buffer$Disk$Type:	DB	0	;Disk Type for sector in buffer
;
Data$In$Disk$Buffer: 	DB	0	;When Non-zero, the Disk Buffer has
					;  data from the disk in it.
Must$Write$Buffer: 	DB	0	;Non-zero when data has been written
					;  into Disk$Buffer but not yet
					;  written out to disk. 
;
Selected$Dk$Trk:		;Variables for Selected Disk, Track and Sector
				; (Selected by SELDSK, SETTRK and SETSEC).
Selected$Disk:		DB	0	;) These are moved and compared
Selected$Track:		DW	0	;) as a group so do not alter order.

Selected$Sector:	DB	0	;Not part of group but needed here.

Selected$Physical$Sector: DB	0	;Selected Physical Sector derived 
					;  from Selected (CP/M) sector by 
					;  shifting it right the number of 
					;  bits specified by Sector$Bit$Shift.

;

Disk$Error$Flag: 	DB	0	;Non-zero to indicate an error
					;  that could not be recovered
					;  by the disk drivers. The BDOS
					;  will output a 'Bad Sector' message.
Disk$Hung$Flag:		DB	0	;Non-zero if a Watchdog timeout
					;  occurs
Disk$Timer		EQU	600	;Number of 16.66ms clock ticks
					;  for a 10 second timeout
;
				;Flags used inside the deblocking code

Read$Operation: 	DB	0	;Non-zero when a CP/M 128-byte
					;  sector is to be read.
Selected$Disk$Deblock:	DB	0	;Non-zero when the selected disk
					;  needs deblocking (Set in SELDSK).
Selected$Disk$Type:	DB	0	;Indicates 8" or 5 1/4" Floppy or 
					;  M$Disk selected. (Set in SELDSK).
;
;#
;
;	Read in the 128-byte CP/M sector specified by previous calls
;	to Select Disk, Set Track and Sector. The sector will be read
;	into the address specified in the previous Set DMA Address call.
;
;	If reading from a disk drive using sectors larger than 128 bytes,
;	deblocking code will be used to 'unpack' a 128-byte sector from
;	the physical sector.
READ:
	LDA	Selected$Disk$Deblock	;Check if Deblocking needed 
	ORA	A			;(flag was set in SELDSK call)
	JZ	Read$No$Deblock		;No, use normal non-deblocked

				;The deblocking algorithm used is such
				;  that a Read operation can be viewed,
				;  up until the actual data transfer as though
				;  it was the first write to an unallocated 
				;  Allocation Block
	MVI	A,1			;Indicate that it is really a Read
	STA	Read$Operation		;  that is to be performed

	MVI	A,Write$Allocated	;Fake deblocking code into believing
	STA	Write$Type		;  that this is a write to an
					;  allocated Allocation Block.
	JMP	Perform$Read$Write	;Use common code to execute read
;
;#
;	Write a 128-byte sector from the current DMA Address out
;	the previously selected Disk, Track and Sector.
;
;	On arrival here, the BDOS will have set register C to indicate
;	whether this write operation is to an already Allocated Allocation
;	Block (which means a pre-read of the sector may be needed), or
;	to the Directory (in which case the data will be written to the
;	disk immediately).
;
;	Only writes to the directory take place immediately, in all other
;	cases, the data will be moved from the DMA address into the disk
;	buffer, and only be written out when circumstances force the
;	transfer. The number of physical disk operations can therefore
;	be reduced considerably.
;
WRITE:
	LDA	Selected$Disk$Deblock	;Check if deblocking is required
	ORA	A			;(flag set in SELDSK call)
	JZ	Write$No$Deblock

	XRA	A			;Indicate that a write operation
	STA	Read$Operation		;  is required (i.e NOT a read)
	MOV	A,C			;Save the BDOS Write Type
	ANI	1			;  but only distinguish between
					;  Write to allocated block or
	STA	Write$Type		;  directory write
;
;
;#
;
Perform$Read$Write:	;Common code to execute both reads and
			;  writes of 128-byte sectors.
	XRA	A		;Assume that no disk errors will
	STA	Disk$Error$Flag	;occur

	LDA	Selected$Sector	;Convert Selected 128-byte sector
	RAR			;  into physical sector by dividing by 4
	RAR
	ANI	3FH		;Remove any unwanted bits
	STA	Selected$Physical$Sector
					;
	LXI	H,Data$In$Disk$Buffer	;Check if disk buffer already has
	MOV	A,M			;  data in it.
	MVI	M,1			;(Unconditionally indicate that
					; the buffer now has data in it)
	ORA	A			;Did it indeed have data in it?
	JZ	Read$Track$into$Buffer	;No, proceed to read a physical
					;  track into the buffer.
				;
				;The buffer does have a physical track
				;  in it. Check if it is the right one.
				;
	LXI	D,In$Buffer$Dk$Trk	;Check if track in buffer is the
	LXI	H,Selected$Dk$Trk	;  same as that selected earlier.
	CALL	Compare$Dk$Trk		;Compare ONLY Disk and Track
	JZ	Track$In$Buffer		;Yes - it is already in buffer

					;No, it will have to be read in
					;  over current contents of buffer
	LDA	Must$Write$Buffer	;Check if buffer has data in that
	ORA	A			;  must be written out first.
	CNZ	Write$Physical		;Yes, write it out
;
Read$Track$into$Buffer:
	CALL	Set$In$Buffer$Dk$Trk	;Set in buffer variables from
					;  Selected Disk, Track
					;  to reflect which track is in the
					;  buffer now
	CALL	Read$Physical		;Read the track into the buffer
	XRA	A			;Reset the flag to reflect buffer
	STA	Must$Write$Buffer	;  contents.
;
Track$In$Buffer:		;Selected Track and
				;  Disk is already in the buffer.
				;Convert the Selected CP/M (128-byte)
				;  sector into a relative address down
				;  the buffer.
	LDA	Selected$Sector	;Get Selected Sector Number
	MOV	L,A		;Multiply by 128 by shifting 16-bit value
	MVI	H,0		;left 7 bits
	DAD	H		;* 2
	DAD	H		;* 4
	DAD	H		;* 8
	DAD	H		;* 16
	DAD	H		;* 32
	DAD	H		;* 64
	DAD	H		;* 128
;
	LXI	D,Disk$Buffer	;Get base address of Disk Buffer
	DAD	D		;Add on Sector number * 128
				;HL -> 128-byte sector number start
				;  address in disk buffer
	XCHG			;DE -> Sector in Disk Buffer
	LHLD	DMA$Address	;Get DMA address set in SETDMA call
	XCHG			;Assume a Read operation, so
				;  DE -> DMA Address
				;  HL -> Sector in Disk Buffer
	MVI	C,128/8		;Because of the faster method used
				;  to move data in and out of the
				;  disk buffer, (eight bytes moved per
				;  loop iteration) the count need only
				;  be 1/8th of normal.
				;At this point -
				;	C = Loop Count
				;	DE -> DMA Address
				;	HL -> Sector in Disk Buffer
	LDA	Read$Operation	;Determine whether data is to be moved
	ORA	A		;  out of the buffer (Read) or into the
	JNZ	Buffer$Move	;  buffer (Write)
				;Writing into buffer
					;(A must be 0 get here)
	INR	A			;Set flag to force a write
	STA	Must$Write$Buffer	;  of the disk buffer later on.
	XCHG				;Make DE -> Sector in Disk Buffer
					;     HL -> DMA Address
;
;
Buffer$Move:
	CALL	Move$8		;Moves 8 bytes * C times from (HL)
				;  to (DE)

				;
	LDA	Write$Type	;If Write to Directory, write out
	CPI	Write$Directory	;  buffer immediately
	LDA	Disk$Error$Flag	;Get error flag in case delayed write or read
	RNZ			;Return if delayed write or read
				;
	ORA	A		;Check if any disk errors have occured
	RNZ			;Yes, abandon attempt to write to directory
				;
	XRA	A		;Clear flag that indicates buffer must be
	STA	Must$Write$Buffer	;  written out.
	CALL	Write$Physical	;Write buffer out to physical track
	LDA	Disk$Error$Flag	;Return error flag to caller
	RET
;
;
;
Set$In$Buffer$Dk$Trk:			;Indicate Selected Disk, Track
					;  now residing in buffer
	LDA	Selected$Disk
	STA	In$Buffer$Disk

	LHLD	Selected$Track
	SHLD	In$Buffer$Track	

	LDA	Selected$Disk$Type	;Also reflect disk type 
	STA	In$Buffer$Disk$Type	

	RET
;
;
Compare$Dk$Trk:			;Compares just the Disk and Track
				;  pointed to by DE and HL
	MVI	C,3		;Disk (1), Track (2)
Compare$Dk$Trk$Loop:
	LDAX	D		;Get comparitor
	CMP	M		;Compare with comparand
	RNZ			;Abandon comparison if inequality found
	INX	D		;Update comparitor pointer
	INX	H		;Update comparand pointer
	DCR	C		;Count down on loop count
	RZ			;Return (with Zero flag set)
	JMP	Compare$Dk$Trk$Loop
;
;
Move$Dk$Trk:			;Moves the Disk, Track
				;  variables pointed at by HL to
				;  those pointed at by DE
	MVI	C,3		;Disk (1), Track (2)
Move$Dk$Trk$Loop:
	MOV	A,M		;Get source byte
	STAX	D		;Store in destination
	INX	D		;Update pointers
	INX	H
	DCR	C		;Count down on byte count
	RZ			;Return if all bytes moved
	JMP	Move$Dk$Trk$Loop
;
;#
;
;	Move 8 bytes
;
;	This routine moves 8 bytes at a block, C times, from
;	(HL) to (DE). It uses "drop through" coding to speed
;	up execution.
;
;	Entry Parameters
;
;		C = Number of 8 byte blocks to move
;		DE -> Destination Address
;		HL -> Source Address
;		
Move$8:	
	MOV	A,M		;Get byte from source
	STAX	D		;Put into destination
	INX	D		;Update pointers
	INX	H
	MOV	A,M		;Get byte from source
	STAX	D		;Put into destination
	INX	D		;Update pointers
	INX	H
	MOV	A,M		;Get byte from source
	STAX	D		;Put into destination
	INX	D		;Update pointers
	INX	H
	MOV	A,M		;Get byte from source
	STAX	D		;Put into destination
	INX	D		;Update pointers
	INX	H
	MOV	A,M		;Get byte from source
	STAX	D		;Put into destination
	INX	D		;Update pointers
	INX	H
	MOV	A,M		;Get byte from source
	STAX	D		;Put into destination
	INX	D		;Update pointers
	INX	H
	MOV	A,M		;Get byte from source
	STAX	D		;Put into destination
	INX	D		;Update pointers
	INX	H
	MOV	A,M		;Get byte from source
	STAX	D		;Put into destination
	INX	D		;Update pointers
	INX	H

	DCR	C		;Count down on loop counter
	JNZ	Move$8		;Repeat until done
	RET

;
;#
;
;	Introduction to the Disk Controllers on this computer system.
;
;	There are two 'smart' disk controllers on this system, one
;	for the 8" Floppy Diskette drives, and one for the 5 1/4"
;	Mini-diskette drives.
;
;	The controllers are 'hard-wired' to monitor certain locations
;	in memory to detect when they are to perform some disk
;	operation. The 8" controller looks at location 0040H, and
;	the 5 1/4" controller looks at location 0045H. These are
;	called their Disk Control Bytes. If the most significant
;	bit of a Disk Control Byte is set, the controller will then
;	look at the word following the respective Control Bytes.
;	This word must contain the address of a valid Disk Control
;	Table that specifies the exact disk operation to be performed.
;
;	Once the operation has been completed, the controller resets
;	its Disk Control Byte to 00H, and it is this that indicates
;	completion to the disk driver code.
;
;	The controller also sets a return code in a Disk Status Blocke -
;	both controllers use the SAME location for this; 0043H.
;	If the first byte of this Status Block is less than 80H, then
;	a disk error has occurred. For this simple BIOS, no further details
;	of the Status settings are relevant. Note that the disk controller
;	has built-in re-try logic - reads and writes are attempted ten
;	times before the controller returns an error.
;
;	The Disk Control Table layout is shown below. Note that the
;	controllers have the capability for Control Tables to be
;	chained together so that a sequence of disk operations can
;	be initiated. In this BIOS this feature is not used. However,
;	the controller requires that the chain pointers in the
;	Disk Control Tables be pointed back to the main Control Bytes
;	in order to indicate the end of the chain.
;
Disk$Control$8		EQU	40H	;8" Control Byte
Command$Block$8		EQU	41H	;Control Table Pointer
;
Disk$Status$Block	EQU	43H	;8" AND 5 1/4" Status Block
;
Disk$Control$5		EQU	45H	;5 1/4" Control Byte
Command$Block$5		EQU	46H	;Control Table Pointer
;
;
;	Floppy Disk Control Tables
;
Floppy$Command:			DB	0	;Command
Floppy$Read$Code		EQU	01H
Floppy$Write$Code		EQU	02H
Floppy$Unit:			DB	0	;Unit (Drive) number = 0 or 1
Floppy$Head:			DB	0	;Head number = 0 or 1 
Floppy$Track:			DB	0	;Track number
Floppy$Sector:			DB	0	;Sector number
Floppy$Byte$Count:		DW	0	;Number of bytes to read/write
Floppy$DMA$Address:		DW	0	;Transfer Address
Floppy$Next$Status$Block:	DW	0	;Pointer to next Status Block
						; if commands are chained.
Floppy$Next$Control$Location:	DW	0	;Pointer to next Control Byte
						;  if commands are chained.
;
;#
;
;
Write$No$Deblock:		;Write contents of Disk Buffer to
				;  correct sector.
	MVI	A,Floppy$Write$Code	;Get Write Function code
	JMP	Common$No$Deblock	;Go to common code
Read$No$Deblock:		;Read previously selected Sector
				;  into Disk Buffer.
	MVI	A,Floppy$Read$Code	;Get Read Function code
Common$No$Deblock:
	STA	Floppy$Command	;Set Command Function code
				;Set up non-deblocked command table

	LDA	Selected$Disk$Type	;Check if Memory Disk operation
	CPI	M$Disk
	JZ	M$Disk$Transfer	;Yes it is M$Disk

No$Deblock$Retry:		;Re-entry point to retry after error
	LXI	H,128		;Bytes per sector
	SHLD	Floppy$Byte$Count
	XRA	A		;8" Floppy only has head 0
	STA	Floppy$Head
				;
	LDA	Selected$Disk	;8" Floppy controller only knows about
				;  units 0 and 1 so Selected$Disk must
				;  be converted
	ANI	01H		;Turn into 0 or 1
	STA	Floppy$Unit	;Set unit number
				;
	LDA	Selected$Track	
	STA	Floppy$Track	;Set track number
				;
	LDA	Selected$Sector
	STA	Floppy$Sector	;Set sector number
				;
	LHLD	DMA$Address	;Transfer directly between DMA Address
	SHLD	Floppy$DMA$Address	;and 8" controller.
				;
				;The disk controller can accept chained
				;  disk control tables, but in this case,
				;  they are not used, so the 'Next' pointers
				;  must be pointed back at the initial
				;  control bytes in the base page.
	LXI	H,Disk$Status$Block		;Point next status back at
	SHLD	Floppy$Next$Status$Block	;  main status block
						;
	LXI	H,Disk$Control$8		;Point next control byte
	SHLD	Floppy$Next$Control$Location	;  back at main control byte
					;
	LXI	H,Floppy$Command	;Point controller at control table
	SHLD	Command$Block$8
					;
	LXI	H,Disk$Control$8	;Activate controller to perform
	MVI	M,80H			;  operation.
	JMP	Wait$For$Disk$Complete

;
;#
;	Memory Disk Driver
;
;	This routine must use an intermediary buffer as the
;	DMA Address in Bank ("Track") 0, occupies the same
;	place in the overall address space as the M$Disk itself.
;	The M$Disk$Buffer is above the 48K mark, and therefore
;	remains in the address space regardless of which bank/track
;	is selected.
;
;
;	For writing, the 128-byte sector must be processed :
;
;		1. Move sector DMA$Address -> M$Disk$Buffer
;		2. Select correct track (+1 to get bank number)
;		3. Move sector M$Disk$Buffer -> M$Disk image
;		4. Select Bank 0
;
;	For reading, the processing is :
;
;		1. Select correct track/bank
;		2. Move sector M$Disk image -> M$Disk$Buffer
;		3. Select Bank 0
;		4. Move sector M$Disk$Buffer -> DMA$Address
;
;	If there is any risk of any interrupt causing control
;	to be transferred to an address below 48K, interrupts MUST
;	be disabled when any Bank other than 0 is selected.
;
M$Disk$Transfer:
	LDA	Selected$Sector	;Compute address in memory
	MOV	L,A		;  by muliplying sector * 128
	MVI	H,0
	DAD	H		;* 2
	DAD	H		;* 4
	DAD	H		;* 8
	DAD	H		;* 16
	DAD	H		;* 32
	DAD	H		;* 64
	DAD	H		;* 128

	LDA	Selected$Track	;Compute which half of bank sector
				;  is in by using LS bit of Track
	MOV	B,A		;Save copy for later
	ANI	1		;Isolate lower/upper indicator
	JZ	M$Disk$Lower$Half

	LXI	D,(48 * 1024) / 2	;Upper half - so bias address
	DAD	D

M$Disk$Lower$Half:		;HL -> Sector in memory
	MOV	A,B		;Recover Selected Track
	RAR			;Divide by 2 to get Bank number
	INR	A		;Bank 1 is first track
	MOV	B,A		;Preserve for later use

	LDA	Floppy$Command	;Check if reading or writing
	CPI	Floppy$Write$Code
	JZ	M$Disk$Write	;Writing
				;Reading

	CALL	Select$Bank	;Select correct memory bank
	LXI	D,M$Disk$Buffer	;DE -> M$Disk$Buffer, HL -> M$Disk image
	MVI	C,128/8		;Number of 8 byte blocks to move
	CALL	Move$8

	MVI	B,0		;Revert to normal memory bank
	CALL	Select$Bank

	LHLD	DMA$Address	;Get user's DMA Address
	LXI	D,M$Disk$Buffer
	XCHG			;DE -> User's DMA, HL -> M$Disk Buffer
	MVI	C,128/8		;Number of 8 byte blocks to move
	CALL	Move$8

	XRA	A		;Indicate no error
	RET

M$Disk$Write:			;Writing
	PUSH	H		;Save sector's address in M$Disk image
	LHLD	DMA$Address	;Move sector into M$Disk$Buffer
	LXI	D,M$Disk$Buffer
	MVI	C,128/8		;Number of 8 byte blocks to move
	CALL	Move$8		;(Does not use B register)
				;B = Memory Bank to select
	CALL	Select$Bank

	POP	D		;Recover sector's M$Disk image address
	LXI	H,M$Disk$Buffer
	MVI	C,128/8
	CALL	Move$8		;Move into M$Disk image

	MVI	B,0		;Select bank 0
	CALL	Select$Bank

	XRA	A		;Indicate no error
	RET
;
;#
;	Select Bank
;
;	This routine switches in the required memory bank.
;	Note that the hardware port that controls bank selection
;	also has other bits in it - these are preserved across
;	bank selections.
;
;	Entry Parameter
;
;		B = Bank Number
;
Bank$Control$Port	EQU	40H
Bank$Mask		EQU	1111$1000B	;To PRESERVE other bits
;
Select$Bank:
	IN	Bank$Control$Port	;Get current setting in port
	ANI	Bank$Mask		;Preserve all other bits
	ORA	B			;Set bank code
	OUT	Bank$Control$Port	;Select the Bank
	RET
;
;#
;

Write$Physical:			;Write contents of Disk Buffer to
				;  correct sector.
	MVI	A,Floppy$Write$Code	;Get Write Function code
	JMP	Common$Physical	;Go to common code
Read$Physical:			;Read previously selected Sector
				;  into Disk Buffer.
	MVI	A,Floppy$Read$Code	;Get Read Function code
;
Common$Physical:
	STA	Floppy$Command	;Set command table

;
Deblock$Retry:			;Re-entry point to re-try after error
	LDA	In$Buffer$Disk$Type	;Get disk type currently in buffer
	CPI	Floppy$5		;Confirm it is a 5 1/4" Floppy
	JZ	Correct$Disk$Type	;Yes
	MVI	A,1			;No, indicate disk error
	STA	Disk$Error$Flag
	RET
Correct$Disk$Type:		;Setup disk control table
				;
	LDA	In$Buffer$Disk	;Convert disk number to 0 or 1
	ANI	1		;  for disk controller
	STA	Floppy$Unit

	LHLD	In$Buffer$Track	;Setup head and track number
	MOV	A,L		;Even numbered tracks will be on
	ANI	1		;  head 0, odd numbered on head 1
	STA	Floppy$Head	;Set head number

	MOV	A,L		;Note : this is single byte value
	RAR			;/2 for track (carry off from ANI above)
	STA	Floppy$Track

	MVI	A,1			;Start with sector 1 as a whole
	STA	Floppy$Sector		;  track will be transferred
					;
	LXI	H,Bytes$Per$Track	;Set byte count for complete
	SHLD	Floppy$Byte$Count	;  track to be transferred
					;
	LXI	H,Disk$Buffer		;Set transfer address to be
	SHLD	Floppy$DMA$Address	;  Disk Buffer.
					;
					;As only one control table is in
					;  use, close the Status and Busy
					;  chain pointers back to the
					;  main control bytes.
	LXI	H,Disk$Status$Block
	SHLD	Floppy$Next$Status$Block
	LXI	H,Disk$Control$5
	SHLD	Floppy$Next$Control$Location

	LXI	H,Floppy$Command	;Setup command block pointer
	SHLD	Command$Block$5

	LXI	H,Disk$Control$5	;Activate 5 1/4" disk controller
	MVI	M,80H
;
Wait$For$Disk$Complete:		;Wait until Disk Status Block indicates
				;  operation has completed, then check
				;  if any errors occurred.
				;On entry HL -> Disk Control byte
	XRA	A			;Ensure hung flag clear
	STA	Disk$Hung$Flag

	LXI	H,Disk$Timed$Out	;Setup Watchdog timer
	LXI	B,Disk$Timer		;Time delay
	CALL	Set$Watchdog
	LXI	H,Disk$Control$5	;Check complete (fix: AJL 082983)
Disk$Wait$Loop:
	MOV	A,M			;Get control byte
	ORA	A
	JZ	Disk$Complete		;Operation done

	LDA	Disk$Hung$Flag		;Also check if timed out
	ORA	A
	JNZ	Disk$Error		;Will be set to 40H

	JMP	Disk$Wait$Loop

Disk$Timed$Out:			;Control arrives here from Watchdog
				;  routine itself - so this is effectively
				;  part of the interrupt service routine.
	MVI	A,40H			;Set Disk Hung error code
	STA	Disk$Hung$Flag		;  into error flag to pull
					;  control out of loop
	RET				;Return to Watchdog routine

Disk$Complete:
	LXI	B,0			;Reset Watchdog timer
					;HL is irrelevant here
	CALL	Set$Watchdog

	LDA	Disk$Status$Block	;Complete - now check status
	CPI	80H			;Check if any errors occurred
	JC	Disk$Error		;Yes
;
Disk$Error$Ignore:
	XRA	A			;No
	STA	Disk$Error$Flag		;Clear error flag
	RET

;
;#
;	Disk Error Message handling
;
;
Disk$Error$Messages:		;This table is scanned, comparing the
				;  Disk Error Status with those in the
				;  table. Given a match, or even when
				;  then end of the table is reached, the
				;  address following the status value
				;  points to the correct message text.
	DB	40H
	DW	Disk$Msg$40
	DB	41H
	DW	Disk$Msg$41
	DB	42H
	DW	Disk$Msg$42
	DB	21H
	DW	Disk$Msg$21
	DB	22H
	DW	Disk$Msg$22
	DB	23H
	DW	Disk$Msg$23
	DB	24H
	DW	Disk$Msg$24
	DB	25H
	DW	Disk$Msg$25
	DB	11H
	DW	Disk$Msg$11
	DB	12H
	DW	Disk$Msg$12
	DB	13H
	DW	Disk$Msg$13
	DB	14H
	DW	Disk$Msg$14
	DB	15H
	DW	Disk$Msg$15
	DB	16H
	DW	Disk$Msg$16
	DB	0			;<== Terminator
	DW	Disk$Msg$Unknown	;Unmatched code
;
DEM$Entry$Size	EQU	3	;Disk Error Message Table entry size
;
;	Message Texts
;
Disk$Msg$40:	DB	'Hung',0	;Timeout message
Disk$Msg$41:	DB	'Not Ready',0
Disk$Msg$42:	DB	'Write Protected',0
Disk$Msg$21:	DB	'Data',0
Disk$Msg$22:	DB	'Format',0
Disk$Msg$23:	DB	'Missing Data Mark',0
Disk$Msg$24:	DB	'Bus Timeout',0
Disk$Msg$25:	DB	'Controller Timeout',0
Disk$Msg$11:	DB	'Drive Address',0
Disk$Msg$12:	DB	'Head Address',0
Disk$Msg$13:	DB	'Track Address',0
Disk$Msg$14:	DB	'Sector Address',0
Disk$Msg$15:	DB	'Bus Address',0
Disk$Msg$16:	DB	'Illegal Command',0
Disk$Msg$Unknown:	DB	'Unknown',0
;
Disk$EM$1:			;Main disk error message - part 1
		DB	BELL,CR,LF
		DB	'Disk ',0
;
				;Error Text output next
;
Disk$EM$2:			;Main disk error message - part 2
		DB	' Error ('
Disk$EM$Status:	DB	0,0	;Status code in Hex
		DB	')',CR,LF,'     Drive '
Disk$EM$Drive:	DB	0	;Disk Drive code, A,B...
		DB	', Head '
Disk$EM$Head:	DB	0	;Head number
		DB	', Track '
Disk$EM$Track:	DB	0,0	;Track number
		DB	', Sector '
Disk$EM$Sector:	DB	0,0	;Sector number
		DB	', Operation - '
		DB	0		;Terminator
;
Disk$EM$Read:	DB	'Read.',0	;Operation names
Disk$EM$Write:	DB	'Write.',0
;
;
Disk$Action$Confirm:
		DB	0	;Set to character entered by user
		DB	CR,LF,0
;
;	Disk Error Processor
;
;	This routine builds and outputs an error message.
;	The user is then given the opportunity to :
;
;		R - Retry the operation that caused the error.
;		I - Ignore the error and attempt to continue.
;		A - Abort the program and return to CP/M.
;
Disk$Error:
	PUSH	PSW		;Preserve error code from controller
	LXI	H,Disk$EM$Status	;Convert code for message
	CALL	CAH			;Converts A to hex

	LDA	In$Buffer$Disk		;Convert disk id. for message
	ADI	'A'			;Make into letter
	STA	Disk$EM$Drive

	LDA	Floppy$Head		;Convert head number
	ADI	'0'
	STA	Disk$EM$Head

	LDA	Floppy$Track		;Convert track number
	LXI	H,Disk$EM$Track
	CALL	CAH

	LDA	Floppy$Sector		;Convert sector number
	LXI	H,Disk$EM$Sector
	CALL	CAH

	LXI	H,Disk$EM$1		;Output first part of message
	CALL	Output$Error$Message

	POP	PSW			;Recover error status code
	MOV	B,A			;For comparisons
	LXI	H,Disk$Error$Messages - DEM$Entry$Size
					;HL -> Table - one entry
	LXI	D,DEM$Entry$Size	;Get entry size for loop below
Disk$Error$Next$Code:
	DAD	D			;Move to next (or first) entry

	MOV	A,M			;Get code number from table
	ORA	A			;Check if end of table
	JZ	Disk$Error$Matched	;Yes - pretend a match occurred
	CMP	B			;Compare to actual code
	JZ	Disk$Error$Matched	;Yes - exit from loop
	JMP	Disk$Error$Next$Code	;Check next code
;
Disk$Error$Matched:
	INX	H			;HL -> Address of text
	MOV	E,M			;Get Address into DE
	INX	H
	MOV	D,M
	XCHG				;HL -> Text
	CALL	Output$Error$Message	;Display explanatory text

	LXI	H,Disk$EM$2		;Display second part of message
	CALL	Output$Error$Message

	LXI	H,Disk$EM$Read		;Choose operation text
					;  (assume a read)
	LDA	Floppy$Command		;Get controller command
	CPI	Floppy$Read$Code
	JZ	Disk$Error$Read		;Yes
	LXI	H,Disk$EM$Write		;No - change address in HL
Disk$Error$Read:
	CALL	Output$Error$Message	;Display operation type
;
Disk$Error$Request$Action:		;Ask the user what to do next
	CALL	Request$User$Choice	;Display prompt and wait for input
					;  Returns with A = Upper case char.
	CPI	'R'			;Retry?
	JZ	Disk$Error$Retry
	CPI	'A'			;Abort
	JZ	System$Reset
	CPI	'I'			;Ignore
	JZ	Disk$Error$Ignore
	JMP	Disk$Error$Request$Action
;
Disk$Error$Retry:			;The decision on where to return to
					;  depends on whether the operation
					;  failed on a deblocked or
					;  non-deblocked drive
	LDA	Selected$Disk$Deblock
	ORA	A
	JNZ	Deblock$Retry
	JMP	No$Deblock$Retry
;
System$Reset:				;This is a radical approach, but
					;  it does cause CP/M to restart
	MVI	C,0			;System Reset
	CALL	BDOS

;
;
;	A to Upper
;
;	Converts the contents of the A register to an upper
;	case letter if it is currently a lower case letter.
;
;	Entry Parameters
;
;		A = Character to be converted
;
;	Exit Parameters
;
;		A = Converted character
;
A$To$Upper:
	CPI	'a'		;Compare to lower limit
	RC			;No need to convert
	CPI	'z' + 1		;Compare to upper limit
	RNC			;No need to convert
	ANI	5FH		;Convert to upper case
	RET
;
;	Convert A register to Hexadecimal
;
;	This subroutine converts the A register to Hexadecimal.
;
;	Entry Parameters
;
;		A = Value to be converted and output
;		HL -> Buffer area to receive two characters of output
;
;	Exit Parameters
;
;		HL -> Byte following last hex byte output
;
CAH:
	PUSH	PSW		;Take a copy of the value to be converted
	RRC			;Shift A right four places
	RRC
	RRC
	RRC
	CALL	CAH$Convert	;Convert to ASCII
	POP	PSW		;Get original value again
				;Drop into subroutine, which converts
				;  and returns to caller
CAH$Convert:
	ANI	0000$1111B	;Isolate LS four bits
	ADI	'0'		;Convert to ASCII
	CPI	'9' + 1		;Compare to maximum
	JC	CAH$Numeric	;No need to convert to A -> F
	ADI	7		;Convert to a letter
CAH$Numeric:
	MOV	M,A		;Save character away
	INX	H		;Update character pointer
	RET

;
;
;#
;
;	Disk Control Table images for Warm Boot
;
Boot$Control$Part$1:
	DB	1			;Read Function
	DB	0			;Unit (Drive) number
	DB	0			;Head number
	DB	0			;Track number
	DB	2			;Starting sector number
	DW	8*512			;Number of bytes to read
	DW	CCP$Entry		;Read into this address
	DW	Disk$Status$Block	;Pointer to next Status Block
	DW	Disk$Control$5		;Pointer to next Control Table
Boot$Control$Part2:
	DB	1			;Read Function
	DB	0			;Unit (Drive) number
	DB	1			;Head number
	DB	0			;Track Number
	DB	1			;Starting Sector number
	DW	3*512			;Number of bytes to read
	DW	CCP$Entry + (8*512)	;Read into this address
	DW	Disk$Status$Block	;Pointer to next Status Block
	DW	Disk$Control$5		;Pointer to next Control Table

;
;
;#
;
WBOOT:		;Warm Boot Entry
			;On warm boot, the CCP and BDOS must be reloaded
			;  into memory. In this BIOS, only the 5 1/4"
			;  diskettes will be used, therefore this code
			;  is hardware specific to the controller. Two
			;  prefabricated control tables are used.
	LXI	SP,80H
	LXI	D,Boot$Control$Part1	;Execute first read of warm boot
	CALL	Warm$Boot$Read		;Load Drive 0, Track 0, 
					;  Head 0, Sectors 2 to 8
	LXI	D,Boot$Control$Part2	;Execute second read
	CALL	Warm$Boot$Read		;Load Drive 0, Track 0,
					;  Head 1, Sectors 1 - 3
	CALL	Patch$CPM		;Make custom enhancements patches
	JMP	Enter$CPM		;Setup base page and enter CCP
;
Warm$Boot$Read:			;On entry, DE -> Control Table image
				;This control table is moved into
				;  the main disk control table and
				;  then the controller activated.
	LXI	H,Floppy$Command	;HL -> actual control table
	SHLD	Command$Block$5		;Tell the controller its address
					;Move the control table image
					;  into the control table itself.
	MVI	C,13		;Set byte count
Warm$Boot$Move:
	LDAX	D		;Get image byte
	MOV	M,A		;Store into actual control table
	INX	H		;Update pointers
	INX	D
	DCR	C		;Count down on byte count
	JNZ	Warm$Boot$Move	;Continue until all bytes moved

	LXI	H,Disk$Control$5	;Activate controller
	MVI	M,80H
Wait$For$Boot$Complete:
	MOV	A,M			;Get status byte		
	ORA	A			;Check if complete
	JNZ	Wait$For$Boot$Complete	;No
					;Yes, check for errors
	LDA	Disk$Status$Block
	CPI	80H		
	JC 	Warm$Boot$Error		;Yes, an error occurred
	RET
;
Warm$Boot$Error:
	LXI	H,Warm$Boot$Error$Message
	CALL	Display$Message
	JMP	WBOOT			;Restart warm boot
;
Warm$Boot$Error$Message:
	DB	CR,LF,'Warm Boot Error - retrying...',CR,LF,0
;
;
;#
;
Ghost$Interrupt:	;Control will only arrive here under the most
			;  unusual circumstances as the Interrupt
			;  Controller will have been programmed to
			;  suppress unused interrupts.
			;
	PUSH	PSW			;Save pre-interrupt registers
	MVI	A,IC$EOI		;Indicate End of Interrupt
	OUT	IC$OCW2$Port
	POP	PSW
	RET
;
;
;#
;
;	Patch CP/M
;
;	This routine makes some very special patches to the
;	CCP and BDOS in order to make some custom enhancements
;
;	Public Files:
;		On large hard disk systems it is extremely useful
;		to partition the disk using the USER number features.
;		However it becomes very wasteful of disk space because
;		multiple copies of common programs must be stored in
;		each user area. This patch makes User 0 PUBLIC - it
;		can be accessed from any other user area.
;		*** WARNING ***
;		Files in User 0 MUST be set to System and Read/Only
;		status to avoid them being accidentally damaged.
;		Because of the side-effects associated with Public
;		Files, the patch can be turned on or off using
;		a flag in the Long Term Configuration Block.
;
;	User Prompt :
;		When using CP/M's USER command and user numbers
;		in general, it is all too easy to become confused
;		and forget which user number you are "in". This
;		patch modifies the CCP to display a prompt which
;		shows not only the default disk id., but also the
;		current user number, and an indication of whether
;		Public Files are enabled:
;
;				P3B>  or  3B>
;				^
;				When Public Files are enabled.
;
;	Equates for Public Files
;
PF$BDOS$Exit$Point	EQU	BDOS$Entry + 758H
PF$BDOS$Char$Matches	EQU	BDOS$Entry + 776H
PF$BDOS$Resume$Point	EQU	BDOS$Entry + 75BH
PF$BDOS$Unused$Bytes	EQU	13
;
;
;	Equates for User Prompt
;
UP$CCP$Exit$Point	EQU	CCP$Entry + 388H
UP$CCP$Resume$Point	EQU	CCP$Entry + 38BH
UP$CCP$Get$User		EQU	CCP$Entry + 113H
UP$CCP$Get$Disk$Id	EQU	CCP$Entry + 1D0H
UP$CCP$CONOUT		EQU	CCP$Entry + 8CH
;
;
;	Setup the intervention points
;
Patch$CPM:
	MVI	A,JMP		;Set up opcode
	STA	PF$BDOS$Exit$Point
	STA	UP$CCP$Exit$Point
	LXI	H,Public$Patch
	SHLD	PF$BDOS$Exit$Point + 1
	LXI	H,Prompt$Patch	;Get address of intervening code
	SHLD	UP$CCP$Exit$Point + 1

	RET			;Return to enter CP/M
;
;
;
Public$Patch:			;Control arrives here from the BDOS
				;The BDOS is in the process of scanning
				;  down the target file name in the
				;  Search Next function
				;  HL -> The name of the file searched for
				;  DE -> Directory entry
				;  B = Character count

	LDA	CB$Public$Files	;Check if Public Files are to be enabled
	ORA	A
	JZ	No$Public$Files	;No

	MOV	A,B		;Get character count
	ORA	A		;Check if looking at first byte
				;  (that contains the user number)
	JNZ	No$Public$Files ;No, ignore this patch

	LDAX	D		;Get user number from directory entry
	CPI	0E5H		;Check if active directory entry
	JZ	No$Public$Files	;Yes, ignore this patch

	MOV	A,M		;Get user number
	ORA	A		;Check if User 0
	JZ	PF$BDOS$Char$Matches	;Force character match

No$Public$Files:		;Replaced patched out code
	MOV	A,B			;Check if count indicates that
	CPI	PF$BDOS$Unused$Bytes	;  registers are pointing at
					;  unused bytes field of FCB
	JMP	PF$BDOS$Resume$Point	;Return to BDOS
;
Prompt$Patch:			;Control arrives here from the CCP
				;The CCP is just about to get the
				;  drive id. when control gets here.
				;The CCP's version of CONOUT is used
				;  so that the CCP can keep track of
				;  the cursor position.

	LDA	CB$Public$Files	;Check if Public Files are enabled
	ORA	A
	JZ	UP$Private$Files	;No

	MVI	A,'P'
	CALL	UP$CCP$CONOUT	;Use CCP's CONOUT routine

UP$Private$Files:
	CALL	UP$CCP$Get$User	;Get current user number
	CPI	9 + 1		;Check if one or two digits
	JNC	UP$2$Digits
	ADI	'0'		;Convert to ASCII
UP$1$Digit:
	CALL	UP$CCP$CONOUT	;Output the character
	CALL	UP$CCP$Get$Disk$Id	;Get disk identifier
	JMP	UP$CCP$Resume$Point	;Return to CCP
;
UP$2$Digits:
	ADI	'0' - 10	;Subtract 10 and convert to ASCII
	PUSH	PSW		;Save converted second digit
	MVI	A,'1'		;Output leading '1'
	CALL	UP$CCP$CONOUT
	POP	PSW		;Recover second digit
	JMP	UP$1$Digit	;Output remainder of prompt and return to
				;  the CCP

;
;#
;
;	Configuration Block Get Address
;
;	This routine is called by Utility Programs running in the TPA.
;	Given a specific code number it returns the address of a specific
;	object in the Configuration Block.
;
;	By using this routine, Utility Programs need not know the exact
;	layout of the Configuration Block.
;
;	Entry Parameters
;
;		C = Object Identity Code (in effect, this is the
;			subscript of the object's address in the
;			table below)
;
;==========================
CB$Get$Address:				;<=== BIOS Entry Point (Private)
;==========================
	PUSH	PSW			;Save User's registers
	PUSH	B
	PUSH	D

	MOV	L,C			;Make Code into a word
	MVI	H,0
	DAD	H			;Convert Code into word offset
	LXI	D,CB$Object$Table	;Get base address of table
	DAD	D			;HL -> Object's address in table
	MOV	E,M			;Get LS byte
	INX	H
	MOV	D,M			;Get MS byte
	XCHG				;HL = Address of object

	POP	D			;Recover User's registers
	POP	B
	POP	PSW

	RET
;
;#
;
CB$Object$Table:
				;	Code
				;        vv
	DW	Date			;01 Date in ASCII
	DW	Time$In$ASCII		;02 Time in ASCII
	DW	Time$Date$Flags		;03 Flags indicated if Time/Date set
	DW	CB$Forced$Input		;04 Forced input pointer
	DW	CB$Startup		;05 System Startup message
					;   Redirection words
	DW	CB$Console$Input	;06
	DW	CB$Console$Output	;07
	DW	CB$Auxiliary$Input	;08
	DW	CB$Auxiliary$Output	;09
	DW	CB$List$Input		;10
	DW	CB$List$Output		;11
	
	DW	CB$Device$Table$Addresses ;12
	DW	CB$12$24$Clock		;13 Selects 12/24 hr format Clock
	DW	RTC$Ticks$per$Second	;14
	DW	RTC$Watchdog$Count	;15
	DW	RTC$Watchdog$Address	;16
	DW	CB$Function$Key$Table	;17
	DW	CONOUT$Escape$Table	;18

	DW	D0$Initialize$Stream	;19
	DW	D0$Baud$Rate$Constant	;20
	DW	D1$Initialize$Stream	;21
	DW	D1$Baud$Rate$Constant	;22
	DW	D2$Initialize$Stream	;23
	DW	D2$Baud$Rate$Constant	;24
	DW	Interrupt$Vector	;25
	DW	LTCB$Offset		;26
	DW	LTCB$Length		;27
	DW	CB$Public$Files		;30
	DW	Multi$Command$Buffer	;31
;
;#
;	The Short Term Configuration Block.
;
;	It contains variables that can be set once CP/M
;	has been initiated, but that are never preserved
;	from one loading of CP/M to the next. This part of
;	the Configuration Block are the last initialized bytes
;	in the BIOS.
;
;	The two values below are used by utility programs that
;	need to read in the Long Term Configuration Block from disk.
;	The BIOS starts on a 256-byte page boundary - and therefore
;	will always be on a 128-byte sector boundary in the reserved
;	area on the disk. A utility program can then, using the
;	CB$Get$Address Private BIOS call, determine how many 128-byte
;	sectors need to be read in by the formula :
;
;		(LCTB$Offset + LTCB$Length) / 128
;
;	The LTCB$Offset is the offset from the start of the BIOS
;	where the first byte of the Long Term Configuration Block
;	starts. Using the Offset and the Length, the utility can
;	copy the RAM version of the LTCB down over the disk image
;	that it has read in from the disk, and then write the
;	updated LTCB back onto the disk.
;
LTCB$Offset:	DW	Long$Term$CB - BIOS$Entry
LTCB$Length:	DW	Long$Term$CB$End - Long$Term$CB
;
;	Forced Input Pointer
;
;	If CONIN ever finds that this pointer is pointing at a non-zero
;	byte, then this byte will be injected into the console input
;	stream as though it had been typed on the console. The
;	pointer is then updated to the next byte in memory.

CB$Forced$Input:	DW	CB$Startup
;
;
Date:			;Current System Date
	DB	'10/17/82',LF	;Unless otherwise set to the contrary
				;  this is the Release Date of the system.
				;Normally, it will be set by the DATE utility.
	DB	0		;00-byte terminator

Time$in$ASCII:		;Current System Time
HH:	DB	'00'		;Hours
	DB	':'
MM:	DB	'00'		;Minutes
	DB	':'
SS:	DB	'00'		;Seconds
Time$in$ASCII$End:		;Used when updating the time
	DB	LF
	DB	0		;00-byte terminator
;
;
Time$Date$Flags:	;This byte contains two flags that are  used 
			;  to indicate whether the Time and/or Date 
			;  have been set either programmatically or 
			;  by using the TIME and DATE utilities. These 
			;  flags can be tested by utility programs that
			;  need to have the correct time and date set.
	DB	0
Time$Set	EQU	0000$0001B
Date$Set	EQU	0000$0010B

;
;#
;	Uninitialized Buffer Areas
;
;	With the exception of the main Disk$Buffer, which does contain
;	a few bytes of code, all of the other uninitialized variables
;	occur here. This has the effect of reducing the number of
;	bytes that need be stored in the CP/M image out on the disk,
;	as uninitialized areas do not need to be kept on the disk.
;
;
;#
;
;	The Cold Boot initialization code is only needed once.
;	It can be overwritten once it has been executed.
;	Therefore, it is 'hidden' inside the main disk buffer.
;
;
Disk$buffer:	DS	Physical$Sector$Size * Physical$Sec$Per$Track
;
					;Save the location counter
After$Disk$Buffer	EQU	$	;$ = Current value of location counter
;
			ORG	Disk$Buffer	;Wind the location counter back
;
Initialize$Stream:	;This stream of data is used by the
			;Initialize subroutine. It has the following
			;format :
			;
			;	DB	Port Number to be initialized
			;	DB	Number of byte to be output
			;	DB	xx,xx,xx,xx Data to be output
			;	:
			;	:
			;	DB	Port number of 00H terminates
			;
			;
;
;	Initialization Stream declared here
	DB	IC$ICW1$Port	;Program the 8259 interrupt controller
	DB	1
	DB	IC$ICW1

	DB	IC$ICW2$Port
	DB	1
	DB	IC$ICW2

	DB	IC$OCW1$Port
	DB	1
	DB	IC$OCW1
	
	DB	83H			;Program the 8253 clock generator
	DB	1
	DB	00$11$010$0B		;Counter 0, periodic interrupt, mode 2

	DB	80H			;RTC uses channel 0
	DB	2
	DW	17921			;19721 * 930 Nanoseconds =
					;  16.666 Milliseconds). 60 ticks/sec.
	DB	0		;Port number of 0 terminates
;
;
Signon$Message:	
	DB	'CP/M 2.2.'
	DW	VERSION		;Current version number
	DB	' '
	DW	MONTH		;Current Date
	DB	'/'
	DW	DAY
	DB	'/'
	DW	YEAR
	DB	CR,LF,LF
	DB	'Enhanced BIOS',CR,LF,LF
	DB	'Disk Configuration :',CR,LF,LF
	DB	'     A: 0.35 Mbyte 5" Floppy',CR,LF
	DB	'     B: 0.35 Mbyte 5" Floppy',CR,LF,LF
	DB	'     C: 0.24 Mbyte 8" Floppy',CR,LF
	DB	'     D: 0.24 Mbyte 8" Floppy',CR,LF
	DB	'     M: 0.19 Mbyte Memory Disk',CR,LF,LF
;
	DB	0
;
;	Messages for M$Disk
;
M$Disk$Setup$Message:
	DB	'     M$Disk already contains valid information.',CR,LF,0
M$Disk$Not$Setup$Message:
	DB	'     M$Disk has been initialized to empty state.',CR,LF,0
;
M$Disk$Dir$Entry:		;Dummy Directory entry used to determine
				;  if the M$Disk contains valid information
	DB	15		;User 15
	DB	'M$Disk  '
	DB	' '+80H,' '+80H,' '	;System and Read/Only
	DB	0,0,0,0
	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;
Default$Disk	EQU	0004H	;Default disk in base page
;
BOOT:		;Entered directly from the BIOS JMP Vector.
		;Control will be transferred here by the CP/M
		;Bootstrap Loader.
		;
				;Initialize system.
				;This routine uses the Initialize$Stream
				;declared above.

	DI			;Disable Interrupts to prevent any
				;side-effects during initialization.
	LXI	H,Initialize$Stream	;HL -> Data stream
	CALL	Output$Byte$Stream	;Output it to the specified
					;  ports

	CALL	General$CIO$Initialization ;Initialize character devices

	LXI	H,Signon$Message	;Display Signon message on console
	CALL	Display$Message
;
	CALL	Patch$CPM	;Make necessary Patches to CCP and BDOS
				;  for custom enhancements

				;Initialize M$Disk.
				;If the M$Disk directory has the
				;  special Reserved File Name "M$disk"
				;  (with lower case letters and marked
				;  SYS and R/O), then the M$Disk is
				;  assumed to contain valid data.
				;If the "M$Disk" file is absent, the
				;  M$Disk Directory entry is moved into
				;  the M$Disk image, and the remainder of
				;  the directory set to 0E5H.
	MVI	B,1		;Select bank 1
	CALL	Select$Bank	;  which contains the M$Disk directory

				;Check if M$Disk directory entry present
	LXI	H,0		;Start address for first directory
	LXI	D,M$Disk$Dir$Entry
	MVI	C,32		;Length to compare
M$Disk$Test:
	LDAX	D		;Get byte from initialized variable
	CMP	M		;Compare with M$Disk image
	JNZ	M$Disk$Not$Setup	;Match fails
	INX	D
	INX	H
	DCR	C
	JZ	M$Disk$Setup	;All bytes match
	JMP	M$Disk$Test
;
M$Disk$Setup:
	LXI	H,M$Disk$Setup$Message	;Inform user
;
M$Disk$Setup$Done:
	CALL	Display$Message

	XRA	A		;Set default disk drive to A:
	STA	Default$Disk
	EI			;Interrupts can now be enabled

	JMP	Enter$CPM	;Go into CP/M
;
M$Disk$Not$Setup:
	LXI	D,0		;Move M$Disk directory entry into
	LXI	H,M$Disk$Dir$Entry	;  M$Disk image
	MVI	C,32/8			;Number of 8 byte blocks to move
	CALL	Move$8
;
				;DE -> next byte after M$Disk directory
				;  entry in image
	MVI	A,0E5H			;Setup to do memory fill
	STAX	D			;Store first byte in "source" area
	MOV	H,D			;Set HL to DE +1
	MOV	L,E
	INX	H
	MVI	C,((2 * 1024) - 32) / 8	;2 Allocation Blocks
					;less 32 bytes for M$Disk entry
	CALL	Move$8			;Use Move$8 to do fill operation

	LXI	H,M$Disk$Not$Setup$Message
	JMP	M$Disk$Setup$Done	;Output message and enter CP/M
;
;
	DB	0		;Dummy
Last$Initialized$Byte:		;<== Address of last initialized byte
;
;	End of Cold Boot Initialisation Code
;
	ORG	After$Disk$Buffer	;Reset location counter
;
Multi$Command$Buffer:	DS	128	;This can be used to insert long
					;  command sequences into the
					;  console input stream by setting
					;  the Forced Input pointer here
;
D0$Buffer$Length	EQU	32	;Must be binary number
D0$Buffer:	DS	D0$Buffer$Length
;
D1$Buffer$Length	EQU	32	;Must be binary number
D1$Buffer:	DS	D1$Buffer$Length
;
D2$Buffer$Length	EQU	32	;Must be binary number
D2$Buffer:	DS	D2$Buffer$Length
;
;	Data Areas for the Character Drivers
;
PI$User$Stack:	DS	2	;Storage area for User's Stack Pointer
				;  when an interrupt occurs
PI$User$HL:	DS	2	;Save area for User's HL
		DS	40	;Stack area for use by Interrupt Service
PI$Stack:			;  routines to avoid overflowing the
				;  User's stack area
;
Directory$Buffer:	DS	128	;Disk Directory Buffer
;
M$Disk$Buffer:		DS	128	;Intermediary buffer for
					;  M$Disk
;
;	Disk Work Areas
;
;	These are used by the BDOS to detect any unexpected
;	change of diskettes. The BDOS will automatically set 
;	such a changed diskette to Read-Only status.
;
Disk$A$Workarea:	DS	32	; A:
Disk$B$Workarea:	DS	32	; B:
Disk$C$Workarea:	DS	16	; C:
Disk$D$Workarea:	DS	16	; D:
;
;
;	Disk Allocation Vectors
;
;	These are used by the BDOS to maintain a bit map of
;	which Allocation Blocks are used and which are free.
;	One byte is used for eight Allocation Blocks, hence the
;	expression of the form (Allocation Blocks/8)+1.
;
Disk$A$Allocation$Vector	DS	(174/8)+1	; A:
Disk$B$Allocation$Vector	DS	(174/8)+1	; B:
;
Disk$C$Allocation$Vector	DS	(242/8)+1	; C:
Disk$D$Allocation$Vector	DS	(242/8)+1	; D:
;
M$Disk$Allocation$Vector	DS	(192/8)+1	; M$Disk

	END	;Of Enhanced BIOS Listing

