; ;************************************************************ ;* * ;* Simple BIOS Listing * ;* * ;************************************************************ ; ; version equ '00' ;Equates used in the sign on message month equ '07' day equ '15' year equ '82' ; ;******************************************************************** ;* * ;* This BIOS is for a computer system with the following * ;* hardware configuration: * ;* * ;* - 8080 CPU * ;* - 64KBytes of RAM * ;* - CRT/Keyboard controller that transfers data * ;* as though it were a serial port (but requires * ;* no baud rate generator or USART programming) * ;* - A serial port, used for both list and "reader"/ * ;* "punch" devices. The serial port chip is an * ;* Intel 8251A with an 8253 baud rate generator. * ;* - 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:. * ;* - Two 8" standard diskette drives (128-byte sectors), * ;* These are used as logical disks C: and D:. * ;* * ;* 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 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 semicolon. (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 0900H ; CCP$Length equ 0800H ;Constant BDOS$Length equ 0e00H ;Constant ; Overall$Length equ ((CCP$Length + BDOS$Length + BIOS$Length) / 1024) + 1 ; 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 ; ; ; org BIOS$Entry ;Assemble code at BIOS address ; ; BIOS jump vector ; Control will be transferred to the appropriate entry point ; from the CCP or the BDOS, both of which compute the relative ; address of the BIOS jump vector in order to locate it. ; Transient programs can also make direct BIOS calls transferring ; control to location xx00H, where xx is the value in location ; 0002H. ; jp 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 jp wboot ;Warm boot -- entered by jumping to location 0000H. ; Reloads the CCP which could have been ; overwritten by previous program in transient ; program area jp const ;Console status -- returns A = 0FFH if there is a ; console keyboard character waiting jp conin ;Console input -- returns the next console keyboard ; character in A jp conout ;Console output -- outputs the character in C to ; the console device jp list ;List output -- outputs the character in C to the ; list device jp punch ;Punch output -- outputs the character in C to the ; logical punch device jp reader ;Reader input -- returns the next input character from ; the logical reader device in A jp home ;Homes the currently selected disk to track 0 jp seldsk ;Selects the disk drive specified in register C and ; returns the address of the disk parameter header jp settrk ;Sets the track for the next read or write operation ; from the BC register pair jp setsec ;Sets the sector for the next read or write operation ; from the A register jp setdma ;Sets the direct memory address (disk read/write) ; address for the next read or write operation ; from the DE register pair jp read ;Reads the previously specified track and sector from ; the selected disk into the DMA address jp write ;Writes the previously specified track and sector onto ; the specified disk from the DMA address jp listst ;Returns A = 0FFH if the list device can accept ; another output character jp sectran ;Translates a logical sector into a physical one ; ; ; ; The cold boot initialization code is only needed once. ; It can be overwritten once it is executed. ; Therefore, it is "hidden" inside the main disk buffer. ; When control is transferred to the BOOT entry point, this ; code will be executed, only being overwritten by data from ; the disk once the initialization procedure is complete. ; ; To hide code in the buffer, the buffer is first declared ; normally. Then the value of the location counter following ; the buffer is noted. Then, using an ORG (ORiGin) statement, the ; location counter is "wound back" to the start of the buffer ; again and the initialization code written normally. ; At the end of this code, another ORG statement is used to ; set the location counter back as it was after the buffer had ; been declared. ; ; Physical$Sector$Size equ 512 ;This is the actual sector size ;for the 5 1/4" mini-floppy diskettes. ;The 8" diskettes use 128-byte sectors. ;Declare the physical disk buffer for the ;5 1/4" diskettes Disk$Buffer: ds Physical$Sector$Size ; ;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 bytes to be output ; DB xx,xx,xx,xx data to be output ; : ; : ; DB Port number of 00H terminator ; ;Note : On this machine, the console port does ; not need to be initialized. This has ; already been done by the PROM bootstrap code. ; ;Initialize the 8251A USART used for ; the list and communications devices. db Communication$Status$Port ;Port number db 6 ;Number of bytes db 0 ;Get chip ready to e programmed by db 0 ; sending dummy data out to it db 0 db 01000010B ;Reset and raise data terminal ready db 01101110B ;1 stop bit, no parity, 8 bits per character ; baud rate divide factor of 16. db 00100101B ;Raise request to send, and enable ; transmit and receive. ; ;Initialize the 8253 programmable interval ; timer used to generate the baud rate for ; the 8251A USART db Communication$Baud$Mode ;Port number db 1 ;Number of bytes db 10110110B ;Select counter 2, load LS byte first, ; Mode 3 (for baud rates), binary count. ; db Communication$Baud$Rate ;Port number db 2 ;Number of bytes dw 0038H ;1200 baud (based on 16X divide-down selected ; in the 8251A USART) ; db 0 ;Port number of 0 terminates ; ; ; Equates for the sign-on message ; cr equ 0dh ;Carriage return lf equ 0ah ;Line feed ; Signon$Message: ;Main sign-on 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 'Simple 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 0 ; Default$Disk equ 4 ;Default disk in base page IN equ 0dbh ;Equate for dummy IN's OUT equ 0d3h ;Equate for dummy OUT's JMP equ 0c3h ;Equate for dummy JP's boot: ;Entered directly from the BIOS JMP vector. ;Control will be transferred here by the CP/M ; bootstrap loader. ;The initialization state of the computer system ; will be determined by the ; PROM bootstrap and the CP/M loader setup. ; ;Initialize system. ;This routine uses the Initialize$Stream ; declared above. di ;Disable interrupts to prevent any ; side effects during initialization. ld hl,Initialize$Stream ;HL -> Data stream ; Initialize$Loop: ld a,(hl) ;Get port number or a ;If 0, then initialization complete jp z,Initialize$Complete ld (Initialize$Port),a ;Set up OUT instruction inc hl ;HL -> Count of number of bytes to output ld c,(hl) ;Get byte count ; Initialize$Next$Byte: inc hl ;HL -> Next data byte ld a,(hl) ;Get next data byte db OUT ;Output to correct port Initialize$Port: db 0 ;<- Set above dec c ;Count down jp nz,Initialize$Next$Byte ;Go back if more bytes inc hl ;HL -> Next port number jp Initialize$Loop ;Go back for next port initialization ; Initialize$Complete: ; ld a,00000001B ;Set IOBYTE to indicate terminal ld (IOBYTE),a ; is to act as console ld hl,Signon$Message ;Display sign-on message on console call Display$Message ; xor a ;Set default disk drive to A: ld (Default$Disk),a ei ;Interrupts can now be enabled ; jp Enter$CPM ;Complete initialization and enter ; CP/M by going to the Console Command ; Processor. ; ; End of cold boot initialization code ; org After$Disk$Buffer ;Reset location counter ; ; Display$Message: ;Displays the specified message on the console. ;On entry, HL points to a stream of bytes to be ; output. A zero byte terminates the message. ld a,(hl) ;Get next message byte or a ;Check if terminator ret z ;Yes, return to caller ld c,a ;Prepare for output push hl ;Save message pointer call conout ;Go to main console output routine pop hl ;Recover message pointer inc hl ;Move to next byte of message jp 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 JP instructions in the ; base page, and also sets the high-level disk driver's ; input/output address (also known as the DMA address). ; ld a,JMP ;Get machine code for JP ld (0),a ;Set up Jp at location 0 ld (5),a ; and at location 5 ; ld hl,Warm$Boot$Entry ;Get BIOS vector address ld (1),hl ;Put it at location 1 ld hl,BDOS$Entry ;Get BDOS entry point address ld (6),hl ;Put it at location 6 ; ld bc,80H ;Set disk I/O address to default call setdma ;Use normal BIOS routine ; ei ;Ensure interrupts are enabled ld a,(Default$Disk) ;Transfer current default disk to ld c,a ; Console Command Processor jp CCP$Entry ;Jump to CCP ; ; ; Serial input/output drivers ; ; These drivers all look at the IOBYTE at location ; 3, which will have been set by the cold boot routine. ; The IOBYTE can be modified by the STAT utility, by ; BDOS calls, or by a program that puts a value directly ; into location 3. ; ; All of the routines make use of a subroutine, Select$Routine, ; that takes the least significant two bits of the A register ; and uses them to transfer control to one of the routines whose ; address immediately follows the call to Select$Routine. ; A second entry point, Select$Routine$21, uses bits ; 2 and 1 to do the same job -- this saves some space ; by avoiding an unnecessary instruction. ; IOBYTE equ 3 ;I/O redirection byte ; ; ; const: ;Get console status ;Entered directly from the BIOS jump vector ; and returns a parameter that reflects whether ; there is incoming data from the console ; ;A = 0 (zero flag set) if no data ;A = 0FFh (zero flag clear) if data ; ;CONST will be called by programs that ; make periodic checks to see if the computer ; operator has pressed any keys -- for example, ; to interrupt an executing program. ; call Get$Console$Status ;Return A = zero or nonzero ;According to status, then convert ; to return parameter convention. or a ;Set flags to reflect status ret z ;If 0, no incoming data ld a,0ffh ;Otherwise return A = 0ffh to ret ; indicate incoming data ; Get$Console$Status: ld a,(IOBYTE) ;Get I/O redirection byte ;Console is selected according to ; bits 1,0 of IOBYTE call Select$Routine ;Select appropriate routine ;These routines return to the caller ; of Get$Console$Status. dw Teletype$In$Status ;00 <- IOBYTE bits 1,0 dw Terminal$In$Status ;01 dw Communication$In$Status ;10 dw Dummy$In$Status ;11 ; ; ; ; conin: ;Get console input character ;Entered directly from the BIOS jump vector; ; returns the next data character from the ; Console in the A register. The most significant ; bit of the data character will be 0, except ; when "reader" (communication port) input has ; been selected. In this case, the full eight bits ; of data are returned to permit binary data to be ; received. ; ;Normally, this routine will be called after ; a call to CONST has indicated that a data character ; is ready, but whenever the CCP or the BDOS can ; proceed no further until console input occurs, ; then CONIN will be called without a preceding ; CONST call. ; ld a,(IOBYTE) ;Get I/O redirection byte call Select$Routine ;Select correct CONIN routine ;These routines return directly ; to CONIN's caller. dw Teletype$Input ;00 <- IOBYTE bits 1,0 dw Terminal$Input ;01 dw Communication$Input ;10 dw Dummy$Input ;11 ; ; ; conout: ;Console output ;Entered directly from BIOS jump vector; ; outputs the data character in the C register ; to the appropriate device according to bits ; 1,0 of IOBYTE ; ld a,(IOBYTE) ;Get I/O redirection byte call Select$Routine ;Select correct CONOUT routine ;These routines return directly ; to CONOUT's caller. dw Teletype$Output ;00 <- IOBYTE bits 1,0 dw Terminal$Output ;01 dw Communication$Output ;10 dw Dummy$Output ;11 ; ; ; listst: ;List device (output) status ;Entered directly from the BIOS jump vector; ; r turns in A list device status that ; indicates whether the list device can accept ; another output character. The IOBYTE's bits ; 7,6 determine the physical device used. ; ;A = 0 (zero flag set): cannot accept data ;A = 0ffh (zero flag clear): can accept data ; ;Digital Research's documentation indicates ; that you can always return with A = 0 ; ("Cannot accept data") if you do not wish to ; implement the LISTST routine. This is NOT TRUE. ;If you do not wish to implement the LISTST routine ; always return with A = 0ffh ("Can accept data"), ;The LIST driver will then take care of things rather ; than potentially hanging the system. ; call Get$List$Status ;Return A = zero or non-zero ; according to status, then convert ; to return parameter convention or a ;Set flags to reflect status ret z ;If 0, cannot accept data for output ld a,0ffh ;Otherwise return A = 0ffh to ret ; indicate can accept data for output ; Get$List$Status: ld a,(IOBYTE) ;Get I/O redirection byte rlca ;Move bits 7,6 to 1,0 rlca call Select$Routine ;Select appropriate routine ;These routines return directly ; to Get$List$Status's caller. dw Teletype$Out$Status ;00 <- IOBYTE bits 1,0 dw Terminal$Out$Status ;01 dw Communication$Out$Status ;10 dw Dummy$Out$Status ;11 ; ; ; list: ;List output ;Entered directly from BIOS jump vector; ; outputs the data character in the C register ; to the appropriate device according to bits ; 7,6 of IOBYTE ; ld a,(IOBYTE) ;Get I/O redirection byte rlca ;Move bits 7,6 to 1,0 rlca call Select$Routine ;Select correct LIST routine ;These routines return directly ; to LIST's caller. dw Teletype$Output ;00 <- IOBYTE bits 1,0 dw Terminal$Output ;01 dw Communication$Output ;10 dw Dummy$Output ;11 ; ; ; punch: ;Punch output ;Entered directly from BIOS jump vector; ; outputs the data character in the C register ; to the appropriate device according to bits ; 5,4 of IOBYTE ; ld a,(IOBYTE) ;Get I/O redirection byte rrca ;Move bits 5,4 to 2,1 rrca rrca call Select$Routine$21 ;Select correct PUNCH routine ;These routines return directly ; to PUNCH's caller. dw Teletype$Output ;00 <- IOBYTE bits 1,0 dw Dummy$Output ;01 dw Communication$Output ;10 dw Terminal$Output ;11 ; ; ; reader: ;Reader input ;Entered directly from BIOS jump vector; ; inputs the next data character from the ; reader device into the A register ;The appropriate device is selected according ; to bits 3,2 of IOBYTE. ld a,(IOBYTE) ;Get I/O redirection byte rrca ;Move bits 3,2 to 2,1 call Select$Routine$21 ;Select correct READER routine ;These routines return directly ; to READER's caller. dw Teletype$Output ;00 <- IOBYTE bits 1,0 dw Dummy$Output ;01 dw Communication$Output ;10 dw Terminal$Output ;11 ; ; ; Select$Routine: ;Transfers control to a specified address ; following its calling address according to ; the value of bits 1,0 in A. rlca ;Shift select values into bits 2,1 ; in order to do word arithmetic ; Select$Routine$21: ;Entry point to select routine selection bits ; are already in bits 2,1 and 00000110B ;Isolate just bits 2,1 ex (sp),hl ;HL -> first word of address after ; call instruction ld e,a ;Add on selection value to address table ld d,0 ; base add hl,de ;HL -> selected routine address ;Get routine address into HL ld a,(hl) ;LS byte inc hl ;HL -> MS byte ld h,(hl) ;MS byte ld l,a ;HL -> routine ex (sp),hl ;Top of stack -> routine ret ;Transfer to selected routine ; ; ; ; Input/Output Equates ; Teletype$Status$Port equ 0edh Teletype$Data$Port equ 0ech Teletype$Output$Ready equ 00000001B ;Status mask Teletype$Input$Ready equ 00000010B ;Status mask ; Terminal$Status$Port equ 1 Terminal$Data$Port equ 2 Terminal$Output$Ready equ 00000001B ;Status mask Terminal$Input$Ready equ 00000010B ;Status mask ; Communication$Status$Port equ 0edh Communication$Data$Port equ 0ech Communication$Output$Ready equ 00000001B ;Status mask Communication$Input$Ready equ 00000010B ;Status mask ; Communication$Baud$Mode equ 0dfh ;Mode Select Communication$Baud$Rate equ 0deh ;Rate Select ; ; ; Serial device control tables ; ; In order to reduce the amount of executable code, ; the same low-level driver code is used for all serial ports. ; On entry to the low-level driver, HL points to the ; appropriate control table. ; Teletype$Table: db Teletype$Status$Port db Teletype$Data$Port db Teletype$Output$Ready db Teletype$Input$Ready ; Terminal$Table: db Terminal$Status$Port db Terminal$Data$Port db Terminal$Output$Ready db Terminal$Input$Ready ; Communication$Table: db Communication$Status$Port db Communication$Data$Port db Communication$Output$Ready db Communication$Input$Ready ; ; ; ; ; The following routines are "called" by Select$Routine ; to perform the low-level input/output ; Teletype$In$Status: ld hl,Teletype$Table ;HL -> control table jp Input$Status ;Note use of jp, Input$Status ; will execute the RETurn. ; Terminal$In$Status: ld hl,Terminal$Table ;HL -> control table jp Input$Status ;Note use of jp, Input$Status ; will execute the RETurn. ; Communication$In$Status: ld hl,Communication$Table ;HL -> control table jp Input$Status ;Note use of jp, Input$Status ; will execute the RETurn. ; Dummy$In$Status: ;Dummy status, always returns ld a,0ffh ; indicating incoming data is ready ret ; ; Teletype$Out$Status: ld hl,Teletype$Table ;HL -> control table jp Output$Status ;Note use of jp, Output$Status ; will execute the RETurn ; Terminal$Out$Status: ld hl,Terminal$Table ;HL -> control table jp Output$Status ;Note use of jp, Output$Status ; will execute the RETurn ; Communication$Out$Status: ld hl,Communication$Table ;HL -> control table jp Output$Status ;Note use of jp, Output$Status ; will execute the RETurn ; Dummy$Out$Status: ;Dummy status, always returns ld a,0ffh ; indicating ready for output ret ; ; Teletype$Input: ld hl,Teletype$Table ;HL -> control table jp Input$Data ;Note use of jp, Input$Data ; will execute the RETurn ; Terminal$Input: ld hl,Terminal$Table ;HL -> control table call Input$Data ;** Special case ** ;Input$Data will return here and 7fh ; so that parity bit can be set 0 ret ; Communication$Input: ld hl,Communication$Table ;HL -> control table jp Input$Data ;Note use of jp, Input$Data ; will execute the RETurn. ; Dummy$Input: ;Dummy input, always returns ld a,1ah ; indicating CP/M end of file ret ; ; ; ; Teletype$Output: ld hl,Teletype$Table ;HL -> control table jp Output$Data ;Note use of jp, Output$Data ; will execute the RETurn ; Terminal$Output: ld hl,Terminal$Table ;HL -> control table jp Output$Data ;Note use of jp, Output$Data ; will execute the RETurn ; Communication$Output: ld hl,Communication$Table ;HL -> control table jp Output$Data ;Note use of jp, Output$Data ; will execute the RETurn ; Dummy$Output: ;Dummy output, always discards ret ; the output character ; ; ; ; ; These are the general purpose low-level drivers. ; On entry, HL points to the appropriate control table. ; For output, the C register contains the data to be output. ; Input$Status: ;Return with A = 0 if no incoming data, ; otherwise A = nonzero. ld a,(hl) ;Get status port ld (Input$Status$Port),a ;*** Self-modifying code *** db IN ;Input to A from correct status port ; Input$Status$Port: db 0 ;<- Set above inc hl ;Move HL to point to input data mask inc hl inc hl and (hl) ;Mask with input status ret ; ; Output$Status: ;Return with A = 0 if not ready for output ; otherwise A = non-zero. ld a,(hl) ;Get status port ld (Output$Status$Port),a ;*** Self-modifying code *** db IN ;Input to A from correct status port ; Output$Status$Port: db 0 ;<- Set above inc hl ;Move HL to point to output data mask inc hl and (hl) ;Mask with output status ret ; ; Input$Data: ;Return with next data character in A. ;Wait for status routine to indicate ; incoming data. push hl ;Save control table pointer call Input$Status ;Get input status in zero flag pop hl ;Recover control table pointer jp z,Input$Data ;Wait until incoming data inc hl ;HL -> data port ld a,(hl) ;Get data port ld (Input$Data$Port),a ;*** Self-modifying code *** db IN ;Input to A from correct data port ; Input$Data$Port: db 0 ;<- Set above ret ; ; Output$Data: ;Output the data character in the C register. ;Wait for status routine to indicate device ; ready to accept another character push hl ;Save control table pointer call Output$Status ;Get output status in zero flag pop hl ;Recover control table pointer jp z,Output$Data ;Wait until ready for output inc hl ;HL -> output port ld a,(hl) ;Get output port ld (Output$Data$Port),a ;*** Self-modifying code *** ld a,c ;Get data character to be output db OUT ;Output data to correct port ; Output$Data$Port: db 0 ;<- Set above 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 a blocking/deblocking code, ; 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" ; 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 to the disk parameter block to ; tell the disk drivers each logical disk's physical ; diskette type, 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 ;Shares same buffer as A: dw Floppy$5$Parameter$Block ;Same DPB as A: dw Disk$B$Workarea ;Private work area 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 work area dw Disk$C$Allocation$Vector ;Private allocation vector ; ;Logical Disk D: (8" Floppy) dw Floppy$8$Skewtable ;Shares same skew table as C: 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 work area dw Disk$D$Allocation$Vector ;Private allocation vector ; ; Directory$Buffer: ds 128 ; ; ; ; Disk Types ; Floppy$5 equ 1 ;5 1/4" mini floppy Floppy$8 equ 2 ;8" floppy (SS SD) ; ; Blocking/deblocking indicator ; Need$Deblocking equ 10000000B ;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 Floppy$5$Parameter$Block: dw 72 ;128-byte sectors per track db 4 ;Block shift db 15 ;Block mask db 1 ;Extent mask dw 174 ;Maximum allocation block number dw 127 ;Number of directory entries - 1 db 11000000B ;Bit map for reserving 1 alloc. block db 00000000B ; for file directory dw 32 ;Disk changed work area size dw 1 ;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 11000000B ;Bit map for reserving 2 alloc. blocks db 00000000B ; for file directory dw 16 ;Disk changed work area size dw 2 ;Number of tracks before directory ; ; ; 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: ; ; Number$of$Logical$Disks equ 4 ; ; seldsk: ;Select disk in 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. ; ld hl,0 ;Assume an error ld a,c ;Check if requested disk valid cp Number$of$Logical$Disks ret nc ;Return if > maximum number of disks ; ld (Selected$Disk),a ;Save selected disk number ;Set up to return DPH address ld l,a ;Make disk into word value ld h,0 ;Compute offset down disk parameter ; header table by multiplying by ; parameter header length (16 bytes) add hl,hl ; *2 add hl,hl ; *4 add hl,hl ; *8 add hl,hl ; *16 ld de,Disk$Parameter$Headers ;Get base address add hl,de ;DE -> Appropriate DPH push hl ;Save DPH address ; ;Access disk parameter block ; to extract special prefix byte that ; identifies disk type and whether ; deblocking is required ; ld de,10 ;Get DPB pointer offset in DPH add hl,de ;DE -> DPB address in DPH ld e,(hl) ;Get DPB address in DE inc hl ld d,(hl) ex de,hl ;DE -> DPB dec hl ;DE -> prefix byte ld a,(hl) ;Get prefix byte and 0fh ;Isolate disk type ld (Disk$Type),a ;Save for use in low-level driver ld a,(hl) ;Get another copy of prefix byte and Need$Deblocking ;Isolate deblocking flag ld (Deblocking$Required),a ;Save for use in low-level driver pop hl ;Recover DPH pointer ret ; ; ; Set logical track for next read or write ; settrk: ld h,b ;Selected track in BC on entry ld l,c ld (Selected$Track),hl ;Save for low-level driver ret ; ; ; Set logical sector for next read or write ; ; setsec: ld a,c ;Logical sector in C on entry ld (Selected$Sector),a ;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 ld l,c ;Move to HL to save ld h,b ld (DMA$Address),hl ;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 0 ) db 32,33,34,35 ;08,09,10,11 0 ) db 12,13,14,15 ;12,13,14,15 0 ) db 28,29,30,31 ;16,17,18,19 0 ) Head 0 db 08,09,10,11 ;20,21,22,23 0 ) db 24,25,26,27 ;24,25,26,27 0 ) db 04,05,06,07 ;28,29,30,31 0 ) db 20,21,22,23 ;32,33,34,35 0 ) ; db 36,37,38,39 ;36,37,38,39 0 ] db 52,53,54,55 ;40,41,42,43 0 ] db 68,69,70,71 ;44,45,46,47 0 ] db 48,49,50,51 ;48,49,50,51 0 ] db 64,65,66,67 ;52,53,54,55 0 ] Head 1 db 44,45,46,47 ;56,57,58,59 0 ] db 60,61,62,63 ;60,61,62,63 0 ] db 40,41,42,43 ;64,65,66,67 0 ] db 56,57,68,59 ;68,69,70,71 0 ] ; ; 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 ex de,hl ;HL -> skew table base add hl,bc ;add on logical sector number ld l,(hl) ;Get physical sector number ld h,0 ;Make into 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 ; that must be written out. This is indicated by ; a flag, Must$Write$Buffer, set in the ; deblocking code ; ld a,(Must$Write$Buffer) ;Check if physical buffer must or a ; be written out to disk jp nz,HOME$No$Write ld (Data$In$Disk$Buffer),a ;No, so indicate that buffer ; is now unoccupied. HOME$No$Write: ld 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 actually 512 bytes long (it was ; declared at the front of the BIOS and holds the "one-time" ; initialization code used for the cold boot procedure). ; ; The blocking/deblocking code attempts to minimize the amount ; of actual disk I/O by storing the disk, track, and physical sector ; currently residing in the Physical Buffer. If a read request is for ; a 128-byte CP/M "sector" that already is in the physical buffer, ; then no disk access occurs. ; ; Allocation$Block$Size equ 2048 Physical$Sec$Per$Track equ 18 CPM$Sec$Per$Physical equ Physical$Sector$Size/128 CPM$Sec$Per$Track equ CPM$Sec$Per$Physical*Physical$Sec$Per$Track 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 is set 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 is set to write to ; the file directory. ; Write$Allocated equ 0 Write$Directory equ 1 Write$Unallocated equ 2 ; Write$Type: db 0 ;Contains the type of write ; indicated by the BDOS. ; ; In$Buffer$Dk$Trk$Sec: ;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 In$Buffer$Sector: db 0 ; these lines. ; Data$In$Disk$Buffer: db 0 ;When nonzero, the disk buffer has ; data from the disk in it. Must$Write$Buffer: db 0 ;Nonzero when data has been ; written into Disk$Buffer but ; not yet written out to disk ; Selected$Dk$Trk$Sec: ;Variables for selected disk, track, and sector ; (Selected by SELDSK, SETTRK,n and SETSEC) Selected$Disk: db 0 ; These are moved and Selected$Track: dw 0 ; compared as a group so Selected$Sector: db 0 ; do not alter order. 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 ; Selected$Disk$Type: db 0 ;Set by SELDSK to indicate either ; 8" or 5 1/4" floppy Selected$Disk$Deblock: db 0 ;Set by SELDSK to indicate whether ; deblocking is required. Unallocated$Dk$Trk$Sec: ;Parameters for writing to a previously ; unallocated allocation block. Unallocated$Disk: db 0 ; These are moved and compared Unallocated$Track: dw 0 ; as a group so do not alter Unallocated$Sector: db 0 ; these lines. Unallocated$Record$Count: db 0 ;Number of unallocated "records" ; in current previously unallocated ; allocation block. Disk$Error$Flag: db 0 ;Nonzero to indicate an error ; that could not be recovered ; by the disk drivers. BDOS will ; output a "bad sector" message. ; ;Flags used inside the deblocking code Must$Preread$Sector: db 0 ;Nonzero if a physical sector must ; be read into the disk buffer ; either before a write to an ; allocated block can occur, or ; for a normal CP/M 128-byte ; sector read Read$Operation: db 0 ;Nonzero when a CP/M 128-byte ; sector is to be read Deblocking$Required: db 0 ;Nonzero when the selected disk ; needs deblocking (set in SELDSK) Disk$Type: db 0 ;Indicates 8" or 5 1/4" floppy ; selected (set in SELDSK). ; ; ; Read in the 128-byte CP/M sector specified by previous calls ; to select disk and to set track and sector. The sector will be read ; into the address specified in the previous call to set DMA address. ; ; 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: ld a,(Deblocking$Required) ;Check if deblocking needed or a ;(flag was set in SELDSK call) jp z,Read$No$Deblock ;No, use normal nondeblocked ;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. xor a ;Set the record count to 0 ld (Unallocated$Record$Count),a ; for first "write" inc a ;Indicate that it is really a read ld (Read$Operation),a ; that is to be performed ld (Must$Preread$Sector),a ; and force a preread of the sector ; to get it into the disk buffer ld a,Write$Unallocated ;Fake deblocking code into responding ld (Write$Type),a ; as if this is the first write to an ; unallocated allocation block. jp Perform$Read$Write ;Use common code to execute read ; ; Write a 128-byte sector from the current DMA address to ; 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 preread of the sector may be needed), ; to the directory (in which case the data will be written to the ; disk immediately), or to the first 128-byte sector of a previously ; unallocated allocation block (in which case no preread is required). ; ; 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 written out when circumstances force the ; transfer. The number of physical disk operations can therefore ; be reduced considerably. ; write: ld a,(Deblocking$Required) ;Check if deblocking is required or a ;(flag set in SELDSK call) jp z,Write$No$Deblock xor a ;Indicate that a write operation ld (Read$Operation),a ; is required (i.e. NOT a read) ld a,c ;Save the BDOS write type ld (Write$Type),a cp Write$Unallocated ;Check if the first write to an ; unallocated allocation block jp nz,Check$Unallocated$Block ;No, check if in the middle of ; writing to an unallocated block ;Yes, first write to unallocated ; allocation block -- initialize ; variables associated with ; unallocated writes. ld a,Allocation$Block$Size/128 ;Get number of 128-byte ; sectors and ld (Unallocated$Record$Count),a ; set up a count. ; ld hl,Selected$Dk$Trk$Sec ;Copy disk, track and sector ld de,Unallocated$Dk$Trk$Sec ; into unallocated variables call Move$Dk$Trk$Sec ; ; Check if this is not the first write to an unallocated ; allocation block -- if it is, the unallocated record count ; has just been set to the number of 128-byte sectors in the ; allocation block. ; Check$Unallocated$Block: ld a,(Unallocated$Record$Count) or a jp z,Request$Preread ;No, this is a write to an ; allocated block ;Yes, this is a write to an ; unallocated block dec a ;Count down on number of 128-byte sectors ; left unwritten to in allocation block ld a,(Unallocated$Record$Count) ; and store back new value. ld hl,Selected$Dk$Trk$Sec ;Check if the selected disk, track, ld de,Unallocated$Dk$Trk$Sec ; and sector are the same as for call Compare$Dk$Trk$Sec ; those in the unallocated block. jp nz,Request$Preread ;No, a preread is required ;Yes, no preread is needed. ;Now is a convenient time to ; update the current sector and see ; if the track also needs updating. ; ;By design, Compare$Dk$Trk$Sec ; returns with ; DE -> Unallocated$Sector ex de,hl ; HL -> Unallocated$Sector inc (hl) ;Update Unallocated$Sector ld a,(hl) ;Check if sector now > maximum cp CPM$Sec$Per$Track ; on a track jp c,No$Track$Change ;No (A < (HL) ) ;Yes, ld (hl),0 ;Reset sector to 0 ld hl,(Unallocated$Track) ;Increase track by 1 inc hl ld (Unallocated$Track),hl ; No$Track$Change: ;Indicate to later code that ; no preread is needed. xor a ld (Must$Preread$Sector),a ;Must$Preread$Sector=0 jp Perform$Read$Write ; Request$Preread: xor a ;Indicate that this is not a write ld (Unallocated$Record$Count),a ; into an unallocated block. inc a ld (Must$Preread$Sector),a ;Indicate that a preread of the ; physical sector is required. ; ; Perform$Read$Write: ;Common code to execute both reads and ; writes of 128-byte sectors. xor a ;Assume that no disk errors will ld (Disk$Error$Flag),a ; occur ld a,(Selected$Sector) ;Convert selected 128-byte sector rra ; into physical sector by dividing by 4 rra and 3fh ;Remove any unwanted bits ld (Selected$Physical$Sector),a ; ld hl,Data$In$Disk$Buffer ;Check if disk buffer already has ld a,(hl) ; data in it. ld (hl),1 ;(Unconditionally indicate that ; the buffer now has data in it) or a ;Did it indeed have data in it? jp z,Read$Sector$Into$Buffer ;No, proceed to read a physical ; sector into the buffer. ; ;The buffer does have a physical sector ; in it. ; Note: The disk, track, and PHYSICAL ; sector in the buffer need to be ; checked, hence the use of the ; Compare$Dk$Trk subroutine. ; ld de,In$Buffer$Dk$Trk$Sec ;Check if sector in buffer is the ld hl,Selected$Dk$Trk$Sec ; same as that selected earlier call Compare$Dk$Trk ;Compare ONLY disk and track jp nz,Sector$Not$In$Buffer ;No, it must be read in ld a,(In$Buffer$Sector) ;Get physical sector in buffer ld hl,Selected$Physical$Sector cp (hl) ;Check if correct physical sector jp z,Sector$In$Buffer ;Yes, it is already in memory ; Sector$Not$In$Buffer: ;No, it will have to be read in ; over current contents of buffer ld a,(Must$Write$Buffer) ;Check if buffer has data in that or a ; must be written out first call nz,Write$Physical ;Yes, write it out ; Read$Sector$Into$Buffer: call Set$In$Buffer$Dk$Trk$Sec ;Set in buffer variables from ; selected disk, track, and sector ; to reflect which sector is in the ; buffer now. ld a,(Must$Preread$Sector) ;In practice, the sector need only or a ; be physically read in if a preread ; is required call nz,Read$Physical ;Yes, preread the sector xor a ;Reset the flag to reflect buffer ld (Must$Write$Buffer),a ; contents. ; Sector$In$Buffer: ;Selected sector on correct track and ; disk is already in the buffer. ;Convert the selected CP/M (128-byte) ; sector into a relative address down ; the buffer. ld a,(Selected$Sector) ;Get selected sector number and Sector$Mask ;Mask off only the least significant bits ld l,a ;Multiply by 128 by shifting 16-bit value ld h,0 ; left 7 bits add hl,hl ;* 2 add hl,hl ;* 4 add hl,hl ;* 8 add hl,hl ;* 16 add hl,hl ;* 32 add hl,hl ;* 64 add hl,hl ;* 128 ; ld de,Disk$Buffer ;Get base address of disk buffer add hl,de ;Add on sector number * 128 ;HL -> 128-byte sector number start ; address in disk buffer ex de,hl ;DE -> sector in disk buffer ld hl,(DMA$Address) ;Get DMA address set in SETDMA call ex de,hl ;Assume a read operation, so ; DE -> DMA address ; HL -> sector in disk buffer ld 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 ld a,(Read$Operation) ;Determine whether data is to be moved or a ; out of the buffer (read) or into the jp nz,Buffer$Move ; buffer (write) ;Writing into buffer ;(A must be 0 get here) inc a ;Set flag to force a write ld (Must$Write$Buffer),a ; of the disk buffer later on. ex de,hl ;Make DE -> sector in disk buffer ; HL -> DMA address ; ; Buffer$Move: ;The following move loop moves eight bytes ; at a time from (HL) to (DE), C contains ; the loop count. ld a,(hl) ;Get byte from source ld (de),a ;Put into destination inc de ;Update pointers inc hl ld a,(hl) ;Get byte from source ld (de),a ;Put into destination inc de ;Update pointers inc hl ld a,(hl) ;Get byte from source ld (de),a ;Put into destination inc de ;Update pointers inc hl ld a,(hl) ;Get byte from source ld (de),a ;Put into destination inc de ;Update pointers inc hl ld a,(hl) ;Get byte from source ld (de),a ;Put into destination inc de ;Update pointers inc hl ld a,(hl) ;Get byte from source ld (de),a ;Put into destination inc de ;Update pointers inc hl ld a,(hl) ;Get byte from source ld (de),a ;Put into destination inc de ;Update pointers inc hl ld a,(hl) ;Get byte from source ld (de),a ;Put into destination inc de ;Update pointers inc hl dec c ;Count down on loop counter jp nz,Buffer$Move ;Repeat until CP/M sector moved ; ld a,(Write$Type) ;If write to directory, write out cp Write$Directory ; buffer immediately ld a,(Disk$Error$Flag) ;Get error flag in case delayed write or read ret nz ;Return if delayed write or read ; or a ;Check if any disk errors have occured ret nz ;Yes, abandon attempt to write to directory ; xor a ;Clear flag that indicates buffer must be ld (Must$Write$Buffer),a ; written out call Write$Physical ;Write buffer out to physical sector ld a,(Disk$Error$Flag) ;Return error flag to caller ret ; ; Set$In$Buffer$Dk$Trk$Sec: ;Indicate selected disk, track, and ; sector now residing in buffer ld a,(Selected$Disk) ld (In$Buffer$Disk),a ld hl,(Selected$Track) ld (In$Buffer$Track),hl ld a,(Selected$Physical$Sector) ld (In$Buffer$Sector),a ret ; Compare$Dk$Trk: ;Compares just the disk and track ; pointed to by DE and HL ld c,3 ;Disk (1), track (2) jp Compare$Dk$Trk$Sec$Loop ;Use common code Compare$Dk$Trk$Sec: ;Compares the disk, track, and sector ; variables pointed to by DE and HL ld c,4 ;Disk (1), track (2), and sector (1) Compare$Dk$Trk$Sec$Loop: ld a,(de) ;Get comparitor cp (hl) ;Compare with comparand ret nz ;Abandon comparison if inequality found inc de ;Update comparitor pointer inc hl ;Update comparand pointer dec c ;Count down on loop count ret z ;Return (with zero flag set) jp Compare$Dk$Trk$Sec$Loop ; ; Move$Dk$Trk$Sec: ;Moves the disk, track and sector ; variables pointed at by HL to ; those pointed at by DE ld c,4 ;Disk (1), track (2), and sector (1) Move$Dk$Trk$Sec$Loop: ld a,(hl) ;Get source byte ld (de),a ;Store in destination inc de ;Update pointers inc hl dec c ;Count down on byte count ret z ;Return if all bytes moved jp Move$Dk$Trk$Sec$Loop ; ; ; ; 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 monitors location 0040h, and ; the 5 1/4" controller monitors location 0045h. These are ; called their disk control bytes. If the most significant ; bit of a disk control byte is set, the controller will ; 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 zero. This indicates completion ; to the disk driver code. ; ; The controller also sets a return code in a disk status block -- ; 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 retry 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 1 Floppy$Write$Code equ 2 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. ld a,Floppy$Write$Code ;Get write function code jp Common$No$Deblock ;Go to common code Read$No$Deblock: ;Read previously selected sector ; into disk buffer. ld a,Floppy$Read$Code ;Get read function code Common$No$Deblock: ld (Floppy$Command),a ;Set command function code ;Set up nondeblocked command table ld hl,128 ;Bytes per sector ld (Floppy$Byte$Count),hl xor a ;8" floppy only has head 0 ld (Floppy$Head),a ; ld a,(Selected$Disk) ;8" Floppy controller only has information ; on units 0 and 1 so Selected$Disk must ; be converted and 1 ;Turn into 0 or 1 ld (Floppy$Unit),a ;Set unit number ; ld a,(Selected$Track) ld (Floppy$Track),a ;Set track number ; ld a,(Selected$Sector) ld (Floppy$Sector),a ;Set sector number ; ld hl,(DMA$Address) ;Transfer directly between DMA address ld (Floppy$DMA$Address),hl ; 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. ld hl,Disk$Status$Block ;Point next status back at ld (Floppy$Next$Status$Block),hl ; main status block ; ld hl,Disk$Control$8 ;Point next control byte ld (Floppy$Next$Control$Location),hl ; back at main control byte ; ld hl,Floppy$Command ;Point controller at control table ld (Command$Block$8),hl ; ld hl,Disk$Control$8 ;Activate controller to perform ld (hl),80h ; operation. jp Wait$For$Disk$Complete ; ; Write$Physical: ;Write contents of disk buffer to ; correct sector. ld a,Floppy$Write$Code ;Get write function code jp Common$Physical ;Go to common code Read$Physical: ;Read previously selected sector ; into disk buffer. ld a,Floppy$Read$Code ;Get read function code ; Common$Physical: ld (Floppy$Command),a ;Set command table ; ld a,(Disk$Type) ;Get disk type (set in SELDSK) cp Floppy$5 ;Confirm it is a 5 1/4" Floppy jp z,Correct$Disk$Type ;Yes ld a,1 ;No, indicate disk error ld (Disk$Error$Flag),a ret Correct$Disk$Type: ;Set up disk control table ; ld a,(In$Buffer$Disk) ;Convert disk number to 0 or 1 and 1 ; for disk controller ld (Floppy$Unit),a ld hl,(In$Buffer$Track) ;Set up track number ld a,l ;Note: This is single byte value ld (Floppy$Track),a ; for the controller. ; ;The sector must be converted into a ; head number and sector number. ; Sectors 0 - 8 are head 0, 9 - 17 ; are head 1 ld b,0 ;Assume head 0 ld a,(In$Buffer$Sector) ;Get physical sector number ld c,a ;Save copy in case it is head 0 cp 9 ;Check if < 9 jp c,Head$0 ;Yes it is < 9 sub 9 ;No, modify sector number back ; in the 0 - 8 range ld c,a ;Put sector in B inc b ;Set to head 1 Head$0: ld a,b ;Set head number ld (Floppy$Head),a ld a,c ;Set sector number inc a ; (physical sectors start at 1) ld (Floppy$Sector),a ; ld hl,Physical$Sector$Size ;Set byte count ld (Floppy$Byte$Count),hl ; ld hl,Disk$Buffer ;Set transfer address to be ld (Floppy$DMA$Address),hl ; disk buffer ; ;As only one control table is in ; use, close the status and busy ; chain pointers back to the ; main control bytes. ld hl,Disk$Status$Block ld (Floppy$Next$Status$Block),hl ld hl,Disk$Control$5 ld (Floppy$Next$Control$Location),hl ld hl,Floppy$Command ;Set up command block pointer ld (Command$Block$5),hl ld hl,Disk$Control$5 ;Activate 5 1/4" disk controller ld (hl),80h ; Wait$For$Disk$Complete: ;Wait until Disk Status Block indicates ; operation complete, then check ; if any errors occurred. ;On entry HL -> disk control byte ld a,(hl) ;Get control byte or a jp nz,Wait$For$Disk$Complete ;Operation still not yet done ; ld a,(Disk$Status$Block) ;Complete -- now check status cp 80h ;Check if any errors occurred jp c,Disk$Error ;Yes xor a ;No ld (Disk$Error$Flag),a ;Clear error flag ret Disk$Error: ld a,1 ;Set disk-error flag nonzero ld (Disk$Error$Flag),a ret ; ; ; ; Disk control table images for warm boot ; Boot$Control$Part1: 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. ld sp,80h ld de,Boot$Control$Part1 ;Execute first read of warm boot call Warm$Boot$Read ;Load drive 0, track 0, ; head 0, sectors 2 to 8 ld de,Boot$Control$Part2 ;Execute second read call Warm$Boot$Read ;Load drive 0, track 0, ; head 1, sectors 1 - 3 jp Enter$CPM ;Set up 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. ld hl,Floppy$Command ;HL -> actual control table ld (Command$Block$5),hl ;Tell the controller its address ;Move the control table image ; into the control table itself ld c,13 ;Set byte count Warm$Boot$Move: ld a,(de) ;Get image byte ld (hl),a ;Store into actual control table inc hl ;Update pointers inc de dec c ;Count down on byte count jp nz,Warm$Boot$Move ;Continue until all bytes moved ld hl,Disk$Control$5 ;Activate controller ld (hl),80h Wait$For$Boot$Complete: ld a,(hl) ;Get status byte or a ;Check if complete jp nz,Wait$For$Boot$Complete ;No ;Yes, check for errors ld a,(Disk$Status$Block) cp 80h jp c,Warm$Boot$Error ;Yes, an error occurred ret ; Warm$Boot$Error: ld hl,Warm$Boot$Error$Message call Display$Message jp wboot ;Restart warm boot ; Warm$Boot$Error$Message: db cr,lf,'Warm Boot Error - retrying...',cr,lf,0 ; ; end ;Of simple BIOS listing