;--------------------------------------------------------------------------------------------------------
;-
;- Name:            cbios_bank4.asm
;- Created:         January 2020
;- Author(s):       Philip Smart
;- Description:     Sharp MZ series CPM BIOS System.
;-                  This assembly language program is written to utilise the banked flashroms added with
;-                  the MZ-80A RFS hardware upgrade for the CPM CBIOS in order to preserve RAM for actual
;-                  CPM TPA programs.
;-
;- Credits:         
;- Copyright:       (c) 2018-26 Philip Smart <philip.smart@net2net.org>
;-
;- History:         Jan 2020 - Seperated Bank from RFS for dedicated use with CPM CBIOS.
;-                  May 2020 - Advent of the new RFS PCB v2.0, quite a few changes to accommodate the
;-                             additional and different hardware. The SPI is now onboard the PCB and
;-                             not using the printer interface card.
;-                  Mar 2021 - Updates for the RFS v2.1 board.
;--------------------------------------------------------------------------------------------------------
;- This source file is free software: you can redistribute it and-or modify
;- it under the terms of the GNU General Public License as published
;- by the Free Software Foundation, either version 3 of the License, or
;- (at your option) any later version.
;-
;- This source file is distributed in the hope that it will be useful,
;- but WITHOUT ANY WARRANTY; without even the implied warranty of
;- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;- GNU General Public License for more details.
;-
;- You should have received a copy of the GNU General Public License
;- along with this program.  If not, see <http://www.gnu.org/licenses/>.
;--------------------------------------------------------------------------------------------------------

            ; Bring in definitions and macros.
            INCLUDE "cpm_buildversion.asm"
            INCLUDE "cpm_definitions.asm"
            INCLUDE "macros.asm"

            ;============================================================
            ;
            ; USER ROM CPM CBIOS BANK 4 - Floppy Disk Controller functions.
            ;
            ;============================================================
            ORG      UROMADDR

            ;-----------------------------------------------------------------------------------------
            ; Common code spanning all banks to ensure that a Monitor is selected upon power up/reset.
            ;-----------------------------------------------------------------------------------------
            NOP
            LD      B,16                                                 ; If we read the bank control reset register 15 times then this will enable bank control and then the 16th read will reset all bank control registers to default.
CBIOS1_0:   LD      A,(BNKCTRLRST)
            DJNZ    CBIOS1_0                                             ; Apply the default number of coded latch reads to enable the bank control registers.
            LD      A,BNKCTRLDEF                                         ; Set coded latch, SDCS high, BBMOSI to high and BBCLK to high which enables SDCLK.
            LD      (BNKCTRL),A
            NOP
            NOP
            NOP
            XOR     A                                                    ; We shouldnt arrive here after a reset, if we do, select MROM bank 0
            LD      (BNKSELMROM),A
            NOP
            NOP
            NOP
            LD      (BNKSELUSER),A                                       ; and start up - ie. SA1510 Monitor - this occurs as User Bank 0 is enabled and the jmp to 0 is coded in it.
            ;
            ; No mans land... this should have switched to Bank 0 and at this point there is a jump to 00000H.
            JP      00000H                                               ; This is for safety!!


            ;-------------------------------------------------------------------------------
            ; Jump table for entry into this pages functions.
            ;-------------------------------------------------------------------------------
            ALIGN_NOPS UROMJMPTBL
            JP      ?DSKINIT                                             ; DSKINIT
            JP      ?SETDRVCFG                                           ; SETDRVCFG
            JP      ?SETDRVMAP                                           ; SETDRVMAP
            JP      ?SELDRIVE                                            ; SELDRIVE
            JP      ?GETMAPDSK                                           ; GETMAPDSK
            JP      ?DSKREAD                                             ; DSKREAD
            JP      ?DSKWRITE                                            ; DSKWRITE


            ;-------------------------------------------------------------------------------
            ; START OF FDC CONTROLLER FUNCTIONALITY
            ;-------------------------------------------------------------------------------

            ;------------------------------------------------------------------------------------------------
            ; Initialise drive and reset flags, Set motor off
            ;
            ;------------------------------------------------------------------------------------------------
?DSKINIT:   XOR     A                                                        
            OUT     (FDC_MOTOR),A                                        ; Motor off
            LD      (TRK0FD1),A                                          ; Track 0 flag drive 1
            LD      (TRK0FD2),A                                          ; Track 0 flag drive 2
            LD      (TRK0FD3),A                                          ; Track 0 flag drive 3
            LD      (TRK0FD4),A                                          ; Track 0 flag drive 4
            LD      (MOTON),A                                            ; Motor on flag
            LD      (MTROFFTIMER),A                                      ; Clear the down counter for motor off.
            LD      A,(FDCROMADDR)                                       ; Check to see if the FDC AFI ROM is installed, use this as
            OR      A                                                    ; an indicator that the FDC is present.
            RET

            ; Function to create a mapping table between a CPM disk and a physical disk.
?SETDRVMAP: PUSH    HL
            PUSH    DE
            PUSH    BC

            ; Zero out the map.
            LD      B,MAXDISKS
            LD      HL,DISKMAP
            LD      A,0FFH
SETDRVMAP1: LD      (HL),A
            INC     HL
            DJNZ    SETDRVMAP1
            LD      HL,DISKMAP                                           ; Place in the Map for next drive.
            ; Now go through each disk from the Disk Parameter Base list.
            LD      B,0                                                  ; Disk number count = CDISK.
            LD      DE,0                                                 ; Physical disk number, D = FDC, E = SDC.
SETDRVMAP2: LD      A,B
            CP      MAXDISKS
            JR      Z,SETDRVMAP6
            INC     B
            PUSH    HL
            PUSH    DE
            PUSH    BC
            ; For the Disk in A, find the parameter table.
            RLC     A                                                    ; *2
            RLC     A                                                    ; *4
            RLC     A                                                    ; *8
            RLC     A                                                    ; *16
            LD      HL,DPBASE                                            ; Base of disk description block.
            LD      B,0
            LD      C,A 
            ADD     HL,BC                                                ; HL contains address of actual selected disk block.
            LD      C,10
            ADD     HL,BC                                                ; HL contains address of pointer to disk parameter block.
            LD      E,(HL)
            INC     HL
            LD      D,(HL)                                               ; DE contains address of disk parameter block.
            EX      DE,HL
            LD      A,(HL)
            LD      E,A
            LD      BC,15
            ADD     HL,BC                                                ; Move to configuuration byte which identifies the disk type.
            ;
            POP     BC
            POP     DE
            LD      A,(HL)
            POP     HL

            BIT     4,A                                                  ; Disk type = FDC?
            JR      NZ,SETDRVMAP2A        
            BIT     3,A
            JR      Z,SETDRVMAP4                                         ; Is this an FDC controlled disk, if so store the mapping number in the map unchanged.
            ;
            LD      A,E
            OR      020H                                                 ; This is a RAM drive, add 020H to the mapping number and store.
            INC     E
            JR      SETDRVMAP5
            ;
SETDRVMAP2A:BIT     3,A                                                  ; Is this an SD Card disk, if so, add 080H to the mapping number and store.
            JR      Z,SETDRVMAP3
            LD      A,E
            OR      080H
            INC     E
            JR      SETDRVMAP5
SETDRVMAP3: LD      A,E                                                  ; This is a ROM drive, add 040H to the mapping number and store.
            OR      040H
            INC     E
            JR      SETDRVMAP5
            ;
SETDRVMAP4: LD      A,D
            INC     D
SETDRVMAP5: LD      (HL),A
            INC     HL
            JR      SETDRVMAP2
            ;
SETDRVMAP6: POP     BC
            POP     DE
            POP     HL
            RET

            ; Function to setup the drive parameters according to the CFG byte in the disk parameter block.
?SETDRVCFG: PUSH    HL
            PUSH    DE
            PUSH    BC
            LD      A,(CDISK)
            RLC     A                                                    ; *2
            RLC     A                                                    ; *4
            RLC     A                                                    ; *8
            RLC     A                                                    ; *16
            LD      HL,DPBASE                                            ; Base of disk description block.
            LD      B,0
            LD      C,A 
            ADD     HL,BC                                                ; HL contains address of actual selected disk block.
            LD      C,10
            ADD     HL,BC                                                ; HL contains address of pointer to disk parameter block.
            LD      E,(HL)
            INC     HL
            LD      D,(HL)                                               ; DE contains address of disk parameter block.
            EX      DE,HL
            LD      A,(HL)
            LD      E,A

            LD      BC,15
            ADD     HL,BC                                                ; Move to configuuration byte.
            XOR     A
            BIT     2,(HL)
            JR      Z,SETDRV0
            INC     A
SETDRV0:    LD      (INVFDCDATA),A                                       ; Data inversion is set according to drive parameter.
            LD      A,4
            BIT     1,(HL)
            JR      Z,SETDRV1
            LD      A,2
            BIT     0,(HL)
            JR      Z,SETDRV1
            LD      A,1
SETDRV1:    LD      (SECTORCNT),A                                        ; Set the disk sector size.
            LD      D,A
            CP      4
            LD      A,E
            JR      Z,SETDRV1A
            OR      A
            RR      A
            LD      E,A
            LD      A,D
            CP      2
            LD      A,E
            JR      Z,SETDRV1A
            OR      A
            RR      A                                                    ; Convert sectors per track from 128 bytes to 256 byte sectors.
SETDRV1A:   INC     A                                                    ; Add 1 to ease comparisons.
            LD      (SECPERTRK),A                                        ; Only cater for 8bit, ie. 256 sectors.
            DEC     A
            OR      A
            RR      A
            INC     A                                                    ; Add 1 to ease comparisons.
            LD      (SECPERHEAD),A                                       ; Convert sectors per track to sectors per head.
            ;
            XOR     A                                                    ; Disk type = FDC
            BIT     4,(HL)
            JR      NZ,SETDRV1B                                          ; 4 = 1?
            BIT     3,(HL)
            JR      Z,SETDRV2                                            ; 3 = 0 - FDC
            LD      A,DSKTYP_RAM
            JR      SETDRV2
SETDRV1B:   LD      A,DSKTYP_ROM                                         ; Disk type = ROMFS
            BIT     3,(HL)                                               ; 3 = 0? Thus 1:0 = ROM
            JR      Z,SETDRV2
            LD      A,DSKTYP_SDC                                         ; Disk type = SD Card, ie. 1:1 
SETDRV2:    LD      (DISKTYPE),A
            POP     BC
            POP     DE
            POP     HL
            RET

            ; Method to get the current disk drive mapped to the correct controller.
            ; The CPM CDISK is mapped via MAPDISK[CDISK] and the result:
            ; Bit 7 = 1 - SD Card drive.
            ; Bit 6 = 1 - ROM Drive.
            ; BIT 7:6 = 00 - Floppy drive.
?GETMAPDSK: PUSH    HL
            PUSH    BC
            LD      A,(CDISK)
            LD      HL,DISKMAP
            LD      C,A
            LD      B,0
            ADD     HL,BC
            LD      A,(HL)                                               ; Get the physical number after mapping from the CDISK.
            POP     BC
            POP     HL
            RET

            ; Select FDC drive (make active) based on value in DISKMAP[CDISK].
?SELDRIVE:  CALL    ?GETMAPDSK
            CP      020H                                                 ; Anything with bit 7:5 set is not an FDC drive.
            RET     NC                                                   ; This isnt a physical floppy disk, no need to perform any actions, exit.
            LD      (FDCDISK),A
            CALL    DSKMTRON                                             ; yes, set motor on and wait
            LD      A,(FDCDISK)                                          ; select drive no
            OR      084H                                                     
            OUT     (FDC_MOTOR),A                                        ; Motor on for drive 0-3
            XOR     A                                                        
            LD      (FDCCMD),A                                           ; clr latest FDC command byte
            LD      HL,00000H                                                
SELDRV2:    DEC     HL                                                       
            LD      A,H                                                      
            OR      L                                                        
            JP      Z,SELDRVERR                                          ; Reset and print message that this is not a valid disk.
            IN      A,(FDC_STR)                                          ; Status register.
            CPL                                                              
            RLCA                                                             
            JR      C,SELDRV2                                            ; Wait on Drive Ready Bit (bit 7)
            LD      A,(FDCDISK)                                          ; Drive number
            LD      C,A
            LD      HL,TRK0FD1                                           ; 1 track 0 flag for each drive
            LD      B,000H                                                   
            ADD     HL,BC                                                ; Compute related flag 1002/1003/1004/1005
            BIT     0,(HL)                                                   
            JR      NZ,SELDRV3                                           ; If the drive hasnt been intialised to track 0, intialise and set flag.
            CALL    DSKSEEKTK0                                           ; Seek track 0.
            SET     0,(HL)                                               ; Set bit 0 of trk 0 flag
SELDRV3:    CALL    ?SETDRVCFG
            RET

            ; Turn disk motor on if not already running.
DSKMTRON:   LD      A,255                                                ; Ensure motor is kept running whilst we read/write.
            LD      (MTROFFTIMER),A
            LD      A,(MOTON)                                            ; Test to see if motor is on, if it isnt, switch it on.
            RRCA
            JR      NC, DSKMOTORON
            RET
DSKMOTORON: PUSH    BC
            LD      A,080H
            OUT     (FDC_MOTOR),A                                        ; Motor on
            LD      B,010H                                               ; 
DSKMTR2:    CALL    MTRDELAY                                             ; 
            DJNZ    DSKMTR2                                              ; Wait until becomes ready.
            LD      A,001H                                               ; Set motor on flag.
            LD      (MOTON),A                                            ; 
            POP     BC
            RET      

FDCDLY1:    PUSH    DE
            LD      DE,00007H
            JP      MTRDEL2

MTRDELAY:   PUSH    DE
            LD      DE,01013H
MTRDEL2:    DEC     DE
            LD      A,E
            OR      D
            JR      NZ,MTRDEL2                                           
            POP     DE
            RET    

?DSKWRITE:  LD      A,MAXWRRETRY
            LD      (RETRIES),A
            LD      A,(SECTORCNT)
            LD      B,A
            LD      A,(HSTSEC)
DSKWRITE0A: DJNZ    DSKWRITE0B
            JR      DSKWRITE1
DSKWRITE0B: OR      A
            RL      A
            JR      DSKWRITE0A
DSKWRITE1:  INC     A
            LD      (SECTORNO), A                                        ; Convert from Host 512 byte sector into local sector according to paraameter block.
            LD      HL,(HSTTRK)
            LD      (TRACKNO),HL

DSKWRITE2:  CALL    SETTRKSEC                                            ; Set current track & sector, get load address to HL
DSKWRITE3:  CALL    SETHEAD                                              ; Set side reg
            CALL    SEEK                                                 ; Command 1b output (seek)
            JP      NZ,SEEKRETRY                                         ; 
            CALL    OUTTKSEC                                             ; Set track & sector reg

            LD      IX, 0F3FEH                                           ; As below. L03FE
            LD      IY,WRITEDATA                                         ; Write sector from memory.
            DI     
            ;
            LD      A,0B4H                                               ; Write Sector multipe with Side Compare for side 1.
            CALL    DISKCMDWAIT
            LD      D,2                                                  ; Regardless of 4x128, 2x256 or 1x512, we always read 512bytes by the 2x INI instruction with B=256.
STRTDATWR:  LD      B,0                                                  ; 256 bytes to load.
            JP      (IX)

WRITEDATA:  OUTI    
            JP      NZ, 0F3FEH                                           ; This is crucial, as the Z80 is running at 2MHz it is not fast enough so needs
                                                                         ; hardware acceleration in the form of a banked ROM, if disk not ready jumps to IX, if
                                                                         ; data ready, jumps to IY.
            DEC     D
            JP      NZ,0F3FEH                                            ; If we havent read all sectors to form a 512 byte block, go for next sector.
            JR      DATASTOP      

            ; Read disk starting at the first logical sector in param block 1009/100A
            ; Continue reading for the given size 100B/100C and store in the location 
            ; Pointed to by the address stored in the parameter block. 100D/100E
?DSKREAD:   LD      A,MAXRDRETRY
            LD      (RETRIES),A
            LD      A,(SECTORCNT)
            LD      B,A
            LD      A,(HSTSEC)
DSKREAD0A:  DJNZ    DSKREAD0B
            JR      DSKREAD1
DSKREAD0B:  OR      A
            RL      A
            JR      DSKREAD0A
DSKREAD1:   INC     A
            LD      (SECTORNO), A                                        ; Convert from Host 512 byte sector into local sector according to paraameter block.
            LD      HL,(HSTTRK)
            LD      (TRACKNO),HL
DSKREAD2:   CALL    SETTRKSEC                                            ; Set current track & sector, get load address to HL
DSKREAD3:   CALL    SETHEAD                                              ; Set side reg
            CALL    SEEK                                                 ; Command 1b output (seek)
            JP      NZ,SEEKRETRY                                         ; 
            CALL    OUTTKSEC                                             ; Set track & sector reg
            LD      IX, 0F3FEH                                           ; As below. L03FE
            LD      IY,READDATA                                          ; Read sector into memory.
            DI     
            ;
            LD      A,094H                                               ; Read Sector multiple with Side Compare for side 1.
            CALL    DISKCMDWAIT
            LD      D,2                                                  ; Regardless of 4x128, 2x256 or 1x512, we always read 512bytes by the 2x INI instruction with B=256.
STRTDATRD:  LD      B,0                                                  ; 256 bytes to load.
            JP      (IX)

            ; Get data from disk sector to staging area.
READDATA:   INI     
            JP      NZ,0F3FEH                                            ; This is crucial, as the Z80 is running at 2MHz it is not fast enough so needs
                                                                         ; hardware acceleration in the form of a banked ROM, if disk not ready jumps to IX, if
                                                                         ; data ready, jumps to IY.
            DEC     D
            JP      NZ,0F3FEH                                            ; If we havent read all sectors to form a 512 byte block, go for next sector.
            ;
            ;
DATASTOP:   LD      A,0D8H                                               ; Force interrupt command, Immediate interrupt (I3 bit 3=1) of multiple sector read.
            CPL    
            OUT     (FDC_CR),A
            CALL    WAITRDY                                              ; Wait for controller to become ready, acknowledging interrupt.
            IN      A,(FDC_STR)                                          ; Check for errors.
            CPL     
            AND     0FFH
            JR      NZ,SEEKRETRY   
UPDSECTOR:  PUSH    HL
            LD      A,(SECTORCNT)
            LD      HL,SECTORNO
            ADD     A,(HL)                                               ; Update sector to account for sectors read. NB. All reads will start at such a position
            LD      (HL), A                                              ; that a read will not span a track or head. Ensure that disk formats meet an even 512byte format.
            POP     HL
MOTOROFF:   LD      A,MTROFFMSECS                                         ; Schedule motor to be turned off.
            LD      (MTROFFTIMER),A
            XOR     A                                                    ; Successful read, return 0
            EI
            RET    

SEEKRETRY:  LD      B,A                                                  ; Preserve the FDC Error byte.
            LD      A,(RETRIES)
            DEC     A
            LD      (RETRIES),A
            LD      A,B
            JP      Z,RETRIESERR
            CALL    DSKSEEKTK0
            LD      A, (READOP) 
            OR      A
            LD      A,(TRACKNO)                                          ; NB. Track number is 16bit, FDC only uses lower 8bit and assumes little endian read.
            JP      Z, DSKWRITE2                                         ; Try write again.
            JP      DSKREAD2                                             ; Try the read again.

DISKCMDWAIT:LD      (FDCCMD),A
            CPL    
            OUT     (FDC_CR),A
            CALL    WAITBUSY
            RET     

            ; Send a command to the disk controller.
DSKCMD:     LD      (FDCCMD),A                                           ; Store latest FDC command.
            CPL                                                          ; Compliment it (FDC bit value is reversed).
            OUT     (FDC_CR),A                                           ; Send command to FDC.
            CALL    WAITRDY                                              ; Wait to become ready.
            IN      A,(FDC_STR)                                          ; Get status register.
            CPL                                                          ; Inverse (FDC is reverse bit logic).
            RET   

            ; Seek to programmed track.
SEEK:       LD      A,01BH                                               ; Seek command, load head, verify stepping 6ms.
            CALL    DSKCMD
            AND     099H
            RET

            ; Set current track & sector, get load address to HL
SETTRKSEC:  CALL    ?SELDRIVE
            LD      A,(TRACKNO)                                          ; NB. Track number is 16bit, FDC only uses lower 8bit and assumes little endian read.
            LD      HL, HSTBUF
            RET     

            ; Compute side/head.
SETHEAD:    CPL                                                          ; 
            OUT     (FDC_DR),A                                           ; Output track no for SEEK command.
            PUSH    HL
            LD      HL,SECPERHEAD
            LD      A,(SECTORNO)                                         ; Check sector, if greater than sector per track, change head.
            CP      (HL)
            POP     HL
            JR      NC,SETHD2                                            ; Yes, even, set side/head 1
            LD      A,001H                                               ; No, odd, set side/head 0
            JR      SETHD3                   

            ; Set side/head register.
SETHD2:     XOR     A                                                    ; Side 0
SETHD3:     CPL                                                          ; Side 1
            OUT     (FDC_SIDE),A                                         ; Side/head register.
            RET     

            ; Set track and sector register.
OUTTKSEC:   PUSH    HL
            LD      HL,SECPERHEAD
            ;
            LD      C,FDC_DR                                             ; Port for data retrieval in the INI instruction in main block.                 
            LD      A,(TRACKNO)                                          ; Current track number, NB. Track number is 16bit, FDC only uses lower 8bit and assumes little endian read.
            CPL                             
            OUT     (FDC_TR),A                                           ; Track reg
            ;
            LD      A,(SECTORNO)                                         ; Current sector number
            CP      (HL)
            JR      C,OUTTKSEC2
            SUB     (HL)
            INC     A                                                    ; Account for the +1 added to ease comparisons.
OUTTKSEC2:  CPL                             
            OUT     (FDC_SCR),A                                          ; Sector reg
            POP     HL
            RET                       

            ; Seek to track 0.
DSKSEEKTK0: CALL    DSKMTRON                                             ; Make sure disk is spinning.
            LD      A,00BH                                               ; Restore command, seek track 0.
            CALL    DSKCMD                                               ; Send command to FDC.
            AND     085H                                                 ; Process result.
            XOR     004H   
            RET     Z      
            JP      DSKSEEKERR

            ; Wait for the drive to become ready.
WAITRDY:    PUSH    DE
            PUSH    HL
            CALL    FDCDLY1
            LD      E,007H
WAITRDY2:   LD      HL,00000H
WAITRDY3:   DEC     HL
            LD      A,H
            OR      L
            JR      Z,WAITRDY4                                           
            IN      A,(FDC_STR)
            CPL     
            RRCA    
            JR      C,WAITRDY3                                          
            POP     HL
            POP     DE
            RET     

WAITRDY4:   DEC     E
            JR      NZ,WAITRDY2                                        
            POP     HL
            POP     DE
            JP      WAITRDYERR

WAITBUSY:   PUSH    DE
            PUSH    HL
            CALL    FDCDLY1
            LD      E,007H                                               ; 7 Chances of a 16bit down count delay waiting for DRQ.
WAITBUSY2:  LD      HL,00000H
WAITBUSY3:  DEC     HL
            LD      A,H
            OR      L
            JR      Z,WAITBUSY4                                          ; Down counter expired, decrement retries, error on 0.
            IN      A,(FDC_STR)                                          ; Get the FDC Status
            CPL                                                          ; Switch to positive logic.
            RRCA                                                         ; Shift Busy flag into Carry.
            JR      NC,WAITBUSY3                                         ; Busy not set, decrement counter and retry.
            POP     HL
            POP     DE
            RET     

WAITBUSY4:  DEC     E
            JR      NZ,WAITBUSY2                                         
            POP     HL
            POP     DE
            JP      DSKERR


            ; Error processing. Consists of printing a message followed by debug data (if enabled) and returning with carry set
            ; to indicate error.
DSKERR:     LD      DE,LOADERR                                           ; Loading error message
            JR      HDLERROR                                             

SELDRVERR:  LD      DE,SELDRVMSG                                         ; Select drive error message.
            JR      HDLERROR                                              
            
WAITRDYERR: LD      DE,WAITRDYMSG                                        ; Waiting for ready timeout error message.
            JR      HDLERROR                                               
 
DSKSEEKERR: LD      DE,DSKSEEKMSG                                        ; Disk seek to track error message.
            JR      HDLERROR                                                

RETRIESERR: BIT     2,A                                                  ; Data overrun error if 1.
            LD      DE,DATAOVRMSG
            JR      NZ, RETRIESERR2
            BIT     3,A                                                  ; CRC error if 1.
            LD      DE,CRCERRMSG
            JR      NZ,RETRIESERR2
            LD      DE,RETRIESMSG                                        ; Data sector read error message.
RETRIESERR2:

            ; Process error, dump debug data and return fail code.
HDLERROR:   LD      HL,MSGSTRBUF                                         ; Copy the error messae
HOLERR1:    LD      A,(DE)
            LD      (HL),A
            CP      NUL
            JR      Z,HOLERR2
            INC     DE
            INC     HL
            JR      HOLERR1
HOLERR2:    LD      DE,MSGSTRBUF
HOLPRTSTR:  LD      A,(DE)
            OR      A
            JR      Z,HOLERR3
            INC     DE
HOLPRTSTR2: LD      HL,QPRNT
            PUSH    AF
            LD      A,ROMBANK13 << 4 | ROMBANK15                         ; Call CBIOS Bank 2 from CBIOS Bank 4
            CALL    BANKTOBANK
            JR      HOLPRTSTR
HOLERR3:    XOR     A
            CALL    ?DSKINIT
            CALL    DSKMTRON
            LD      A,001H                                               ; Indicate error by setting 1 in A register.
            EI
            RET

            ;-------------------------------------------------------------------------------
            ; END OF FDC CONTROLLER FUNCTIONALITY
            ;-------------------------------------------------------------------------------


            ;--------------------------------------
            ;
            ; Message table
            ;
            ;--------------------------------------
LOADERR:    DB      "DISK ERROR - LOADING",         CR, NUL
SELDRVMSG:  DB      "DISK ERROR - SELECT",          CR, NUL
WAITRDYMSG: DB      "DISK ERROR - WAIT",            CR, NUL
DSKSEEKMSG: DB      "DISK ERROR - SEEK",            CR, NUL
RETRIESMSG: DB      "DISK ERROR - RETRIES",         CR, NUL
DATAOVRMSG: DB      "DISK ERROR - DATA OVERRUN",    CR, NUL
CRCERRMSG:  DB      "DISK ERROR - CRC ERROR",       CR, NUL

            ; Align to end of bank.
            ALIGN   UROMADDR + 07F7h
            ORG     UROMADDR + 07F7h
            DB      024H,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFH
