From 0982b4eb5ea99eab2d57534aefc8cde00946f564 Mon Sep 17 00:00:00 2001 From: Bobbi Webber-Manners Date: Sun, 13 Oct 2019 19:46:02 -0400 Subject: [PATCH] Added BDOSINIT routine to initialize Z80 SP --- README.md | 6 +++++- SOFTCARD80.ASM#040000 | 2 +- SOFTCARD80.BIN#040000 | Bin 20611 -> 20724 bytes zapple2.po | Bin 819200 -> 819200 bytes 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 870a3e2..fae8a0a 100644 --- a/README.md +++ b/README.md @@ -73,12 +73,16 @@ support for all the CP/M 2.2 system calls, which should allow a CP/M program to run on the Softcard Z80 CPU and have all the system calls routed to the 6502 and serviced using the Apple II ROM monitor routines and the ProDOS MLI. -This is at an embryonic stage at the moment as it only provides three system +This is at an embryonic stage at the moment as it only provides a few system calls: - BDOS call 01h: `C_READ` - Console input - BDOS call 02h: `C_WRITE` - Console output +- BDOS call 09h: `C_WRITESTR` - Console write string - BDOS call 0Bh: `C_STAT` - Console status +- BDOS call 0Ch: `S_BDOSVER` - Return version number +- BDOS call 0Dh: `DRV_ALLRESET` - Reset disks +- BDOS call 0Eh: `DRV_SET` - Select disk There are two parts to the BDOS emulation: diff --git a/SOFTCARD80.ASM#040000 b/SOFTCARD80.ASM#040000 index 15f2da9..3687512 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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; SOFTCARD EQU 0E400H ; Softcard in slot 4 ($C400) 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 START ; Skip to the actual program ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; BDOS entry point must be at address 0005H for CP/M compatibility ; Function to invoke is passed in C ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ORG 0005H BDOS JP BDOSI ; BDOS code is at top of memory ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; 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 START 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=0DH S_BDOSVER Return version number ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ORG 0D000H ; $B000 on 6502 - OK FOR NOW ORG 05000H ; Move it down to avoid Z80asm bug BDOSI 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, FFH 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 ; BDOS data CURDRV DEFB 0 ; Currently logged in drive \ No newline at end of file +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; 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 05200H ; Top of Z80 stack SOFTCARD EQU 0E400H ; Softcard in slot 4 ($C400) 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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; BDOS entry point must be at address 0005H for CP/M compatibility ; Function to invoke is passed in C ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ORG 0005H BDOS JP BDOSIMP ; BDOS code is at top of memory ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; 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 ; BDOS data CURDRV DEFB 0 ; Currently logged in drive \ No newline at end of file diff --git a/SOFTCARD80.BIN#040000 b/SOFTCARD80.BIN#040000 index 03426389f8289bb8a366e1b3c87820d97384ee19..94177f88edd9acab6819dc82bb1c6003426d7de3 100644 GIT binary patch delta 275 zcmZo($oOR;Blmv>hQka2|NkFm3z+Q4_(NLIk&%z{EGq+>V+0@5Q6S?Wi(T}NQ!#0XG8fA&DZZz{gg6@>}Wk2RlY1whu-OPfi|J z+a^hdvq#c9Nhk%pS|Clbl S2>`O}7!JP+u=}5Xk^ulQ*l6Ye delta 130 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 AYybcN