Model Master 128+Acorn Z80 Second Processor

discuss bbc micro and electron emulators (including mame) here!
Coeus
Posts: 3557
Joined: Mon Jul 25, 2016 12:05 pm
Contact:

Re: Model Master 128+Acorn Z80 Second Processor

Post by Coeus »

AndyG wrote: Fri Feb 23, 2024 8:42 am Happy to test if needed…
It was Sazhen86 who posted the text you quoted.

But, answering the original question, one possibility is to add this to the start of the IsBlockDir subroutine:

Code: Select all

IsBlockDir:     LD    A,(currDRIVE)
                AND   $FE
                JR    NZ,LF089
It isn't actually testing if the disk is a hard disc or a floppy disc, just testing the numeric version of the drive letter for being drive A/B, assuming anything higher is a hard disk. I think that is OK as it is the BIOS that contains the disk parameter blocks and is responsible for mapping the drive letters to the actual hardware.

Adding this shifts the following code slightly and, later, there is an overlap between some variables and a piece of credit text. The credit:

Code: Select all

                org    $f1e0
                DB    "HDBIOS 1.24 (12 Sep 2010) (C)JGH"
but we need to change the space allocated to variables to this:

Code: Select all

DRIVE           EQU   $F1E0                 ; Current drive
TRACK           EQU   DRIVE+1               ; Current track
etc.
Coeus
Posts: 3557
Joined: Mon Jul 25, 2016 12:05 pm
Contact:

Re: Model Master 128+Acorn Z80 Second Processor

Post by Coeus »

I was about to ask what the Winchester Utilities were and how these differ from version 2.x, which I already mentioned, when I discovered there is a thread on here: https://www.stardot.org.uk/forums/viewtopic.php?t=17150 And, it turns out, they appear to be the same thing, or at least related.

In particular, I note the command for starting CP/M from the hard disc under these utilities: *CPM.Z

In the original Z80 client ROM (1.20, as patched to make 1.21) reading, on Ctrl-Break, the system tracks from floppy disk, with OSWORD &7F, to boot CP/M is hard coded. To get CP/M booted from a hard disc or other media there are a few options:

1. On the host side of the tube, intercept/implement OSWORD &7F and read/write to this other media instead of the floppy disc. This could be done, for example, by having a ROM in a higher priority slot than DFS, though the Master having the DFS in the 1MBit ROM makes it harder. One obvious way is with MMFS or similar SD card system for accessing floppy images but, with some custom code, it could access an image file on an ADFS hard disk or just about anything else.

2. Implement a custom loader. The '*' prompt from the Z80 client ROM enables any Z80-compatible code to be run, not just CP/M so a file of Z80 code can either embed the various parts of CP/M with a new BIOS or fetch them from somewhere else, for example a hard disk. That is, presumably, what CPM.Z is doing - it is disk file, not the CPM command built into the client ROM. This custom loader route could also be used to update the running from RAM copy of the Z80 client ROM and maybe the Acorn Winchester utilities does that too.

3. CP/M could be embedded in a language ROM. The host OS looks for a language to run and, if found, pushes it over the tube to the 2nd processor which can then run it or ignore it. If the language the host OS found was actually a loader with the various bits of CP/M embedded in it, CP/M could actually be booted from ROM. I also wonder if the SJ Research network version already does this. As mentioned above, it does implement *FX63 to load the CCP.
Sazhen86
Posts: 92
Joined: Wed Dec 30, 2020 8:55 pm
Contact:

Re: Model Master 128+Acorn Z80 Second Processor

Post by Sazhen86 »

Coeus wrote: Fri Feb 23, 2024 12:57 pm
AndyG wrote: Fri Feb 23, 2024 8:42 am Happy to test if needed…
But, answering the original question, one possibility is to add this to the start of the IsBlockDir subroutine:

Code: Select all

IsBlockDir:     LD    A,(currDRIVE)
                AND   $FE
                JR    NZ,LF089
I finally found 2 minutes to rub together and I created a version 1.26 that had code similar to the above, I just compared the drive with 02 as that's done elsewhere. I like your approach better though.

Code: Select all

IsBlockDir:     LD    A,(currDRIVE)         ; Get current drive.
                CP    $02
                JR    NC,LF089        

Testing that showed it be way slower when copying the COBOL disc to the hard disc than version 1.25, around 18 minutes versus 7 minutes. I tried shifting the calls to IsDirCacheOk to after the calls to IsBlockDir so that it was only called for directory blocks, which made no difference at all, still around 18 minutes.

I'm not currently sure when the directory cache is useful as copying between 2 different drives will invalidate it. And it only lasts for 3.84s before the timer invalidates it. I guess there's a case where it improves using a single drive in some way.

I'll stick with version 1.25 for now until I have more time to do some profiling.
Coeus
Posts: 3557
Joined: Mon Jul 25, 2016 12:05 pm
Contact:

Re: Model Master 128+Acorn Z80 Second Processor

Post by Coeus »

Sazhen86 wrote: Sat Feb 24, 2024 3:09 am Testing that showed it be way slower when copying the COBOL disc to the hard disc than version 1.25, around 18 minutes versus 7 minutes. I tried shifting the calls to IsDirCacheOk to after the calls to IsBlockDir so that it was only called for directory blocks, which made no difference at all, still around 18 minutes.
I am surprised to read that. I would not have been surprised to find that the dir cache didn't help much, perhaps because its use case is pretty narrow, but it is disappointing to find it makes things worse. Also, I would be thinking as you did: on a copy from floppy to hard disk you want the dir cache to cache only the floppy drive and remain valid through the whole copying operation.

On exactly what the use case is for the dir cache, I would bet it is assembling a program with source, object and listing all on the same disk. Firstly, because that would an example of doing something disk intensive that sticks to one drive, and secondly because it is something that would be familiar to the developers of the BIOS and people are always motivated to fix the things that effect them personally.
Sazhen86
Posts: 92
Joined: Wed Dec 30, 2020 8:55 pm
Contact:

Re: Model Master 128+Acorn Z80 Second Processor

Post by Sazhen86 »

Coeus wrote: Sun Feb 25, 2024 4:07 pm On exactly what the use case is for the dir cache, I would bet it is assembling a program with source, object and listing all on the same disk. Firstly, because that would an example of doing something disk intensive that sticks to one drive, and secondly because it is something that would be familiar to the developers of the BIOS and people are always motivated to fix the things that effect them personally.
You make a good point about compiling on a single floppy, so I tried compiling PI.CBL on drive B with both 1.24 and 1.25. Not a particularly scientific test, but there was a difference 28.9 seconds versus 30.4 seconds.

I did some more looking and found that the DMA address and DIRBUF were overlapping in 1.26 when DRIVE was relocated to F1E0, so I moved the DIRBUF to F1F4:

Code: Select all

DSKDEFR:        EQU   DSKRDWR+1             ; Disk write deferred
DMAADDR:        EQU   DSKDEFR+1             ; DMA

LF1F2:          EQU    $F1F4                ; DIRBUF, 128 bytes.
LF272:          EQU    LF1F2+128            ; Disk deblock area
LF472:          EQU    LF272+512            ; Control block to read TIME
LF477:          EQU    LF472+5              ; Previously read TIME
LF47C           EQU    LF477+5
This seems have restored the copy times for the COBOL disk to normal (just less than 8 minutes) and still gives the advantage in compilation time of PI.CBL.

I've attached version 1.26 if anyone wants to give it a try. Don't use it with your best disks though, there's no guarantee it's not going to do something unpleasant to them.

Here's the source I used to create version 1.26:

Code: Select all

                                            ;;; Acorn Z80 CP/M BIOS

                .processor z80
                .include   "defs.asm"
                .org       $ea00

alvC_Z80Tube    EQU   $F700
alvC_CoPro      EQU   $FCDE
DiskAcc         EQU   $FFA4
LoadCCP         EQU   $FFA7
alvB            EQU   $F5C6
GBPBchn         EQU   alvB+$19
GBPBadr         EQU   GBPBchn+1
GBPBnum         EQU   GBPBchn+5
GBPBptr         EQU   GBPBchn+9
CSVd0           EQU   $F4E0                 ; CSV for Drive 0
CSVd1           EQU   CSVd0+$20             ; CSV for Drive 1
ALVd0           EQU   $F520                 ; ALV for Drive 0
ALVd1           EQU   $F5C6                 ; ALV for drive 1

BIOS_BOOT:      JP    ColdBoot
BIOS_WBOOT:     JP    WarmBoot
BIOS_CONST:     JP    ConsoleStatus         ; CON
BIOS_CONIN:     JP    ConsoleIn             ; CON
BIOS_CONOUT:    JP    ConsoleOut            ; CON
BIOS_LIST:      JP    ListOut               ; LST (printer)
BIOS_PUNCH:     JP    PunchOut              ; PUN
BIOS_READER:    JP    ReaderIn              ; RDR
BIOS_HOME:      JP    DiskHome
BIOS_SELDSK:    JP    SelectDisk
BIOS_SETTRK:    JP    SetTrack
BIOS_SETSEC:    JP    SetSector
BIOS_SETDMA:    JP    SetDMA
BIOS_READ:      JP    DiskRead
BIOS_WRITE:     JP    DiskWrite
BIOS_LISTST:    JP    ListStatus            ; LST (printer)
BIOS_SECTRAN:   JP    SectorTranslate

DriveCfs:       DB    8
DriveC:         DB    "$.CPMDISK",13,"*******"
                
DPH_Base:       DW    $0000                 ; DPH for drive 0
                DW    $0000
                DW    $0000
                DW    $0000
                DW    LF1F2
                DW    DPB_Acorn400k
                DW    CSVd0
                DW    ALVd0

                DW    $0000                 ; DPH for drive 1
                DW    $0000
                DW    $0000
                DW    $0000
                DW    LF1F2
                DW    DPB_Acorn400k
                DW    CSVd1
                DW    ALVd1

DPH_C:          DW    $0000                 ; DPH for drive C
                DW    $0000
                DW    $0000
                DW    $0000
                DW    LF1F2
                DW    DPB_HardDrive
                DW    $0000
                DW    $0000                 ; Set on ColdBoot

; DPB for AcornCPM 400k disk

DPB_Acorn400k:  DW    $0014                 ; SPT=20   Sectors Per Track
                DB    $04                   ; BSH=4    Block Shift
                DB    $0F                   ; BLM=15   Block Mask
                DB    $01                   ; EXM=1    Extent Mask
                DW    $00C3                 ; DSM=195  Disk Sector Max
                DW    $007F                 ; DRM=127  Maximum directory entry number
                DB    $C0                   ; AL0=$C0  Directory occupies
                DB    $00                   ; AL1=$00    first two blocks
                DW    $0020                 ; CKS=32   Size of directory checksum vector
                DW    $0003                 ; OFF=3    Reserved tracks before logical start of disk

; Total disk size is 128*(BLM+1)*(DSM+1)=392K
; Physical disk size is 128*SPT*OFF+128*(BLM+1)*(DSM+1)=399.5K

; DPB for 8M Hard Drive

DPB_HardDrive:  DW    $0100                 ; SPT=256  Sectors Per Track
                DB    $05                   ; BSH=5    Block Shift
                DB    $1F                   ; BLM=31   Block Mask
                DB    $01                   ; EXM=1    Extent Mask
                DW    $07FF                 ; DSM=2047 Disk Sector Max
                DW    $03FF                 ; DRM=1023 Maximum directory entry number
                DB    $FF                   ; AL0=$FF  Directory occupies
                DB    $00                   ; AL1=$00    first eight blocks
                DW    $0000                 ; CKS=0    Size of directory checksum vector
                DW    $0000                 ; OFF=0    Reserved tracks before logical start of disk

; Total disk size is 128*(BLM+1)*(DSM+1)=8192K
; Physical disk size is 128*SPT*OFF+128*(BLM+1)*(DSM+1)=8192K


                .org  $EA93
LEA93:          LD    A,$83                 ; Reset input stream, specify CON=UC1 RDR=TTY PUN=TTY LST=LPT
LEA95:          LD    ($0003),A             ; Reset IOBYTE
                LD    L,$02
                LD    A,$02
                CALL  OSBYTE                ; Input stream=kbd, serial enabled
                JP    LEBBD

ConsoleStatus:  CALL  LEB02
                DB    $81                   ; IOBYTE b0-1, test TTY/CRT/BAT/UC1
                DW    TTYStatus
                DW    CRTStatus
                DW    ReaderStatus
                DW    UC1Status

ConsoleIn:      CALL  LEB02
                DB    $01                   ; IOBYTE b0-b1, input from TTY/CRT/BAT/UC1
                DW    TTYIn
                DW    CRTIn
                DW    ReaderIn
                DW    OSRDCH

ConsoleOut:     CALL  LEB02
                DB    $01                   ; IOBYTE b0-b1, output to TTY/CRT/BAT/UC1
                DW    TTYOut
                DW    CRTOut
                DW    ListOut
                DW    UC1Out

ListOut:        CALL  LEB02
                DB    $03                   ; IOBYTE b6-7, output to TTY/CRT/LPT/UL1
                DW    TTYOut
                DW    CRTOut
                DW    LPTOut
                DW    UL1Out

ListStatus:     CALL  LEB02
                DB    $03                   ; IOBYTE b6-7, test TTY/CRT/LPT/UL1
                DW    SerStatus
                DW    TTYempty
                DW    UL1Status
                DW    UL1Status

PunchOut:       CALL  LEB02
                DB    $05                   ; IOBYTE b4-5, output to TTY/PTP/UP1/UP2
                DW    TTYOut
                DW    CRTOut
                DW    NullOut
                DW    NullOut

ReaderIn:       CALL  LEB02
                DB    $07                   ; IOBYTE b2-3, input from TTY/PTR/UR1/UR2
                DW    TTYIn
                DW    CRTIn
                DW    NullIn
                DW    NullIn

ReaderStatus:   CALL  LEB02
                DB    $07                   ; IOBYTE b2-3, test TTY/PTR/UR1/UR2
                DW    TTYStatus
                DW    CRTStatus
                DW    TTYnonempt
                DW    TTYnonempt

; Indirect character I/O call
; ===========================
; CALL is followed by an IOBYTE mask flag, then four addresses
; If bit 7 of the mask flag is clear, then a flag is cleared
; The IOBYTE is rotated by the mask flag to get an index into
; the inline addresses. The selected address is then jumped to.
;
LEB02:          POP   HL
                LD    a,(hl)
                INC   HL                    ; Get IOBYTE mask flag
                BIT   7,A
                LD    B,A
                RES   7,B
                JR    NZ,LEB10              ; If $80+n, don't clear something
                XOR   A
                LD    (LF167),A             ; $00+n, clear this flag
LEB10:          LD    A,($0003)             ; Get IOBYTE
LEB13:          RLCA
                DJNZ  LEB13                 ; Rotate left by mask flag
                AND   $06
                LD    D,$00                 ; Index into four inline addresses
                LD    E,A
                ADD   HL,DE
                LD    E,(HL)                ; Jump to the inline address specified by
                INC   HL                    ; IOBYTE rotated by the flag byte
                LD    D,(HL)
                EX    DE,HL
                JP    (HL)

; Null input - return EOF
; -----------------------
NullIn:         LD    A,$1A

; Null output - sink
; ------------------
NullOut:        RET

; Read UC1 status
; ===============
UC1Status:      LD    HL,LF167              ; Test flag
                XOR   A
                OR    (HL)                  ; If zero, check current input stream
                JR    Z,LEB2E
                DEC   (HL)                  ; Decrement flag
                XOR   A
                RET                         ; Return $00

LEB2E:          LD    HL,$FF00
                LD    A,$B1
                CALL  OSBYTE                ; Read input stream
LEB36:          CALL  LEB8C
                RET   NZ
                LD    A,$D8                 ; Read soft key length
                LD    HL,$ff00
                CALL  OSBYTE
                LD    A,L
                AND   A
                JR    NZ,TTYnonempt
                LD    HL,LF167              ; Set flag to 12
                LD    (HL),$0C
                RET

; Output to UC1 - RDCH/WRCH
; =========================
UC1Out:         LD    A,C                   ; Send via PROUT to WRCH or TERMOUT
                JP    $FF9E

; Read CRT status
; ===============
CRTStatus:      LD    l,$00
                JR    LEB36                 ; Jump to test buffer 0 (keyboard)

; CRT input (ie, KBD/VDU)
; =======================
CRTIn:          LD    L,$02                 ; Keyboard Input, Serial Enabled
LEB56:          LD    A,$02                 ; Set input stream
                CALL  OSBYTE
                CALL  OSRDCH
                PUSH  AF                    ; Wait for input character
                LD    A,L
                AND   A
                JR    NZ,LEB65              ; If previous stream<>0, reselect it
                LD    L,$02                 ; Otherwise, enable serial, keyboard input
LEB65:          LD    A,$02                 ; Set input stream
                CALL  OSBYTE
                POP   AF
                RET                         ; Return with character read

; CRT Output - VDU/KBD
; ====================
CRTOut:         LD    HL,$00F4              ; Output=*,noprint,*,nospool,printer,novduprinter,vdu,noserial
LEB6F:          LD    A,$03                 ; Enter here with HL=*FX3 value
                CALL  OSBYTE                ; Select output stream
                PUSH  HL                    ; Save previous output stream
                XOR   A                     ; Switch off terminal mode
                CALL  $FFC8
                PUSH  AF                    ; Remember previous state
                LD    A,C                   ; Output the character
                CALL  OSWRCH
                POP   AF                    ; Restore previous terminal state
                CALL  $FFC8
                POP   HL                    ; Get previous output stream
                LD    H,$00
                LD    A,$03
                JP    OSBYTE                ; Restore previous output and return

; Read TTY status
; ===============
TTYStatus:      LD    L,$01                 ; Serial Input buffer
LEB8C:          LD    A,$98
                CALL  OSBYTE                ; Examine buffer
                JR    NC,TTYnonempt         ; Buffer not empty
TTYempty:       XOR   A
                RET                         ; Return A=$00 if empty
TTYnonempt:     XOR   A
                DEC   A
                RET                         ; Return A=$FF if not empty

; Console Input
; =============
TTYIn:          LD    L,$01
                JR    LEB56                 ; Jump to read from Serial Input

; TTY Output - serial output
; ==========================
TTYOut:         LD    HL,$00F7              ; Output=*,noprint,*,nospool,printer,novduprinter,novdu,serial
                JR    LEB6F

SerStatus:      LD    HL,$FFFD
                JR    LEBC7                 ; Jump to check SerialOut buffer with HL=-3

; LPT Output - test printer before outputing
; ==========================================
LPTOut:         CALL  LEBD2                 ; Test printer status

; UL1 Output - output straight to printer
; =======================================
UL1Out:         LD    A,C                   ; Test if C=0
                AND   A
                LD    HL,$001A              ; Output=*,printer,*,nospool,printer,novduprinter,novdu,noserial
                JR    NZ,LEB6F              ; If C=0, jump to output to printer
                LD    A,$06
                LD    L,$FF                 ; Set printer ignore char to $FF so can output $00
                CALL  OSBYTE
                LD    HL,$001A
                CALL  LEB6F                 ; Write character to printer
LEBBD:          LD    A,$06
                LD    L,$00                 ; Set printer ignore char to $00
                JP    OSBYTE
UL1Status:      LD    HL,$FFFC              ; HL=-4 - printer buffer
LEBC7:          LD    A,$80                 ; Check buffer status
                CALL  OSBYTE
                LD    A,L                   ; Return $00 if no space left (bug, should be AND H)
                AND   A
                RET   Z
                XOR   A
                DEC   A
                RET                         ; Return $FF if space available (bug, don't need XOR A)

; Test printer status
; ===================
LEBD2:          LD    A,($0003)             ; Get IOBYTE LST and CON fields
                AND   $C3
                CP    $82
                RET   Z                     ; If LST=LPT, CON=BAT, exit
                CALL  LEC80                 ; Check and return if printer available
                RET   NZ
                LD    A,$86                 ; Read POS and VPOS
                CALL  OSBYTE
                LD    (LF168),HL
                LD    A,$87                 ; Read MODE.
                CALL  OSBYTE
                LD    A,H
                LD    HL,$1F36              ; H=32-1 lines, L=54
                AND   A
                JR    Z,LEBFD               ; MODE 0, 32 lines
                CP    $03
                JR    Z,LEBFB               ; MODE 3, 25 lines
                LD    HL,$0000
                JR    LEBFD                 ; Others, use 00x00
LEBFB:          LD    H,$18                 ; H=25-1 lines
LEBFD:          PUSH  HL
                CALL  LEC6C                 ; PRINT TAB(54,Y); or (0,0);
                CALL  LEC8E
                DB    "Printer off line"
                DB    0
                CALL  LEC80
                POP   HL                    ; Get coord back
                PUSH  HL
                CALL  LEC6C                 ; PRINT TAB(54,Y); or (0,0);
                CALL  LEC8E
                DB    "SPACE starts Printer Sink", 0
LEC3A:          CALL  UL1Status
                JR    NZ,LEC5A
                CALL  ConsoleStatus
                AND   A
                JR    Z,LEC3A
                CALL  ConsoleIn
                CP    ' '
                JR    NZ,LEC3A              ; Not SPACE, loop back
                LD    L,$03
                LD    A,$15
                CALL  OSBYTE                ; Flush printer buffer
                LD    L,$00
                LD    A,$05
                CALL  OSBYTE                ; Select printer sink
LEC5A:          POP   HL
                CALL  LEC6C                 ; Get coords and PRINT TAB(54,Y); or (0,0);
                LD    B,$19                 ; Print 25 spaces to overwrite message
LEC60:          PUSH  BC
                LD    C,' '
                CALL  ConsoleOut            ; Print a space
                POP   BC
                DJNZ  LEC60
                LD    HL,(LF168)            ; Get original POS/VPOS back
LEC6C:          PUSH  BC
                PUSH  HL                    ; Save HL and BC
                LD    C,$1F
                CALL  ConsoleOut            ; VDU 31 - TAB
                POP   HL                    ; Get X coord
                PUSH  HL
                LD    C,L
                CALL  ConsoleOut            ; Send X coord
                POP   HL                    ; Get Y coord
                LD    C,H
                CALL  ConsoleOut            ; Send Y coord
                POP   BC                    ; Restore BC
                RET

; Test if output to printer online
; ================================
LEC80:          LD    DE,$AFC8              ; Test 45,000 times
LEC83:          CALL  UL1Status             ; Test printer buffer
                RET   NZ                    ; return if not full
                DEC   DE                    ; Decrement timer
                LD    A,E
                OR    D
                JR    NZ,LEC83              ; Loop until timed out and buffer not emptied
                XOR   A
                RET                         ; Return with Z set, printer offline

; Print inline message until $00 byte
; ===================================
LEC8E:
                EX (SP),HL                  ; Get address from stack
                PUSH DE                     ; Save everything else
                PUSH BC
                PUSH AF
LEC92:          LD   A,(HL)                 ; Get a byte
                INC  HL                     ; step to next
                AND  A
                JR   Z,LEC9E                ; Zero byte, end of message
                PUSH HL
                CALL LECA3                  ; Print the character
                POP  HL
                JR LEC92                    ; Loop back for next
LEC9E:          POP  AF                     ; Restore everything
                POP  BC
                POP  DE
                EX (SP),HL                  ; Stack pointer
                RET                         ; and return to it

; CON_ASCII
; =========
LECA3:          CP    $0D
                JR    NZ,LECAC              ; Jump to output non-<CR>
                CALL  LECAC                 ; Print <CR>
                LD    A,$0A                 ;  and add a <LF>
LECAC:          LD    C,A
                JP    ConsoleOut            ; Print character

; Complain about disk being booted from
LECB0:          CALL  LEC8E                 ; Print message:
                DB    13
                DB    "Not a CP/M system disc in A",0
                CALL  ConsoleIn             ; Wait for a key

; WARM BOOT - RESET jumps to here
; ===============================
WarmBoot:       LD    SP,$F4E0              ; Use internal MOS stack
                EI                          ; enable INTs (NB! ColdBoot doesn't explictly enable INTs)
                CALL  GetCCP                ; Load CCP and BDOS
                CALL  LED32                 ; Calculate CCP/BDOS checksum
                LD    HL,LED43
                CP    (HL)                  ; Is is same as at previous ColdBoot?
                JR    NZ,LECB0              ; No, jump to complain and reload
                CALL  LED44                 ; Initialise things, error handler, esc state, zero page jumps

; A consequence of the way the WarmBoot CCP/BDOS validity check works is
; that between any two ColdBoots, only the same CCP/BDOS will be recognised
; as being valid on reloading. This prevents you being able to, for
; instance, soft reboot from a different CCP/BDOS. A ColdBoot is required to
; load a different CCP/BDOS in and have it's checksum used.
;
; Enter CCP
; ---------
LECE6:          LD    A,($0004)             ; Get current drive
                CALL  SelectTest
                JP    $D403
SelectTest:     PUSH  AF
                AND   $0F
                LD    C,A
                CALL  SelectDisk            ; Try to select this drive.
                POP   AF                    ; Get drive back.
                LD    C,A
                LD    A,H
                OR    L
                RET   NZ                    ; If valid return C=user+drive.
                LD    C,A                   ; If not valid, return C=0+0
                RET

; DiskPatch
; (HL+1)=address
; (HL+6)=&4B for write, &53 for read
; (HL+9)=num OR &20
; currDRIVE=drive
; currTRACK=track
; currBLOCK=block
; all registers trashable, return A=result
DiskPatch:      LD    A,(currDRIVE)         ; Get current drive.
                CP    $02
                JR    NC,HDAccess           ; Jump to do hard drive access.
                PUSH  HL
                LD    A,$04                 ; Ensure DFS is selected if needed.
                CALL  EnsureFS
                POP   HL
                PUSH  AF                    ; Save previous filing system.
                CALL  DiskAcc               ; Do the disk access
                POP   HL                    ; Get old filing system back.
                PUSH  AF                    ; Save result.
                LD    A,H                   ; Reselect old FS if needed.
                CALL  RestoreFS
                POP   AF                    ; Return with result.
                RET
HDAccess:       LD    DE,GBPBchn            ; Copy address to GBPBaddr
                LD    BC,9
                LDIR                        ; BC now &0000, HL=>num OR 32
                LD    (GBPBnum+2),BC        ; num=&0000xxxx
                LD    A,(HL)
                AND   $1F
                LD    B,A                   ; B=num
                DEC   HL
                DEC   HL
                DEC   HL                    ; HL=>cmd
                RLD
                ADD   A,A                   ; &4B/&53 -> &04/&05 -> &08/&0A -> &01/&03
                SUB   7
                JP    DiskPatch2

; Calculate checksum of CCP/BDOS
; ==============================
LED32:          XOR   A
                LD    HL,BIOS_BOOT-$E00+6   ; Point to start of BDOS
LED36:          ADD   A,(HL)                ; Add the byte.
                INC   HL                    ; step to next
                EX    DE,HL
                SCF
                LD    HL,BIOS_BOOT-1
                SBC   HL,DE
                EX    DE,HL                 ; Have we got past $E9FF yet?
                JR    NZ,LED36              ; Loop back until all done
                RET
LED43:          DB    $89                   ; CCP/BDOS checksum

; Various initialisations
; =======================
LED44:          XOR   A                     ; Clear various things
                LD    (DirCacheOk),A
                LD    (LF1E6),A
                LD    (LF1E8),A
                LD    (DeBlockDirty),A
                CALL  $FFBF                 ; Ensure RSTERR at $0038 is set up
                LD    HL,($FF84)            ; Set default error handler
                LD    ($FFFA),HL
                LD    A,$E5
                LD    HL,1
                CALL  OSBYTE                ; ESC key returns ASCII
                LD    A,$C3                 ; JP opcode
                LD    ($0000),A             ; Set RESET and BDOS entries
                LD    ($0005),A
                LD    HL,BIOS_WBOOT         ; Point RESET to BIOS WBOOT entry
                LD    ($0001),HL
                LD    HL,BIOS_BOOT-$E00+6   ; Point BDOS to BDOS function entry
                LD    ($0006),HL
                LD    BC,$0080              ; Continue to set DMA to defaut $0080

; Set DMA
; =======
; BC=Disk Memory Address
SetDMA:         LD  (DMAADDR),BC            ; Store current DMA
                RET

; Home Disk
; =========
DiskHome:       LD  BC,$0000                ; Prepare TRACK=0
                LD  A,(DeBlockDirty)
                OR  A
                JR  NZ,SetTrack             ; If ???, skip
                LD  (LF1E6),A               ; If ???, set ??? to zero

; Select track
; ============
; BC=track
SetTrack:       LD    A,C
                LD    (TRACK),A             ; Store current track
                RET

; Select disk
; ===========
; C=drive number, E.b0=not first occurance since reset
SelectDisk:     LD    A,C
                CP    $02                   ; Test if hard drive present.
                CALL  Z,DiskTest
                LD    HL,0                  ; Exit with HL=0 if no hard drive.
                RET   NC
                LD    BC,DPH_Base
                LD    (DRIVE),A             ; Store current drive
                LD    L,A
                ADD   HL,HL                 ; Multiply drive by 16 to index into DPB table
                ADD   HL,HL
                ADD   HL,HL
                ADD   HL,HL
                ADD   HL,BC                 ; Add base of DPB table
                RET                         ; Return HL=DPB for this drive

; Select sector
; =============
; BC=sector
SetSector:      LD    A,C
                LD    (SECTOR),A            ; Store current sector
                RET

; Read a sector from disk
; =======================
; Read one 128-byte sector specified by DRIVE, TRACK, SECTOR
; On return, A=$00 - Ok
;            A=$01 - Sector error
;
DiskRead:       LD    (DISKSP0+1),SP        ; Use internal stack for disk operations
                LD    SP,$F4CC
                XOR   A
                LD    (LF1E8),A             ; LF1E8=$00
                LD    A,$01                 ; $01=DiskRead
                LD    (DSKRDWR),A
                LD    (LF1ED),A
                LD    A,$02                 ; Read can be deferred, no pre-read necessary
                LD    (DSKDEFR),A
                JP    LEE2F

; Write a sector to disk
; ======================
; Write one 128-byte sector specified by DRIVE, TRACK, SECTOR
; On entry,  C=0 - Normal sector write     - Write can be deferred
;            C=1 - Write to directory      - Write must be immediate
;            C=2 - Write to unused sectors - Write can be deferred, no pre-read is necessary
;                  (also set by any read)
; On return, A=$00 - Ok
;            A=$01 - Sector error
;
DiskWrite:      LD    (DISKSP0+1),SP        ; Use internal stack for disk operations
                LD    SP,$F4CC
                XOR   A                     ; $00=DiskWrite
                LD    (DSKRDWR),A
                LD    A,C
                LD    (DSKDEFR),A           ; DSKDEFR=write type
                CP    $02
                JR    NZ,LEDF0              ; No pre-read needed, jump forward

; Write can be deferred, no pre-read, writing to unused sectors
                LD    A,$10
                LD    (LF1E8),A             ; LF1E8=$10
                LD    A,(DRIVE)
                LD    (LF1E9),A             ; Copy D/T/S to ...
                LD    A,(TRACK)
                LD    (LF1EA),A
                LD    A,(SECTOR)
                LD    (LF1EB),A

; All types of write
LEDF0:          LD    A,(LF1E8)
                OR    A
                JR    Z,LEE27               ; $00 - jump for ...
                DEC   A
                LD    (LF1E8),A             ; <>$00, set to $FF for write
                LD    A,(DRIVE)
                LD    HL,LF1E9
                CP    (HL)
                JR    NZ,LEE27              ; Different drive
                LD    A,(TRACK)
                LD    HL,LF1EA
                CP    (HL)
                JR    NZ,LEE27              ; Different track
                LD    A,(SECTOR)
                LD    HL,LF1EB
                CP    (HL)
                JR    NZ,LEE27              ; Different sector
                INC   (HL)
                LD    A,(HL)
                CP    $14                   ; Last sector on track?
                JR    C,LEE21
                LD    (HL),$00              ; Set sector back to zero.
                LD    HL,LF1EA
                INC   (HL)                  ; increment track.
LEE21:          XOR   A
                LD    (LF1ED),A
                JR    LEE2F
LEE27:          XOR   A
                LD    (LF1E8),A
                INC   A
                LD    (LF1ED),A

; DiskRead and DiskWrite merge here
; ---------------------------------
LEE2F:          XOR   A
                LD    (LF1EC),A
;                                   ; Hook here for additional drives by sector access
                LD    A,(SECTOR)
                OR    A
                RRA
                OR    A
                RRA
                LD    (BLOCK),A             ; BLOCK=SECTOR/4
                LD    HL,LF1E6
                LD    A,(HL)
                LD    (HL),$01
                OR    A
                JR    Z,LEE68
                LD    A,(DRIVE)
                LD    HL,currDRIVE
                CP    (HL)
                JR    NZ,LEE61
                LD    A,(TRACK)
                LD    HL,currTRACK
                CP    (HL)
                JR    NZ,LEE61
                LD    A,(BLOCK)
                LD    HL,currBLOCK
                CP    (HL)
                JR    Z,LEE85               ; Requested block is in deblock area

; Requested block not in deblock area
; -----------------------------------
LEE61:          LD    A,(DeBlockDirty)
                OR    A
                CALL  NZ,LEF04              ; Flush disk buffer - write it to disk
LEE68:          LD    A,(DRIVE)
                LD    (currDRIVE),A         ; Set that requested block is in deblock area
                LD    A,(TRACK)
                LD    (currTRACK),A
                LD    A,(BLOCK)
                LD    (currBLOCK),A
                LD    A,(LF1ED)
                OR    A
                CALL  NZ,LEEC7              ; Read from disk to disk buffer if needed
                XOR   A
                LD    (DeBlockDirty),A

; Access data in deblock area
; ---------------------------
LEE85:          LD    A,(SECTOR)            ; Get sector bottom two bits - 0, 1, 2, 3
                AND   $03
                LD    L,A
                LD    H,$00
                ADD   HL,HL                 ; Multiply by 128 to index into deblock area
                ADD   HL,HL
                ADD   HL,HL
                ADD   HL,HL
                ADD   HL,HL
                ADD   HL,HL
                ADD   HL,HL
                LD    DE,LF272
                ADD   HL,DE                 ; HL=Deblock area + 128*block{b0-b1}
                LD    DE,(DMAADDR)
                LD    BC,$0080              ; Prepare to copy 128 bytes - one record
                LD    A,(DSKRDWR)
                OR    A
                JR    NZ,LEEAB              ; Skip past if READ
                LD    A,$01
                LD    (DeBlockDirty),A
                EX    DE,HL                 ; Swap source and dest for WRITE
LEEAB:          LDIR                        ; Copy data to/from deblock area
                LD    A,(DSKDEFR)
                CP    $01                   ; Was it 'Write to directory'?
                LD    A,(LF1EC)
DISKSP0:        LD    SP,0                  ; Restore stack (modified from elsewhere)
                NOP
                RET   NZ                    ; Return result if not 'Write to directory'
                OR    A
                RET   NZ                    ; Return result if error
                XOR   A
                LD    (DeBlockDirty),A
                CALL  LEF04                 ; Update directory cache
                LD    A,(LF1EC)
                RET                         ; Return result

; Read/Write a block to/from deblock area
; ---------------------------------------
LEEC7:          CALL  IsDirCacheOk          ; Check if TIME has expired
                CALL  IsBlockDir            ; Is requested block within directory?
                LD    A,$53
                JR    Z,LEF1E               ; No, jump past with A=$53 - DSKRD

; Access part of the directory
; ----------------------------
                LD    A,(DirCacheOk)
                AND   A
                JR    Z,LEEE9               ; Directory cache not valid, jump to reload
                LD    A,(CacheCBDrive)
                LD    HL,currDRIVE
                CP    (HL)
                JR    NZ,LEEE9              ; Different drive, load directory
                LD    A,(CacheCBTrack)
                LD    HL,currTRACK
                CP    (HL)
                JR    Z,LEEFF               ; Same drive and track, cache still valid, use it
LEEE9:          LD    HL,CacheCBDrive       ; Point to control block to load directory to cache
                LD    A,(currDRIVE)
                LD    (HL),A                ; Set drive in control block
                LD    A,(currTRACK)
                LD    (CacheCBTrack),A      ; Set track in control block
                CALL  LEF49                 ; Clear cache and read new directory
                RET   NZ
                LD    A,$FF
                LD    (DirCacheOk),A        ; Set directory data valid
LEEFF:          LD    A,$01
                JP    LEFFF                 ; Jump to read data from cached directory

; Update directory cache after writing to disk buffer
; ---------------------------------------------------
LEF04:          CALL  IsDirCacheOk          ; Check if directory cache has expired
                LD    A,$4B
                CALL  LEF1E                 ; Do 'DiskWrite'
                JR    NZ,LEF17              ; Error occured, invalidate cache
                CALL  IsBlockDir
                RET   Z                     ; Return if not directory data
                LD    A,$00
                JP    LEFFF                 ; Also write to cached data

LEF17:          PUSH AF
                XOR  A
                LD   (DirCacheOk),A         ; Set cache has expired
                POP  AF
                RET

; Read/Write data area
; --------------------
LEF1E:          LD    (LEFBB),A             ; Store disk access command
                LD    A,(currDRIVE)
                LD    (LEFB5),A             ; Store drive in control block
                LD    A,(currTRACK)
                CP    $50
                JR    C,LEF3A               ; Track<80, side 0, jump forward
                LD    C,A
                LD    A,(LEFB5)
                ADD   A,$02                 ; Add 2 to drive for side 1
                LD    (LEFB5),A
                LD    A,$9F
                SUB   C                     ; Convert track 80-159 to 79-0
LEF3A:          LD    (LEFBC),A             ; Store track in control block
                LD    A,(currBLOCK)
                CALL  LEFAC
                LD    (LEFBD),A
                LD    HL,LEFB5
LEF49:          PUSH  HL
                CALL  LF063                 ; Set previous TIME=current TIME
                POP   HL
; Return here to retry
; Hook here to add extra drives by block access
LEF4E:          CALL  DiskPatch             ; Disk Access, HL=>control block
                AND   A
                JR    Z,LEFA7               ; Ok, jump to update flag and return
                CP    $12
                JR    NZ,LEFA5              ; Not 'DiskReadOnly', jump to return disk error
                PUSH  HL
                CALL  LEC8E
                DB    13, "Bdos Err On ", 0
LEF6A:          LD    A,(currDRIVE)
                AND   $01
                ADD   A,'A'
                LD    C,A
                CALL  ConsoleOut            ; Print drive letter
                CALL  LEC8E
                DB    ": R/O (Disc is Write Protected)", 0
                CALL  ConsoleIn             ; Wait for a keypress
                POP   HL
                CP    'R'
                JR    Z,LEF4E               ; 'R'etry
                CP    'r'
                JR    Z,LEF4E               ; 'r'etry
                RST   $00                   ; Not Retry, reboot
LEFA5:          LD    A,$01
LEFA7:          AND   A
LEFA8:          LD   (LF1EC),A
                RET

; Convert BLOCK 0-4 to sector 0/4/8/2/6
; -------------------------------------
LEFAC:          LD    C,A
                LD    B,$00
                LD    HL,InterLeaveTab
                ADD   HL,BC
                LD    A,(HL)
                RET

; OSWORD $7F control block
; ------------------------
LEFB5:          DB    $00                   ; Drive/Side
LEFB6:          DW    LF272                 ; Data address
                DW    0
                DB    $03
LEFBB:          DB    $53                   ; Command
LEFBC:          DB    $28                   ; Track
LEFBD:          DB    $00                   ; Sector
                DB    $22                   ; 2x256-byte sectors
                DB    $00                   ; Result

; OSWORD $7F control block to access directory cache
; --------------------------------------------------
CacheCBDrive:   DB    $00                   ; Drive/Side
                DW    $2600                 ; Data address in directory cache
                DW    $FFFF
                DB    $03
                DB    $53                   ; Command
CacheCBTrack:   DB    $03                   ; Track
                DB    $00                   ; Sector
                DB    $2A                   ; 10x256-byte sectors
LEFCA:          DB    $00                   ; Result

; XXX

InterLeaveTab:  DB    0                     ; Sector interleave table
                DB    4
                DB    8
                DB    2
                DB    6

; Continue to look for hard drive
; -------------------------------
DiskTest:       XOR   A                             ; Prepare 'no disk access'
DiskPatch2:     LD    (GBPBnum+0),BC
                LD    B,A                   ; num=&0000nn00, B=0 test, =1 write, =3 read
                LD    DE,(currTRACK)
                LD    D,C                   ; D=0, E=TRACK, B=cmd, C=0, HL=xxx
                LD    A,(currBLOCK)
                ADD   A,A
                ADD   A,A                   ; A=BLOCK moved up to b7
                SRL   E
                RRA
                LD    H,A
                LD    L,C                   ; DE=<0-TRACK>, A=<Tb0-BLOCK-0> -> DE=&00<TRK>, HL=<BLK><00>
                INC   H
                JR    NZ,DiskNoInc
                INC   DE                    ; DEHL=<Tb0-BLOCK-0><00000000>+256 - index into MyZ80 disk image
DiskNoInc:      LD    (GBPBptr+0),HL
                LD    (GBPBptr+2),DE
;
                LD     A,(DriveCfs)
                CALL   EnsureFS             ; Select DriveC filing system if needed
                PUSH   AF                   ; Save previous FS, 
                LD     C,$FF                ; prepare C=&FF for not found (no drive present)
                LD     HL,DriveC
                JP     DiskPatch3
;

; Read or write data to/from directory cache in I/O memory
; --------------------------------------------------------
LEFFF:          LD    (LF02C),A             ; Store read/write command in control block
                LD    A,(currBLOCK)
                CALL  LEFAC                 ; Convert BLOCK to sector number
                LD    HL,$2500              ; HL=cache-256
                INC   A
                LD    B,A                   ; B=sector+1
                LD    DE,$0100
LF010:          ADD   HL,DE                 ; Index into cache at $2600+sector*256
                DJNZ  LF010
                LD    (LF022),HL
                LD    HL,LF020              ; Store in control block
                LD    A,$FF
                CALL  OSWORD                ; Make transfer.
                XOR   A
                RET

; OSWORD $FF control block - read/write I/O memory
; ------------------------------------------------
LF020:          DB    $0D                   ; Send block length=13
                DB    $01                   ; Receive block length=1
LF022:          DW    $2600                 ; I/O transfer address
                DW    $0000
                DW    LF272
                DW    $0000                 ; CoPro transfer address
                DW    $0200                 ; Data length
LF02C:          DB    $01                   ; Transfer type 0=read from I/O, 1=write to I/O

; Compare current TIME to previous TIME+3.84s
; -------------------------------------------
IsDirCacheOk:   LD    A,$01
                LD    HL,LF472
                CALL  OSWORD                ; Read current TIME
                LD    DE,(LF477+0)
                LD    HL,(LF472+0)          ; Compare current TIME to stored TIME
                OR    A
                SBC   HL,DE
                RR    B
                LD    DE,$0180
                OR    A
                SBC   HL,DE
                JR    NC,LF05E
                LD    DE,(LF477+2)
                LD    HL,(LF472+2)
                RL    B
                SBC   HL,DE
                JR    NZ,LF05E
                LD    HL,(LF477+4)
                LD    A,(LF472+4)
                SBC   A,L
                RET   Z
LF05E:          XOR   A                     ; TIME has expired
                LD   (DirCacheOk),A
                RET

LF063:          LD    A,(currDRIVE)         ; Compare with directory drive
                LD    HL,CacheCBDrive
                CP    (HL)
                RET   NZ                    ; Different drives, return
                LD    HL,LF472              ; Copy currTIME to prevTIME
                LD    DE,LF477
                LD    BC,$0005
                LDIR
                RET

; Check if requested block is within the directory
; ------------------------------------------------
IsBlockDir:     LD    A,(currDRIVE)
                AND   $FE
                JR    NZ,LF089
                LD    A,(currTRACK)         ; Check requested TRACK
                CP    $03                   ; Track 3 - directory, exit with A=$FF
                JR    Z,LF08B
                CP    $04
                JR    NZ,LF089              ; Not track 4 - exit with A=$00
                LD    A,(currBLOCK)         ; Check requested block
                CP    $03
                JR    C,LF08B               ; Block<3 - directory, exit with A=$FF
LF089:          XOR   A                     ; A=$00, not directory
                RET
LF08B:          XOR   A
                DEC   A                     ; A=$FF, directory
                RET

; Check for any boot files
; ------------------------
LF08E:          LD    C,$0E
                CALL  $DC06                 ; Reset disk system
                LD    DE,strBOOT            ; Search for "BOOT.COM"
                LD    C,$11
                CALL  $DC06
                INC   A
                JR    Z,LF0BF               ; Not found, try next one
                CALL  LEC8E                 ; Print message:
                DB    "Running BOOT.COM",13,0
                LD    HL,cmdBOOT            ; Point to "BOOT" command string
LF0B6:          LD    DE,$D407
                LD    BC,$000D              ; Copy command into CCP command buffer
                LDIR
                RET

LF0BF:          LD    DE,subBOOT
                LD    C,$11
                CALL  $DC06                 ; Search for "BOOT.SUB"
                INC   A
                RET   Z                     ; Not there, exit
                LD    DE,strSUBMIT          ; Search for "SUBMIT.COM"
                LD    C,$11
                CALL  $DC06
                INC   A
                JR    Z,LF0F1               ; Not there, report the problem - if BOOT.SUB is present, SUBMIT.COM needs to also be there
                CALL  LEC8E                 ; Print message
                DB    "Submitting BOOT.SUB",13,0
LF0EC:          LD    HL,cmdSUBMIT
                JR    LF0B6                 ; Jump to copy "SUBMIT BOOT" into CCP command buffer
LF0F1:
;               CALL  LEC8E         ; Print message:
;               DB    "No SUBMIT.COM",0
                RET

cmdBOOT:        DB    4, "BOOT", 0
cmdSUBMIT:      DB    11, "SUBMIT BOOT", 0
strSUBMIT:      DB    0, "SUBMIT  COM", 0
subBOOT:        DB    0, "BOOT    SUB", 0   ; Will be overwritten when SUMBIT.COM searched for
strBOOT:        DB    0, "BOOT    COM", 0   ; Will be overwritten when BOOT.SUB searched for
;
; F53B - Start of core MOS code
; F800 - Start of Z80Tube MOS code
; COLD BOOT - Entered on startup after booting from Reset
; =======================================================
ColdBoot:       LD    SP,$F4E0              ; Use internal MOS stack
LF167           EQU   ColdBoot+20
LF168           EQU   ColdBoot+21
                CALL  LED32                 ; Get 8-bit checksum of CCP/BDOS in memory
                LD    (LED43),A
                CALL  LED44                 ; Initialise things, error handler, esc state, zero page jumps
                CALL  LEA93                 ; Initialise input stream and printer ignore state
                LD    HL,($EA04)
                LD    ($EA01),HL            ; ColdBoot now goes to WarmBoot
                LD    HL,alvC_Z80Tube+256   ; DriveC allocation vector for Z80Tube
                LD    A,(HL)                ; Check for Z80Tube at &F800
                DEC   H
                CP    $C3
                JR    Z,ColdALV             ; Use Z80Tube-256 as alvC
                LD    HL,alvC_CoPro         ; DriveC allocation vector for hardware CoPro
ColdALV:        LD    (DPH_C+14),HL         ; Set up DriveC allocation vector
                CALL  LEC8E                 ; Print startup message
                DB    13,"Acorn CP/M 2.2 - HDBIOS 1.26",13,0
                LD    C,13
                CALL  $DC06                 ; Reset disks
                LD    A,2
                CALL  SelectTest            ; See if drive C present, C=2 or 0
                PUSH  BC
                LD    A,C
                LD    (4),A                 ; Set system drive
                LD    E,C
                CALL  LF08E                 ; Check for boot files on this drive
                POP   BC
                JP    $D400                 ; Enter CCP with C=drive

DiskPatch3:     LD    A,(HL)
                CP    '!'                   ; Is filename present?
                JR    C,NoImageFile         ; No image filename
                LD    A,$C0
                CALL  OSFIND                ; Open hard drive image file
                AND   A
                JR    Z,NoImageFile         ; Skip past if no image file
                LD    HL,GBPBchn            ; Point to control block
                LD    (HL),A                ; set channel
                PUSH  AF 
                LD    A,B
                AND   A
                CALL  NZ,OSGBPB             ; do disk data transfer if nonzero
                POP   HL
                XOR   A
                LD    C,A
                CALL  OSFIND                ; Close channel, C=&00 for Ok
NoImageFile:    POP   AF
                CALL  RestoreFS             ; Reselect oldFS if needed
                LD    A,B
                AND   A
                LD    A,C
                RET   NZ                    ; If disk access done, return A=&00/&FF
                CP    $FF
                LD    A,2
                RET                         ; If disk test done, return A=2 (drive C), C=ok, NC=absent
;
RestoreFS:      AND   A
                LD    H,A
                JR    NZ,SelectFS           ; A<>&00, select FS
EnsureFS:       AND   A
                RET   Z                     ; Nothing to reselect
                LD    H,A           
                XOR   A
                LD    E,A
                CALL  OSARGS                ; A=FS, wantedFS in H preserved
                XOR   H
                RET   Z
                XOR   H                     ; If currentFS=wantedFS, return with A=0
SelectFS:       PUSH  AF
                LD    L,18
                LD    A,143                 ; Select FS in H
                CALL  OSBYTE
                POP   AF
                RET                         ; Return previous FS or &00 in A

; Sector Translate
; ================
; On entry, BC=sector
; On exit,  HL=translated sector
;
SectorTranslate:
                LD    H,B
                LD    L,C
                RET                         ; No translation
;
GetCCP:         LD    A,63                  ; Ask for a CCP with *fx63
                CALL  OSBYTE
                INC   L
                RET   NZ  
                JP    LoadCCP               ; Otherwise, use Z80MOS
                org    $f1e0
                DB    "HDBIOS 1.26 (12 Sep 2010) (C)JGH"

DRIVE           EQU   $F1E0                 ; Current drive
TRACK           EQU   DRIVE+1               ; Current track
SECTOR          EQU   TRACK+1               ; Current sector
currDRIVE:      EQU   SECTOR+1
currTRACK:      EQU   currDRIVE+1
currBLOCK:      EQU   currTRACK+1
BLOCK:          EQU   currBLOCK+1
DirCacheOk:     EQU   BLOCK+1               ; $00 - TIME has expired, $FF - dir cache still valid
LF1E6:          EQU   DirCacheOk+1          ; Used in HOME
DeBlockDirty:   EQU   LF1E6+1
LF1E8:          EQU   DeBlockDirty+1
LF1E9:          EQU   LF1E8+1               ; DRIVE
LF1EA:          EQU   LF1E9+1               ; TRACK
LF1EB:          EQU   LF1EA+1               ; SECTOR
LF1EC:          EQU   LF1EB+1               ; READ/WRITE result
LF1ED:          EQU   LF1EC+1
DSKRDWR:        EQU   LF1ED+1
DSKDEFR:        EQU   DSKRDWR+1             ; Disk write deferred
DMAADDR:        EQU   DSKDEFR+1             ; DMA

LF1F2:          EQU    $F1F4                ; DIRBUF, 128 bytes.
LF272:          EQU    LF1F2+128            ; Disk deblock area
LF472:          EQU    LF272+512            ; Control block to read TIME
LF477:          EQU    LF472+5              ; Previously read TIME
LF47C           EQU    LF477+5

; F4CC - internal stack for Disk routines, 40 entries
; F4E0 - internal stack for Boot, 9 entries
Attachments
CPM HD 1.26.dsd
(127.5 KiB) Downloaded 4 times
Coeus
Posts: 3557
Joined: Mon Jul 25, 2016 12:05 pm
Contact:

Re: Model Master 128+Acorn Z80 Second Processor

Post by Coeus »

Sazhen86 wrote: Sat Feb 24, 2024 3:09 am Testing that showed it be way slower....
Something else that has a bearing on the question of the directory cache.

Earlier in the thread I mentioned the Acorn Z80 Winchester Utilities disk. Over in the tread where this was submitted as a new item to archive, it was discovered that the disc actually contained the source for a version 2 of the Z80 tube client/boot ROM. This was the one, I think, that JGH was disparaging about.

But, there is also a version 2.1 of the BIOS and, while I am sure the client/boot ROM and BIOS were designed to work together, the BIOS still works fine under the 1.21 client/boot ROM - it just doesn't auto-boot. This version of the BIOS does not include the directory cache. Possibly there was space pressure but, perhaps, we can also conclude that it wasn't sufficiently effective for whoever wrote the revised BIOS to prioritise it over other things.

This 2.1 BIOS seems to work pretty well. It supports up to four hard discs and up to two floppies and it performs better than the JGH modification to BIOS 1.2 that we've been looking at because it keeps the ADFS hard disk image file open between accesses. Also, like the JGH 1.2 HDBIOS, it ensures DFS is the current filing system before using OSWORD &7F for floppy disc sector access and ensures ADFS is the selected filing system before accessing the hard disk image file.

I don't think I have seen it published as a separate file. The way it gets set up is that one runs the HDMAN.COM program from the Acorn Z80 Winchester Utilities floppy and chooses to initialise a hard disk for CP/M. That then creates a $.CPM directory and puts various files in it. I haven't checked what all of them do but:
  • The file '1' is image file (container) for the first CP/M hard disk. If you choose more than one hard disk, each gets a single digit filename in sequence.
  • The file 'S' is a copy of the CCP and BDOS concatenated as a single ADFS file. This is the mechanism whereby BDOS and CCP are re-loaded after a warm boot, replacing loading the these from system tracks: the BIOS has an embedded OSCLI command to load this file on warm boot.
  • The 'Z' file is then a concatenation of the 'S' file with the 2.1 BIOS. Cold boot is done by running this file: I think the 2.1 client/boot ROM automates this but under client/boot ROM 1.2 it can be launched by *CPM.Z. There are some tables in the BIOS that are updated by the HDMAN program according to the choices you make:
    • Towards the start, at ea36 (in-memory address), is a drive table. This is a set of pointers (16bit), one for each of the possible drive letters A to P. If a drive is defined the relevant entry points to the DPH for that disk.
    • Towards the end are the DPH and DPB structures. The DPHs are left alone but the DPBs are updated with a suitable geometry and size of allocation unit for the size of hard disc you have chosen. You can find these by following the pointer from the drive table, via the DPH to the DPB.
Some people complained the the (first) hard disk was mapped as drive A and it took some digging to find that the floppies are G and H but the drive table could be re-arranged for any arrangement anyone prefers.

I'll carry on on another post....
Coeus
Posts: 3557
Joined: Mon Jul 25, 2016 12:05 pm
Contact:

Re: Model Master 128+Acorn Z80 Second Processor

Post by Coeus »

Out of interest, I disassembled BIOS 2.1, which is how I spotted that the directory cache had gone. I have put up a GitHub project (https://github.com/SteveFosdick/acorn-hdbios2) but, as I have not yet started hand editing the disassembly, that does actually contain the disassembled version, only the files needed to generat it, so I will attach it.

The code to handle de-blocking and deferred writes is still there. There are some small changes but it looks pretty similar. You mentioned earlier wanting to understand it. One thing that would help is understanding what all the various copies of drive/track/sector/block are doing.

There is one set that are set by the BIOS calls to set the drive/track/sector so these relate to the requested sector. I have these as:

Code: Select all

bios_drive:     defb 000h                ; f0cc	00 	. 
bios_track:     defb 000h                ; f0cd	00 	. 
                defb 000h                ; f0ce	00 	. 
bios_sector:    defb 000h                ; f0cf	00 	. 
and the JGH version has these as:

Code: Select all

DRIVE           EQU   $F1DE                 ; Current drive
TRACK           EQU   DRIVE+1               ; Current track
SECTOR          EQU   TRACK+1               ; Current sector
Then there is another set that I believe records which block is currently in the de-block area:

Code: Select all

curr_drive:     defb 000h                ; f0d0	00 	. 
curr_track:     defb 000h                ; f0d1	00 	. 
                defb 000h                ; f0d2	00 	. 
curr_block:     defb 000h                ; f0d3	00 	. 
However there is a third set that seems specific to writing, i.e. not used in the read path:

Code: Select all

lf0d8h:         defb 000h                ; f0d8	00 	. 
lf0d9h:         defb 000h                ; f0d9	00 	. 
                defb 000h                ; f0da	00 	. 
lf0dbh:         defb 000h                ; f0db	00 	. 
and the JGH version has:

Code: Select all

LF1E9:          EQU   LF1E8+1               ; DRIVE
LF1EA:          EQU   LF1E9+1               ; TRACK
LF1EB:          EQU   LF1EA+1               ; SECTOR
I suspect it is tied up with deferred writes but I do not understand why a third set is needed. Any ideas?
Attachments
hdbios-dasm.txt
(100.35 KiB) Downloaded 5 times
Sazhen86
Posts: 92
Joined: Wed Dec 30, 2020 8:55 pm
Contact:

Re: Model Master 128+Acorn Z80 Second Processor

Post by Sazhen86 »

Coeus wrote: Wed Feb 28, 2024 4:43 pm I suspect it is tied up with deferred writes but I do not understand why a third set is needed. Any ideas?
I suspect you're right that is something to do with the deferred write code and whether it needs a pre-read of the other sectors in that 512 byte block or not.

Work and life have got in the way recently and I've not had much chance to look more into this. And when I did find a little time I disappeared down a rabbit hole of looking at the code for BDOS and CCP and the Z80 Tube code as well as the BIOS. And that had me loading them into Ghidra, another rabbit hole.

When I find some time and dig myself out of those rabbit holes I'll get see if I can change focus to version 2.1.

I've also had to rebuidt the PSU in my Master 128 as it died and then I spent far too long trying to get mode 7 to look correct with an RGBtoHDMI. Hoglet helped out a lot and now it looks perfect so I can cross that off the list.

Too many projects, not enough time, as ever.
Post Reply

Return to “8-bit acorn emulators”