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.
 
 
 
 
 
 

700 lines
17 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 addhla, bcd2bin, bin2bcd
;extrn cout, phex8
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,3 ; 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
; 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
ret
cinit:
; Setup CON: I/O vector based on HBIOS console device
ld b,0FAh ; HBIOS Peek Function
ld d,0 ; Bank 0 has HCB
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 hl,8000H ; device 0
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
cp 2 ; check for 2+ char devices
jr c,cinit$3 ; if not, skip aux assignment
or a ; check for zero
ld hl,4000h ; assume aux on second char device
jr nz,cinit$2 ; if console on unit 0, assumption good
ld hl,8000h ; otherwise, aux goes to first char device
cinit$2:
;ld hl,4000H ; device 1
ld (@aivec),hl ; assign to aux input
ld (@aovec),hl ; assign to aux output
cinit$3:
; 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
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 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
; 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
; ; *** debug ***
; ;call newline2
; ld a,(drvlstc)
; ld b,a
; call phex8
; ld a,' '
; call cout
; ld hl,drvlst
;temp1:
; ld a,(hl)
; inc hl
; call phex8
; djnz temp1
; ; *** debug ***
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
; zero out remaining dph table entries
ld a,16 ; dph table entries
sub l ; subtract entries used
ret z ; return if all entries used
ld b,a ; save as loop counter
ld a,l ; current dph to accum
rlca ; *2 for word entry
ld hl,@dtbl ; start of dtbl
call addhla ; hl now points to entry
dinit6a:
xor a ; zero accum
ld (hl),a ; zero lsb
inc hl ; next byte
ld (hl),a ; zero msb
inc hl ; next byte
djnz dinit6a
ret ; finished
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
jr dinit8 ; and restart loop
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 ; 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:
;if zpm
;
;; Swap A: and system drive (make A: the system drive)
;ld bc,(@dtbl) ; get drive A DPH
;ld hl,@dtbl ; point to boot drive DPH
;ld a,(@sysdr)
;rlca
;call addhla
;ld e,(hl) ; set boot drive to drive A DPH
;ld (hl),c ; ... and save boot drive DPH
;inc hl
;ld d,(hl)
;ld (hl),b
;ld (@dtbl),de ; set drive a DPH to boot drive
;
;xor a ; update @sysdr
;ld (@sysdr),a
;
;endif
; 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
; ; now,
; ; copy CCP to bank 0 for reloading
; lxi h,0100h ! lxi b,0C80h ; clone 3K, just in case
; lda @cbnk ! push psw ; save current bank
;ld$1:
; mvi a,tpa$bank ! call ?bnksl ; select TPA
; mov a,m ! push psw ; get a byte
; mvi a,2 ! call ?bnksl ; select extra bank
; pop psw ! mov m,a ; save the byte
; inx h ! dcx b ; bump pointer, drop count
; mov a,b ! ora c ; test for done
; jnz ld$1
; pop psw ! call ?bnksl ; restore original bank
; ; now,
; ; copy CCP to bank 0 for reloading
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
;; Set first search path to system boot drive
;inc a
;ld (@srch1),a
;ld e,a
;ld c,14
;call bdos
ret
no$CCP: ; here if we couldn't find the file
ld hl,ccp$msg
call ?pmsg
call ?conin
jp ?ldccp
?rlccp:
if banked
; lxi h,0100h ! lxi b,0C00h ; clone 3K
;rl$1:
; mvi a,2 ! call ?bnksl ; select extra bank
; mov a,m ! push psw ; get a byte
; mvi a,tpa$bank ! call ?bnksl ; select TPA
; pop psw ! mov m,a ; save the byte
; inx h ! dcx b ; bump pointer, drop count
; mov a,b ! ora c ; test for done
; jnz rl$1
; ret
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
; convert yymmss in time buffer -> cpm3 epoch date offset
call date2cpm ; time buf (yr, mon, day) -> SCB (@date)
; 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, bypss 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
ex de,hl ; save HL in DE
ld hl,daystbl ; point to table of cum days by month
ld a,(tim$mon) ; get current month
call bcd2bin ; convert to binary
dec a ; index from zero
rlca ; table entries are 2 bytes
call addhla ; offset to desired month entry
ld a,(hl) ; get the entry into HL
inc hl ; ...
ld h,(hl) ; ...
ld l,a ; ...
ex de,hl ; HL = day count, DE = months count
add hl,de ; add months count into day count
; Add leap day for current year if appropriate
dec c ; C still has leap counter
jr nz,d2c20 ; skip if not leap year
ld a,(tim$mon) ; get cur mon
cp 3 ; March?
jr c,d2c20 ; skip if mon less than March
inc hl ; add leap day for cur year
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
ret
cpm2date:
; Convert CPM epoch date offset in SCB
; into YYMMSS values and store result in
; time buffer at HL.
ld a,019h
ld (tim$yr),a
ld a,001h
ld (tim$mon),a
ld a,001h
ld (tim$day),a
ret
daystbl:
; cumulative days elapsed by month (non-leap year)
dw 0 ; January
dw 31 ; February (non-leap)
dw 59 ; March
dw 90 ; April
dw 120 ; May
dw 151 ; June
dw 181 ; July
dw 212 ; August
dw 243 ; September
dw 273 ; October
dw 304 ; November
dw 334 ; 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
if zpm
signon$msg db 13,10,'ZPM3'
if banked
db ' [BANKED]'
endif
db ' on HBIOS v'
biosver
db 13,10,13,10,0
ccp$msg db 13,10,'BIOS Err on '
ccp$msg$drv db '?'
db ': No ZCCP.COM file',0
ccp$fcb db 0,'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 ' on HBIOS v'
biosver
db 13,10,13,10,0
ccp$msg db 13,10,'BIOS Err on '
ccp$msg$drv db '?'
db ': No CCP.COM file',0
ccp$fcb db 0,'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