Zapple-II/SOFTCARD80.ASM#040000
Bobbi Webber-Manners bebfe445a5 Implemented F_RENAME.
2019-10-17 11:59:49 -04:00

1165 lines
37 KiB
Plaintext

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Z80 code running on Softcard
; Implements CP/M style BDOS interface
; Requires the companion SOFTCARD65 6502 code
; Bobbi 2019
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
BDOSADDR EQU 06000H ;
STCKTOP EQU 06FFFH ; Top of Z80 stack (below IOBUFs)
SOFTCARD EQU 0E400H ; Softcard in slot 4 ($C400)
OFFSET EQU 01000H ; Offset to add to Z80 addr to get 6502
; address. Correct for Z80 addr <= 0AFFFH
; 6502 zero page, in Z80 address space
CMD EQU 0F006H ; 6502 $06
AREG EQU 0F007H ; 6502 $07
XREG EQU 0F008H ; 6502 $08
YREG EQU 0F009H ; 6502 $09
ADDR EQU 0F0EBH ; 6502 $EB (LSB)
ADDRH EQU 0F0ECH ; 6502 $EC (MSB)
; Addresses of 6502 routines, in 6502 address space
COUT EQU 0FDEDH ; Print char in A
RDKEY EQU 0FD0CH ; Read key, return in A
BELL EQU 0FBE4H ; Sound the bell
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Entry point when Z80 cold starts is 0000H
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ORG 0000H
JP BDOSINIT ; Initialize BDOS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; See obsolescence.wix.com/obsolescence/cpm-internals for info
; on low storage usage ...
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
IOBYTE DEFB 0 ; Maps virtual to real devices
CURDRV DEFB 0 ; Currently selected drive 0=A: etc
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; BDOS entry point must be at address 0005H for CP/M compatibility
; Function to invoke is passed in C
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
BDOS JP BDOSIMP ; BDOS code is at top of memory
RVEC1 DEFW 0000H ; Restart vector 1
RVEC2 DEFW 0000H ; Restart vector 2
RVEC3 DEFW 0000H ; Restart vector 3
RVEC4 DEFW 0000H ; Restart vector 4
RVEC5 DEFW 0000H ; Restart vector 5
RVEC6 DEFW 0000H ; Restart vector 6
RVEC7 DEFW 0000H ; Restart vector 7
; Space for private BDOS data (implementation dependent) up to 005BH
DMAADDR DEFW 0080H ; DMA address defaults to FILEBUF (0080H)
LOGVEC DEFW 0000H ; Vector of logged in drives
ROVEC DEFW 0000H ; Vector of read-only drives
USERNUM DEFB 00H ; User number TODO: This goes elsewhere
; End of private, implementation dependent space
ORG 005CH ; Standard addr of 32 byte FCB1
FCB1 ; File control block #1
FCB1DRV DEFB 00H ; FCB Drive (0 current, 1 A:, 2 B: etc)
FCB1NAM DEFM 'FILENAMEEXT' ; FCB filename and extension
FCB1EX DEFB 00H ; FCB extent field
FCB1S1 DEFB 00H ; FCB S1 field
FCB1S2 DEFB 00H ; FCB S2 field
FCB1RC DEFB 00H ; FCB RC field
FCB1MAP ; Map of blocks in file (overlaps FCB2)
ORG 006CH ; Standard addr of 32 byte FCB2
FCB2 ; File control block #2
FCB2DRV DEFB 00H ; FCB Drive (0 current, 1 A:, 2 B: etc)
FCB2NAM DEFM 'FILENAMEEXT' ; FCB filename and extension
FCB2EX DEFB 00H ; FCB extent field
FCB2S1 DEFB 00H ; FCB S1 field
FCB2S2 DEFB 00H ; FCB S2 field
FCB2RC DEFB 00H ; FCB RC field
FCB2MAP ; Map of blocks in file (overlaps buffer)
ORG 0080H ; Standard addr of 128 byte File Buffer
FILEBUF DEFS 128 ; Command args go here too (Pascal string)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The application program proper starts at 0100H
; in order to be compatible with CP/M .COM programs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ORG 0100H
; Print signon message using C_WRITESTR
PROGSTRT LD DE,WELCOME ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
; Print the alphabet using C_WRITE
LD B,'A' ; First character
L1 LD E,B ; Character to print
LD C,B_C_WRITE ;
PUSH BC ; Preserve B (and C)
CALL BDOS ;
POP BC ; Restore B (and C)
INC B ;
LD A,'Z' ; Last character
CP B ;
JP Z,S1 ;
JP L1 ;
; Loop until there is a keystroke waiting using C_STAT
S1 LD C,B_C_STAT ;
CALL BDOS ;
CP 0 ; Anything?
JR Z,S1 ; If not, loop
; Print a couple of asterisks
LD E,'*' ;
LD C,B_C_WRITE ;
CALL BDOS ;
LD E,'*' ;
LD C,B_C_WRITE ;
CALL BDOS ;
; Create an FCB
LD A,1 ; A: drive
LD (FCB1DRV),A ;
LD A,'T' ; Filename
LD (FCB1NAM),A ;
LD A,'E' ; Filename
LD (FCB1NAM+1),A ;
LD A,'S' ; Filename
LD (FCB1NAM+2),A ;
LD A,'T' ; Filename
LD (FCB1NAM+3),A ;
LD A,'F' ; Filename
LD (FCB1NAM+4),A ;
LD A,'I' ; Filename
LD (FCB1NAM+5),A ;
LD A,'L' ; Filename
LD (FCB1NAM+6),A ;
LD A,'E' ; Filename
LD (FCB1NAM+7),A ;
LD A,'T' ; Extension
LD (FCB1NAM+8),A ;
LD A,'M' ; Extension
LD (FCB1NAM+9),A ;
LD A,'P' ; Extension
LD (FCB1NAM+10),A ;
; Create and open a file using ProDOS MLI
; Creates 'A/TESTFILE.TMP'
; Directory 'A' needs to exist already
LD DE,CMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_MAKE ;
CALL BDOS ;
CALL CHECKOK
; Set the DMA buffer to point to our text
LD DE,TEXTBUF ;
LD C,B_F_DMAOFF ;
CALL BDOS ;
; Write to the file
LD DE,WMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
; Close the file
LD DE,CLMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_CLOSE ;
CALL BDOS ;
CALL CHECKOK
; Overwrite DMA buffer just to be sure it is read
LD A,'X' ;
LD HL,(DMAADDR) ;
LD (HL),A ;
INC HL
LD (HL),A ;
INC HL
LD (HL),A ;
; Open the file
LD DE,OMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_OPEN ;
CALL BDOS ;
CALL CHECKOK
; Read from the file
LD DE,RMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_READ ;
CALL BDOS ;
CALL CHECKOK
; Print out what we just read
LD DE,(DMAADDR) ;
LD C,B_C_WRTSTR ;
CALL BDOS ;
; Close the file
LD DE,CLMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_CLOSE ;
CALL BDOS ;
CALL CHECKOK
; Delete the file
; LD DE,DMSG ; Address of string
; LD C,B_C_WRTSTR ;
; CALL BDOS ;
;
; LD DE,FCB1 ; Default FCB address
; LD C,B_F_DELETE ;
; CALL BDOS ;
;
; CALL CHECKOK
; Read keyboard and echo to screen C_READ, C_WRITE
L2 LD C,B_C_READ ;
CALL BDOS ;
LD E,A ; Prepare to echo keystroke
LD C,B_C_WRITE ;
CALL BDOS ;
JP L2 ; Forever and ever
; Check an MLI call was successful and print out message accordingly
CHECKOK LD A,(AREG) ; Look at the return code
CP 0 ; Success?
JP Z,COKS1 ;
PUSH AF ; Preserve A
LD DE,FAILMSG1 ; Fail message
LD C,B_C_WRTSTR ;
CALL BDOS ;
POP AF ; Restore A
LD L,A ; Copy to HL for NUM2HEX
LD H,0 ; ...
LD DE,HEXBUF ; Generate hex string to HEXBUF
CALL NUM2HEX ; ...
LD A,0FFH ; 0FFH for error
LD L,A ; Return code in L also
LD DE,HEXBUF+2 ; Write hex value to console
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FAILMSG2 ; Fail message
LD C,B_C_WRTSTR ;
CALL BDOS ;
RET ;
COKS1 LD DE,SUCCMSG ; Success message
LD C,B_C_WRTSTR ;
CALL BDOS ;
RET
WELCOME DEFB 13
DEFM 'Zapple-II Test Stub...'
DEFB 13, '$'
CMSG DEFB 13
DEFM 'Creating & opening A/TESTFILE.TMP'
DEFB 13, '$'
WMSG DEFB 13
DEFM 'Writing record to A/TESTFILE.TMP'
DEFB 13, '$'
CLMSG DEFB 13
DEFM 'Closing A/TESTFILE.TMP'
DEFB 13, '$'
OMSG DEFB 13
DEFM 'Opening A/TESTFILE.TMP'
DEFB 13, '$'
RMSG DEFB 13
DEFM 'Reading record from A/TESTFILE.TMP'
DEFB 13, '$'
DMSG DEFB 13
DEFM 'Deleting A/TESTFILE.TMP'
DEFB 13, '$'
SUCCMSG DEFM 'Success!'
DEFB 13, '$'
FAILMSG1 DEFM 'FAIL (0x$'
FAILMSG2 DEFM ')'
DEFB 13, '$'
TEXTBUF DEFM 'Mary had a little lamb. Its fleece was white as snow. '
DEFM 'And everywhere that Mary went, that lamb was sure to go.$'
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Implementation of CP/M STYLE BDOS
; Function to invoke is passed in C, as follows:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
B_C_TERMCPM EQU 00H ; System reset
B_C_READ EQU 01H ; Console read
B_C_WRITE EQU 02H ; Console write
B_C_RAWIO EQU 06H ; Direct console I/O
B_GET_IOB EQU 07H ; Get IOBYTE
B_SET_IOB EQU 08H ; Set IOBYTE
B_C_WRTSTR EQU 09H ; Console write string
B_C_RDSTR EQU 0AH ; Read console string
B_C_STAT EQU 0BH ; Console status
B_S_BDOSVER EQU 0CH ; Return version number
B_DRV_ALLRST EQU 0DH ; Reset disks
B_DRV_SET EQU 0EH ; Select disk
B_F_OPEN EQU 0FH ; Open file
B_F_CLOSE EQU 10H ; Close file
B_F_DELETE EQU 13H ; Delete file
B_F_READ EQU 14H ; Read file sequentially
B_F_WRITE EQU 15H ; Write file sequentially
B_F_MAKE EQU 16H ; Create and open file
B_F_RENAME EQU 17H ; Rename file
B_DRV_LOGVEC EQU 18H ; Return bitmap of logged-in drives
B_DRV_GET EQU 19H ; Return current drive
B_F_DMAOFF EQU 1AH ; Set DMA address
B_DRV_SRO EQU 1CH ; Software write-protect current drive
B_DRV_ROVEC EQU 1DH ; Return bitmap of read-only drives
B_F_USERNUM EQU 20H ; Get/set user number
B_F_SIZE EQU 23H ; Compute file size
B_DRV_RESET EQU 25H ; Selectively reset disk drives
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ORG BDOSADDR
BDOSINIT LD SP,STCKTOP ; Initialize SP
LD A,0 ;
LD (IOBYTE),A ; Initialize IOBYTE
LD (CURDRV),A ; Drive A:
LD (FILEBUF),A ; Zero chars in command tail
LD (FRN1),A ; Initialize FRNs to zero
LD (FRN2),A ; ...
LD (FRN3),A ; ...
LD (FRN4),A ; ...
LD HL,0080H ; Initialize DMAADDR to 0080H
LD (DMAADDR),HL ; ...
LD HL,0000H ; Initialize LOGVEC & ROVEC to 0000H
LD (LOGVEC),HL ; ...
LD (ROVEC),HL ; ...
JP PROGSTRT ; Run user program
BDOSIMP LD A,C ; Prepare to check C is in range
CP 41 ; Max syscall# for CP/M 2.2 is 40
JP NC,UNIMP ; If >41 then call UNIMP
LD HL,BDOSVEC ; Start of vector table
SLA C ; Multiply C by 2
LD B,0 ; MSB of BC is zero
ADD HL,BC ; Address of vector in HL
LD C,(HL) ; Read LSB of address to jump to
INC HL ; Read MSB of address to jump to
LD H,(HL) ; ...
LD L,C ; Address needs to be in HL
JP (HL) ; Jump to it!
; Vector table
BDOSVEC DEFW C_TERMCPM ; C=00H
DEFW C_READ ; C=01H
DEFW C_WRITE ; C=02H
DEFW UNIMP ; C=03H (A_READ) AUX
DEFW UNIMP ; C=04H (A_WRITE) AUX
DEFW UNIMP ; C=05H (L_WRITE) PRN
DEFW C_RAWIO ; C=06H
DEFW GET_IOB ; C=07H
DEFW SET_IOB ; C=08H
DEFW C_WRITESTR ; C=09H
DEFW C_READSTR ; C=0AH
DEFW C_STAT ; C=0BH
DEFW S_BDOSVER ; C=0CH
DEFW DRV_ALLRST ; C=0DH
DEFW DRV_SET ; C=0EH
DEFW F_OPEN ; C=0FH
DEFW F_CLOSE ; C=10H
DEFW UNIMP ; C=11H (F_SFIRST)
DEFW UNIMP ; C=12H (F_SNEXT)
DEFW F_DELETE ; C=13H
DEFW F_READ ; C=14H
DEFW F_WRITE ; C=15H
DEFW F_MAKE ; C=16H
DEFW F_RENAME ; C=17H
DEFW DRV_LOGVEC ; C=18H
DEFW DRV_GET ; C=19H
DEFW F_DMAOFF ; C=1AH
DEFW UNIMP ; C=1BH (DRV_ALLOCVEC)
DEFW DRV_SETRO ; C=1CH
DEFW DRV_ROVEC ; C=1DH
DEFW UNIMP ; C=1EH (F_ATTRIB)
DEFW UNIMP ; C=1FH (DRV_DPB)
DEFW F_USERNUM ; C=20H
DEFW UNIMP ; C=21H (F_READRAND)
DEFW UNIMP ; C=22H (F_WRITERAND)
DEFW F_SIZE ; C=23H
DEFW UNIMP ; C=24H (F_RANDREC)
DEFW DRV_RESET ; C=25H
DEFW UNIMP ; C=26H (*nothing* in CP/M 2.2)
DEFW UNIMP ; C=27H (*nothing* in CP/M 2.2)
DEFW UNIMP ; C=28H (F_WRITEZF)
; Unimplemented BDOS call, just ring the bell
UNIMP LD HL,BELL ; We are going to call BELL
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
RET ; Return to calling program
; System reset. Jump to $0000 - doesn't return
C_TERMCPM RST 0 ; Quick jump to zero
; Wait for a character from the console, return it in A and L
; Also echoes the char to the console
C_READ LD HL,RDKEY ; We are going to call RDKEY
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
LD A,(AREG) ; Grab the return value
PUSH AF ; Preserve A (and F)
LD HL,COUT ; Echo the character using COUT
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
POP AF ; Restore A (and F)
AND 7FH ; Mask high bit
LD L,A ; Copy A to L
RET ; Return to calling program
; Write character in E to the console
; TODO: Handle tabs, ^S and ^Q
C_WRITE LD A,80H ; Set high bit
OR E ; ...
CP 8AH ; Check for linefeed
RET Z ; If LF, don't print it
LD (AREG),A ; Pass char to COUT in 6502 A
LD HL,COUT ; We are going to call COUT
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
RET ; Return to calling program
; If E if 0FFH then input a character from console and return it in A and L
; without echoing the input character. Otherwise output char in E to the
; console (no tabs, ^S or ^Q supported)
C_RAWIO LD A,E ; See if E if 0FFH
CP 0FFH ; ...
JP Z,RIS1 ; If so, then read
; Write to console
LD A,80H ; Set high bit
OR E ; ...
CP 8AH ; Check for linefeed
RET Z ; If LF, don't print it
LD (AREG),A ; Pass char to COUT in 6502 A
LD HL,COUT ; We are going to call COUT
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
RET
; If character is waiting, read from console & return in A
; Otherwise, return 00H in A
RIS1 LD A,3 ; CMD=3 means peek at keyboard
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it
LD A,(AREG) ; Grab the return value
CP 0 ; If zero, no chars are waiting
JP Z,RIS2 ; ...
LD HL,RDKEY ; We are going to call RDKEY
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
LD A,(AREG) ; Grab the return value
AND 7FH ; Mask high bit
LD L,A ; Copy A to L
RET ;
RIS2 LD A,0 ; No chars waiting, A=0
LD L,A ; Return in L also
RET
; Get the IOBYTE in A and L
GET_IOB LD A,(IOBYTE) ;
LD L,A ; Copy to L
RET
; Set the IOBYTE
; E contains the IOBYTE value to set
SET_IOB LD A,E ;
LD (IOBYTE),A ;
RET
; Write ASCII string to console. '$' is the terminator
; DE contains the address of the string
C_WRITESTR LD A,(DE) ; Fetch character from string
CP '$' ; Is it '$'?
RET Z ; If so, we are done
PUSH DE ; We are gonna need E
LD E,A ; For C_WRITE
CALL C_WRITE ; Sent char to console
POP DE ; Recover the pointer
INC DE ; Advance pointer
JP C_WRITESTR ; Handle the next char
; Read console string
; DE points to the string buffer. First byte of the buffer is the capacity
; of the buffer. This function writes the number of bytes written in second
; byte. Entry finishes on CR or when the buffer is filled up.
; TODO: Line editing is supposed to be supported here
C_READSTR LD H,D ; HL will be the working pointer
LD L,E ; ...
INC HL ; Advance to first character ...
INC HL ; ... 3rd byte of the buffer
PUSH DE ; Put DE into IX
POP IX ; ...
LD A,0 ; Set number of chars read to zero
LD (IX+1),A ; ...
CRSL1 PUSH HL ; Preserve HL
CALL C_READ ; Read a character into A
POP HL ; Restore HL
CP 13 ; Carriage return?
RET Z ; If so, we are done
CP 10 ; Line feed?
RET Z ; If so, we are done
LD B,A ; Stash character in B
LD A,(IX+0) ; Buffer capacity -> A
SUB (IX+1) ; Subtract characters read
CP 0 ; If no space left ...
RET Z ; ... we are done
LD (HL),B ; Write character to buffer
INC HL ; Advance to next character
INC (IX+1) ; Increment character count
JP CRSL1 ; Loop
RET
; Returns 0 in A and L if no chars waiting, non zero otherwise
C_STAT LD A,3 ; CMD=3 means peek at keyboard
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it
LD A,(AREG) ; Grab the return value
LD L,A ; Copy A to L
RET
; Returns system type in B and H, BDOS version in A and L
S_BDOSVER LD B,0 ; System is 8080 CP/M
LD H,B ; Also in H
LD A,22H ; Pretend to v2.2
LD L,A ; Also in A
RET
; Reset disks
; Makes A: drive the default
; TODO: Empty all disk buffers
DRV_ALLRST LD A,0 ; 0 means drive A:
LD (CURDRV),A ; Store in CURDRV
LD BC,FILEBUF ; FILEBUF is at 0080H
LD (DMAADDR),BC ; Reset DMA address
RET
; Select disk
; Disk to select is passed in E (A: is 0, B: is 1 etc.)
; Return 00 for success, 0FFH for error in A and L
DRV_SET LD A,E ; Prepare to compare disk number
CP 16 ; Support 16 'drives' A: - P:
JP NC,DSERR ; If A>15 ... error
LD (CURDRV),A ; Store the requested drive number
LD A,0 ; Return code meaning success
JP DSRET ;
DSERR LD A,0FFH ; Return code for error
DSRET LD L,A ; Return code in L too
RET
; Open file
; DE is the address of the FCB describing the file to open
; Returns error codes in A and L:
; 0 through 3 for success, 0FFH is file not found
F_OPEN LD IX,PATHBUF ; Destination buffer
CALL MAKEPATH ; Populate PATHLEN and PATH
PUSH DE ; Preserve pointer to FCB
; Work out which IOBUF to allocate for this file
LD A,0 ; Looking for FRN slot with value 0
CALL GETIOADDR ; Returns FRN slot in A, IOBUF in HL
CP 0FFH ; Check for error
JP Z,FOERR ; If no slots available, error out
LD (FOIOB),A ; Record the buffer index in local var
LD BC,OFFSET ; Add offset to convert to 6502 address
ADD HL,BC ; ...
LD (FOMLII),HL ; Store in parameter list
LD HL,FOMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FOERR ; Handle error
POP DE ; Restore pointer to FCB
; Store ProDOS FRN in S2 field of FCB
LD A,(FOMLIN) ; Get ProDOS file reference number
LD H,D ; Pointer to FCB ...
LD L,E ; ... into HL
LD BC,14 ; Offset to S2 field (reserved field)
ADD HL,BC ; Compute address
LD (HL),A ; Store file reference number is S2 field
; Store ProDOS FRN in slot FRN1 - FRN4
LD A,(FOIOB) ; Obtain IOBUF idx (1,2,3,4)
LD HL,FRN1-1 ; Compute address of FRN slot to use
LD B,0 ; ...
LD C,A ; ...
ADD HL,BC ; ...
lD A,(FOMLIN) ; Get ProDOS file reference number
LD (HL),A ; Store in FRN slot
LD A,0 ; Success
LD L,A ; Copy to L
RET ; Done
FOERR POP DE ; Restore the stack
LD A,0FFH ; Error return status
LD L,A ; Copy to L
RET ; Done (error)
FOMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0C8H ; ProDOS OPEN call
DEFW FOMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FOMLIPL DEFB 3 ; ProDOS PL: Three parameters
FOMLIP DEFW PATHBUF+OFFSET ; ProDOS PL: pointer to path in 6502 addr
FOMLII DEFW 0000H ; ProDOS PL: pointer to IO buffer
FOMLIN DEFB 0 ; ProDOS PL: File reference number
FOIOB DEFB 0 ; Local variable to record IOBUF idx
; Close file
; DE is the address of the FCB describing the file to close
; Returns error codes in A and L:
F_CLOSE LD H,D ; Pointer to FCB ...
LD L,E ; ... into HL
LD BC,14 ; Offset to S2 field (reserved field)
ADD HL,BC ; Compute address
LD A,(HL) ; Obtain file reference num from FCB S2
LD (FCMLIN),A ; Store in parameter list
LD HL,FCMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FCERR ; Handle error
LD A,(FCMLIN) ; Obtain file reference number again
CALL GETIOADDR ; Returns FRN slot in A, IOBUF in HL
CP 0FFH ; Check for error
JP Z,FCERR ; If FRN not found, error out
LD HL,FRN1-1 ; Compute addr of FRN slot to set to zero
LD B,0 ; ...
LD C,A ; ...
ADD HL,BC ; ...
LD A,0 ; And zero it
LD (HL),A ; ...
LD A,0 ; Return zero for error
LD L,A ; Return in L also
RET
FCERR LD A,0FFH ; 0FFH for error
LD L,A ; Return code in L also
RET
FCMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0CCH ; ProDOS CLOSE call
DEFW FCMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FCMLIPL DEFB 1 ; ProDOS PB: One parameter
FCMLIN DEFB 0 ; ProDOS PB: File reference number
; Delete file
; DE is the address of the FCB describing the file to delete
; Returns error codes in A and L:
F_DELETE LD IX,PATHBUF ; Destination buffer
CALL MAKEPATH ; Populate PATHLEN and PATH
LD HL,FDMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FDERR ; Handle error
LD L,A ; ... and L too
RET
FDERR LD A,0FFH ; 0FFH for error
LD L,A ; Return code in L also
RET
FDMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0C1H ; ProDOS DESTROY call
DEFW FDMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FDMLIPL DEFB 1 ; ProDOS PL: Seven parameters
FDMLIP DEFW PATHBUF+OFFSET ; ProDOS PL: Pointer to path in 6502 addr
; Read next record
; DE is the address of the FCB describing the file from which to read
; Returns error codes in A and L:
; 0 OK, 1 EOF, 9 invalid FCB, 10 media changed, 0FFH h/w error
F_READ LD H,D ; Pointer to FCB ...
LD L,E ; ... into HL
LD BC,14 ; Offset to S2 field (reserved field)
ADD HL,BC ; Compute address
LD A,(HL) ; Obtain file reference num from FCB S2
LD (FRMLIN),A ; Store in parameter list
LD HL,(DMAADDR) ; Read from DMA buffer address
LD BC,OFFSET ; Convert to 6502 address
ADD HL,BC ; ...
LD (FRMLIDB),HL ; Store I/O buffer address in parm list
LD HL,FRMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FRERR ; Handle error
LD A,0 ; Zero for success
LD L,A ; Return code in L also
RET ; Done
FRERR LD A,0FFH ; TODO: All errors are 0FFH for now
LD L,A ; Return code in L aslo
RET ; Done (error)
FRMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0CAH ; ProDOS READ call
DEFW FRMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FRMLIPL DEFB 4 ; ProDOS PL: Four parameters
FRMLIN DEFB 0 ; ProDOS PL: File reference number
FRMLIDB DEFW 0000H ; ProDOS PL: Data buffer
FRMLIRC DEFW 128 ; ProDOS PL: Request count (bytes to read)
FRMLITC DEFW 0000H ; ProDOS PL: Number of bytes transferred
; Write next record
; DE is the address of the FCB describing the file to which to write
; Returns error codes in A and L:
; 0 OK, 1 dir full, 2 disk full, 9 invalid FCB, 10 media changed, 0FFH h/w error
F_WRITE LD H,D ; Pointer to FCB ...
LD L,E ; ... into HL
LD BC,14 ; Offset to S2 field (reserved field)
ADD HL,BC ; Compute address
LD A,(HL) ; Obtain file reference num from FCB S2
LD (FWMLIN),A ; Store in parameter list
LD HL,(DMAADDR) ; Write to DMA address
LD BC,OFFSET ; Convert to 6502 address
ADD HL,BC ; ...
LD (FWMLIDB),HL ; Store I/O buffer address in parm list
LD HL,FWMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FWERR ; Handle error
LD A,0 ; Zero for success
LD L,A ; Return code in L also
RET ; Done
FWERR LD A,0FFH ; TODO: All errors are 0FFH for now
LD L,A ; Return code in L aslo
RET ; Done (error)
FWMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0CBH ; ProDOS WRITE call
DEFW FWMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FWMLIPL DEFB 4 ; ProDOS PL: Four parameters
FWMLIN DEFB 0 ; ProDOS PL: File reference number
FWMLIDB DEFW 0000H ; ProDOS PL: Data buffer
FWMLIRC DEFW 128 ; ProDOS PL: Request count (bytes to read)
FWMLITC DEFW 0000H ; ProDOS PL: Number of bytes transferred
; Create (and open) file
; DE is the address of the FCB describing the file to create
; Returns error codes in A and L:
; 0 through 3 for success, 0FFH is file could not be created
F_MAKE LD IX,PATHBUF ; Destination buffer
CALL MAKEPATH ; Populate PATHLEN and PATH
LD HL,FMMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FMERR ; Handle error
CALL F_OPEN ; Open the file using same FCB (DE ptr)
RET ; Return with status from F_OPEN
FMERR LD A,0FFH ; 0FFH for error
LD L,A ; Return code in L also
RET
FMMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0C0H ; ProDOS CREATE call
DEFW FMMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FMMLIPL DEFB 7 ; ProDOS PL: Seven parameters
FMMLIP DEFW PATHBUF+OFFSET ; ProDOS PL: Pointer to path in 6502 addr
FMMLIA DEFB 0C3H ; ProDOS PL: Access (0C3H full access)
FMMLIT DEFB 0 ; ProDOS PL: File type (0 means typeless)
FMMLIAT DEFW 0000H ; ProDOS PL: Aux file type (always 0000H)
FMMLIS DEFB 1 ; ProDOS PL: Storage type (1 for file)
FMMLICD DEFW 0000H ; ProDOS PL: Create date (always 0000H)
FMMLICT DEFW 0000H ; ProDOS PL: Create time (always 0000H)
; Rename file
; DE is the address of the FCB describing the file to be renamed. The new name
; is stuffed into FCB+16 (where the allocation map usually goes)
; Returns error codes in A and L - 0 to 3 for success, 0FFH for file not found
F_RENAME LD IX,PATHBUF ; Destination buffer 1
CALL MAKEPATH ; Populate PATHLEN and PATH for first file
LD IX,PATHBUF2 ; Destination buffer 2
CALL MAKEPATH ; Populate PATHLEN and PATH for second file
LD HL,FRNMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FRNERR ; Handle error
LD A,0 ; Success
LD L,A ; Return code in L also
RET
FRNERR LD A,0FFH ; 0FFH for error
LD L,A ; Return code in L also
RET
FRNMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0C2H ; ProDOS RENAME call
DEFW FRNMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FRNMLIPL DEFB 2 ; ProDOS PL: Two parameters
FRNMLIP1 DEFW PATHBUF+OFFSET ; ProDOS PL: Pointer to path in 6502 addr
FRNMLIP2 DEFW PATHBUF2+OFFSET ; ProDOS PL: Pointer to path in 6502 addr
; Return bitmap of logged-in drives in HL
DRV_LOGVEC LD HL,(LOGVEC) ;
RET
; Return current drive in A
DRV_GET LD A,(CURDRV) ;
RET
; Set DMA address
; DMA address is passed in DE
F_DMAOFF LD (DMAADDR),DE ;
RET
; Software write-protect current disk
DRV_SETRO LD B,80H ; Set MS-bit in B, will rotate this below
LD A,(CURDRV) ; Current drive (0 A:, 1 B: ...)
INC A ; It is easier if A: is 1, B is 2 etc.
CP 9 ; See if it in LS-byte or MS-byte of ROVEC
JP NC,DSRMSB ; If A>8 then drive bit in in MS-byte
DSRL1 RL B ;
DEC A ;
JP NZ,DSRL1 ;
LD A,(ROVEC) ; Fetch the LS-byte of ROVEC
OR B ; Set the bit using OR
LD (ROVEC),A ; Store LS-byte back to ROVEC
RET ; We're done
DSRMSB LD C,8 ; Subtract 8 from the drive number
SUB C ; A = A-8
DSRL2 RL B ;
DEC A ;
JP NZ,DSRL2 ;
LD A,(ROVEC+1) ; Fetch the MS-byte of ROVEC
OR B ; Set the bit using OR
LD (ROVEC+1),A ; Store MS-byte back to ROVEC
RET
; Return bitmap of read-only drives in HL
DRV_ROVEC LD HL,(ROVEC) ; Bit 0 of L is A:, bit 7 of H is P:
RET
; Get/set user number
; E contains the user number to set, or 0FFH to get current user number
; If E is 0FFH then user number is returned in A
F_USERNUM LD A,E ; See if it is get or set
CP 0FFH ; 0FFH means 'get'
JP Z,FUNS1 ; It is 'get'
LD (USERNUM),A ; Set user number
RET
FUNS1 LD A,(USERNUM) ; Get user number
RET
; Compute file size
; DE contains address of FCB describing the file
; Error codes are returned in A and L (0 for success, 0FFH if file not found)
; Returns the number of 128 byte records in random record count field of FCB
F_SIZE CALL MAKEPATH ; Populate PATHLEN and PATH
LD HL,FSMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FSERR ; Handle error
LD HL,(FSMLIBU) ; Obtain the blocks used field
ADD HL,HL ; Mult x 2 to get 128 byte records
; Store records used in R0,R1 fields of FCB
PUSH DE ; Copy DE ...
POP IX ; ... into IX
LD (IX+21H),L ; Store LSB of recs used in R0
LD (IX+22H),H ; Store LSB of recs used in R1
LD A,0 ; Store zero in R2
LD (IX+23H),A ; ...
LD A,0 ; Success
LD L,A ; Return in L also
RET
FSERR LD A,0FFH ; File not found
LD L,A ; Return in L also
RET
FSMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0C4H ; ProDOS GET_FILE_INFO call
DEFW FSMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FSMLIPL DEFB 0AH ; ProDOS PL: Ten parameters
FSMLIP DEFW PATHBUF+OFFSET ; ProDOS PL: Pointer to path in 6502 addr
FSMLIAC DEFB 0 ; ProDOS PL: Access
FSMLIT DEFB 0 ; ProDOS PL: File type
FSMLIAT DEFW 0000H ; ProDOS PL: Aux type
FSMLIS DEFB 1 ; ProDOS PL: Storage type
FSMLIBU DEFW 0000H ; ProDOS PL: Blocks used
FSMLIMD DEFW 0000H ; ProDOS PL: Modification date
FSMLIMT DEFW 0000H ; ProDOS PL: Modification time
FSMLICD DEFW 0000H ; ProDOS PL: Create date
FSMLICT DEFW 0000H ; ProDOS PL: Create time
; Selectively reset disk drives
; DE contains bitmap of drives to reset (bit 0 of E if A:, bit 7 of D is P:)
; Returns A=00H if okay, A=FFH if error
; Resetting means removing the read-only status
DRV_RESET LD A,(ROVEC) ; Do the LSB ...
XOR E ; ... with E
LD (ROVEC),A ;
LD A,(ROVEC+1) ; Then the MSB ...
XOR D ; ... with D
LD (ROVEC+1),A ;
RET
; Make a ProDOS MLI call
; Address of 6502 JSR instruction in front of ProDOS parameter list is
; passed in in register pair HL, in Z80 address space
; Return code is passed back in A
PRODOS LD BC,OFFSET ; Add offset to convert Z80->6502 address
ADD HL,BC ; ...
LD (ADDR),HL ; Store it for 6502
LD A,2 ; CMD=2 means ProDOS MLI call
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
LD A,(AREG) ; Get return code from the MLI call
RET
; Populate the PATH buffer (and PATHLEN) by copying from FCB
; DE contains a pointer to the FCB
; IX contains pointer to the path buffer into which to write
; Be sure not to trash DE
; TODO FINISH THIS - NEEDS TO HANDLE EXTENSION TOO!!!
MAKEPATH PUSH IX ; Copy IX->IY
POP IY ; So IY keeps track of size byte at start
INC IX ; Advance past the size byte before writing
LD A,(DE) ; Get drive number from FCB
CP 0 ; See if it is zero (default drive)
JP NZ,MPS1 ; If drive explicit
LD A,(CURDRV) ; If default drive use CURDRV
INC A ; CURDRV is zero based
MPS1 ADD A,'A'-1 ; Convert to drive letter
LD (IX+0),A ; Store as first char of path
LD A,'/' ; Second char of path is '/'
LD (IX+1),A ; ...
LD C,2 ; Use C to count chars in filename
LD H,D ; Copy address of FCB from DE ...
LD L,E ; ... to HL
INC HL ; HL points to filename in FCB
INC IX ; Advance IX to next char of path to write
INC IX ; ...
MPL1 LD A,(HL) ; Obtain filename character
CP ' ' ; See if it is a space (? or NULL maybe?)
JP Z,MPS3 ; If so we are done with filename
EX AF,AF' ; We need to re-use A here
LD A,C ; Get character count
CP 10 ; Drive letter, slash and 8 char filename
JP Z,MPS2 ; If so we are done with filename
EX AF,AF' ; Swap back to original A reg
LD (IX+0),A ; Copy to PATH buffer
INC C ; Count the chars
INC HL ; Next byte of filename in FCB
INC IX ; Next byte of PATH buffer
JP MPL1 ; Loop till done
MPS2 EX AF,AF' ; Swap back to original A reg
MPS3 ; EXTENSION
; ...
LD A,C ; Store length of string
LD (IY+0),A ; We kept size byte in IY at start
RET
; Find IOBUF address for a file reference number
; Scan through FRN1, FRN2, FRN3, FRN4 to find the file reference number in A
; If found, return FRN slot# 1,2,3,4 in A, I/O buffer address in HL
; If not found, return A=0FFH
; Trashes B
GETIOADDR LD B,A ; Stash file ref number into B
LD A,(FRN1) ; Does it match FRN1?
CP B ; ...
JP Z,GIOAS1 ; ...
LD A,(FRN2) ; Does it match FRN2?
CP B ; ...
JP Z,GIOAS2 ; ...
LD A,(FRN3) ; Does it match FRN3?
CP B ; ...
JP Z,GIOAS3 ; ...
LD A,(FRN4) ; Does it match FRN4?
CP B ; ...
JP Z,GIOAS4 ; ...
LD A,0FFH ; No match, return A=0FFH
RET ; ...
GIOAS1 LD HL,IOBUF1 ; Address of I/O buf 1 -> HL
LD A,1 ; FRN slot 1
RET
GIOAS2 LD HL,IOBUF2 ; Address of I/O buf 2 -> HL
LD A,2 ; FRN slot 2
RET
GIOAS3 LD HL,IOBUF3 ; Address of I/O buf 3 -> HL
LD A,3 ; FRN slot 3
RET
GIOAS4 LD HL,IOBUF4 ; Address of I/O buf 4 -> HL
LD A,4 ; FRN slot 4
RET
; Convert value in HL into an HEX ASCII string, pointed to by DE
; Courtesy of http://map.grauw.nl/sources/external/z80bits.html#5.2
; Trashes A
NUM2HEX LD A,H ;
CALL N2H1 ;
LD A,H ;
CALL N2H2 ;
LD A,L ;
CALL N2H1 ;
LD A,L ;
JR N2H2 ;
N2H1 RRA ;
RRA ;
RRA ;
RRA ;
N2H2 OR 0F0H ;
DAA ;
ADD A,0A0H ;
ADC A,40H ;
LD (DE),A ;
INC DE ;
RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Additional private scratch space for BDOS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 64 byte buffer for storing a file path as a Pascal style string
PATHBUF
PATHLEN DEFB 0
PATH DEFS 64
; 64 byte buffer for storing a second file path as a Pascal style string
PATHBUF2
PATHLEN2 DEFB 0
PATH2 DEFS 64
; Record file reference numbers for each I/O buffer
; Or 0 if no file is using the buffer
FRN1 DEFB 0
FRN2 DEFB 0
FRN3 DEFB 0
FRN4 DEFB 0
; Buffer for printing hex numbers to console
HEXBUF DEFB 0,0,0,0,'$'
; Four 1024 byte ProDOS I/O buffers
; These must start on a page boundary
; ProDOS occupies the space from $BF00 up (in 6502 addresses)
; Also ProDOS has one buffer from $BB00-$BEFF I think. (Check with John Brooks!)
; IOBUF1 $AB00-$ADFF = starts at 9B00H for Z80
; IOBUF2 $AF00-$B2FF
; IOBUF3 $B300-$B6FF
; IOBUF4 $B700-$BAFF
ORG 7000H ; Set to 7000H by experiment ...
IOBUF1 DEFS 1024
ORG 7400H
IOBUF2 DEFS 1024
ORG 7800H
IOBUF3 DEFS 1024
ORG 7C00H
IOBUF4 DEFS 1024
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;