; 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