; **** FATCACHE.S -- 26 Jan 1993
; **** FATCACHE version 1.0
; Hard disk cache and FAT speedup routine

; Written by Phil Jensen and added to by Tim Rule

; Source code For Hisoft Devpac ST 2

; Copyright Phil Jensen and Tim Rule
;  If you use any of this code in your own program without express
;  permission then you are violating this copyright.  You can contact
;  the copyright holders through CompuServe - Phil Jensen (70004,1416)
;                                             Tim Rule (100135,2013)
; Or by mail to the addresses below:

;     Philip H. Jensen	  or	Tim Rule
;     172 Durnell Ave.		26 Redding Drive
;     Boston			Amersham
;     MA 02131			Buckinghamshire
;     USA			HP6 5PX
;     				United Kingdom

; This source file is public domain.  You may copy it and distribute it as
; you wish but it reamins the property of the copyright holders.
; This source code is NOT part of the fatcache package and should not be
; distributed with it.  The code has only been released for programmers
; to see how it works (so they will trust it).  This source code would only
; confuse a non-programmer.

start	bra	install_cache		; go to once-only code

cnbufs:	dc.l	128			; # of buffers (easy to patch here)
mask_drv dc.w	$ffd			; drive mask (lsb fat speedup flag)
ident	dc.b	'Fatcache 1.0',0	; so we know we're here
	EVEN
	
; TECHNICAL DOCUMENTATION:
; A handler is installed on the vector used for rwabs, the bios
; function used to read/write a disk sector.  This code must be
; installed AFTER the hard disk driver itself.  The handler basically
; ignores (passes through) all requests involving more than a single
; sector; because of the way TOS is coded, this suffices to cache
; all FAT and directory sectors.
;   By default, this is a write-through cache:  all write requests
; call the disk driver as well as updating the cache.  There is code
; to support dirty sector caching, and a way to turn this on and off.
; This is documented later in the source, and obviously requires
; EXTREME CARE in its use, lest the file system be damaged.
;   Single sector and multiple sector requests can interact, although
; it doesn't happen much in normal use of TOS.
;   o	Multi-sector read requests can just go through, unless dirty
;	  caching is enabled; if so, and if there is any overlap with
;	  cached dirty sectors, they are first written out so that the
;	  read will see the current data.
;   o	Multi-sector write requests just invalidate (remove from the
;	  cache) any overlapping sectors.
;
; It remains to discuss the FAT speedup logic.  We speed up two operations
; of Tos: (I) searching for a free cluster (for extending a file),
; and (II) counting free clusters (for the get-disk-free-space call).
; Using my names for certain subroutines of Tos, in the cases we are
; interested in:
;	we (i.e. bios rwabs) are called from
;	  *call_bios*, which is called from
;	    *get_block*, which is called from one of two places in
;	      *transfer_data*, which is called from
;		*do_read*, which is called from
;		  *read_fat*, which is called either from
;		    *advance_cluster* [case (I)], or
;		    *tos_Dfree* [case (II)].
; In both cases, the call to *read_fat* is in a loop, either looking for
; a free cluster or counting free clusters.  It is feasible to examine
; the stack to validate that we are in one of these two situations,
; and then to return AS THOUGH FROM A LATER ITERATION OF THE LOOP,
; adjusting the appropriate variables.  We do whatever work we can on the
; current FAT block and return as though the call was the last one for
; the block (of course in case (I) we return as if for the first free
; cluster in the block, if any).

Tos = 1
Super = $20
Ptermres = $31

Bios = 13
BiosConout = $30002

hdv_rw = $000476		; low mem address of rwabs vector

; Magic numbers for FAT speedup code:
bios_regsave = $4a2		; stack where bios trap saves registers
t_bios_regsave = $4a2		; ... Tos 1.2
;   return addresses and stack offsets
Ret1 = $3a			; stack offset for get_block return address
get_block_RA = $fc5fec		; which should be either this
get_block_RB = $fc6290		;   or this
t_get_block_RA = $fc62a2	; ... Tos 1.2
t_get_block_RB = $fc6546	; ... Tos 1.2
Ret2 = $8a			; xfer_data return address
transfer_data_R = $fc5c1e	;   should be this
t_transfer_data_R = $fc5ed6	; ... Tos 1.2
Ret3 = $b4			; do_read return address
do_read_R = $fc5dc8		;   should be this
t_do_read_R = $fc607e		; ... Tos 1.2
Ret4 = $d2			; read_fat return address
read_fat_RFF = $fc5ea4		;   should be this if finding free cluster
read_fat_RCF = $fc7808		;   or this is counting them
t_read_fat_RFF = $fc615a	; ... Tos 1.2
t_read_fat_RCF = $fc7abe	; ... Tos 1.2
RFN = $d6			; cluster # (first arg to read_fat) is here

; Bios_rwabs arguments are on the stack
; at the following offsets when we get control:
rwflag = 4	; low bit says read/write
ubuff = 6
count = 10
sector = 12	; \ sector and drive fetched together
drive = 14	; / to get secdrv (sector in high word, drive in low word)
; If sector is FFFF, there is actually a long sector at 16,
; but we don't support that.

; Fields in our buffer header structure:
fwd = 0		; forward link (links are offsets relative to bp)
bak = 2		; and backward (for LRU chaining)
drvsec = 4	; drive in high word, sector in low word
; (free buffers have this longword == -1: suffices to test sign)
bufadr = 8	; pointer to data itself
dirtyf = 11	;   dirty flag is low bit of bufadr!!!!
bhsz = 12	; length of this structure

; Fields in our global data area (first header is dummy, with extra data):
newest = 0	; forward link from header
oldest = 2	; backward link from header (least recently used buffer)
drvmsk = 4	; mask of drives we are caching (bit n of word for drive n)
wthru = 6	; byte, normally SET, requiring write-thru

bp	equr	a6	; base for our data
ds	equr	d7	; drive in high word, sector in low word
cnt	equr	d6	; sector count (high word clear)
bh	equr	d5	; offset to cache buffer header (as in ...(bp,bh))
cb	equr	a5	; callback routine for scache routine

; call here to do rwabs using previous handler (actual disk operation)
do_rwabs:
	jmp	$55555555
	; (install code knows modified longword is right before myvec!!)
	;
	; here's where we point the hdv_rw vector:::
myvec:	lea	freemem,bp		; base address for our data
	moveq	#0,cnt			; get things we like to have in regs
	move.w	count(sp),cnt
	move.l	sector(sp),ds		; sector high, drive low
	cmp.w	#16,ds			; drive must be 0..15
	bhs	do_ioctl		; if not, may be special request
	move.w	drvmsk(bp),d0
	btst	ds,d0			; is this a drive that interests us?
	beq.s	do_rwabs		; no, just chain along to real driver
	swap	ds			; now get drive high, sector low
	cmp.w	#$ffff,ds		; request for long sector number?
	beq.s	do_rwabs		; yes, forget it
	moveq	#~2,d0			; ignore "ignore media change" bit
	and.w	rwflag(sp),d0		; read/write flag
	beq.s	read			; 0 says we're reading
	subq.w	#1,d0
	bne.s	do_rwabs		; give up if any non-vanilla bits
	; else must be write request

	cmp.w	#1,cnt			; writing: if not 1 sector
	bne.s	inval			; may invalidate blocks (unlikely)
	bsr	acache			; find or select cache slot
	; (for this one, doesn't matter if we found it or not)
	move.l	ds,drvsec(bp,bh)	; (in case we didn't have it before)
	moveq	#~1,d0			; get cache buffer address,
	and.l	bufadr(bp,bh),d0	; masking off dirty bit
	move.l	d0,a1			; this is dest for copy
	move.l	ubuff(sp),a0		; and this is source
	bsr	copsec
	tst.b	wthru(bp)		; now, are we doing write-thru?
	bne.s	do_rwabs		; yes, let the real disk op proceed
	bset	#0,dirtyf(bp,bh)	; no, just mark buffer as dirty
	moveq	#0,d0			; give happy return
	rts

; writing more than one sector: don't bother to keep track of them in
; cache, just invalidate them if they're there (which is unlikely).
inval:	lea	invalw,cb
	bsr	scache
	bra.s	do_rwabs		; then let the disk op proceed

invalw:	moveq	#-1,d0
	move.l	d0,drvsec(bp,bh)
	bclr	#0,dirtyf(bp,bh)	; keep this clean too
	; (by rights, we should move this to tail of list,
	;  but it doesn't come up often enough to bother with)
	rts

read:	cmp.w	#1,cnt			; reading: only cache if 1 sector
	beq.s	read1
; read call for multiple sectors - usually just go on to real driver,
;   but for safety, must check that none of them are dirty in the cache.
	tst.b	wthru(bp)
	bne.s	godo			; if doing write-thru, can't be dirty
	lea	wdirty,cb		; callback rtn to write if dirty
	bsr	scache
godo:	bra	do_rwabs		; okay, got that settled

read1:	bsr	acache
	bne.s	readc			; found, read from cache
	; no, need to access the disk
	movem.l	rwflag(sp),d0-d2	; get all arguments
	movem.l	bh/ds/bp,-(sp)		; save our context
	movem.l	d0-d2,-(sp)		; duplicate args for real call
	bsr.s	godo			; do the read
	add.w	#12,sp			; remove arguments
	movem.l	(sp)+,bh/ds/bp		; restore things
	tst.l	d0			; no trouble, I hope...
	beq.s	readok
	rts				; if error, just pass it up

readok:	move.l	ds,drvsec(bp,bh)	; record cached sector
	move.l	ubuff(sp),a0		; copy from where we just read it
	move.l	bufadr(bp,bh),a1	; to cache buffer (can't be dirty)
	bra.s	finrd			; copy it, then check for FAT access

; here when found sector sitting in cache - may be dirty, though
readc:	moveq	#~1,d0			; get cache buffer address,
	and.l	bufadr(bp,bh),d0	; avoiding dirty bit
	move.l	d0,a0			; this is source
	move.l	ubuff(sp),a1		; this is dest
finrd:	bsr	copsec

	; now ready for the FAT speedup business...
	; If called from loop looking for a free cluster, optimize
	; Note: if FAT acceleration has been disble, by config utility or
	;       by the installation routine, then this next instruction
	;       is substituted for a jump to bypass the code.
chkfat:	cmp.l	#get_block_RA,Ret1(sp)	; call to get_block
	beq.s	chkf2
mk1	cmp.l	#get_block_RB,Ret1(sp)	; or this one
	bne.s	fatrts			; no, no special work
chkf2:	cmp.l	#transfer_data_R,Ret2(sp)	; call to transfer_data
	bne.s	fatrts
mk2	cmp.l	#do_read_R,Ret3(sp)	; call to do_read
	bne.s	fatrts
	; (By the way, this last test also excludes 12-bit FATs.)

	; We are working on a call to read_fat.
	; Some of these are in loops in which we can easily optimize away
	;   a great deal of CPU overhead by looking at the FAT block here.
	; To make our loop simpler, we only work on full FAT blocks,
	;   avoiding the last one.  Following code tests for that:
	move.w	RFN(sp),d1		; get cluster # (arg to read_fat)
	moveq	#0,d2			; (save low byte, i.e. entry within
	move.b	d1,d2			;  block, in d2)
	st	d1			; set low byte to FF (last in block)
	move.l	$40(sp),a0		; dmd for this logical drive
	cmp.w	$10(a0),d1		; numcl (should be numcl+2, but...)
	bge.s	fatrts			;  don't hack in last block
	;
	; d2 has entry within block (0..255)
	move.l	ubuff(sp),a0		; to buffer where we just copied blk
	add.w	d2,a0			; point to entry of interest
	add.w	d2,a0			; (two-byte entries)
	neg.w	d2			; make it 0..-255
	add.w	#255,d2			; 255..0: max calls we can optimize
	; (last entry in block is just handled normally)
	moveq	#0,d1			; this will be # of calls optimized
	;
	; now go to appropriate loop for the call we're hacking...
mk3	cmp.l	#read_fat_RFF,Ret4(sp)	; call from advance_cluster
	beq.s	advcg
mk4	cmp.l	#read_fat_RCF,Ret4(sp)	; call from tos_Dfree
	beq.s	dfreg
fatrts:	moveq	#0,d0			; give happy return from rwabs
	rts

; looking for free cluster: enter loop at advcg
advcl:	addq.w	#1,d1
advcg:	move.w	(a0)+,d0		; get FAT entry
	beq.s	advcf			; found a free one
	cmp.w	d2,d1			; just looked at last entry in block?
	bne.s	advcl
advcf:	move.l	bios_regsave,a0		; use d1 to bump loop counters
	add.w	d1,$06(a0)		; bump current cluster (restored to d4)
	add.w	d1,$c6(sp)		; bump cluster limit (restored to d7)
	bra.s	qikret			; go return d0 from read_fat

; counting free clusters: enter loop at dfreg
dfrel:	addq.w	#1,d1
	tst.w	(a0)+			; if entry is zero,
	bne.s	dfreg
	addq.w	#1,$a2(sp)		; bump free cluster count (rest. to d6)
dfreg:	cmp.w	d2,d1			; about to look at last entry in block?
	bne.s	dfrel			; no, optimize it
	move.w	(a0)+,d0		; no more to opt, but get last entry
	add.w	d1,$c6(sp)		; bump current cluster (restored to d7)
	; Join up here to return as though from the last read_fat call for
	; this block (or the first one that hit a free slot, for case (I)).
	; Register d0 has the value read from the FAT (in reality, the bytes
	; would be swapped, but these callers only care about zero/nonzero).
qikret:	addq.w	#1,d1			; we looked at one more entry
	add.w	d1,d1			; and must fake out fcb to match
	move.l	$90(sp),a0		; point to fcb for the FAT
	add.l	d1,$20(a0)		; advance file posn for FAT "file"
	add.w	d1,$28(a0)		; and position-within-cluster
	move.l	bios_regsave,a0
	move.l	(a0),d3			; d3/d4/a3 come from bios save area
	move.l	$4(a0),d4
	move.l	$14(a0),a3
	move.l	$12(sp),d5		; others from various stack frames
	move.l	$a0(sp),d6
	move.l	$c4(sp),d7
	move.l	$a8(sp),a4
	move.l	$c8(sp),a5
	move.l	$ce(sp),a6
	moveq	#$2e,d1
	add.l	d1,bios_regsave	; flush one bios call
	lea	$d2(sp),sp		; flush tos stack
	rts				; Tos won't know what hit it

; ***** subroutines - placed here for short bsr's

; primary routine for searching cache is here
; usage is
;	lea	routine-to-call,cb
;	bsr	scache
scache:	moveq	#0,bh			; (circular list head)
	bra.s	scach2
scach1:	move.l	drvsec(bp,bh),d0	; get drive/sector
	sub.l	ds,d0			; relative to what we care about
	cmp.l	cnt,d0			; in range?
	bhs.s	scach2			; nope
	jsr	(cb)			; do something about it
scach2:	move.w	fwd(bp,bh),bh
	bne.s	scach1			; loop until hit head again
	rts				; note Z flag set here

; acache is the most common way to use scache:
; find block in cache (bne if found it), or select buffer to use
; (if found, move it to head of list, so it won't be reused for a while)
acache:	lea	findb,cb
	bsr.s	scache			; try to find it (and sneak away)
	bsr.s	select			; not found, get one to use
	moveq	#0,d0			; return FALSE
	rts

; when called,
;	0(sp) points after "jsr (cb)" in scache
;	4(sp) points after "bsr.s scache" above
findb:	addq.w	#8,sp			; flush those two return addrs
	bsr.s	touch			; move buffer we found to head
	moveq	#1,d0			; return TRUE from acache
	rts

; select new buffer (oldest) to be used for a new block
; write it out if necessary
select:	move.w	oldest(bp),bh
	bsr.s	wdirty			; write out if necessary
	moveq	#-1,d0
	move.l	d0,drvsec(bp,bh)	; not in use now
	; flow on
; move the buffer bh to the head of the list
touch:	cmp.w	newest(bp),bh		; already at head of list?
	beq.s	touche			; yes, don't bother with this
	move.w	bak(bp,bh),d0		; predecessor
	move.w	fwd(bp,bh),d1		; successor
	move.w	d1,fwd(bp,d0)		; link around it
	move.w	d0,bak(bp,d1)
	move.w	newest(bp),d0		; former head of list
	move.w	bh,newest(bp)		; this is new one
	clr.w	bak(bp,bh)		; back link is to list head
	move.w	d0,fwd(bp,bh)		; forward link to previous newest
	move.w	bh,bak(bp,d0)		; which links back to our buffer
touche:	rts

; need to write out block if it is dirty
; called both when happens to be oldest (about to reuse the buffer)
; and when flushing the cache
wdirty:	bclr	#0,dirtyf(bp,bh)	; won't be dirty any more
	beq.s	wdx			; but if it wasn't, nothing to do
	movem.l	bh/cnt/ds/cb/bp,-(sp)	; save registers
	move.w	drvsec(bp,bh),-(sp)	; rwabs args: drive
	move.w	drvsec+2(bp,bh),-(sp)	; sector
	move.w	#1,-(sp)		; count
	move.l	bufadr(bp,bh),-(sp)	; buffer address
	move.w	#1,-(sp)		; rwflag = WRITE
	bsr	do_rwabs		; go do it
	add.w	#12,sp			; flush args
	movem.l	(sp)+,bh/cnt/ds/cb/bp
wdx:	rts

	; copy one sector from a0 to a1
copsec:	move.l	a0,d0			; watch out for odd addresses
	move.l	a1,d1
	or.l	d1,d0
	lsr.l	#1,d0
	bcs.s	copsua			; go do unlikely case
	moveq	#(512/32)-1,d0
copslp:	move.l	(a0)+,(a1)+
	move.l	(a0)+,(a1)+
	move.l	(a0)+,(a1)+
	move.l	(a0)+,(a1)+
	move.l	(a0)+,(a1)+
	move.l	(a0)+,(a1)+
	move.l	(a0)+,(a1)+
	move.l	(a0)+,(a1)+
	dbra	d0,copslp
okret:	moveq	#0,d0			; happy return
	rts

copsua:	move.w	#512-1,d0		; buffer on odd address - who cares?
copsul:	move.b	(a0)+,(a1)+
	dbra	d0,copsul		; watch it crawl!
	moveq	#0,d0
	rts

; FATCACHE includes a couple of special facilites which are set by passing
; a drive number to Rwabs greater than 15
; Note:  Use these facilites with care or you mak loose data.  The dirty
;     cacheing was deliberately left out of the FATCACHE documentation
;     so people would not use it without knowing what they are doing.
;     If you switch it on then it must be switched off, causing a flush
;     before re-booting your computer.  The main use is for programs such
;     as a fast delete utility to switch on for short periods of time. 
do_ioctl:
	sub.w	#16,ds
	beq.s	flush			; 16 means flush dirty, set wthru
	subq.w	#1,ds
	beq.s	zwthru			; 17 means turn off write-thru
	moveq	#-1,d0			; didn't recognize command
	rts

zwthru:	sf	wthru(bp)		; this is easy
	bra.s	ioctok

flush:	st	wthru(bp)		; re-enable write-thru now
	; flush any dirty blocks present in cache
	clr.w	bh			; circular list head
	bra.s	flu2
flu1:	bsr	wdirty
flu2:	move.w	fwd(bp,bh),bh		; next one
	bne.s	flu1			; until hit header again
ioctok:	moveq	#0,d0
	rts

; eventually come here with a6 = psp, a5 = top of our kept memory
termr:	sub.l	a6,a5			; size to keep
	clr.w	-(sp)
	move.l	a5,-(sp)
	move.w	#Ptermres,-(sp)
	trap	#Tos

; when installed, our data area begins here:
; (all code from here on is once-only)
freemem:

; come here to install cache
install_cache:
	move.l	4(sp),a6		; hold onto our psp for Ptermres
	; After loop, d7.l is set to maximum sector size,
	; or still zero if no hard disk exists.
	lea	message,a1		; Displays title message 
	bsr	outstr

	moveq	#0,d7
	moveq	#2,d3			; go getbpb for CDEFGHIJKL
	moveq	#10-1,d4		; (that makes 10)
	move.w	mask_drv,d5		; drives to check
	btst	d4,d5			; this drive?
	beq	bpbnxt			; No, go to next
bpblp:	move.w	d3,-(sp)		; drive to test
	move.w	#7,-(sp)		; getbpb function
	trap	#13			; of Bios
	addq.w	#4,sp
	tst.l	d0			; got one?
	beq.s	bpbnxt			; nope
	move.l	d0,a0
	move.w	(a0),d0			; get size of a sector
	cmp.w	d7,d0			; new maximum?
	ble.s	bpbnxt
	move.w	d0,d7
bpbnxt:	addq.w	#1,d3
	dbra	d4,bpblp
	tst.w	d7			; got any?
	bne.s	chksect			; yes, can we do the job?
	lea	nohd,a1			; No drives here!
	bsr	outstr
Pterm0	clr.w	-(sp)
	trap	#Tos

chksect	cmp.w	#512,d7
	beq.s	chkunique
	lea	not512,a1
	bsr	outstr
	bra.s	Pterm0
	
chkunique	; Check if we've already installed once
	clr.l	-(sp)			; supervisor to read vector
	move.w	#$20,-(sp)
	trap	#1
	move.l	d0,2(sp)		; for going back to user mode
	move.l	hdv_rw,a4		; hard disk driver vector
	trap	#1			; user mode
	addq.w	#6,sp
	sub.l	#myvec-ident,a4		; check that we are not already
	lea	ident,a5		; installed by looking for our id
checkid	cmp.b	(a4)+,(a5)+		
	bne	chkrom			; Not our id
	cmp.b	#0,(a5)			; End of string?
	bne	checkid
	lea	alhere,a1		; Must already be installed
	bsr	outstr			; ..so display message
	bra.s	Pterm0

message	dc.b	13,10,27,'vFatcache 1.0 By Phil Jensen and Tim Rule'
	dc.b	' Public Domain (C)1993',13,10,0
alhere	dc.b	'  Previous installation detected',13,10,0
nohd:	dc.b	'  No Hard Disk (C:..L:) found',13,10,0
instfat	dc.b	'  Installed FAT acceleration',13,10,0 
norom	dc.b	'  ROMs not supported by FAT acceleration',13,10,0
not512	dc.b	'  Sector size not supported (Must be 512)',13,10,0
	EVEN
	
dumcod	;This jump is moved to beggining of FAT acceleration code
	;to bypass it if necessary
	jmp	fatrts

; We support two different versions of the ROMs.  If we don't support
; it then disable FAT acceleration and if we do then put right values
; into code.
chkrom	clr.l	-(sp)			; supervisor to read vector
	move.w	#$20,-(sp)
	trap	#1
	move.l	d0,2(sp)		; for going back to user mode
	move.w	mask_drv,d0		; first test that fat stuff is
	and.w	#1,d0			; ...not disabled
	beq.s	nohack
	lea	$fc0018,a0
	cmp.l	#$11201985,(a0)
	beq.s	fatinst			; Go to 11/20/85 support (Tos 1.0)
	cmp.l	#$04221987,(a0)
	beq.s	subst12			; Go to 22/4/87 support (Tos 1.2)
	lea	norom,a1		; Display the message
	bsr	outstr	
nohack	move.l	dumcod,chkfat		; Copy in jump to bypass fat stuff 
	move.w	dumcod+4,chkfat+4
	bra	user			; Go install
	; This substitutes in 22nd April 1987 ROM values (Tos 1.2)
subst12	move.l	#t_get_block_RA,chkfat+2 
	move.l	#t_get_block_RB,mk1+2
	move.l	#t_transfer_data_R,chkf2+2
	move.l	#t_do_read_R,mk2+2
	move.l	#t_read_fat_RFF,mk3+2
	move.l	#t_read_fat_RCF,mk4+2
fatinst	lea	instfat,a1		; Tell that FAT speedup is enabled
	bsr	outstr
user	trap	#1			; user mode
	addq.w	#6,sp

; Main install routine is run from the stack so that this space can be used
;  by the cache.
instal:	lea	freemem,a2		; remember where data starts
	lea	myvec,a3		; remember where we want vector
	lea	termr,a4		; go here at end of init code
	move.l	cnbufs,d6		; get desired number of buffers
	; and remember d7 has the buffer size (max sector size)
	; also, a6 has our psp and must be preserved
	lea	endcop,a0		; copy initialize code to stack
	lea	coplim,a1		; until this word has been copied
copcod:	move.w	-(a0),-(sp)
	cmp.l	a1,a0
	bhi.s	copcod
	jmp	(sp)			; finish setup
	
; From here onwards is run from the stack
coplim:		; jmp goes to (copy of) next instruction

	; initialize the buffer ring
	move.w	d6,d1			; # of headers needed
	mulu	#bhsz,d1		; space occupied (except dummy)
	lea	bhsz(a2,d1),a5		; this is end, and first buffer addr
	moveq	#0,d0			; going to loop over buffer headers
	; set up 'round the corner links
	move.w	d1,bak(a2,d0)
	move.w	d0,fwd(a2,d1)
	; set up all other links, and buffer control info
	; on kth iteration, d0 is handle k-1, d1 is handle k
	move.w	d0,d1
	move.w	d6,d3			; count for loop (need d6 later)
	moveq	#-1,d2			; constant for loop
	bra.s	ringg
ringl:	move.w	d0,bak(a2,d1)
	move.w	d1,fwd(a2,d0)
	move.l	d2,drvsec(a2,d1)	; -1 here means not in use
	move.l	a5,bufadr(a2,d1)	; addr of corresponding buffer
	add.w	d7,a5			; on to next one
	move.w	d1,d0
ringg:	add.w	#bhsz,d1
	dbra	d3,ringl		; loop til done

	; a few globals to set up
	st	wthru(a2)		; write-thru required initially
	move.w	mask_drv,d0		; read drive mask from head of code 
	and.w	#%111111111100,d0	; only support C to L
	move.w	d0,drvmsk(a2)		; save it 

	clr.l	-(sp)			; must be supervisor to mung vector
	move.w	#$20,-(sp)
	trap	#1
	move.l	d0,2(sp)		; for going back to user mode
	move.l	hdv_rw,d0		; old vector
	move.l	a3,hdv_rw		; replaced with ours
	move.l	d0,-(a3)		; store old one for chaining
	trap	#1			; back to user mode
	addq.w	#6,sp
	lea	msg1-coplim(sp),a1	; see messages just below
	bsr.s	outstr
	move.l	d6,d1
	bsr.s	outdec
	lea	msg2-coplim(sp),a1
	bsr.s	outstr
	move.l	a5,sp			; new stack at top of what we keep
	jmp	(a4)			; go do Ptermres

msg1:	dc.b	"  Disk cache installed: ",0
msg2:	dc.b	" buffers used",13,10,0
	EVEN

outstr:	move.l	a3,-(sp)
	move.l	a1,a3
	bra.s	ostrg
ostrl:	bsr.s	typo
ostrg:	move.b	(a3)+,d1
	bne.s	ostrl
	move.l	(sp)+,a3
	rts

; type decimal number (no larger than 655359) - d1 (not preserved)
outdec:	divu	#10,d1
	swap	d1
	add.w	#'0',d1			; form digit from remainder
	move.w	d1,-(sp)		; save it (print after quotient)
	clr.w	d1			; now (as long int)
	swap	d1			; get back quotient
	beq.s	outdig			; if zero, just print remainder
	bsr.s	outdec			; no, print quotient first
outdig:	move.w	(sp)+,d1		; get digit we saved
	; type it and return to outdig or to caller of outdec
	;
typo:	move.w	d1,-(sp)
	move.l	#BiosConout,-(sp)
	trap	#Bios
	addq.w	#6,sp
	rts
endcop:



