title 'Boot loader module for CP/M 3.0' ; ; last update 08/31/83 ; CNC ; true equ -1 ; false equ not true ; ; ; banked equ true ;banked system time$enabled equ true ;realtime clock in system interrupt equ true ;interrupt enabled used time$update equ false ;time update backround interrupt CPU$4mhz equ false ;CPU speed CPU$6mhz equ not CPU$4mhz ; ram$disk equ true ; public ?init,?ldccp,?rlccp,?time extrn ?pmsg,?conin extrn @civec,@covec,@aivec,@aovec,@lovec extrn @cbnk,?bnksl extrn @sec,@hour extrn @date extrn @cnt extrn @media,?bank extrn @dtbl if interrupt extrn ?sic3,?sibuf endif ; maclib ports maclib z80 ; bdos equ 5 ; ; if CPU$4mhz tick$cnt: equ 8253h else tick$cnt: equ 0c382h ;tick = 60hz (16.6ms) 6mhz endif ;8235h for 4mhz if banked ; tpa$bank equ 1 ; else tpa$bank equ 0 ; endif ; dseg ;init done from banked memory ; ?init: if interrupt call set$vector ;set up jump in page 0+1 if time$update mvi a,01h ;set to read rtc sta cnt$count ;store counter count call cnt$int ;set up counter endif endif lxi h,8000h ;assign console to internal CRT shld @civec ;store vector shld @covec ;store vector lxi h,4000h ;assign print to terminal port shld @lovec ;store vector lxi h,2000h ;assign aux to modem port shld @aivec ;store vector shld @aovec ;store vector mvi a,01 ; sta @cnt ;reset count lxi h,init$table ;set up misc hardware call out$blocks ;output set up data lxi h,signon$msg ;print signon message call ?pmsg ; ret ; ; out$blocks: mov a,m ;get byte count ora a ;see if zero rz ;return if done with table mov b,a ;put byte count in for outir inx h ;point to port # mov c,m ;put in for outir opcode inx h ;point to data outir ;z80 opcode jmp out$blocks ;loop ; ; cseg ;boot load from common segment ; ; This version of the boot loader loads the CCP from a file ; called CCP.COM on the system drvie a: ; ?ldccp: ; ; ; frist time, load the a:CCP.COM file into TPA ; xra a ; sta ccp$fcb+15 ;zero extent lxi h,0 ;start at beginning shld fcb$nr ; lxi d,ccp$fcb ;open file containing CCP call open ; inr a ;error if no file jz no$ccp ; lxi d,0100h ;start of tpa call setdma ; lxi d,128 ;allow up to 16k call setmulti ; lxi d,ccp$fcb ;load it call read ; ; if banked ; ;copy CCP to bank 0 for reloading ; lxi h,0100h ;clone 3.125K, just in case lxi b,0c80h ; lda @cbnk ;save current bank push psw ; ld$1: mvi a,tpa$bank ;select TPA call ?bnksl ; mov a,m ;get a byte push psw ; mvi a,0 ;select bank 0 call ?bnksl ;select it pop psw ;get byte mov m,a ;write byte to bank inx h ;bump pointer dcx b ;drop count mov a,b ;test for done ora c ;test for done jnz ld$1 ; pop psw ; call ?bnksl ;restore original bank endif ret ;done ; ; can not find file CCP.COM ; no$ccp: lxi h,ccp$msg ;point to message call ?pmsg ;report this call ?conin ;wait for key to be typed jmp ?ldccp ;and try again ; ; ; ?rlccp: di ;disable interrupts if banked if interrupt call set$vector endif lxi h,0100h ;clone 3.125K lxi b,0c80h ; rl$1: mvi a,0 ;select bank 0 call ?bnksl ;select it mov a,m ;get byte push psw ; mvi a,tpa$bank ;select TPA call ?bnksl ; pop psw ; mov m,a ;write a byte inx h ;bump pointer dcx b ;drop count mov a,b ;test for done ora c ;test for done jnz rl$1 ;loop till done if interrupt ; ei ;enable interrupts endif ; ret ; else ;if not banked jmp ?ldccp ;load CCP from disk endif ; ; set vector for interrupts, leave bank 0 selected ; if interrupt ;*** begin interrupt routines *** set$vector: di ;disable interurps im1 ;set z80 mode 1 interrupts if banked mvi a,tpa$bank ;select bank 01 call ?bank ;select it call set$it ;set vector page 0 mvi a,00 ;select bank 0 call ?bank ;select it endif ; call set$it ;set vector page 0 ret ; set$it: mvi a,jmp ;load jump opcode sta 0038h ;store at restart 7 lxi h,int$hnd ;point to interrupt routine shld 0039h ;store it ret ;return ; ; interrupt routine, all interrupt goto restart 7 (z80 mode 1) ; int$hnd: shld svdhl ;save hl pop h ;get return address shld svdret ;save return address push psw ;save /flags lxi h,0 ;zero dad sp ;put stack pointer in shld svdsp ;save stack pointer lxi sp,local$stack ;get local interrupt stack push d ;save push b ;save , all saved ; ; find were interrupt came from (poll) ; if time$enabled if time$update mvi a,1110$1000b ;load read status command cnt 2 out cnt$md ;output command in cnt$2 ;input status counter 2 ral ;test bit 7, output pin state jnc door$int ;jump if not from clock call cnt$int ;service clock int jmp don$int ;done, return from cp/m endif endif ; ; must be interrupt form open door floppy disk ; door$int: xra a ;select bank 0 call ?bank ;routine we need bank 0 call ?sic3 ;call sense interrupt command (diskio) mvi h,00 ;zero lda ?sibuf ;get st0 ani 0e0h ;see if valid interrupt cpi 0c0h ;must be open door jnz not$val ;leave if not lda ?sibuf ;get st0 ani 03h ;get drive # ;patch for drive C: logical = drive D: phyical cpi 03h ;see if drive C: (logical C: = D:) jnz not$drive$c ; mvi a,02 ;make drive C: not$drive$c: ; mov l,a ;to dad h ;*2 lxi d,@dtbl ;get drive table # dad d ;get address of drvie table mov e,m ;get lsb inx h ; mov d,m ; lxi h,11 ;point to media flag dph dad d ;add base mvi a,0ffh ;set disk change status mov m,a ;set media flag drive changed sta @media ;set media flag/ door changed states not$val: lda @cbnk ;point to current bank call ?bank ;set to old bank don$int: pop b ;restore regs pop d ; lhld svdsp ;get old stack pointer sphl ; pop psw ;get / flags lhld svdret ;get return address push h ;put back on stack lhld svdhl ;restore hl ei ;enable interrupts ret ;return ; ; clock interrupt routine ; if time$enabled if time$update cnt$int: lda cnt$count ;get count dcr a ;bump jnz cnt$int1 ;count not ready to update scb call get$time ;read rtc to scb mvi a,6 ;load count for 10hz update cnt$int1: sta cnt$count ;update count mvi a,1011$0000b ;set control word out cnt$md ;set cnt 2 mode 0 lxi h,tick$cnt ;get count for tick mov a,l ;get lsb count out cnt$2 ;output it mov a,h ;get msb count out cnt$2 ;output it ret ;return endif ;time$update end endif ;time$enabled end endif ;*** end interrupt routines *** ; ; time ; ?time: if time$enabled push h ! push d ;save regs mov a,c ;get set or read ora a ;set flags if time$update ;do not need to update scb if int. jz done$time ;jump read time else ; jnz set$time ;if set time call get$time ;read time jmp done$time ;return to cpm endif ; set$time: if interrupt di ;no interrupt when seting clock endif lxi d,rtc$table ;point to real time clock table lxi h,@sec ;point to seconds in SCB mvi b,3 ;set sec,min,hours set$loop: mov a,m ;get bcd to keep the same ani 0fh ;zero msb rrd ;get lsb (z80 ratate digit) stax d ;store RTC table rrd ;get msb inx d ;point to msb RTC table stax d ;store rrd ;put bcd SCB back the same inx d ;point to nect value RTC table dcx h ;point to next bcd value dcr b ;last bcd value ? jnz set$loop ;loop to all set ; date$set: lxi b,00$02h ;b=year count, c=leap year when 0 lhld @date ;get date lxi d,365 ;load value for 1 year year$loop: mov a,c ;get leap year status ora a ;see if zero (0= leap year) stc ;set carry for leap year jz leap$year ;jump if leap year cmc ;set carry 0 for not leap year leap$year: dsbc d ;subtract value of 1 year (365/366) jm year$set ;leave if less than one year inr b ;bump year count inr c ;bump leap year flag mov a,c ;put in a for compare cpi 04h ;see if leap year jnz year$loop ;loop if not mvi c,00h ;zero c for leap year jmp year$loop ;loop year$set: mov a,c ;get leap year status ora a ;see if zero (0=leap year) stc ;set carry for leap year jz leap ;jump if leap year cmc ;reset carry leap: ;add value of year to get number of dadc d ;days form JAN 1 mvi a,78 ;start with year 1978 add b ;add number of years from 1978 call bcd$adjust ;convert to bcd sta rtc$table+11 ;store ones of years mov a,b ;get tens sta rtc$table+12 ;store tens of years ;set month mvi b,01 ;set to jan lxi d,31 ;load de with length of jan xra a ;clear carry flag dsbc d ;subtract value jan jm set$month ;set month if less then 31 jz set$month inr b ;next month mvi e,28 ;load value FEB mov a,c ;get leap year status ora a ; jnz not$leap ;jump around if not leap year inr e ; not$leap: xra a ;clear carry flag dsbc d ;subtract value FEB jm set$month ;set month of year jz set$month inr b ;next month lxiy month$table+2 ;use index reg as pointer loop$day: ldy e,0 ;load data from table xra a ;reset carry flag dsbc d ;subtract value month jm set$month ;jump to set month if negative jz set$month ; inr b ; inxiy ;next address table jmp loop$day ;loop till set set$month: dad d ;add to get remainder mov a,b ;get month call bcd$adjust ;convert to bcd sta rtc$table+9 ;store ones of months mov a,b ;get tens sta rtc$table+10 ;store tens of months mov a,l ;get day of month call bcd$adjust ;convert to bcd sta rtc$table+7 ;store ones of day mov a,b ;get tens sta rtc$table+8 ;write to table ; call write$rtc$table ;write table ; if interrupt ; ei ;clock updated, interrupts ok endif ; ; done$time pop d ! pop h ;recover ret ;no clock yet ; bcd$adjust: mvi b,00 ;zero b hex$bcd:sui 10 ; jc conv$done ;jump if convert done inr b ; jmp hex$bcd ; conv$done: adi 10 ; ret ; ; month$table: db 31 ;JAN db 28 ;FEB db 31 ;MAR db 30 ;APR db 31 ;MAY db 30 ;JUN db 31 ;JUL db 31 ;AUG db 30 ;SEP db 31 ;OCT db 30 ;NOV db 31 ;DEC ; ; read time to SCB from RTC ; get$time: call read$rtc$table ;read RTC to RTC table lxi d,rtc$table ;point to real time clock table lxi h,@sec ;point to seconds in SCB mvi b,3 ;set sec,min,hours get$loop: ldax d ;get lsb RTC table value rrd ;put in msb SCB bcd value inx d ;point to next RTC value ldax d ;get msb RCT table value rrd ;put in msb SCB and shift lsb inx d ;point to next table value dcx h ;next bcd location SCB dcr b ;see if done jnz get$loop ;loop till done ; get$date: lxi h,rtc$table+12 ;point to tens of years call conv$bcd ;convert bcd values sui 78 ;subtract offset year mov c,a ;store in c reg lxi h,0000h ;start with jan 1 1978 lxi d,365 ;number of days in a year mvi b,02h ;load leap year count 2 (0=leap year) get$year: mov a,c ;get year count ora a ;set flags jz year$done ; mov a,b ;get leap year status ora a ;set flags stc ;set carry jz net$leap ; cmc ; net$leap: dadc d ;add with carry dcr c ; inr b ; mov a,b ; cpi 04h ; jnz get$year ; mvi b,0h ; jmp get$year ; year$done: mov a,b ;get leap year staus ora a ; mvi a,28 ;load feb count non yeap year jnz no$leap ; mvi a,29 ;load leap year count feb no$leap:sta month$table+1 ;store feb count push h ;save count lxi h,rtc$table+10 ;point to tens of months call conv$bcd ;convert bcd mov c,a ;store in e mvi b,1 ;month count lxi d,0000h ; lxiy month$table ;point to month table lop$month: mov a,b ; cmp c ; jz days ; ldy e,0 ;load e with month value pop h ;get count dad d ;add push h ;save it inr b ; inxiy ;incerment pointer jmp lop$month ; days: lxi h,rtc$table+8 ;read rtc call conv$bcd ; mov l,a ; mvi h,00 ; pop d ; dad d ;get # days from JAN 1 1978 shld @date ; ret ;return, done ; ; hl points to value in rtc$table conv$bcd: mov a,m ;get ones ani 0Fh ;mask upper bits mov c,a ;put in c rlc ;*2 (change form bcd to hex) rlc ;*4 rlc ;*8 add c ;*9 add c ;*10 mov c,a ;store c temp dcx h ;point to ones mov a,m ;get value ani 0fh ;mask upper bits add c ;add to tens ret ;return with hex value in ; ; ; write RTC table to RTC ; write$rtc$table: lxi h,rtc$table+5 ;ponit to tens of hours mov a,m ;get data ori 0000$1000b ;set to 24 hour format mov m,a ;put back in table mvi c,00h ;point to seconds reg frist lxi h,rtc$table ;point to data table wr$rtc$loop: mov a,c ;get address of rtc reg out rtc$data ;output to data latch rtc mvi a,0001$1100b ;set to stop and write address out rtc$cont ;output strobe high mvi a,0001$0000b ;leave stop on, set strobes low out rtc$cont ;do it mov a,m ;get data to output from RTC table out rtc$data ;output data mvi a,0001$1010b ;output write data strobe, stop on out rtc$cont ;output it nop ;write pluse must be 2 us mvi a,0001$0000b ;set strobe off out rtc$cont ;output it inx h ;point next byte to output inr c ;next reg mov a,c ;put in a to test cpi 14 ;last one ? jnz wr$rtc$loop ;loop till done xra a ;zero a out rtc$cont ;start clock ret ;done ; ; read rtc data to rtc table ; read$rtc$table: mvi c,00h ;point to seconds reg frist lxi h,rtc$table ;point to data table rd$rtc$loop: mov a,c ;get address of rts reg out rtc$data ;output to data latch rtc mvi a,0000$1100b ;set to write address out rtc$cont ;output strobe mvi a,0000$0000b ;set to disable strobe out rtc$cont ;do it mvi a,0000$1001b ;set to read byte out rtc$cont ;do it push h ! push h ;kill some time (read takes 6us) pop h ! pop h ;do not do anything real in rtc$data ;read in data mov m,a ;put in table mvi a,0000$0001b ;turn off CS2 frist out rtc$cont ;have to wait 1us to turn off read xra a ;zero a reg, must delay so no drives out rtc$cont ;conflict ;second read of data (must read 2 time to insure valid) mvi a,0000$1001b ;set to read byte out rtc$cont ;do it push h ! push h ;kill some time (read takes 6us) pop h ! pop h ;do not do anything real in rtc$data ;read in data mov b,a ;put in table mvi a,0000$0001b ;turn off CS2 frist out rtc$cont ;have to wait 1us to turn off read xra a ;zero a reg, must delay so no drives out rtc$cont ;conflict mov a,b ;get second read data cmp m ;compare to table data jnz rd$rtc$loop ;loop till the same inx h ;next table location inr c ;next reg mov a,c ;move to a to test cpi 13 ;see if last jnz rd$rtc$loop ;loop till all table read ;clear format bits in table lxi h,rtc$table+5 ;point to tens of hours mov a,m ;get it ani 07h ;mask (clear 24 hour format bit) mov m,a ;store back in table lxi h,rtc$table+8 ;point to tens of days mov a,m ;get it ani 03h ;mask leap year mov m,a ;put back in table ret ;done ; ; real time clock time/date table ; rtc$table: db 0 ;ones of seconds (0-9) db 0 ;tens of seconds (0-5) db 0 ;ones of minutes (0-9) db 0 ;tens of minutes (0-5) db 0 ;ones of hours (0-9) db 0 ;tens of hours (0-2) db 0 ;day of week (0-6) db 0 ;ones, day of the month (0-9) db 0 ;tens, day of the month (0-3) db 0 ;ones, month (0-9) db 0 ;tens, month (0-1) db 0 ;ones, year (0-9) db 0 ;tens, year (0-9) else ret ;if no clock endif ; ; ; CP/M BDOS function interface ; open: mvi c,15 ;open file control block jmp bdos ; setdma: mvi c,26 ;set data transfer address jmp bdos ; setmulti: mvi c,44 ;set record count jmp bdos ; read: mvi c,20 ;read records jmp bdos ; ; ; signon$msg db 7,13,10,'CP/M Version 3.0,' db ' Jonos LTD. Version 0.2 Bios.',13,10 db 13,10 if CPU$4mhz db '4mhz CPU.',13,10 endif if CPU$6mhz db '6mhz CPU.',13,10 endif if banked db 'Banked memory.',13,10 else db 'Nonbanked memory.',13,10 endif if time$enabled db 'RTC enabled.',13,10 endif if interrupt db 'Open door interrupt enabled.',13,10 endif if time$update db 'Time update interrupt enabled.',13,10 endif db '8" SS/SD Drvie C:',13,10 if ram$disk db 'Ram Disk enabled Drive D:',13,10 endif db 13,10 db 0 ;end string ; ccp$msg db 13,10,'BIOS Error on A: No CCP.COM file',0 ; ccp$fcb db 1,'CCP ','COM',0,0,0,0 ; ds 16 ; fcb$nr db 0,0,0 ; ; init$table db 6,uart$a$s,00h,00h,00h,40h,0ceh,37h ;set 8251`s db 6,uart$b$s,00h,00h,00h,40h,0ceh,37h ;set 8251`s db 0 svdhl: ds 2 ;saved svdret: ds 2 ;saved return address svdsp: ds 2 ;saved cnt$count: ds 1 ;counter count stack: ds 32 ;16 word stack local$stack: equ $