;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; 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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;