home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
CP/M
/
CPM_CDROM.iso
/
mbug
/
mbug097.arc
/
DB512.MAC
< prev
next >
Wrap
Text File
|
1979-12-31
|
17KB
|
683 lines
;
; 512k ENHANCED BIOS
; DISK CACHE ROUTINES
;
; (c) Peter Broughton.
;
; Sector buffering of 0k to 128k.
;
; 28th Dec., 1987
;
; If the following is true then two seperate lists will be kept.
; 1 for normal sectors, 1 for dir sectors (which have a higher priority)
; At the moment (26/1/88) this code isn't debugged
dir_list equ false
; number of bytes per hash table entry
; #0 = backwards pointer, #1 = forewards pointer,
; #2 = { b6, b7 = disk number (0..3), b0..b4 = sector number (0..31) }
; if #2 = 0FFh (b5) then this sector is unused,
; if #2 = 0FEh then this sector is unusable (no corresponding page)
; #3 = track number
bytes_per_entry equ 4
db_back_ptr equ 0
db_fore_ptr equ 1
db_d_s_ptr equ 2
db_track_ptr equ 3
; the hash table has max. 256 entries so occupies 1024 bytes = 2 sectors
; so first available sector (entry) is actually #2
; this also means that there will be
; 8 spare bytes at the start of 'dbuff page 1'
first_used_sector equ 2
db_unused equ 0FFh
db_unusable equ 0FEh
; 2 bytes / disk (initialised to 0FFh) :
; first gives number of system tracks,
; second gives number of system + dir tracks
; system tracks = 0FEh for unbufferable
; This uses the 8 spare bytes in the first buffer page
dir_tracks_on_a equ 0
if (first_used_sector * bytes_per_entry) ne 8
error Not enough room for track table
endif
; initialise disk buffer
db_init:
; don't bother if no pages set for dbuff
ld hl,dbuff_pages
ld a,(hl)
dec a
ret m
push af
; select first disk buffer page ie. page with hash table
dec hl ; dbuff after mdrive, ndrive
ld a,(hl)
inc hl
inc hl
add a,(hl)
add a,3 ; and after TPA, system page
ld (db_page_num_1),a ; First db page (number)
call mem_page
ld (db_page_1),a ; store it for later use
pop af
; calculate number of sectors available,
; with 512 bytes / sector this gives 64 sectors / page
rrca
rrca
or 64-1
; actually pages*64-1 gives number of last used entry
; pages*64-2 is the total number of entries
dec a
ld (db_entries),a
; now make all entries to 'unused' value
; Also sets track table to 'unknown' value
ld hl,0
ld de,1
ld bc,+(bytes_per_entry * 256)-1
ld (hl), db_unused
ldir
; Now must make up linked list.
; First entry is initially assigned to dir list and points to itself.
; Remaining entries must be linked in one big long list.
; IX --> address of entry, B = loop counter,
; H = backwards pointer, L = forewards pointer
; DE = bytes_per_entry (incr. of IX)
; first and last entries and handled differently
ld h,a
inc h ; first entry in normal list points back to last entry
sub 3 ; count 3 less
ld b,a
ld ix,first_used_sector * bytes_per_entry
ld de,bytes_per_entry
ld l,first_used_sector
; first dir entry
ld (ix),l ; points to itself
ld (ix+1),l
; first normal entry points forward to next entry
inc l
inc l
add ix,de
; first entry
ld (ix),h ; backwards to last entry
ld (ix+1),l ; forewards to next
add ix,de
ld h,first_used_sector+1
inc l
db_i_1:
ld (ix),h ; entry's backwards pointer
ld (ix+1),l ; " forewards "
inc h
inc l
add ix,de
djnz db_i_1
; last entry
ld (ix),h
ld (ix+1),first_used_sector+1 ; forewards to first entry
; finished
map bios_map
ret
; disk write has just been done, so new sector must be copied into
; next available entry
db_disk_write:
push hl
push de
push bc
ld a,(db_page_1)
out (map_port),a
; first find if this sector is already there, if it is then link it in
; at most recently used position and keep the same entry as next to be used
; returns nz if not found so must get address, page of next sector
call db_find_and_relink
; note that if this is a dir sector then this routine will steal an
; entry from the normal list and add it to the dir list
call nz,db_get_next_entry
; both calls return with correct page selected and with hl, de and bc ready
; for write to buffer
; so put sector into buffer
ldir
; Z is not necessary but saves code
jr db_ret_z
; disk read is about to be done so check if sector is already buffered
; if it is then make it most recently used and return Z to say
; that the sector does not have to be read from disk
db_disk_preread:
push hl
push de
push bc
ld a,(db_page_1)
out (map_port),a
; find if this sector is already there, if it is then link it in
; at most recently used position and keep the same entry as next to be used
call db_find_and_relink
; returns NZ if sector not found so just return and let disk read go ahead
jr nz,db_ret_nz
; if it is then then correct page has been selected and HL, DE and BC are ready
ex de,hl
; copy buffer to sector
ldir
; successful so return Z
db_ret_z:
pop bc
pop de
pop hl
map bios_map
xor a
ret
; return with NZ
; assumes already NZ
db_ret_nz:
pop bc
pop de
pop hl
map bios_map
ret
; sector was not already buffered so it has just been read
; so must now buffer it
db_disk_postread:
push hl
push de
push bc
ld a,(db_page_1)
out (map_port),a
; find next sector
; returns with page selected, HL, DE and BC loaded for write to buffer
call db_get_next_entry
; copy sector to buffer
ldir
jr db_ret_z
; Search the list for specified sector, if not found then return NZ.
; If found then return Z ready for a write to sector,
; (if read required then EX DE,HL).
; Returns :
; HL = hstbuf, DE = address of found sector
; BC = length of sector (512 bytes only may be sufficient)
; correct page selected
db_find_and_relink:
push ix
; first get disk, sector and track number in appropriate format for search
; H has disk/sector, L has track
call db_get_sect_param
; now do search of table, numeric not link order
ld ix,+(first_used_sector * bytes_per_entry)
ld a,(db_entries)
ld b,a
ld de,bytes_per_entry
db_f_1:
ld a,(ix+2) ; disk/sector
cp l
jr nz,db_f_2
ld a,(ix+3) ; track
cp h
jr z,db_f_fnd ; matches !!
; doesn't match
db_f_2:
add ix,de
djnz db_f_1
; reached last entry without match, return with NZ
pop ix
or -1
ret
; found a match, now comes the hard part
db_f_fnd:
pop ix
; convert loop counter's value to entry number and save
ld a,(db_entries)
add a,first_used_sector
sub b
call remove_and_relink
; there done !!!
db_get_sect_addr:
; This section requires that HL --> somewhere within entry for sector
; for which address and page is required
; Convert to page in A (and selected) and sector address in DE
; 64 entries (256 bytes) per page so H has relative page number
ld a,(db_page_num_1)
add a,h
; select it
call mem_page
; L has number of entry within page (*4), convert this to address
ld d,l
srl d ; 512 bytes / sector
res 0,d
ld e,0
; load other values for block move
ld hl,hstbuf
ld bc,512
; return Z for successful
xor a
ret
; Wish to buffer a new sector, therefore get next one in list and
; move 'next sector' to next entry in list.
; If this is a directory sector then try to steal an entry
; from the normal list.
; This routine will always successfully return a sector address.
; This means that even if this is a dir sector it will always leave
; at least one entry in the normal list.
; Returns :
; HL = hstbuf, DE = address of found sector
; BC = length of sector (512 bytes only may be sufficient)
; correct page selected
db_get_next_entry:
if dir_list
; if dir then try to steal entry from normal list
ld a,(db_is_dir)
or a
jr nz,db_get_new_dir
endif
; Is a normal sector so just get the next available entry in the normal list
ld a,(db_next_sector)
; get address of this entry
call db_num_to_addr
inc hl ; entry's forward pointer
; move next sector pointer foreward along list
ld a,(hl)
ld (db_next_sector),a
; Must get to this point with HL --> (address of ENTRY for cache) +1
db_setup_entry:
; now make entry point to disk sector it is buffering
ex de,hl
; get track in D, disk & sector in E
call db_get_sect_param
ex de,hl
inc hl
ld (hl),e ; disk & sector
inc hl
ld (hl),d ; track
; turn this into a sector address and page and return
; requires entry address ( + 0..3 ) in HL
jr db_get_sect_addr
if dir_list
; Attempt to steal an entry from the normal list and put it into the dir list
db_get_new_dir:
; First check if first in dir list is unused
ld a,(db_next_dir_sector)
call db_num_to_addr
inc hl
inc hl
ld a,(hl)
dec hl ; HL must be left --> entry address +1
cp db_unused ; Is unused
jr z,db_use_this_dir ; so use it
ex de,hl ; Address required later
; Get next 'available' entry in normal list
ld a,(db_next_sector)
ld b,a
call db_num_to_addr ; Get entry address
inc hl
ld a,(hl) ; Forwards pointer
cp b ; If it points to itself then don't steal
jr z,db_dont_steal
; Move next normal sector pointer along one
ld (db_next_sector),a
ld a,b
; Remove it from normal list and link into dir list before next dir entry
call remove_and_relink
; Now have an available entry to buffer dir sector so prepare to buffer
jr db_setup_entry
; Normal entry wasn't available to be stolen (very unlikely)
; so just move along dir list
db_dont_steal:
ex de,hl
; Goes to here if first entry in dir list is marked as unused
db_use_this_dir:
ld a,(hl) ; Forward pointer
ld (db_next_dir_sector),a ; Move to next
jr db_setup_entry
endif
; remove entry (A) from present position and relink before next sector
; return HL --> entry (A) address +1
; all regs. destroyed
remove_and_relink:
ld b,a
; remove it from it's present position, ie. link before and after together
call db_num_to_addr
ld d,(hl) ; entry before
inc hl
ld e,(hl) ; entry after
; link entry before to one after
ld a,d
call db_num_to_addr
inc hl
ld (hl),e
; link entry after to one before
ld a,e
call db_num_to_addr
ld (hl),d
; link it in before 'next sector'
; ie. foreward link to next entry, backward link to same entry
; that next entry links to
if dir_list
; First check if it dir or normal
ld a,(db_is_dir)
or a
ld a,(db_next_dir_sector) ; Use dir list
jr nz,db_rr_1
ld a,(db_next_sector) ; Use normal list
db_rr_1:
else
ld a,(db_next_sector)
endif
ld d,a
call db_num_to_addr
ld c,(hl) ; entry before next entry
ld (hl),b ; now points back to this entry
ld a,c
call db_num_to_addr ; previous entry
inc hl
ld (hl),b ; now point to this entry
ld a,b
call db_num_to_addr ; get address of this entry
ld (hl),c ; point back to previous entry
inc hl
ld (hl),d ; point forward to next entry
ret
; Purge all entries for drive [c]
; This is used during seldsk to ensure that the reboot
; actually gets new data from the new disk.
; This also kills the deblocking buffer.
; All regs preserved except A.
; Must also flag number of system, dir+system tracks as 'unknown' (0FFh).
purge_c:
push hl
push bc
call db_common_purge
; Flag track numbers unknown
ld a,0FFh
ld (hl),a
inc hl
ld (hl),a
; now purge all tracks < 255 (A)
jr db_purge_1
; Purge all directory entries for drive [c]
; This is used before the BDOS delete/rename function is performed
; to ensure that we don't write bad data on a changed disk
; This also kills the deblocking buffer
; All regs preserved except a
purgedir_c:
push hl
push bc
call db_common_purge
inc hl
ld a,(hl) ; Get number of dir + system tracks
; purge all tracks < (A) on disk (C)
db_purge_1:
; Go through in numeric order (not list order), find sectors from
; disk (C), track < (A), mark them as 'unused'
; and relink them as new 'next sector' in normal list
push ix
push de
rrc c ; convert disk num. to MS bits
rrc c
ld h,a ; check < this track
if dir_list
xor a
ld (db_is_dir),a ; So entries are relinked into normal list
endif
ld ix,first_used_sector * bytes_per_entry
ld de,bytes_per_entry
ld a,(db_entries)
ld b,a ; loop counter
ld l,first_used_sector ; entry number
db_p_1:
ld a,(ix+2) ; disk/sector
cp db_unused
jr z,db_p_2
and 0C0h ; mask out sector number
cp c ; is this disk ?
jr nz,db_p_2
ld a,(ix+3)
cp h ; track
jr nc,db_p_2
; disk matches and track is within range
; so mark as unused and relink as next sector
ld (ix+2),db_unused
; don't relink if this is the next dir sector
; ie. must keep at least one (maybe unused) entry in dir list
if dir_list
ld a,(db_next_dir_sector)
cp l
jr z,db_p_2
endif
push hl
push de
push bc
ld a,l
call remove_and_relink
pop bc
pop de
pop hl
ld a,l
ld (db_next_sector),a ; make it next sector
db_p_2:
inc l
add ix,de
djnz db_p_1
pop de
pop ix
pop bc
pop hl
map bios_map
ret
; Routines common to the start of both purge routines
db_common_purge:
; Empty host buffer
call hst_flush
xor a
ld (hstact),a
ld a,(dbuff_pages)
or a
jr z,db_p_ret_1 ; cache is disabled anyway so leave now
ld a,(db_page_1)
out (map_port),a
; Point HL to number of system/dir tracks on disk (C)
if dir_tracks_on_a eq 0
ld h,0
ld l,c
sla l
else
ld b,0
ld hl,dir_tracks_on_a
add hl,bc
add hl,bc
endif
ret
db_p_ret_1:
pop bc ; remove call to this sub
pop bc ; Restore BC, HL
pop hl
ret ; return from call to purge
; Return Z if buffering permissible.
; ie. NZ if buffering disabled or wrong disk type.
; If system sector then NZ.
; If dir sector then Z and flag.
; If normal sector then Z.
db_sector_type:
ld a,(dbuff_pages)
or a
jr z,db_cant_buff_1
push hl
push de
; See if buffering disabled for this disk
ld a,(hstdsk)
ld hl,cache_map
ld e,a
ld d,0
add hl,de
ld a,(hl)
or a
jr nz,db_cant_buff
; now see if track is system or dir track
ld a,(db_page_1)
out (map_port),a
; make HL --> number of system, dir+system tracks for (hstdsk)
if dir_tracks_on_a eq 0
rlc e
ex de,hl
else
ld hl,dir_tracks_on_a
rlc e ; 2 bytes per disk
add hl,de
endif
ld a,(hl)
; if 0FFh then the number of ... tracks on this disk is not known
cp 0FFh
call z,db_get_num_dir ; so find it out
; if 0FEh then it is an unbufferable disk
cp 0FEh
jr z,db_cant_buff
ld a,(hsttrk)
cp (hl) ; is system ?
jr c,db_cant_buff ; don't buffer system
if dir_list
inc hl
cp (hl) ; is dir ?
ld a,true ; flag dir track (sector)
jr c,db_s_t_1
xor a ; flag normal track
db_s_t_1:
ld (db_is_dir),a ; 'is directory sector' flag
endif
pop de
pop hl
; can buffer so return Z
map bios_map
xor a
ret
; can't buffer so return NZ
db_cant_buff:
pop de
pop hl
map bios_map
db_cant_buff_1:
dec a ; NZ since A = 0 or bios_map (0Ch)
ret
; calculate number of dir + system tracks on hstdsk
; HL --> 2 bytes : (number of system tracks),(number of dir + system tracks)
; correct values are loaded into these
; must restore all regs. except A and DE
; Also returns A containing (hl)
db_get_num_dir:
push ix
ld a,(hstdsk)
call x_tab ; Get dpb addr in IX
push ix
pop de
ld a,low spare_dpb
cp e
ld a,0FEh
jr z,db_gn_cant
; Get number of system tracks
ld a,(ix+13)
db_gn_cant:
ld (hl),a
ld e,a
inc hl
; Get number of dir tracks
ld d,(ix+7) ; Get number of highest dir entry
xor a
rl d ; High bit of num dir
rla
inc a ; <=127 dir = 1 track, 128..255 dir = 2 tracks
add a,e ; add number of system tracks
ld (hl),a
dec hl
ld a,e
pop ix
ret
; convert entry number in A to entry address in HL
db_num_to_addr:
ld h,0
ld l,a
; multiply by bytes_per_entry to get address
if bytes_per_entry eq 4
sla l
rl h
sla l
rl h
else
error bytes_per_entry <> 4 so fix this code
endif
ret
; load H with track number, L with disk and sector numbers
db_get_sect_param:
ld hl,(hstdsk) ; H = hsttrk, L = hstdsk
rrc l ; rotate disk number to b6,b7
rrc l
ld a,(hstsec) ; now OR in sector
or l
ld l,a
ret
; all data storage here
db_page_1: ds 1
db_page_num_1: ds 1
; next sector in normal list
db_next_sector:
db first_used_sector+1
if dir_list
; next sector in dir list
db_next_dir_sector:
db first_used_sector
endif
db_entries: ds 1 ; maximum number of entries (pages*64-2)
if dir_list
db_is_dir: ds 1 ; next sector read/write is dir
endif