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:
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.
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