title 'CP/M 3 ROM loader 13 May 85' maclib z80 maclib cpm3 maclib cxequ maclib x6502 boot$8502 equ 1100h lines equ 24 ; number of user lines on screen(s) rownum macro row,col db row+80h,col endm ; ; power on location ; org 00h ; RST 0 mvi a,3eh sta force$map jmp power$up ; continue init somewhere else ; ; boot CP/M entry point ; ; org 08h ; RST 1 lxi sp,boot$stack mvi a,3Fh ; MMU enable RAM bank 0 no I/O jmp loader$start page ; ; * TJMP * user to jmp to a Terminal ROM routine ; user code: ; RST 2 ; db fun# (0,4,8,C,....,44) ; ; org 10h ; RST 2 pop h mov l,m jmp 020h nop nop nop ; ; * RJMP * user to jmp to a ROM routine ; user code: ; RST 3 ; db fun# ; ; org 18h ; RST 3 pop h ; get the return address mov l,m ; get user function # (0,2,...,fe) jmp 028h ; nop nop nop page ; ; * TCALL * used to call a Terminal ROM routine ; user code: ; mvi l,fun# (0,4,8,C,....,44) ; RST 4 ; ; org 20h ; RST 4 lda fun$offset ; =0 if 80 column, <>0 if 40 column ana a ; is this an 80 column function? jrz 28h ; yes, no offset required inr l ; no, advance to next vector inr l ; ; * RCALL * used to call a ROM routine ; user code: ; mvi l,fun# (0,2,4,6,.....,7E) ; RST 5 ; ; org 28h ; RST 5 mvi h,01h ; vectors on page 1 mov a,m inx h mov h,m mov l,a pchl nop page ; ; RST 6 is NOT defined.. this area is used for the ROM date ; ONLY.... ; ; org 30h ; RST 6 db '05/12/85' ; org 38h ; RST 7 (interrupt mode 1 start adr) jmp 0fdfdh page ; ; ; power$up: lxi b,VIC$key$row lxi d,0fffch ; D=ff, E=fc outp d ; set extra 3 scan lines off inx b ; point to clock speed reg outp e ; bits 7-2 unused ; bit 1 enable test mode (1) ; bit 0 2 Mhz (1) / 1 MHz (0) ; ; continue the check to see if a C64 type chartrage is installed ; (EXROM or GAME active) if so we enter C64 mode ; lxi b,mode$reg ; get EXROM and GAME bits mvi a,z80$on outp a ; set bit high inp a ; see if they went high cma ; make highs low ani 30h ; EXROM or GAME enabled? jrz test$key ; no, now test the commodore key ; ; This is a one way trip, no return flights. ; Thus we do not have to do the transfer from RAM ; (as in C128 mode). We just enable C64 and run. ; go$c64: mvi a,enable$c64 outp a ; should never get here RST 0 ; unless there are hardware problems page test$key: lxi b,0dc0fh ; D1CRB mvi a,8h ; turn off timers outp a dcr c ; D1CRA outp a mvi c,03h ; D1DDRB = inputs xra a ; A=00 outp a dcr c ; D1DDRA = outputs dcr a ; A=FF outp a dcr c ; dc01 dcr c ; dc00 mvi a,01111111b ; bit 7 for commodore key outp a inx b ; dc01 point to key$col inp a ani commodore$key lxi b,mode$reg jrz go$c64 go$c128: ; ; set MMU registers to a known state ; lxi h,mmu$init$data+11-1 ; start at the End lxi b,mmu$start+11-1 ; and work forward mvi d,11 ; for all 11 bytes init$mmu$next: mov a,m ; get table value outp a ; send to MMU dcx h dcr c dcr d jrnz init$mmu$next ; ; install 8502 code that will enable C128 mode and ; execute at the location pointed to by FFFC (reset vector) ; lxi h,boot$02$code lxi d,boot$8502 lxi b,boot$size ldir lxi h,swap$code lxi d,enable$z80 lxi b,swap$size ldir ; ; Get ready to enter C128 mode. Install vectors in ram that will ; force the processor to execute RAM code in low memory. ; The RAM code in low memory ENABLES the kernal and does ; an indirect JMP to FFFC (reset vector). ; lxi h,boot$8502 ; C128 start adr shld 0fffah ; install NMI vector shld 0fffch ; install RESET vector shld 0fffeh shld return$z80+1 jmp enable$6502 page ; ; scan buffer for CPM+.SYS file ; scan$dir: call update$buffer ; returns HL=block$buffer lda block$size ; 32 for 1K block, 64 for 2K block ; ..number director entries/sector check$next: shld @dma lxi d,sys$name ; point to system name push psw call name$match cz found pop psw lhld @dma ; get current buffer pointer lxi d,32 dad d dcr a jrnz check$next ret page ; ; compare the strings (11 bytes each) pointed to by ; DE and HL. Return with Zero flag set if equal. ; name$match: mvi b,12 ; number of bytes to match xchg ; [HL]=search name [DE]=dir entry match$next: ldax d ; get string 1 character ani 7fh ; remove any attr. cmp m ; compare to string 2 rnz ; exit if they don't match inx h inx d djnz match$next lda block$size ; cpi 64 ; 2K block? ldax d ; get the dir ext# jrnz ext$1k ; no, ext # ok ; yes, (carry=0) rar ; divide by 2, ext could be 0 or 1 ; ..for the 1st and 2 or 3 for the ext$1k: ; ..second entry sta ext$num null$code: xra a ; return with zero flag set ret page ; ; ; sys$name: ; 12345678901 db 0,'CPM+ SYS' ; must be in user 0's space db 0 ; ; ; ; org 0100h-6 cmp$hl$de: mov a,h cmp d rnz mov a,l cmp e ret page ; org 0100h dw wr$char$80 ; function # 00 dw wr$char$40 ; function # 02 dw crs$pos$80 ; function # 04 dw crs$pos$40 ; function # 06 dw crs$up$80 ; function # 08 dw crs$up$40 ; function # 0A dw crs$down$80 ; function # 0C dw crs$down$40 ; function # 0E dw crs$left$80 ; function # 10 dw crs$left$40 ; function # 12 dw crs$rt$80 ; function # 14 dw crs$rt$40 ; function # 16 dw crs$cr$80 ; function # 18 dw crs$cr$40 ; function # 1A dw CEL$80 ; function # 1C dw CEL$40 ; function # 1E dw CES$80 ; function # 20 dw CES$40 ; function # 22 dw char$ins$80 ; function # 24 dw char$ins$40 ; function # 26 dw char$del$80 ; function # 28 dw char$del$40 ; function # 2A dw line$ins$80 ; function # 2C dw line$ins$40 ; function # 2E dw line$del$80 ; function # 30 dw line$del$40 ; function # 32 dw set$color$80 ; function # 34 dw set$color$40 ; function # 36 dw set$attr$80 ; function # 38 dw set$attr$40 ; function # 3A dw rd$chr$80 ; function # 3C dw rd$chr$40 ; function # 3E page dw wr$chr$80 ; function # 40 dw wr$chr$40 ; function # 42 dw rd$color$80 ; function # 44 dw rd$color$40 ; function # 46 dw null$code ; function # 48 dw null$code ; function # 4A dw null$code ; function # 4C dw null$code ; function # 4E dw convert$record ; function # 50 dw check$cbm ; function # 52 dw bell ; function # 54 dw null$code ; function # 56 dw null$code ; function # 58 dw null$code ; function # 5A dw null$code ; function # 5C dw null$code ; function # 5E dw trk$40 ; function # 60 dw set$cursor$40 ; function # 62 dw line$paint ; function # 64 dw screen$paint ; function # 66 dw prt$msg$both ; function # 68 dw prt$de$both ; function # 6A dw update$it ; function # 6C dw null$code ; function # 6E dw ASCII$to$petASCII ; function # 70 dw cur$adr$40$hl$sz$a ; function # 72 dw cur$adr$80$hl$sz$a ; function # 74 dw lookup$color ; function # 76 dw null$code ; function # 78 dw blk$fill ; function # 7A ret adr, HL on stack dw blk$move ; function # 7C ret adr, HL on stack dw char$install$gp ; function # 7E ret adr, HL on stack ; the last 3 function are called by 1st pushing HL on the stack ; and then doing the call ; user code as follows: ; lxi h,xyz ; value to be passed in HL ; push h ; extra value on stack ; RCALL .... ; ; stack clean page ; ; org 180h ; jmp write$memory jmp read$memory jmp set$update$adr jmp wait ; ; ; loader$start: ; ; setup the MMU for booting CP/M ; sta force$map ; ; clear bank 0 RAM 3000h to feffh. This is the system area. ; lxi h,3000h lxi d,3001h lxi b,0ff00h-3000h-1 mov m,l ldir ; ; move bios and swap code into ram ; lxi h,bios$65$code lxi d,bios$02 lxi b,bios$size ldir lxi h,swap$code lxi d,enable$z80 lxi b,swap$size ldir mvi a,RET ; get z80 return adr sta return$6502 ; store the RET ; ; initilize the 8502 bios ; ; xra a ; cleared by memory fill ; sta vic$cmd call enable$6502 page ; ; set MMU registers to a known state (for CP/M to use) ; lxi h,mmu$init$data+11-1 ; start at the End lxi b,mmu$start+11-1 ; and work forward mvi d,11 ; for all 11 bytes init$mmu$cpm: mov a,m ; get table value outp a ; send to MMU dcx h dcr c dcr d jrnz init$mmu$cpm ; re-enabled RAM bank 0 (no I/O) ; ; Clear the work area ; lxi h,1000h lxi d,1000h+1 lxi b,3000h-1000h-1 ; number of bytes to clear mov m,l ; clear the 1st one ldir ; copy 1st to all page ; ; set 80 column colors and set up Video memory with ASCII char set ; mvi a,26 call wait mvi a,90h ; foreground red background black outp a mvi a,83h ; set attr and color (lt. blue) sta current$atr ; ..for 80 column display mvi a,0eh ; set color (lt. blue) sta attr$40 ; ..for 40 column display call install$ASCII ; convert char set to true ASCII mvi a,25 ; number of lines on the 40 col display sta paint$size ; ; Let the user know we are booting CP/M ; call prt$msg$both db -1 ; clear both screens rownum 1,10 db 'BOOTING CP/M PLUS',0 ; ; point 40 column screen to CP/M screen area ; lxi b,VIC+24 mvi a,vic$screen*4/256+6 ; upper and lower case set (+6) outp a page call check$dsk ; is this a C128 disk ? jnz tell$user ; no, tell the user ; ; ; lxi h,dir$ptrs shld ld$blk$ptr call scan$dir ; check 1st block call scan$dir ; check 2nd block (1K or 2K) lhld block$ptrs ; 1st pointer <>0 if file mov a,h ; name exist ora l ; pointer = 0 jz tell$user ; yes, inform user there is a error ; no, file found, process it page ; ************************************************* ; * * ; * load 1st group to 1K buffer * ; * * ; ************************************************* ; ; ; file$found: lxi h,block$ptrs shld ld$blk$ptr call update$buffer ; ************************************************* ; * * ; * extract boot info * ; * * ; ************************************************* ; ; ; get$boot$info: lxi h,block$buffer lxi d,info$buffer lxi b,12 ldir call prt$msg$both rownum 10,0 db 0 ; end of string marker lxi h,block$buffer+80h call prt$hl$both lxi h,block$buffer+256 ; set scan pointer shld blk$unld$ptr page ; ************************************************* ; * * ; * load keyboard data to system RAM * ; * * ; ************************************************* call prt$msg$both rownum 3,12 ; db 'LOADING DATA TABLES',0 db 'DATA TABLES',0 lhld info$buffer+10 shld key$tbl ; install keyboard translation pointer lxi h,info$buffer+9 call get$size$adr ; HL=adr DE=# (128 btye) records shld fun$tbl load$next$forward: call load$record ; HL =load address (in and out) lxi d,128 ; move pointer back to buf start dad d jrnz load$next$forward page ; ************************************************* ; * * ; * transfer CP/M code to load address * ; * * ; ************************************************* ; ; ; load$common: call prt$msg$both rownum 4,12 ; db 'LOADING COMMON CODE',0 db 'COMMON CODE',0 lxi h,info$buffer+1 ; load common code call load$reverse call prt$msg$both rownum 5,12 ; db 'LOADING BANKED CODE',0 db 'BANKED CODE',0 lxi h,info$buffer+3 ; load banked code call load$reverse page ; ************************************************* ; * * ; * now load the bios8502 code * ; * * ; ************************************************* call prt$msg$both rownum 6,12 ; db 'LOADING BIOS8502 CODE',0 db 'BIOS8502 CODE',0 lxi h,info$buffer+7 ; load banked code call load$reverse lda info$buffer+7 ; get code size (in 256 byte blocks) mov b,a lda info$buffer+6 ; get page pointer (pointer to end) sub b ; find the start ; install jmp adr to BIOS02 sta return$z80+2 ; (jmp) (low) (high) xra a sta return$z80+1 ; (jmp) (low) (high) ; ************************************************* ; * * ; * now let's start executing CP/M Plus * ; * * ; ************************************************* lhld info$buffer+4 ; get start address pchl ; transfer control to CP/M page ; ********************************* ; - - ; - SUBROUTINES - ; - - ; ********************************* ; ; returns with zero flag set if bootable disk in drive ; check$dsk: lxi h,@buffer shld @dma xra a sta vic$sect ; set track 1 sector 0 (1st sector inr a ; on the disk) sta vic$trk call read$sector ; a=0 if no errors call check$cbm ; disk have CBM in first sector ? rnz ; no, exit inr a ; yes, is it double sided? lxi h,block$buffer+1024 ; buffer end address (1K blocks) mvi a,32 ; number of dir entries per block jrnz set$block$size ; yes, set it ; no, set 2K block parameters mvi h,high(block$buffer+2048) add a ; 64 dir entries set$block$size shld block$end sta block$size ; 32=1K, 64=2K (# dir entries/block) xra a ; set zero flag (is CP/M disk) ret page ; ************************************************* ; - - ; - save CPM+.SYS group numbers - ; - - ; ************************************************* ; ; found a dir entry that has the right name ; add block pointers to list ; found: lxi d,block$ptrs ; point to start of block pointers lda ext$num ora a jrz ext$num$0 lxi d,block$ptrs+16 dcr a jnz ext$error ext$num$0: lhld @dma ; get current pointer lxi b,16 ; number of bytes to move dad b ; also advance to block pointers ldir lda block$ptrs ora a ; 1st block present ? rz ; no, read more dir. lhld block$ptrs+15 ; extent full? xra a ; get a zero cmp l ; cmp to block$prt+15 jrz go$boot$it ; no, this is it then cmp h ; cmp to block$prt+16 ; 2nd block present ? rz ; no, read more dir. go$boot$it: jmp file$found ; two parms are still on the stack ; but at this point who cares page ; ; ; load$reverse: call get$size$adr ; HL=adr DE=# records (128 byte) load$next: lxi d,-128 ; move pointer back to buf start dad d call load$record jrnz load$next ret ; ; ; get$size$adr: mov e,m mvi d,0 ; get buffer size (#256 byte) mov a,e ; get size to A ora a jz table$error ; exit if count=0 xchg dad h ; HL=#128 byte blocks shld load$count xchg dcx h mov h,m mvi l,0 ret page ; ; ; load$record: push h ; save to address lhld block$end ; get buffer end adr xchg lhld blk$unld$ptr call cmp$hl$de cz update$buffer xchg lxi h,128 dad d shld blk$unld$ptr pop h ; recover save address push h xchg ; HL=source DE=dest. lxi b,128 ; size of move ldir lhld load$count dcx h shld load$count mov a,l ora h pop h ret page ; ; ; update$buffer: lxi h,block$buffer shld @dma push h ; save block buffer adr for ret lhld ld$blk$ptr ; get the current block pointer mvi d,0 ; zero MSB of block pointer mov e,m ; get LSB of block pointer inx h ; advance pointer shld ld$blk$ptr xchg ; get block number to HL ; ; read the block pointed to by the HL ; into the data buffer ; dad h ; 2x dad h ; 4x 256=1K lda block$size ; =32 for 1K, =64 for 2K rrc rrc rrc ; 32/8=4, 64/8=8 cpi 32/8 ; 1K block size? jrz is$1K$block dad h ; 8x 256=2K is$1K$block: shld @trk next$block: dcr a ; 3 or 7 sectors left to read sta vic$count ; ..1st sector is read anyway push psw mvi a,1 sta F$rd$count+BB ; call convert$record ; set track, sector (adjust for offset) lhld @trk inx h shld @trk ; save for later pop psw jrz rd$1541 lda fast ana a ; 0=1541, 0<>1571 jrz rd$1541 lhld vic$trk ; get track and sector # push h ; save on stack check$next$trk: call convert$record ; convert next track and sector pop h ; recover 1st trk and sector # lda vic$trk ; get trk$number cmp l ; same trk as 1st sector jrnz not$same$trk push h ; resave 1st trk and sect lhld @trk inx h shld @trk lxi h,F$rd$count+BB inr m lxi h,vic$count dcr m jrnz check$next$trk pop h not$same$trk: shld vic$trk ; save the 1st track sector # rd$1541: call read$sector ; read the sector to the buffer lxi h,@dma+1 ; point to dma high byte lda F$rd$count+BB add m mov m,a ; adjust for next read lda vic$count ana a ; test if all sectors read? jrnz next$block ; no loop back pop h ; recover block buffer adr ret page ; ; convert block number to sector and track ; convert$record: mvi a,35 sta temp$1 ; store a track offset of 35 lhld @trk ; get start block # lxi d,680 ; 0 to 680 sectors per side ora a ; clear the carry dsbc d ; negative if <680 jrnc side$1 ; jump if still positive >=680 xra a ; sta temp$1 ; store a track offset of 0 dad d ; add it back side$1: inx h ; skip 1st sector (both sides) inx h ; skip 2nd sector (both sides) lxi d,357 ; get first value to subtract lxi b,21*256+1-1 ; b=sectors/track c=track offset-1 ora a ; clear the carry bit dsbc d ; jrc too$much ; inx h ; add 1 to skip track 18 sector 0 lxi d,490-357 ; get first value to subtract lxi b,19*256+18-1 ; b=sectors/track c=track offset-1 dsbc d ; jrc too$much ; lxi d,598-490 ; get first value to subtract lxi b,18*256+25-1 ; b=sectors/track c=track offset-1 dsbc d ; jrc too$much ; lxi d,0 lxi b,17*256+31-1 ; b=sectors/track c=track offset-1 page ; ; at this point B= number of sectors/track and C=track offset ; after DE is added back to HL (1st inst), HL is the number of ; sector past the current track (in C). ; too$much: dad d ; add back what made sum go negitive mvi d,0 ; number of sectors/track in DE mov e,b ora a ; clear the carry bit sect$pos: inr c ; add one to the current track (1-35) dsbc d ; remove a track's worth of sectors jrnc sect$pos ; less then one?, no jmp dad d ; make HL positive again ; ; at this point HL has the remainder (sector # 0-20) and ; C has the track number (1-35), DE and B still has the ; # sectors/track for the current track. ; lda temp$1 ; get track offset add c ; add to current track sta vic$trk ; save it push h lxi h,special$skew lxi b,21 ; number of sectors in 1st region mov a,e cmp c jrz correct$region dad b ; move past current region dcx b dcx b ; 19 adjust$loop: cmp c jrz correct$region dad b dcx b jr adjust$loop ; ; ; correct$region: pop b ; get logical sector # in BC dad b mov a,m ; get translated sector number to A sta vic$sect ; value from 0 to 20 will be returned inr a ; A is required to be non-zero on ret ret page ; ; ; read$sector: mvi a,3 sta retry read$again: mvi a,vicrd ; read a sector of data sta vic$cmd call disp$dsk$info call enable$6502 mvi a,3fh ; mmu ram bank 0 (no I/O) sta force$map lda vic$data ora a ; problems? jrnz read$error ; yes, check for disk error or media change ; no, go move buffer to DMA address ret page ; ; ; check$cbm: lxi h,@buffer mov a,m cpi 'C' ; C ? rnz ; no, return inr l ; @buffer+1 mov a,m cpi 'B' ; B ? rnz ; no, return inr l ; @buffer+0 mov a,m cpi 'M' ; M ? rnz mvi l,0ffh ; @buffer+0ffh ; point to the double sided flag mov a,m ; read it, 0FFh if double sided ret page ; ; ; ext$error: call prt$msg$both rownum 19,5 db '32K MAX CPM+.SYS SIZE',0 try$again: rst 1 ; ; ; read$error: inr a ; test for -1 jrz try$again lda retry dcr a sta retry jrnz read$again call prt$msg$both rownum 19,5 db 'READ ERROR',0 error$2: call prt$msg$both db ' - HIT RETURN TO RETRY' rownum 20,15 db 'DEL TO ENTER C128 MODE',0 ; ; ; wait$key: lxi b,key$row mvi a,11111110b ; bit 0 for RETURN key outp a inr c ; point to key$col inp a ani 2 ; on bit 1 (0-7) jrz try$again inp a ani 1 ; delete key down jrnz wait$key ; no, wait for RETURN key rst 0 ; yes, reboot C128-mode page ; ; CPM+.SYS file not on this disk test user to install ; a system disk and wait for a CR to continue ; tell$user: call prt$msg$both rownum 19,5 db 'NO',0 error$1: call prt$msg$both db ' CPM+.SYS FILE',0 jr error$2 ; ; ; table$error: call prt$msg$both rownum 19,5 db 'BAD',0 jr error$1 page ; ; ; prt$msg$both: xthl call prt$hl$both xthl ret update$it: lxi h,-1 shld old$offset ; force an update ; ; ; prt$de$both: push d ; ; ; prt$hl$both$loop: pop h ; ; ; prt$hl$both: mov d,m inx h ; advance the pointer lda prt$flg ana a jrz no$flag xra d sta prt$flg mov d,a no$flag: mov a,d ora a rz cpi '$' rz ; yes, return push h lxi h,prt$hl$both$loop push h cpi LF rz page ; ; ; check$both$CR: cpi CR jrnz check$erase$both ; ; do$crlf$both: call crs$cr$40 call crs$cr$80 call crs$down$40 RJMP FR$cursor$down ; cursor down 80 ; ; ; check$erase$both: cpi -1 jrnz check$both$CUR$$POS ; ; Erase both screens ; lxi d,24*256 ; erase the status line 1st call curpos$BC$both call CEL$40 call CEL$80 lxi d,0 ; erase main screen call curpos$BC$both call CES$40 RJMP FR$CES check$both$CUR$POS: ani 80h jrz out$d$both ; curpos$D$both: pop b ; get return adr in BC pop h ; get pointer in HL mov e,m ; E=column # inx h push h ; save new pointer push b ; save return adr res 7,d ; D=row # curpos$BC$both: push d ; save cursor address call crs$pos$40 pop d RJMP FR$cursor$pos ; 80 column page ; ; ; disp$dsk$info: lxi d,24*256+80-6 call crs$pos$80 lxi d,24*256+40-6 call crs$pos$40 lda vic$trk call disp$dec mvi d,' ' call out$d$both lda vic$sect disp$dec: mvi b,'0'-1 conv$loop: inr b sui 10 jrnc conv$loop adi '0'+10 push psw mov a,b call disp$A pop psw disp$A: mov d,a ; ; ; out$d$both: push d call wr$char$40 pop d RJMP FR$wr$char