Files
Sepp J Morris 31738079c4 Upload
Digital Research
2020-11-06 18:50:37 +01:00

4448 lines
130 KiB
NASM
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

; 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