From 4fcad07199e3ff29550da731c72ec4a047240fc7 Mon Sep 17 00:00:00 2001 From: Bobbi Webber-Manners Date: Mon, 14 Oct 2019 17:18:27 -0400 Subject: [PATCH] Implemented a lot more of BDOS. Not working yet! --- SOFTCARD65#060900 | Bin 0 -> 85 bytes SOFTCARD65#069000 | Bin 80 -> 0 bytes SOFTCARD65.S#040000 | 2 +- SOFTCARD80.ASM#040000 | 2 +- SOFTCARD80.BIN#041000 | Bin 20723 -> 25298 bytes zapple2.po | Bin 819200 -> 819200 bytes 6 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 SOFTCARD65#060900 delete mode 100644 SOFTCARD65#069000 diff --git a/SOFTCARD65#060900 b/SOFTCARD65#060900 new file mode 100644 index 0000000000000000000000000000000000000000..715d88124ec4eaf287f7b4a785efa83e058e177b GIT binary patch literal 85 zcmeBWIHJJC>BGRuu$1j2!v)ri3?EL;lZ+Q6mcI4SSjxVPV+p515N9iU8%GN#M@H-0 ogp*7c*m7Pow6Z6hWWK@_Z0i7k(H#E( diff --git a/SOFTCARD65.S#040000 b/SOFTCARD65.S#040000 index 4bd56be..879e9c0 100644 --- a/SOFTCARD65.S#040000 +++ b/SOFTCARD65.S#040000 @@ -1 +1 @@ - ڸ 䍪 ڸ Ǡ Ġՠô Ġՠ Ǡՠ ڸǠՠ ڸǠՠ ڸҠՠ  ڸȠՠà ҍӠՠĠ ڸ̠ՠĠ 썍ԍĠ ڸ ҠҠ ڸ 占РԠ ōˠ 򍍪 ڸ ҠčР Ľ 占Šı 덠 РŠ 卺ıР Ľ ڸŠIJӠ Р Ǡ ؠǠ ٠Ǡ ҠР ҍǠ ؠǠ ٠Ǡ Р 덠Ӡ Ӡ ڸIJР Ľ ڸŠij 捠Ӡ ڸijР Ľ ڸŠĴԠð ɠóӠ 占 ǍРóŠ óӠ ǍóŠǠ ڸӠ ڸĴҠ̠ ˠ РРҩ \ No newline at end of file + ڸ 䍪 ڸ ǠĠՠô Ġՠ Ǡՠ ڸǠՠ ڸǠՠ ڸҠՠ  ڸȠՠà ҍӠՠĠ ڸ̠ՠĠ 썍ԍĠ ڸ ҠҠ ڸ 占РԠ ōˠ 򍍪 ڸ 卪 Ľ 卪 Ľ ڸ Ľ ڸ Ľ 󍪪ҠčР Ľ 占Šı 덠 РŠ 卺ıР Ľ ڸŠIJӠ Р Ǡ ؠǠ ٠Ǡ ҠР ҩǠ ؠǠ ٠Ǡ Р 덠Ӡ Ӡ ڸIJР Ľ ڸŠijРҩ ɧ 򩍠Ǡ ڸӠ ڸijР Ľ ڸŠĴԠð ɠóӠ 占 ǍРóŠ óӠ ǍóŠǠ ڸӠ ڸĴҠ̠ ˠ РРҩ \ No newline at end of file diff --git a/SOFTCARD80.ASM#040000 b/SOFTCARD80.ASM#040000 index ca572c0..322652a 100644 --- a/SOFTCARD80.ASM#040000 +++ b/SOFTCARD80.ASM#040000 @@ -1 +1 @@ -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Z80 code running on Softcard ; This is invoked by the companion SOFTCARD65 6502 code ; Bobbi 2019 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; BDOSADDR EQU 05000H ; Keep below 32K for now (Z80asm bug) STCKTOP EQU 06000H ; Top of Z80 stack SOFTCARD EQU 0E400H ; Softcard in slot 4 ($C400) ; 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 ORG 0060H ; Standard addr of 32 byte FCB FCBDRV DEFB 00H ; FCB Drive (0 current, 1 A:, 2 B: etc) FCBNAM DEFM 'FILENAMEEXT' ; FCB filename and extension FCBEX DEFB 00H ; FCB extent field FCBS1 DEFB 00H ; FCB S1 field FCBS2 DEFB 00H ; FCB S2 field FCBRC DEFB 00H ; FCB RC field FCBMAP DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; File map (16 bytes) ORG 0080H ; Standard addr of 128 byte File Buffer FILEBUF DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; The application program proper starts at 0100H ; in order to be compatible with CP/M .COM programs ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ORG 0100H STCHAR EQU 65 ; First character code 'A' ENDCHAR EQU 91 ; Last character code 'Z' ; 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,STCHAR L1 LD E,B ; Character to print LD C,2 ; 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,ENDCHAR 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 spaces LD E,32 ; LD C,2 ; C_WRITE call CALL BDOS ; CP/M BDOS call LD E,32 ; LD C,2 ; C_WRITE call CALL BDOS ; CP/M BDOS call ; 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 WELCOME DEFM 'ZAPPLE-II TEST STUB' DEFB 13, '$' ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Implementation of 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=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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ORG BDOSADDR BDOSINIT LD SP,STCKTOP ; Initialize SP 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 DEFW UNIMP ; C=04H DEFW UNIMP ; C=05H DEFW UNIMP ; C=06H DEFW UNIMP ; C=07H DEFW UNIMP ; C=08H DEFW C_WRITESTR ; C=09H DEFW UNIMP ; C=0AH DEFW C_STAT ; C=0BH DEFW S_BDOSVER ; C=0CH DEFW DRV_ALLRST ; C=0DH DEFW DRV_SET ; C=0EH DEFW UNIMP ; C=0FH DEFW UNIMP ; C=10H DEFW UNIMP ; C=11H DEFW UNIMP ; C=12H DEFW UNIMP ; C=13H DEFW UNIMP ; C=14H DEFW UNIMP ; C=15H DEFW UNIMP ; C=16H DEFW UNIMP ; C=17H DEFW UNIMP ; C=18H DEFW UNIMP ; C=19H DEFW UNIMP ; C=1AH DEFW UNIMP ; C=1BH DEFW UNIMP ; C=1CH DEFW UNIMP ; C=1DH DEFW UNIMP ; C=1EH DEFW UNIMP ; C=1FH DEFW UNIMP ; C=20H DEFW UNIMP ; C=21H DEFW UNIMP ; C=22H DEFW UNIMP ; C=23H DEFW UNIMP ; C=24H DEFW UNIMP ; C=25H DEFW UNIMP ; C=26H DEFW UNIMP ; C=27H ; 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 ; 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 36 ; 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, reset DMA address to BOOT+80H DRV_ALLRST LD A,0 ; 0 means drive A: LD (CURDRV),A ; Store in CURDRV 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 2 ; Support two 'drives' A:,B: JP NC,DSERR ; If >1 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 \ No newline at end of file +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Z80 code running on Softcard ; Implements CP/M style BDOS interface ; Requires the companion SOFTCARD65 6502 code ; Bobbi 2019 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; BDOSADDR EQU 05000H ; Keep below 32K for now (Z80asm bug) STCKTOP EQU 06000H ; Top of Z80 stack SOFTCARD EQU 0E400H ; Softcard in slot 4 ($C400) ; 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 FCBDRV DEFB 00H ; FCB Drive (0 current, 1 A:, 2 B: etc) FCBNAM DEFM 'FILENAMEEXT' ; FCB filename and extension FCBEX DEFB 00H ; FCB extent field FCBS1 DEFB 00H ; FCB S1 field FCBS2 DEFB 00H ; FCB S2 field FCBRC DEFB 00H ; FCB RC field FCBMAP DEFS 16 ; Map of blocks in file ORG 0080H ; Standard addr of 128 byte File Buffer FILEBUF DEFS 128 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; 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 a file using ProDOS MLI ; Creates 'A/TESTFILE.TMP' ; Directory 'A' needs to exist already LD DE,MSG1 ; Address of string LD C,09H ; C_WRITESTR call CALL BDOS ; CP/M BDOS call LD A,1 ; A: drive LD (FCBDRV),A ; LD A,'T' ; Filename LD (FCBNAM),A ; LD A,'E' ; Filename LD (FCBNAM+1),A ; LD A,'S' ; Filename LD (FCBNAM+2),A ; LD A,'T' ; Filename LD (FCBNAM+3),A ; LD A,'F' ; Filename LD (FCBNAM+4),A ; LD A,'I' ; Filename LD (FCBNAM+5),A ; LD A,'L' ; Filename LD (FCBNAM+6),A ; LD A,'E' ; Filename LD (FCBNAM+7),A ; LD A,'T' ; Extension LD (FCBNAM+8),A ; LD A,'M' ; Extension LD (FCBNAM+9),A ; LD A,'P' ; Extension LD (FCBNAM+10),A ; LD DE,0060H ; Default FCB address LD C,16H ; F_MAKE call CALL BDOS ; CP/M BDOS call LD A,(AREG) ; Look at the return code CP 0 ; Success? JP Z,SUCC ; LD DE,FAILMSG ; Fail message JP PRMSG ; SUCC LD DE,SUCCMSG ; Success message PRMSG LD C,09H ; C_WRITESTR call CALL BDOS ; CP/M BDOS call ; 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 WELCOME DEFB 13 DEFM 'Zapple-II Test Stub...' DEFB 13, '$' MSG1 DEFB 13 DEFM 'Creating A/TESTFILE.TMP' DEFB 13, '$' SUCCMSG DEFM 'Success!' DEFB 13, '$' FAILMSG DEFM 'FAIL!' 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 (IN PROGRESS) ; C=10H F_CLOSE Close file (IN PROGRESS) ; 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 UNIMP ; C=13H (F_DELETE) 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 ; And set the pointer in the ProDOS parameter list 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 LD A,0FFH ; Error too many files open LD L,A ; Copy to L RET ; Error return 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 (FOMLII),HL ; Store in parameter list LD (FOIOB),A ; Record the buffer index for later ; ; TODO NOW THE CODE TO ACTUALLY INVOKE THE MLI CALL ON 6502 ; ; ; TODO HANDLE ERROR RETURN CODES FROM MLI ; 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 refence 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 ; ; TODO HANDLE RETURN CODE ON SUCCESS (0,1,2,3) ; RET FOMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code DEFB 3 ; ProDOS PL: Three parameters FOMLIP DEFW PATHLEN ; ProDOS PL: pointer to path 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 DEFB 60H ; RTS in 6502 code ; Close file ; DE is the address of the FCB describing the file to close ; Returns error codes in BA and HL: ; TODO WRITE THIS!!!! F_CLOSE ; Steps: ; 1) Get the file reference number from field S2 of FCB & fill in FCMLIN ; 2) Hand off pointer to the JSR instruction to 6502 to run ; 3) On return, check return code and set BA and HL accordingly RET FCMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code DEFB 1 ; ProDOS PB: One parameter FCMLIN DEFB 0 ; ProDOS PB: File reference number DEFB 60H ; RTS in 6502 code ; 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 LD (ADDR),HL ; ... 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 CP 0 ; See if there was an error JP NZ,FMS1 ; Handle error ;;;; CALL F_OPEN ; Open the file using same FCB (DE ptr) LD A,0 ;;; TEMP DEBUG - SHOULD CALL F_OPEN ABOVE ;;; RET FMS1 LD A,0FFH ; 0FFH for error LD L,A ; Return code in L also RET RET FMMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code DEFB 7 ; ProDOS PL: Seven parameters FMMLIP DEFW PATHLEN ; ProDOS PL: Pointer to path 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) DEFB 60H ; RTS in 6502 code ; 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 ; 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'-1 ; Convert to drive letter LD (PATH),A ; Store as first char of path LD A,'/' ; 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 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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Additional private scratch space for BDOS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; 64 bytes for storing a file path as a Pascal style string PATHLEN DEFB 0 PATH DEFS 64 ; Four 1024 byte ProDOS I/O buffers ; TODO THESE MUST START ON A PAGE BOUNDARY!!!! IOBUF1 DEFS 1024 IOBUF2 DEFS 1024 IOBUF3 DEFS 1024 IOBUF4 DEFS 1024 ; 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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; \ No newline at end of file diff --git a/SOFTCARD80.BIN#041000 b/SOFTCARD80.BIN#041000 index 05013799e06e870844792fd442822c283725447e..63b5071f887be6876d86abcbe42a7e3f46ab6549 100644 GIT binary patch delta 895 zcmYjPUr19?82`?;=AYVaqY#@GyK_VeO**|@?4Zbmf!aN1D@-W2Yx6%TsfY;8hkCJG zh6KG7NN@HM_!6N=UXn)CxgcN8wQOCGoT;hlU1HSEJEDJT*KL zh>eUI;c(a!@-#k7B}aN6bO#!0&)CDwW&~=064Z2~dlMpNe6(Cp+K|hX?jN8?l8C|W@bE+FkTC2Kxf4|n>=%$`I ze!uJ6`VR7a9e5Ex&oA?3eqzh$Yse4yUw(+_5@8`NRAiLn;QE4)neEqG)0z_otqH8j z^4p=$Upw-q{^gvxqcbOU?@{=P0~)m4Sa)#H!rEOxCSkX+46F&;yBXvtFfc=$9S&Og zn?lPOIL;QXtxLScwY02MkVEWo=|?{&%g{sElmD4+lU literal 20723 zcmeIuy-UMD7{~ETtts^-AWn)%Q#_7`_K)a4A&9F) zu~9m-AUHV{L3|+&;vl9T>JS&F4kF(RTz=1UmnZP)3*if?HeGG;)gRarYx#m(bk<$B z?8X1^SMr3T8R_=0(4Df8I8)&yX>T20%xM0IW{j#j67#RK86%=u?LTf||-I_F~Rg%>$4Z+_#=a|awvzc_e6by**IP5s5eJ?eU^ZI9Y!t>2?ASsC3imNRx3`ErQ` zlPh~!sXU%DSa&FqV%dDzD2`-?3}+|PjZamwMWbYWYLA-d8z~go zSTVaZQ+NxddINz_NY}JbII>Ce zde?b-G)?Pd7-yN`VJK4q=}4xMgYKY;L}TIZ{s?n zjkIEtf1T6%>K?UAY{HA5SHI3bEpR$PozORGn%4)xtXKY2{b_T1pxyfReX6(F>+i9i zen7p_I)9(Kv$=gsyH&VfJ>|}n;4<0L_L7q^kjFxr7WT9+YbULU9}NUOlGs15ev0*- z2h^|`go&`(87?EMl(PA4ELkj&JNCw+u!WdVV(o#I>2NAdo37;)`Mw&vv!yc2u}?OwT*Se%Tth zBEjyEe!JTfs8cK8X-{)KSp??IXLcC%)8m2Oi>8NXnQnqT=HKWeimKVbKQ#-1obR7L z|I+f@xN7?Rh~RI|qQH%-rxQdkEzp|j^JABuzgEnz*}r`@6G@G4n!bS4IJ~=TuH@b;StWem_GG$gCHCWLoSBiNyR2b&o7EB^qh0mrc zLqkBg4aDI#3~%wI`hx(onnOXdNFW+R6cE=)CYxhBK#|$TSrJWgtopU5{32P*ED8rzE8d0`mYDngVw@gi(7m3^qF7(=C`l?$61_4FW@wCTgm(8YIG#M zaPZGn?J9SYj=S2wz2c*lc~}j$DHP|}j&OK$n#O7LL382P9rLy0bClM==J`!O#fPU` zy0-k_*maJH%N+ARg6~s`>vw;+YBt914HKdE#j{o9p=Tb22Xz;d)>?pssJv3-t(uQ(eXH+{<1F2&!h_*)eJbA$fp2mQwf{VxnI-m5HGq%2mHC08p;u2Ciw$M}@{PrCXXldj^_qp(B8g| z=jnHUU&nF!{l|SBFVJs;Ph0$)-sPD1WZ&ZFZtrR``G+lC&HVSDbhRvbvd{liU&oL7 z2DdxrE84D(r~0NG$G+G%)pYDjeN)ZHzS=i&m>$D`6Ho9D&)Xl4+aF$_50gy!ar6B; zIonqH>`=Bsz{S7mTXOcTt|nW#rEq|s{Hm`@nIsKp-rW233D7t=;j*ih$&Qxq|5|

eeHFcjU;SA6i$wr=C67qmdiv1Q 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 O*k1`nCB(lDb^Q-@yznUi