;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; 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 0060H 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 ; End of private, implementation dependent space ORG 0060H ; Standard addr of 32 byte FCB 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 DEFS 16 ; Map of blocks in file 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,09H ; C_WRITESTR call CALL BDOS ; CP/M BDOS call ; Print the alphabet using C_WRITE LD B,'A' L1 LD E,B ; Character to print LD C,02H ; C_WRITE call PUSH BC ; Preserve B (and C) CALL BDOS ; CP/M BDOS call POP BC ; Restore B (and C) INC B LD A,'Z' CP B JP Z,S1 JP L1 ; Loop until there is a keystroke waiting using C_STAT S1 LD C,0BH ; C_STAT call CALL BDOS ; CP/M BDOS call CP 0 ; Anything? JR Z,S1 ; If not, loop ; Print a couple of asterisks LD E,'*' ; LD C,02H ; C_WRITE call CALL BDOS ; CP/M BDOS call LD E,'*' ; LD C,2 ; C_WRITE call CALL BDOS ; CP/M BDOS call ; 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 a file using ProDOS MLI ; Creates 'A/TESTFILE.TMP' ; Directory 'A' needs to exist already LD DE,CMSG ; Address of string LD C,09H ; C_WRITESTR call CALL BDOS ; CP/M BDOS call LD DE,FCB1 ; Default FCB address LD C,16H ; F_MAKE call CALL BDOS ; CP/M BDOS call CALL CHECKOK ; Open the file just created LD DE,OMSG ; Address of string LD C,09H ; C_WRITESTR call CALL BDOS ; CP/M BDOS call LD DE,FCB1 ; Default FCB address LD C,0FH ; F_OPEN call CALL BDOS ; CP/M BDOS call CALL CHECKOK ; Close the file LD DE,CLMSG ; Address of string LD C,09H ; C_WRITESTR call CALL BDOS ; CP/M BDOS call LD DE,FCB1 ; Default FCB address LD C,10H ; F_CLOSE call CALL BDOS ; CP/M BDOS call CALL CHECKOK ; Delete the file LD DE,DMSG ; Address of string LD C,09H ; C_WRITESTR call CALL BDOS ; CP/M BDOS call LD DE,FCB1 ; Default FCB address LD C,13H ; F_DELETE call CALL BDOS ; CP/M BDOS call CALL CHECKOK ; Read keyboard and echo to screen C_READ, C_WRITE L2 LD C,1 ; C_READ call CALL BDOS ; CP/M BDOS call LD E,A ; Prepare to echo keystroke LD C,2 ; C_WRITE call CALL BDOS ; CP/M BDOS call 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,SUCC ; LD DE,FAILMSG1 ; Fail message LD C,09H ; C_WRITESTR call CALL BDOS ; CP/M BDOS call 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,09H ; C_WRITESTR call CALL BDOS ; CP/M BDOS call LD DE,FAILMSG2 ; Fail message LD C,09H ; C_WRITESTR call CALL BDOS ; CP/M BDOS call RET ; SUCC LD DE,SUCCMSG ; Success message LD C,09H ; C_WRITESTR call CALL BDOS ; CP/M BDOS call RET WELCOME DEFB 13 DEFM 'Zapple-II Test Stub...' DEFB 13, '$' CMSG DEFB 13 DEFM 'Creating A/TESTFILE.TMP' DEFB 13, '$' OMSG DEFB 13 DEFM 'Opening A/TESTFILE.TMP' DEFB 13, '$' CLMSG DEFB 13 DEFM 'Closing 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, '$' ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Implementation of CP/M STYLE BDOS ; Function to invoke is passed in C ; C=00H C_TERMCPM System reset ; C=01H C_READ Console read ; C=02H C_WRITE Console write ; C=07H GET_IOB Get IOBYTE ; C=08H SET_IOB Set IOBYTE ; C=09H C_WRITESTR Console write string ; C=0BH C_STAT Console status ; C=0CH S_BDOSVER Return version number ; C=0DH DRV_ALLRESET Reset disks ; C=0EH DRV_SET Select disk ; C=0FH F_OPEN Open file ; C=10H F_CLOSE Close file (IN PROGRESS) ; C=13H F_DELETE Delete file ; C=16H F_MAKE Create file (IN PROGRESS) ; C=17H DRV_LOGVEC Return bitmap of logged-in drives ; C=19H DRV_GET Return current drive ; C=1AH F_DMAOFF Set DMA address ; C=1CH DRV_SETRO Software write-protect current drive ; C=1DH DRV_ROVEC Return bitmap of read-only drives ; C=20H F_USERNUM Get/set user number ; C=25H DRV_RESET 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 (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 UNIMP ; C=06H (C_RAWIO) DEFW GET_IOB ; C=07H DEFW SET_IOB ; C=08H DEFW C_WRITESTR ; C=09H DEFW UNIMP ; C=0AH (C_READSTR) 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 UNIMP ; C=14H (F_READ) DEFW UNIMP ; C=15H (F_WRITE) DEFW F_MAKE ; C=16H DEFW UNIMP ; C=17H (F_RENAME) 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 UNIMP ; C=23H (F_SIZE) 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 ; 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 ; 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 ; Return to calling program ; 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 CALL MAKEPATH ; Populate PATHLEN and PATH ; Work out which IOBUF to allocate for this file LD A,(FRN1) ; See if IOBUF1 is free CP 0 ; ... JP Z,FOS1 ; IOBUF1 is free LD A,(FRN2) ; See if IOBUF2 is free CP 0 ; ... JP Z,FOS2 ; IOBUF2 is free LD A,(FRN3) ; See if IOBUF3 is free CP 0 ; ... JP Z,FOS3 ; IOBUF3 is free LD A,(FRN4) ; See if IOBUF4 is free CP 0 ; ... JP Z,FOS4 ; IOBUF4 is free JP FOERR ; Error too many files open ; Set the pointer in the ProDOS parameter list FOS1 LD HL,IOBUF1 ; Address of IOBUF1 LD A,1 ; Buffer 1 JP FOS5 ; FOS2 LD HL,IOBUF2 ; Address of IOBUF2 LD A,2 ; Buffer 2 JP FOS5 ; FOS3 LD HL,IOBUF3 ; Address of IOBUF3 LD A,3 ; Buffer 3 JP FOS5 ; FOS4 LD HL,IOBUF4 ; Address of IOBUF4 LD A,4 ; Buffer 4 FOS5 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 PUSH DE ; Preserve pointer to FCB LD HL,FOMLI ; Pass address of 6502 JSR instruction CALL PRODOS ; Invoke ProDOS MLI POP DE ; Restore pointer to FCB CP 0 ; See if there was an error JP NZ,FOERR ; Handle error ; 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 LD A,(FOIOB) ; Obtain IOBUF idx (1,2,3,4) CP 1 ; Is it 1? ... JP Z,FOS6 ; ... yes CP 2 ; Is it 2? ... JP Z,FOS7 ; ... yes CP 3 ; Is it 3? ... JP Z,FOS8 ; ... yes JP FOS9 ; Must be 4 FOS6 LD A,(FOMLIN) ; LD (FRN1),A ; JP FOS10 ; FOS7 LD A,(FOMLIN) ; LD (FRN2),A ; JP FOS10 ; FOS8 LD A,(FOMLIN) ; LD (FRN3),A ; JP FOS10 ; FOS9 LD A,(FOMLIN) ; LD (FRN4),A ; JP FOS10 ; FOS10 LD A,0 ; Success LD L,A ; Copy to L RET ; Done FOERR 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 PATHLEN+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 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 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 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 PATHLEN+OFFSET ; ProDOS PL: Pointer to path in 6502 addr ; 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 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) LD A,0 ;;; TEMP DEBUG - SHOULD CALL F_OPEN ABOVE ;;; LD L,A ; Return code in L also RET 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 PATHLEN+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) ; 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 ; 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 ; Be sure not to trash DE ; TODO FINISH THIS!!! MAKEPATH 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'+80H-1 ; Convert to drive letter (high bit set) LD (PATH),A ; Store as first char of path LD A,'/'+80H ; Second char of path is '/' LD (PATH+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 LD IX,PATH+2 ; IX points to next char of path to write 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 ADD A,80H ; Set the high bit 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 (PATHLEN),A ; ... 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 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 bytes for storing a file path as a Pascal style string PATHLEN DEFB 0 PATH 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 ; ... we need BASIC.SYSTEM for now since we use EXEC! IOBUF2 DEFS 1024 IOBUF3 DEFS 1024 IOBUF4 DEFS 1024 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;