Files
RomWBW/Source/CPM3/boot.z80
Wayne Warthen ee0fac37f9 Early partition table support
Adding infrastructure for partition table support.  Backward compatible.  Not ready for end user usage yet.

Bumped version to 3.1.1 to demarcate this change.
2020-05-03 19:05:44 -07:00

731 lines
18 KiB
Z80 Assembly

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, bcd2bin, bin2bcd
extrn cout, phex8, phex16, crlf, crlf2
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
; 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 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
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
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 a,(bnkramd) ; first bank of ram disk
ld a,080h ; first bank of ram disk
;call hb_bnksel ; select bank
call 0FFF3h ; select bank
; 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:
;ld a,(bnkuser) ; usr bank (tpa)
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
ld a,(datehack)
or a
jr nz,time$get1
; 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)
; this is a temporary hack!!!
; since we cannot actually set the date on the RTC, we
; just read the current RTC date and use that so that we
; don't clobber a potentially good date.
; 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
;
; now we set a hack active flag so that future time$get
; calls do not update the date field in the SCB
;
ld a,0FFh ; true value
ld (datehack),a ; save it
; 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
datehack db 00h
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
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