You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

799 lines
20 KiB

title 'Boot loader module for CP/M 3.0'
maclib options.lib
public ?init,?ldccp,?rlccp,?time
public @bootdu,@bootsl
extrn ?pmsg,?conin
extrn ?mvinit,?bnkxlt,?xmove,?move
extrn @civec,@covec,@aivec,@aovec,@lovec
extrn @cbnk,?bnksl,?bank
extrn @sysdr,@ccpdr
extrn dph0
extrn @dtbl,@ctbl
extrn @date,@hour,@min,@sec
extrn @srch1
extrn @hbbio
extrn addhla
extrn phex16, phex8
extrn cin, cout
extrn crlf, crlf2
extrn bcd2bin, bin2bcd
include c:ver.lib
bdos equ 5
if banked
tpa$bank equ 1
else
tpa$bank equ 0
endif
dseg ; init done from banked memory
?init:
call ?mvinit
; Install RomWBW CBIOS stamp in page zero
ld hl,stpimg
ld de,stploc
ld bc,stpsiz
ldir
if banked
; Clone page zero from bank 0 to additional banks
ld b,4 ; last bank
ld c,0 ; src bank
init$2:
push bc ; save bank id's
call ?xmove ; set src/dest banks
ld bc,0100h ; size is one page
ld hl,0 ; dest adr is 0
ld de,0 ; src adr is 0
call ?move ; do it
pop bc ; restore bank id's
djnz init$2 ; loop till done
endif
call cinit ; char device init
ld hl,signon$msg ; signon message
call ?pmsg ; print it
; Check for HBIOS/CBIOS mismatch
ld b,0F1h ; hbios version
rst 08 ; do it, de=maj/min/up/pat
ld a,d ; a := maj/min
cp ((rmj << 4) | rmn) ; match?
jr nz,init$3 ; handle ver mismatch
ld a,e ; a := os up/pat
and 0F0h ; pat not included in match
cp (rup << 4) ; match?
jr nz,init$3 ; handle ver mismatch
jr init$4 ; all good, continue
init$3:
; display version mismatch
ld hl,vermis$msg ; version mismatch
call ?pmsg ; display it
init$4:
; Get boot disk unit and save it
ld bc,0F8E0h ; HBIOS func: get boot info
rst 08 ; do it, D := boot unit, E: := boot slice
ld a,d ; move boot unit to A
ld (@bootdu),a ; save it
ld a,e ; move boot slice to A
ld (@bootsl),a ; save it
call dinit
call clrram
ret
cinit:
; Setup CON: I/O vector based on HBIOS console device
ld b,0FAh ; HBIOS Peek Function
ld a,(@hbbio) ; HBIOS bank id
ld d,a ; ... goes in D
ld hl,112h ; Offset 112h is current console device
rst 08 ; Call HBIOS, value in E
push de ; save console unit value
ld b,e ; Use as loop counter
inc b ; ... but loop 1 extra time
ld hl,0 ; Clear vector bitmap
scf ; Set carry
cinit$1:
rr h ; Rotate carry flag
rr l ; ... into correct vector position
djnz cinit$1 ; loop as needed
ld (@civec),hl ; assign to console input
ld (@covec),hl ; assign to console output
; Setup AUX: I/O vector if there are 2+ char devices in system
ld bc,0F800h ; HBIOS GET Character Device Count
rst 08 ; do it, count in E
ld a,e ; device count to accum
pop de ; recover console unit num to E
push af ; save device count
cp 2 ; check for 2+ char devices
jr c,cinit$3 ; if not, skip aux assignment
ld a,e ; console unit num to A
or a ; check for zero
ld hl,4000h ; assume aux on second char device
jr z,cinit$2 ; if console on unit 0, assumption good
ld hl,8000h ; otherwise, aux goes to first char device
cinit$2:
ld (@aivec),hl ; assign to aux input
ld (@aovec),hl ; assign to aux output
cinit$3:
pop af ; recover device count
; Truncate char table based on actual num of char devices
rlca ; A still has char device count
rlca ; * 8 for ctbl entry size
rlca ; "
ld hl,@ctbl ; Start of char table
call addhla ; Skip used entries
xor a ; Zero to accum
ld (hl),0 ; Set table terminator
ret ; done
dinit:
; loop through all disk devices to count hard disk units
ld b,0F8h ; SYS GET
ld c,010h ; Disk Drive Unit Count
rst 08 ; e := disk unit count
ld b,e ; count to b
ld a,b ; count to a
or a ; set flags
ret z ; !!! handle zero devices (albeit poorly) !!!
; loop thru devices to count total hard disk volumes
ld c,0 ; init c as device list index
ld d,0 ; init d as total device count
ld e,0 ; init e for hard disk device count
ld hl,drvlst ; init hl ptr to drive list
;
dinit2:
call dinit3 ; check drive
inc c ; next unit
djnz dinit2 ; loop
ld a,d ; total device count to d
ld (drvlstc),a ; save the count
jr dinit4 ; continue
dinit3:
push de ; save de (hard disk volume counter)
push hl ; save drive list ptr
push bc ; save loop control
ld b,17h ; hbios func: report device info
rst 08 ; call hbios, unit to c
ld a,d ; device type to a
pop bc ; restore loop control
pop hl ; restore drive list ptr
pop de ; restore de
cp 30h ; hard disk device?
jr nc,dinit3a ; if so, handle special
ld (hl),c ; save unit num in list
inc hl ; bump ptr
inc d ; inc total device count
ret
;
dinit3a:
; check for hard disk removable cartridge drives
cp 0A0h ; ppa (zip drive) is removable
jr z,dinit3b ; if so, skip media check
cp 0B0h ; imm (zip drive) is removable
jr z,dinit3b ; if so, skip media check
; check for active and return if not
push de ; save de (hard disk volume counter)
push hl ; save drive list ptr
push bc ; save loop control
ld b,18h ; hbios func: sense media
ld e,1 ; perform media discovery
rst 08
pop bc ; restore loop control
pop hl ; restore drive list ptr
pop de ; restore de
ret nz ; if no media, just return
dinit3b:
; if active...
ld (hl),c ; save unit num in list
inc hl ; bump ptr
inc d ; inc total device count
inc e ; increment hard disk count
ret ; and return
dinit4: ; set slices per volume (hdspv) based on hard disk volume count
ld a,e ; hard disk volume count to a
ld e,8 ; assume 8 slices per volume
dec a ; dec accum to check for count = 1
jr z,dinit5 ; yes, skip ahead to implement 8 hdspv
ld e,4 ; now assume 4 slices per volume
dec a ; dec accum to check for count = 2
jr z,dinit5 ; yes, skip ahead to implement 4 hdspv
ld e,2 ; in all other cases, we use 2 hdspv
dinit5:
ld a,e ; slices per volume value to accum
ld (hdspv),a ; save it
ld hl,0 ; dph index
ld a,(@bootdu) ; boot disk unit
ld d,a ; ... to d
ld a,(@bootsl) ; boot slice
ld e,a ; ... to e
ld b,1 ; one slice please
call dinit8a ; make DPH for A:
ld a,(drvlstc) ; active drive list count to accum
ld b,a ; ... and move to b for loop counter
ld de,drvlst ; de is ptr to active drive list
dinit6:
; loop thru all units available
push de ; preserve drive list ptr
ex de,hl ; list ptr to hl
ld c,(hl) ; get unit num from list
ex de,hl ; list ptr back to de
push bc ; preserve loop control
push hl ; preserve dph pointer
ld b,17h ; hbios func: report device info
rst 08 ; call hbios, d := device type
pop hl ; restore dph pointer
pop bc ; get unit index back in c
push bc ; resave loop control
call dinit7 ; make drive map entry(s)
pop bc ; restore loop control
inc c ; increment list index
pop de ; restore drive list ptr
inc de ; increment active drive list ptr
djnz dinit6 ; loop as needed
ret
dinit7: ; process a unit (all slices)
ld e,0 ; initialize slice index
ld b,1 ; default loop counter
ld a,d ; device type to accum
ld d,c ; unit number to d
cp 030h ; hard disk device?
jr c,dinit8 ; nope, leave loop count at 1
ld a,(hdspv) ; get slices per volume to accum
ld b,a ; move to b for loop counter
dinit8: ; test to avoid reallocating boot disk unit/slice
ld a,(@bootdu) ; boot disk unit to accum
cp d ; compare to cur unit
jr nz,dinit8a ; if ne, ok to continue
ld a,(@bootsl) ; boot slice to accum
cp e ; compare to cur slice
jr nz,dinit8a ; if ne, ok to continue
inc e ; is boot du/slice, skip it
djnz dinit8 ; loop till done with unit
ret
dinit8a:
; d=unit, e=slice, l=dph#, b=slice cnt
ld a,l ; dph # to accum
cp 16 ; dph table size
ret z ; bail out if overflow
push hl ; save dph #
rlca ; *2 for adr entry
ld hl,@dtbl ; dph table start
call addhla ; offset hl to desired entry
ld a,(hl) ; dereference
inc hl
ld h,(hl)
ld l,a
dec hl ; backup to slice field
ld (hl),e ; update slice number
dec hl ; backup to unit number
ld (hl),d ; update unit number
pop hl ; restore dph #
inc hl ; next dph #
inc e ; next slice
djnz dinit8 ; loop till done with unit
ret
; RomWBW CBIOS page zero stamp starts at $40
; $40-$41: Marker ('W', ~'W')
; $42-$43: Version bytes: major/minor, update/patch
; $44-$45: CBIOS Extension Info address
stploc equ 40h
stpimg db 'W',~'W' ; marker
db rmj << 4 | rmn ; first byte of version info
db rup << 4 | rtp ; second byte of version info
dw cbx ; address of cbios ext data
stpsiz equ $ - stpimg
cseg ; ram disk init must be done from resident memory
;
; Initialize ram disk by filling directory with 'e5' bytes
; Fill first 8k of ram disk track 1 with 'e5'
;
clrram:
di ; no interrupts
ld a,(0FFE0h) ; get current bank
push af ; save it
ld b,0FAh ; HBIOS Peek Function
ld a,(@hbbio) ; HBIOS bank id
ld d,a ; ... goes in D
ld hl,1DCh ; Offset 1DCh is ram disk bank 0
rst 08 ; Call HBIOS, value in E
ld a,e ; move to A for bank sel
cp 0FFh
jr z,clrram3
;call hb_bnksel ; select bank
call 0FFF3h ; select bank
; Check the first sector (512 bytes) for all zeroes. If so,
; it implies the RAM is uninitialized.
ld hl,0 ; start at begining of ram disk
ld bc,512 ; compare 512 bytes
xor a ; compare to zero
clrram000:
cpi ; a - (hl), hl++, bc--
jr nz,clrram00 ; if not zero, go to next test
jp pe,clrram000 ; loop thru all bytes
jr clrram2 ; all zeroes, jump to init
clrram00:
; Check first 32 directory entries. If any start with an invalid
; value, init the ram disk. Valid entries are e5 (empty entry) or
; 0-15 (user number).
ld hl,0
ld de,32
ld b,32
clrram0:
ld a,(hl)
cp 0E5h
jr z,clrram1 ; e5 is valid
cp 16
jr c,clrram1 ; 0-15 is also valid
jr clrram2 ; invalid entry! jump to init
clrram1:
add hl,de ; loop for 32 entries
djnz clrram0
; jr clrram2 ; *debug*
jr clrram3 ; all entries valid, bypass init
clrram2:
ld hl,0 ; source adr for fill
ld bc,2000h ; length of fill is 8k
ld a,0E5h ; fill value
call fill ; do it
or 0ffh ; flag value for cleared
ld (clrflg),a ; save it
clrram3:
pop af ; recover original bank
;call hb_bnksel ; select bank
call 0FFF3h ; select bank
ei ; resume interrupts
ld a,(clrflg) ; get cleared flag
or a ; set flags
ld hl,clr$msg ; clear ram disk message
call nz,?pmsg ; print msg if true
ret
;
; Fill memory at hl with value a, length in bc. All regs used.
; Length *must* be greater than 1 for proper operation!!!
;
fill:
ld d,h ; set de to hl
ld e,l ; so destination equals source
ld (hl),a ; fill the first byte with desired value
inc de ; increment destination
dec bc ; decrement the count
ldir ; do the rest
ret ; return
cseg ; boot loading most be done from resident memory
; This version of the boot loader loads the CCP from a file
; called CCP.COM on the system drive.
?ldccp:
; Force CCP to use system boot drive as initial default
;ld a,(@sysdr) ; get system boot drive
;ld (@ccpdr),a ; set CCP current drive
; First time, load the CCP.COM file into TPA
;ld a,(@sysdr) ; get system boot drive
;;ld (4),a ; save in page zero???
;inc a ; drive + 1 for FCB
;ld (ccp$fcb),a ; stuff into FCB
;add 'A' - 1 ; drive letter
;ld (ccp$msg$drv),a ; save for load msg
;xor a
;ld (ccp$fcb+15),a
ld hl,0
ld (fcb$nr),hl
ld de,ccp$fcb
call open
inc a
jr z,no$CCP
ld de,0100H
call setdma
ld de,128
call setmulti
ld de,ccp$fcb
call read
if banked
ld hl,0100h ; clone 3K, just in case
ld bc,0C80h
ld a,(@cbnk) ; save current bank
push af
ld$1:
ld a,tpa$bank ; select TPA
call ?bnksl
ld a,(hl) ; get a byte
push af
ld a,2 ; select extra bank
call ?bnksl
pop af ; save the byte
ld (hl),a
inc hl ; bump pointer, drop count
dec bc
ld a,b ; test for done
or c
jr nz,ld$1
pop af ; restore original bank
call ?bnksl
endif
ret
no$CCP: ; here if we couldn't find the file
ld hl,ccp$msg
call ?pmsg
call ?conin
jp ?ldccp
?rlccp:
if banked
ld hl,0100h ; clone 3K
ld bc,0C80h
rl$1:
ld a,2 ; select extra bank
call ?bnksl
ld a,(hl) ; get a byte
push af
ld a,tpa$bank ; select TPA
call ?bnksl
pop af ; save the byte
ld (hl),a
inc hl ; bump pointer, drop count
dec bc
ld a,b ; test for done
or c
jr nz,rl$1
ret
else
jr ?ldccp
endif
?time:
; per CP/M 3 docs, *must* preserve HL, DE
push hl
push de
; force return through time$ret
ld hl,time$ret
push hl
; branch to get or set routine
ld a,c ; get switch value
or a ; test for zero
jr z,time$get ; 0 means get time
jr time$set ; else set time
time$ret:
; restore HL, DE
pop de
pop hl
ret
time$get:
; RTC -> cpm date/time in SCB
; read time from RTC
ld b,020h ; HBIOS func: get time
ld hl,tim$buf ; time buffer
rst 08 ; do it
ret nz ; bail out on error
; convert yymmss in time buffer -> cpm3 epoch date offset
call date2cpm ; time buf (yr, mon, day) -> SCB (@date)
time$get1:
; set time fields in SCB
ld a,(tim$hr) ; get hour from time buf
ld (@hour),a ; ... and put in SCB
ld a,(tim$min) ; get minute from time buf
ld (@min),a ; ... and put in SCB
ld a,(tim$sec) ; get second from time buf
ld (@sec),a ; ... and put in SCB
ret
time$set:
; CPM date/time in SCB -> RTC
; convert CPM3 epoch date offset in SCB -> yymmss in time buffer
call cpm2date ; SCB (@date) -> time buf (yr, mon, day)
; copy CPM3 time values from SCB -> time buffer
ld a,(@hour) ; get hour from SCB
ld (tim$hr),a ; ... and put in tim$hr
ld a,(@min) ; get minute from SCB
ld (tim$min),a ; ... and put in tim$min
ld a,(@sec) ; get second from SCB
ld (tim$sec),a ; ... and put in tim$sec
; send time to RTC
ld b,021h ; HBIOS func: set time
ld hl,tim$buf ; ... from time buffer
rst 08 ; do it
ret
date2cpm:
; Convert YYMMSS from time buffer at HL
; into offset from CPM epoch and store
; result in SCB.
ld hl,0 ; initialize day counter
; Add in days for elapsed years
ld a,(tim$yr) ; get current year
call bcd2bin ; convert to binary
sub 78 ; epoch year
jr nc,d2c1 ; if not negative, good to go
add a,100 ; else, adjust for Y2K wrap
d2c1:
ld b,a ; loop counter
ld c,3 ; leap counter, 78->79->80, so 3
ld de,365 ; days in non-leap year
or a ; check for zero
jr z,d2c10 ; skip if zero
d2c2:
add hl,de ; add non-leap days
dec c ; dec leap counter
jr nz,d2c3 ; if not leap, bypass leap inc
inc hl ; add leap day
ld c,4 ; reset leap year counter
d2c3:
djnz d2c2 ; loop for all years
d2c10:
; Add in days for elapsed months
ld de,daysmon ; point to start of days per mon tbl
ld a,(tim$mon) ; get current month
call bcd2bin ; convert to binary
dec a ; don't include cur mon
jr z,d2c20 ; done if Jan
ld b,a ; save as loop counter
cp 2 ; Mar = 2
jr c,d2c11 ; bypass if < Mar (no leap month)
dec c ; C still has leap year counter
jr nz,d2c11 ; skip if not leap year
inc hl ; add in the leap day
d2c11:
ld a,(de) ; get days for cur mon
call addhla ; add to running count
inc de ; bump to next mon ptr
djnz d2c11 ; loop for # of months
d2c20:
; Add in days elapsed within month
; Note that we don't adjust the date to be a zero
; offset which seems wrong. From what I can tell
; the CP/M epoch is really 1/0/1978 rather than the
; 1/1/1978 that the documentation claims. Below seems
; to work correctly.
ld a,(tim$day) ; get day
call bcd2bin ; make binary
call addhla ; add in days
ld (@date),hl ; store in SCB
;call phex16 ; *debug*
;call crlf ; *debug*
ret
cpm2date:
; Convert CPM epoch date offset in SCB
; into YYMMSS values and store result in
; time buffer at HL.
; We start by subtracting years keeping a count
; of the number of years. Every fourth year is a leap
; year, so we account for that as we go.
ld hl,(@date) ; get the count of days since epoch
dec hl ; because we want 1/1/78 to be offset 0
ld c,78 ; init the years value
c2d1:
ld de,365 ; normal number of days per year
ld a,c
ld b,0 ; init leap year flag
and 03h ; check for leap year
jr nz,c2d2 ; if not zero, no need to adjust
inc de ; add a day for leap year
ld b,1 ; leap year flag for later
c2d2:
or a ; clear carry
sbc hl,de ; subtract
jr c,c2d3 ; get out if we went too far
inc c ; add a year to year value
ld a,c ; to accum
cp 100 ; century rollover?
jr nz,c2d1 ; nope, loop
ld c,0 ; reset for start of century
jr c2d1 ; loop
c2d3:
ld a,c ; years to accum
call bin2bcd ; convert to bcd
ld (tim$yr),a ; ... and save it
;call phex8 ; *debug*
;
; Now we use the days per month table to find the
; month.
add hl,de ; restore days remaining
ld c,0 ; init month value (zero indexed)
c2d4:
ld a,c ; get month value
push hl ; save hl (days remaining)
ld hl,daysmon ; point to start of table
call addhla ; point to month entry
ld e,(hl) ; get months day count to E
ld d,0 ; zero msb, DE is days in month
pop hl ; recover hl (days remaining)
ld a,c ; month value to accum
cp 1 ; leap month? check for Feb
jr nz,c2d5 ; no, leave alone
ld a,b ; get leap year flag (set above)
or a ; leap year?
jr z,c2d5 ; if not, skip ahead
inc de ; account for leap year
c2d5:
or a ; clear carry
sbc hl,de ; subtract days for the month
jr c,c2d6 ; get out if we went too far
inc c ; next month
jr c2d4 ; continue
c2d6:
inc c ; switch from 0 to 1 offset
ld a,c ; move to accum
call bin2bcd ; convert to bcd
ld (tim$mon),a ; save it
;call phex8 ; *debug*
;
; Leftover days is day value
add hl,de ; restore days remaining
ld a,l ; only need lsb
inc a ; switch from 0 to 1 offset
call bin2bcd ; convert to bcd
ld (tim$day),a ; save it
;call phex8 ; *debug*
ret
daysmon:
; days per month
db 31 ; January
db 28 ; February (non-leap)
db 31 ; March
db 30 ; April
db 31 ; May
db 30 ; June
db 31 ; July
db 31 ; August
db 30 ; September
db 31 ; October
db 30 ; November
db 31 ; December
; RTC time buffer (all values packed bcd)
tim$buf:
tim$yr db 80h
tim$mon db 05h
tim$day db 10h
tim$hr db 01h
tim$min db 02h
tim$sec db 03h
open:
ld c,15
jp bdos
setdma:
ld c,26
jp bdos
setmulti:
ld c,44
jp bdos
read:
ld c,20
jp bdos
clrflg db 0 ; RAM disk cleared flag
clr$msg db 'RAM Disk Initialized',13,10,13,10,0
vermis$msg db 7,'*** WARNING: HBIOS/CBIOS Version Mismatch ***',13,10,13,10,0
if zpm
signon$msg db 13,10,'ZPM3'
if banked
db ' [BANKED]'
endif
db ' for HBIOS v'
biosver
db 13,10,13,10,0
ccp$msg db 13,10,'BIOS Err on '
ccp$msg$drv db 'A'
db ': No ZCCP.COM file',0
ccp$fcb db 1,'ZCCP ','COM',0,0,0,0
ds 16
fcb$nr db 0,0,0
else
signon$msg db 13,10,'CP/M v3.0'
if banked
db ' [BANKED]'
endif
db ' for HBIOS v'
biosver
db 13,10,13,10,0
ccp$msg db 13,10,'BIOS Err on '
ccp$msg$drv db 'A'
db ': No CCP.COM file',0
ccp$fcb db 1,'CCP ','COM',0,0,0,0
ds 16
fcb$nr db 0,0,0
endif
@bootdu db 0 ; boot disk unit
@bootsl db 0 ; boot slice
hdspv db 2 ; slices per volume for hard disks (must be >= 1)
drvlst ds 32 ; active drive list used durint drv_init
drvlstc db 0 ; entry count for active drive list
; The following section contains key information and addresses for the
; RomWBW CBIOS. A pointer to the start of this section is stored with
; with the CBX data in page zero at $44 (see above).
cbx:
devmapadr dw 0 ; device map address
drvtbladr dw @dtbl ; drive map address (filled in later)
dphtbladr dw dph0 ; dpb map address
cbxsiz equ $ - cbx
end