title 'BUFFERS - buffer handling routines' ; File : $BUFFERS.A86$ ; ; Description : ; ; Original Author : DIGITAL RESEARCH ; ; Last Edited By : $CALDERA$ ; ;-----------------------------------------------------------------------; ; Copyright Work of Caldera, Inc. All Rights Reserved. ; ; THIS WORK IS A COPYRIGHT WORK AND CONTAINS CONFIDENTIAL, ; PROPRIETARY AND TRADE SECRET INFORMATION OF CALDERA, INC. ; ACCESS TO THIS WORK IS RESTRICTED TO (I) CALDERA, INC. EMPLOYEES ; WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE OF ; THEIR ASSIGNMENTS AND (II) ENTITIES OTHER THAN CALDERA, INC. WHO ; HAVE ACCEPTED THE CALDERA OPENDOS SOURCE LICENSE OR OTHER CALDERA LICENSE ; AGREEMENTS. EXCEPT UNDER THE EXPRESS TERMS OF THE CALDERA LICENSE ; AGREEMENT NO PART OF THIS WORK MAY BE USED, PRACTICED, PERFORMED, ; COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED, ; CONDENSED, EXPANDED, COLLECTED, COMPILED, LINKED, RECAST, ; TRANSFORMED OR ADAPTED WITHOUT THE PRIOR WRITTEN CONSENT OF ; CALDERA, INC. ANY USE OR EXPLOITATION OF THIS WORK WITHOUT ; AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO CRIMINAL AND ; CIVIL LIABILITY. ;-----------------------------------------------------------------------; ; ; *** Current Edit History *** ; *** End of Current Edit History *** ; ; $Log$ ; ; BUFFERS.A86 1.13 94/11/30 16:26:08 ; added support for using multiple FAT copies on reads if one fails ; BUFFERS.A86 1.12 93/08/06 16:19:11 ; make geblk public ; BUFFERS.A86 1.8 93/07/07 21:06:25 ; Smirnoff'd ; BUFFERS.A86 1.6 93/03/16 22:30:29 ; UNDELETE support changes ; BUFFERS.A86 1.5 93/03/05 18:00:26 ; Fix bug clearing cluster of new sub directory ; ENDLOG ; Date Who Modification ; --------- --- --------------------------------------- ; 9 Sep 91 Initial version created for VLADIVAR ; 3 mar 93 correct zeroblk bug NOLIST eject ! include i:fdos.equ eject ! include bdos.equ eject ! include i:doshndl.def eject LIST eject PCMODE_DATA dseg extrn current_ddsc:dword if DELWATCH extrn fdos_stub:dword endif BDOS_DATA dseg word fatrec rw 1 ; current FAT record fatbytl rb 1 ; low byte of split FAT entry fatbyth rb 1 ; high byte of split FAT entry split_fat rb 1 ; 0/FFh to indicate split entry eject extrn adrive:byte EXTRN chdblk:WORD EXTRN clsize:WORD EXTRN cur_dma:WORD EXTRN cur_dma_seg:WORD extrn dosfat:WORD EXTRN fatadd:WORD extrn lastcl:word EXTRN mult_sec:WORD EXTRN nfatrecs:WORD EXTRN nfats:WORD extrn pblock:dword extrn physical_drv:byte extrn psecsiz:word extrn rwmode:byte ; data/directory/FAT, read/write extrn secperclu:word extrn bcb_root:dword ; PCMODE disk buffer root extrn deblock_seg:word BDOS_CODE cseg extrn clus2sec:near extrn discard_dirbuf:near extrn fdos_error:near extrn flush_dirbuf:near extrn hshdscrd:near extrn read_block:near extrn select_adrive:near ; select drive AL extrn write_block:near public alloc_cluster ; allocate data block public alloc_chain ; allocate a chain public buffers_check ; check if buffers exist for this drive PUBLIC delfat ; release data blocks PUBLIC discard_all ; discard all buffers on ADRIVE public discard_dir ; discard directory buffers on ADRIVE public discard_dirty ; discard directory buffers on ADRIVE PUBLIC fixfat ; set value of FAT entry public flush_drive ; flush buffers to disk public locate_buffer ; locate a buffer PUBLIC update_dat ; flush write pending buffers public update_ddsc_free ; count free blocks on drive PUBLIC update_dir ; update directory entry PUBLIC update_fat ; write out modified FAT records public zeroblk ; zero cluster (MKDIR) if DELWATCH public allocate_cluster ; allocate free cluster on adrive public change_fat_entry ; write a new value into the FAT endif update_ddsc_free: ;---------------- ; make sure DDSC_FREE is up to date ; a by-product of this is to checksum the FAT, so we can spot changes ; of removable media push es les bx,ss:current_ddsc mov cx,es:DDSC_FREE[bx] ; get current free space jcxz update_ddsc_free30 ; if none recount to make sure inc cx ; is count uninitialised ? (=FFFF) jz update_ddsc_free30 ; if so better count the free space update_ddsc_free10: pop es ret update_ddsc_free30: ; rebuild our free space count xor ax,ax ; assume no free space yet lea di,DDSC_BLOCK[bx] ; ES:DI -> DDSC_BLOCK stosw ; DDSC_BLOCK = 0 stosw ; DDSC_FREE = 0 inc ax ; skip reserved block #'s 0 and 1 update_ddsc_free40: inc ax ; move to next data block # cmp ax,lastcl ; are we beyond end of disk ja update_ddsc_free10 ; stop if all free blocks counted push ax ; save current index call getblk ; get contents of FAT entry, update ZF pop ax ; restore current FAT index jnz update_ddsc_free40 ; try next block if not free inc es:DDSC_FREE[bx] ; one more free block jmps update_ddsc_free40 ; try next block discard_dirty: ;------------- ; This gets called after a write-protect error is returned mov ah,BF_DIRTY ; discard dirty FAT, dir & data jmps discard_buffers discard_all: ;----------- mov ah,BF_ISFAT+BF_ISDIR+BF_ISDAT jmps discard_buffers ; discard all the buffers discard_dir: ;----------- mov ah,BF_ISDIR ; dir only, leave data and FAT ; jmps discard_buffers discard_buffers: ;--------------- ; entry: adrive = drive to discard ; AH = flags for type to discard i.e. BF_ISFAT, etc. mov al,adrive ; get the work drive call discard_dirbuf ; discard 32-byte directory buffer call hshdscrd ; discard hashing info for drive les si,bcb_root ; get first buffer discard_buffers10: cmp al,es:BCB_DRV[si] ; does the drive match? jne discard_buffers20 ; try next one if not test ah,es:BCB_FLAGS[si] ; does the type match? jz discard_buffers20 ; try next one if not mov es:BCB_DRV[si],0FFh ; else discard the buffer mov es:BCB_FLAGS[si],0 discard_buffers20: if DOS5 mov si,es:BCB_NEXT[si] ; get next buffer address cmp si,word ptr bcb_root else les si,es:BCB_NEXT[si] ; get next buffer address cmp si,0ffffh endif jne discard_buffers10 ; and repeat until all done discard_buffers30: push ds ! pop es ; restore ES and return ret ;------------- buffers_check: ;------------- ; entry: AL = drive to check (preserved) ; AH = flags ; exit: ZF = 1 if all buffers clean on this drive push ds ; we use DS here cause it's quicker... lds si,ss:bcb_root ; start with most recently used buffers_check10: cmp al,BCB_DRV[si] ; check if for different drive jne buffers_check20 ; skip if not our problem test ah,BCB_FLAGS[si] ; test if its one we are looking for jnz buffers_check30 ; return with non-zero condition buffers_check20: if DOS5 mov si,BCB_NEXT[si] ; get next buffer address cmp si,ss:word ptr bcb_root else lds si,BCB_NEXT[si] ; get next buffer address cmp si,0ffffh endif jne buffers_check10 ; loop back if more to do xor dx,dx ; set ZF = 1 buffers_check30: pop ds ; restore DS after BCBs done ret eject ; entry: AX = first block to release ; exit: AX and following released delfat: ; release chain of clusters ;------ cmp ax,2 ; is block number too small? jb delfat10 ; yes, then stop it cmp ax,lastcl ; is block number too large? ja delfat10 ; yes, then stop it push ax ; else save the number call getblk ; get the next link xchg ax,cx ; CX = link pop ax ; AX = this block sub bx,bx ; set it to 0000 push cx ; save the link for next pass call fixfat ; release the block pop ax ; AX = next block or end jmps delfat ; try again until all released delfat10: ; all blocks in chain freed ret ; On Entry: ; AX = block to read ; On Exit: ; AX = next FAT block index ; Public getnblk getnblk: ;UWORD getnblk(blk); ;------- ; push ax call getblk ; get current setting pop bx jz getnblk10 ; return if something there ret getnblk10: mov ax,dosfat ; if unallocated then allocate it push ax xchg ax,bx ; AX = blk, BX = i call fixfat pop ax mov dx,ax ; DX = end of chain xor cx,cx ; no blocks follow this one ret ; On Entry: ; AX = block to read ; On Exit: ; AX = contents ; ZF = 1 if AX == 0000h (disk full) Public getblk ;------ getblk: ;------ push es ! push bx call fatptr ; get address of block AX in buffer mov ax,es:[bx] ; get the word from FAT jnz getblk10 ; skip if on odd address (must be 12 bit) cmp dosfat,FAT12 ; else check if 16 or 12 bit je getblk20 ; skip if even 12 bit pop bx ! pop es test ax,ax ; update ZF ret getblk10: shr ax,1 ; shift top 12 bits down shr ax,1 shr ax,1 shr ax,1 getblk20: and ax,0FFFh ; leave bottom 12 bits only pop bx ! pop es ret alloc_cluster: ;------------- ; On Entry: ; AX = previous cluster (hint for desired start) ; On Exit: ; AX = start of chain ; CY set on failure ; mov cx,1 ; jmp alloc_chain alloc_chain: ;----------- ; On Entry: ; AX = previous cluster (hint for desired start) ; CX = # clusters wanted ; On Exit: ; AX = start of chain, 0 on failure ; CY set on failure ; ; We want to allocate a chain of CX clusters, AX was previous cluster ; We return with CY clear and AX = 1st cluster in chain on success, ; CY set on failure ; ; When allocating a new chain we first ask SSTOR how much physical space is ; present on the disk. Until SSTOR reports at least 2 clusters free we ; repeatedly call DELWATCH to purge files and recover space. If DELWATCH is ; unable to free space we return "disk full". ; ; When allocating a block we normally are normally given a target block to ; start searching from. We allow DELWATCH to alter this value when it frees ; space to optimise the search. ; push ax ! push cx ; save entry parameters call update_ddsc_free ; make sure DDSC_FREE is correct if DELWATCH alloc_chain10: pop dx ! push dx ; DX = clusters wanted les bx,ss:current_ddsc mov cx,es:DDSC_FREE[bx] ; CX = clusters available mov al,adrive ; AL = current drive cmp cx,dx ; do we have enough room in the FAT ? jb alloc_chain20 ; if not ask DELWATCH to purge mov ah,SSTOR_SPACE ; does Superstore have room for data? callf ss:fdos_stub ; call stub routine test cx,cx ; are we out of space ? jnz alloc_chain40 ; no, go ahead and allocate the chain mov es:DDSC_FREE[bx],cx ; SSTOR says there's none, lets agree call update_fat ; flush FAT to bring SSTOR up to date jmps alloc_chain10 ; go round again and ask DELWATCH to ; free up some more space ; we loop until either SSTOR says OK ; or DELWATCH frees all it can alloc_chain20: mov ah,DELW_FREECLU ; ask DELWATCH to purge a file callf ss:fdos_stub ; call stub routine cmp cx,es:DDSC_FREE[bx] ; can DELWATCH free up any space ? jne alloc_chain10 ; yes, go and try again alloc_chain30: pop cx ! pop ax ; failure, restore stack jmps alloc_chain80 ; and exit in failure alloc_chain40: endif pop cx ! pop ax ; restore entry parameters push cx ; save # required call allocate_cluster ; try to allocate 1st cluster pop cx ; recover # required test ax,ax ; could we ? jz alloc_chain80 dec cx ; one less to allocate push ax ; save head of chain jcxz alloc_chain60 alloc_chain50: push cx push ax ; save current end of chain call allocate_cluster ; allocate another cluster pop bx ; BX = end of chain test ax,ax ; could we allocate anything ? jz alloc_chain70 ; no, bail out and free partial chain xchg ax,bx ; AX = previous cluster, link cluster push bx ; BX to end of the chain call fixfat pop ax ; AX = new end of chain pop cx loop alloc_chain50 alloc_chain60: pop ax ; return the start of the chain as it's clc ; long enough now... ret alloc_chain70: ; We haven't enough free clusters - lets free what we allocated so far pop cx ; discard count pop ax ; AX = start of chain call delfat ; release the chain alloc_chain80: xor ax,ax stc ; we couldn't manage it ret allocate_cluster: ;---------------- ; On Entry: ; AX = cluster to start from (AX = none known) ; On Exit: ; AX = cluster allocated ; test ax,ax ; previous block known? jnz alloc_cl10 ; skip if it is push ds lds bx,ss:current_ddsc mov ax,ds:DDSC_BLOCK[bx] ; else continue from last allocated block pop ds alloc_cl10: mov bx,lastcl ; highest block number on current disk cmp ax,bx ; is it within disk size? jb alloc_cl20 ; skip if it is sub ax,ax ; start at the beginning alloc_cl20: mov si,ax ; remember start of search test ax,ax ; is this the 1st block? jnz alloc_cl30 ; no inc ax ; start at beginning alloc_cl30: ; main loop: inc ax ; skip to block after current push ax ! push si ; quick save call getblk ; get the content of this block pop si ! pop ax jz alloc_cl50 ; return if free cmp ax,bx ; are we at the end yet? jb alloc_cl30 ; no, try next block xor ax,ax ; wrap to start of disk mov bx,si ; remember starting position last time test bx,bx ; have we been all the way round ? jnz alloc_cl20 ; no, lets search from start push ds lds bx,ss:current_ddsc mov ds:DDSC_FREE[bx],ax ; we definitely have none left pop ds ret ; return (0); alloc_cl50: push ds ; block # AX is available lds bx,ss:current_ddsc mov ds:DDSC_BLOCK[bx],ax ; remember for next time pop ds push ax mov bx,dosfat ; mark this block as end of file call fixfat ; for convenience pop ax test ax,ax ; update ZF from AX ret ; return block number if DELWATCH ; Update a FAT entry with a new value change_fat_entry: ;---------------- ; On Entry: ; AX = block number to change ; DX = new value ; On Exit: ; None ; mov bx,dx ; jmps fixfat endif ; entry: AX = block number to change ; BX = new value ; exit: DS,ES = sysdat ;------ fixfat: ;------ push bx ; save new value push ax call update_ddsc_free ; make sure DDSC_FREE is correct pop ax cmp dosfat,FAT16 ; check if 16-bit FAT jne fixfat30 ; skip if 12 bit FAT call fatptr ; ES:BX -> FAT word to modify pop ax ; restore new value xor dx,dx ; get a zero (no change of space) test ax,ax ; are we setting to 0 or non-zero? xchg ax,es:[bx] ; set the word in the buffer jnz fixfat10 ; skip if releasing block test ax,ax ; check if word was 0 before jz fixfat20 ; skip if setting 0 to 0 inc dx ; DX = 0001h, one free cluster more jmps fixfat15 fixfat10: ; allocating or fixing block test ax,ax ; check if word was 0 before jnz fixfat20 ; skip if setting non-0 to non-0 dec dx ; one free cluster less now fixfat15: ; DX = change in free space (-1,1) les si,current_ddsc add es:DDSC_FREE[si],dx ; update free space count fixfat20: les si,bcb_root ; ES:SI -> buffer control block or es:BCB_FLAGS[si],BF_DIRTY ; mark the buffer as dirty push ds ! pop es ; ES back to local DS ret ; We're dealing with a 12-bit FAT... fixfat30: ; changing 12-bit FAT entry call fatptr ; get address of block AX in ES:BX pop cx ; get new value mov dx,es:[bx] ; get old value jz fixfat40 ; skip if even word mov ax,0FFF0h ; set mask for new value add cx,cx ; else shift new value into top bits add cx,cx add cx,cx add cx,cx jmps fixfat50 ; set the new word fixfat40: mov ax,00FFFh ; set mask for new value and cx,ax fixfat50: ; AX = mask, CX = new, DX = old mov si,0 ; assume space doesn't change jnz fixfat60 ; skip if new value is zero test dx,ax ; test if old value was zero as well jz fixfat70 ; yes, no change in free space inc si ; else one more block available jmps fixfat70 fixfat60: ; new value is non-zero test dx,ax ; is old value non-zero as well? jnz fixfat70 ; yes, no change in free space dec si ; else one block less free now fixfat70: not ax ; flip the mask bits around and dx,ax ; zero out old value or dx,cx ; combine old & new value mov es:[bx],dx ; update the FAT xchg ax,si ; AX = free space change (-1, 0 , 1) les si,current_ddsc add es:DDSC_FREE[si],ax ; update free space count les si,bcb_root ; get buffer control block or es:BCB_FLAGS[si],BF_DIRTY ; mark the buffer as dirty cmp split_fat,0 ; is 12-bit entry split across sectors je fixfat80 ; need some magic if so ; handle a split FAT update mov dx,fatrec ; lower sector number inc dx ; get the upper sector call locate_fat ; find the buffer or es:BCB_FLAGS[si],BF_DIRTY ; mark buffer as write pending mov al,fatbyth ; get the high byte mov es:BCB_DATA[si],al ; store the high byte at the beginning mov dx,fatrec ; get the previous sector call locate_fat ; read into memory or es:BCB_FLAGS[si],BF_DIRTY ; mark buffer as write pending mov bx,psecsiz dec bx ; BX = sector size - 1 mov al,fatbytl ; get the low byte mov es:BCB_DATA[si+bx],al fixfat80: push ds ! pop es ; ES back to local DS ret ; On Entry: ; AX = cluster number ; On Exit: ; AX preserved ; ES:BX -> address of word ; BCBSEG = segment of FAT FCB ; ZF = 1 if word on even address ; SPLIT_FAT = 0FFh if xing sector boundary ; ; CX = entries left in sector (if FAT16 - performance optimisation) ; Public fatptr fatptr: ;------ push ax ; save block number mov bx,ax sub dx,dx ; AX/DX = cluster # cmp dosfat,FAT16 ; is it 16 bit FAT? je fatptr10 shr ax,1 ; shift for 1 1/2 byte, else 2 byte fatptr10: add ax,bx ; AX = offset into FAT adc dx,0 ; AX/DX = 32 bit offset mov cx,psecsiz ; CX = sector size div cx ; AX = sector offset dec cx ; CX = sector size - 1 push dx ; DX = offset within FAT sector push cx add ax,fatadd ; make it absolute sector address mov fatrec,ax ; save FAT sector for FIXFAT xchg ax,dx ; DX = FAT sector call locate_fat ; locate the sector pop cx ; CX = sector size - 1 pop bx ; restore offset within FAT sector pop ax ; restore cluster # sub cx,bx ; CX = bytes left in sector - 1 lea bx,BCB_DATA[si+bx] ; ES:BX -> buffer data cmp dosfat,FAT16 ; is it 16 bit media jne fatptr20 ; skip if 12 bit media shr cx,1 ; CX = extra entries left in sector cmp ax,ax ; always set ZF = 1 ret ; return ES:BX -> word in FAT fatptr20: ; it's a 12 bit FAT, is it a split FAT? mov split_fat,0 ; assume no boundary crossing jcxz fatptr30 ; end of sector, it's a split FAT test al,1 ; ZF = 1 if even cluster ret ; return ES:BX -> word in FAT buffer fatptr30: ; block split across two sectors push ax mov split_fat,0FFh ; yes, the difficult case mov al,es:[bx] ; get the low byte from 1st sector mov fatbytl,al ; save it for later mov dx,fatrec ; get the FAT record is inc dx ; get 2nd sector call locate_fat ; read the 2nd sector sub bx,bx lea bx,BCB_DATA[si+bx] ; ES:BX -> buffer data mov al,es:[bx] ; get 1st byte from next sector mov fatbyth,al ; save the high byte push ds ; ES = local DS pop es mov bx,offset fatbytl ; ES:BX -> pop ax test al,1 ; set non-zero condition, odd word ret if DOS5 ; entry: DX = sector number to read ; exit: ES:SI = BCB locate_fat: ;---------- mov ah,0 ; set sector address overflow = 0 mov cx,0ff00h+BF_ISFAT ; request a FAT buffer w/ preread locate_buffer: ;------------- ; On Entry: ; AH:DX = sector to locate ; adrive = driver ; CH = 0FFh if preread required ; CL = buffer type ; On Exit: ; ES:SI -> BCB_ ; mov al,adrive ; get our drive number les si,bcb_root ; get it from the right buffer list locate10: cmp dx,es:BCB_REC[si] ; does our sector address match? jne locate20 ; skip if it doesn't cmp ah,es:BCB_REC2[si] ; does record address overflow match? jne locate20 ; skip if not cmp al,es:BCB_DRV[si] ; does the drive match? je locate30 ; found if it all matches locate20: ; MRU buffer doesn't match mov si,es:BCB_NEXT[si] ; try the next cmp si,word ptr bcb_root ; while there are more buffers jne locate10 push ax ! push cx ! push dx ; save all registers mov si,es:BCB_PREV[si] ; recycle least recently used buffer call flush_buffer ; write buffer to disk pop dx ! pop cx ! pop ax ; restore all registers mov es:BCB_DRV[si],al ; fill in the BCB: drive mov es:BCB_REC[si],dx ; record low,middle mov es:BCB_REC2[si],ah ; record high mov es:BCB_FLAGS[si],cl ; mark as clean, ISFAT,ISDIR or ISDAT test ch,ch ; is preread required? jz locate30 ; skip if it isn't call fill_buffer ; read it from disk locate30: cmp si,word ptr bcb_root ; are we already at the head ? jne locate40 ; if not move ourself there ret locate40: mov bx,es:BCB_NEXT[si] ; BX = next buffer mov di,es:BCB_PREV[si] ; DI = previous buffer mov es:BCB_NEXT[di],bx ; unlink buffer from the mov es:BCB_PREV[bx],di ; chain mov bx,si xchg bx,word ptr bcb_root ; become the new head, BX = old head mov es:BCB_NEXT[si],bx ; old chain follow us mov di,si xchg di,es:BCB_PREV[bx] ; back link to our buffer, DI = LRU buffer mov es:BCB_PREV[si],di ; link ourselves to LRU buffer mov es:BCB_NEXT[di],si ; forward link to our buffer ret ; Flush all dirty FAT buffers for drive AL ; entry: AL = drive to flush (0-15) ; exit: CY = 0 if no error ; ax,bx,cx,dx,es preserved flush_fat: ;--------- ; entry: AL = drive for FAT flush mov ah,BF_ISFAT ; flush all dirty FAT buffers jmps flush_drive ; shared code for all flushes ;---------- update_dir: ;---------- call flush_dirbuf ; flush local dirbuf to buffers ;--------- flush_dir: ;--------- mov ah,BF_ISDIR ; write out dirty directories jmps flush_adrive ; update the disk ;---------- update_dat: ;---------- mov ah,BF_ISDAT ; write out dirty data jmps flush_adrive ; update the disk ;---------- update_fat: ;write out modified FAT buffers ;---------- mov ah,BF_ISFAT ; flush all dirty FAT buffers ; jmp flush_adrive ; update the disk if dirty flush_adrive: ;------------ mov al,adrive ; AL = currently selected drive ; jmp flush_drive ; Write out all dirty data buffers for a given drive ; entry: AL = drive to be flushed ; AH = mask of buffer types to be flushed ; exit: AX,DX preserved ; Note: sector buffers will be written in the ; sequence in which they appear on disk (low to high) flush_drive: ;----------- push es push si flush_drive10: les si,bcb_root ; start with the first buffer mov bx,0FFFFh ; assume no buffer found flush_drive20: test es:BCB_FLAGS[si],BF_DIRTY ; has buffer been written to? jz flush_drive40 ; no, do the next one test es:BCB_FLAGS[si],ah ; is it one of these buffers? jz flush_drive40 ; no, do the next one cmp al,es:BCB_DRV[si] ; does the drive match? jne flush_drive40 ; skip if wrong drive ; we've found a buffer to flush cmp bx,0FFFFh ; first buffer ever found in list? jz flush_drive30 ; yes, save as new best candidate ; else check if < previous lowest addr mov dx,es:BCB_REC[si] sub dx,ds:BCB_REC[bx] mov dl,es:BCB_REC2[si] ; compare the disk addresss sbb dl,ds:BCB_REC2[bx] jnb flush_drive40 ; CY = 0 if new BCB higher flush_drive30: ; else ES = best BCB so far mov bx,si ; save it for later flush_drive40: mov si,es:BCB_NEXT[si] ; get next buffer address cmp si,ss:word ptr bcb_root jne flush_drive20 cmp bx,0FFFFh ; did we find a dirty buffer? jz flush_drive50 ; no, all buffers cleaned mov si,bx ; ES:SI -> BCB to flush call flush_buffer ; write sector to disk jmps flush_drive10 ; check if more dirty buffers flush_drive50: pop si pop es ret else ; entry: DX = sector number to read ; exit: ES:SI = BCB locate_fat: ;---------- mov ah,0 ; set sector address overflow = 0 mov cx,0ff00h+BF_ISFAT ; request a FAT buffer w/ preread locate_buffer: ;------------- ; On Entry: ; AH:DX = sector to locate ; adrive = driver ; CH = 0FFh if preread required ; CL = buffer type ; On Exit: ; ES:SI -> BCB_ ; mov al,adrive ; get our drive number les si,bcb_root ; get it from the right buffer list mov bx,0FFFFh ; no previous buffers yet locate1: cmp dx,es:BCB_REC[si] ; does our sector address match? jne locate2 ; skip if it doesn't cmp ah,es:BCB_REC2[si] ; does record address overflow match? jne locate2 ; skip if not cmp al,es:BCB_DRV[si] ; does the drive match? je locate6 ; found if it all matches locate2: ; MRU buffer doesn't match cmp es:BCB_LINK_OFF[si],0FFFFh je locate3 ; are there more buffers? push es ! pop ds mov bx,si ; remember previous buffer les si,es:BCB_NEXT[si] ; move on to next buffer jmps locate1 locate3: ; we found the LRU buffer push ax ! push cx ! push dx ; save all registers call pick_cheapest ; determine cheapest buffer ; ES:SI -> cheapest buffer ; DS:BX -> previous link test es:BCB_FLAGS[si],BF_DIRTY jz locate5 ; skip if buffer not dirty mov al,es:BCB_DRV[si] ; get the buffer's drive mov ah,es:BCB_FLAGS[si] ; flush all buffers of same type and ah,BF_ISFAT+BF_ISDIR+BF_ISDAT push ss ! pop ds call flush_drive ; gives us burst mode behaviour ; but might re-arrange buffers call find_prev ; find preceding BCB for re-link ; so DS:BX -> BCB_LINK == ES:SI locate5: pop dx ! pop cx ! pop ax ; restore all registers mov es:BCB_DRV[si],al ; fill in the BCB: drive mov es:BCB_REC[si],dx ; record low,middle mov es:BCB_REC2[si],ah ; record high mov es:BCB_FLAGS[si],cl ; mark as clean, ISFAT,ISDIR or ISDAT test ch,ch ; is preread required? jz locate6 ; skip if it isn't push es ! push si push ds ! push bx ; save the previous buffer segment push ss ! pop ds ; for unlinking our buffer call fill_buffer ; read it from disk, don't return ; on physical errors pop bx ! pop ds pop si ! pop es locate6: cmp bx,0FFFFh ; are we the MRU buffer jz locate9 ; yes, leave it at the head locate8: ; arrive here if found as not 1st mov ax,es:BCB_LINK_OFF[si] mov BCB_LINK_OFF[bx],ax mov ax,es:BCB_LINK_SEG[si] mov BCB_LINK_SEG[bx],ax ; unlink this buffer ; we now want to attach the push ss ! pop ds ; BCB at ES:BX to the root mov ax,ds:word ptr bcb_root mov es:BCB_LINK_OFF[si],ax mov ax,ds:word ptr bcb_root+2 mov es:BCB_LINK_SEG[si],ax mov ds:word ptr bcb_root,si ; insert the new entry mov ds:word ptr bcb_root+2,es locate9: push ss ! pop ds ; DS back to normal ret pick_cheapest: ; find cheapest replacement sector ;------------- ; entry: ES:SI = least recently used BCB ; DS:BX = previous buffer ; exit: ES, BX unmodified if LRU buffer marked as cheap ; or no other cheap buffer found ; -or- ; ES:SI, DS:BX modified to cheapest buffer and previous buffer test es:BCB_FLAGS[si],BF_DIRTY ; is this buffer very expensive? jz pck_chp5 ; return if it is cheapest "cheap" buffer ; else is cheapest "expensive" buffer ; find the cheapest "cheap" buffer les si,ss:bcb_root ; as we start at the root with mov ax,0FFFFh ; no previous buffer pck_chp1: ; check if buffer at DS is cheap test es:BCB_FLAGS[si],BF_ISFAT+BF_ISDIR+BF_DIRTY ; check the "not cheap" flag jnz pck_chp2 ; skip if expensive buffer mov ds,dx mov bx,ax ; remember previous buffer pck_chp2: mov dx,es ; remember this buffer mov ax,si ; when we move onto next one les si,es:BCB_NEXT[si] ; get next buffer cmp si,0FFFFh ; end of the line ? jne pck_chp1 ; go again if still buffers pck_chp3: ; done all buffers cmp bx,0FFFFh ; did we find the root ? jne pck_chp4 les si,ss:bcb_root ; return ES:SI -> root ret pck_chp4: les si,ds:BCB_NEXT[bx] ; ES:SI = cheapest buffer pck_chp5: ; ES:SI = cheapest buffer ret ; DS:BX = previous find_prev: ;--------- ; entry: ES:SI = BCB to find previous buffer for ; exit: DS:BX = predecessor, BX = FFFF if first ; mov ax,es mov dx,si ; AX:DX -> buffer we want to find link for mov bx,0FFFFh ; assume it's the first buffer les si,ss:bcb_root ; we start at the root with find_prv1: mov cx,es cmp dx,si ; does offset match ? jne find_prv2 cmp ax,cx ; does segment match ? je find_prv3 ; yes, return this one then find_prv2: mov ds,cx ; remember previous link mov bx,si les si,es:BCB_NEXT[si] ; else have a go at the next one jmps find_prv1 ; and repeat until match find_prv3: ret ; Flush all dirty FAT buffers for drive AL ; entry: AL = drive to flush (0-15) ; exit: CY = 0 if no error ; ax,bx,cx,dx,es preserved flush_fat: ;--------- ; entry: AL = drive for FAT flush mov ah,BF_ISFAT ; flush all dirty FAT buffers jmps flush_drive ; shared code for all flushes ;---------- update_dir: ;---------- call flush_dirbuf ; flush local dirbuf to buffers ;--------- flush_dir: ;--------- mov ah,BF_ISDIR ; write out dirty directories jmps flush_adrive ; update the disk ;---------- update_dat: ;---------- mov ah,BF_ISDAT ; write out dirty data jmps flush_adrive ; update the disk ;---------- update_fat: ;write out modified FAT buffers ;---------- mov ah,BF_ISFAT ; flush all dirty FAT buffers ; jmp flush_adrive ; update the disk if dirty flush_adrive: ;------------ mov al,adrive ; AL = currently selected drive ; jmp flush_drive ; Write out all dirty data buffers for a given drive ; entry: AL = drive to be flushed ; AH = mask of buffer types to be flushed ; exit: AX,DX preserved ; Note: sector buffers will be written in the ; sequence in which they appear on disk (low to high) flush_drive: ;----------- push ds push es push bx ; save registers push si flush_dr0: les si,ss:bcb_root ; start with the first buffer mov bx,0FFFFh ; assume no buffer found flush_dr1: test es:BCB_FLAGS[si],BF_DIRTY ; has buffer been written to? jz flush_dr3 ; no, do the next one test es:BCB_FLAGS[si],ah ; is it one of these buffers? jz flush_dr3 ; no, do the next one cmp al,es:BCB_DRV[si] ; does the drive match? jne flush_dr3 ; skip if wrong drive ; we've found a buffer to flush cmp bx,0FFFFh ; first buffer ever found in list? jz flush_dr2 ; yes, save as new best candidate ; else check if < previous lowest addr mov dl,es:BCB_REC2[si] ; compare the disk addresss sub dl,ds:BCB_REC2[bx] mov dx,es:BCB_REC[si] sbb dx,ds:BCB_REC[bx] jnb flush_dr3 ; CY = 0 if old BCB lower flush_dr2: ; else ES = best BCB so far push es pop ds mov bx,si ; save it for later flush_dr3: les si,es:BCB_NEXT[si] ; get next buffer address cmp si,0ffffh jne flush_dr1 flush_dr4: ; DS:BX = best BCB cmp bx,0FFFFh ; did we find a dirty buffer? jz flush_dr5 ; no, all buffers cleaned mov si,bx ; ES:SI -> BCB to flush push ds ! pop es push ss ! pop ds call flush_buffer ; write sector to disk jmps flush_dr0 ; check if more dirty buffers flush_dr5: pop si pop bx pop es pop ds ; restore registers ret endif flush_buffer: ;------------ ; entry: ES:SI = address of BCB ; exit: buffer flushed if BCB_FLAGS & BF_DIRTY ; note: preserves AX,BX,CX,DX,ES test es:BCB_FLAGS[si],BF_DIRTY ; is the buffer dirty? jz flush_buf9 ; skip update if not modified flush_buf1: push es ! push si push ax ! push bx ; else save all registers push cx ! push dx mov al,es:BCB_DRV[si] ; get the buffer drive cmp al,adrive ; same as the selected drive? je flush_buf2 ; skip if already selected push es ; save the BCB push si push ds ! pop es ; ES = SYSDAT call select_adrive ; select drive AL, ZF = 1 if logged in pop si pop es ; recover BCB jc flush_buf5 ; don't flush to bad drive flush_buf2: mov cx,nfats ; else FAT sectors written CX times mov al,0000$0011b ; mark as FAT write test es:BCB_FLAGS[si],BF_ISFAT jnz flush_buf3 ; go ahead mov cx,1 ; directory/data written once only mov al,0000$0101b ; mark as directory write test es:BCB_FLAGS[si],BF_ISDIR jnz flush_buf3 ; if not dir, must be data mov al,0000$0111b ; mark as data buffer write flush_buf3: ; CX = # of times to write sector mov rwmode,al sub ax,ax ; offset for write = 0 flush_buf4: ; loop back to here for other copies push ax push cx ; save loop variables call setup_rwx ; compute disk address call write_buff ; write the sector pop cx pop ax add ax,nfatrecs ; move to next FAT copy loop flush_buf4 ; repeat for all FAT copies flush_buf5: and es:BCB_FLAGS[si],not BF_DIRTY ; mark it as no longer dirty mov al,physical_drv ; work drive for BDOS function cmp al,adrive ; drive from last IO_SELDSK je flush_buf6 ; skip if flush to work drive ; else reselect BDOS drive after flush push ds ! pop es ; ES = SYSDAT call select_adrive ; reselect the work drive flush_buf6: pop dx ! pop cx ; restore all registers pop bx ! pop ax pop si ! pop es flush_buf9: ; all done, CY = 0 if O.K. ret ;------- zeroblk: ; AX = blk ;------- xor bx,bx ; Start at begining of cluster call clus2sec ; translate to sector address xchg ax,dx ; DX = low 16 bits of address mov ah,al ; AH:DX = 24 bit sector address mov cx,secperclu ; CX == sectors/cluster zeroblk10: ; repeat for all sectors in cluster push ax push cx push dx mov cx,BF_ISDIR ; locate directory sector w/o preread call locate_buffer ; this will find the cheapest buffer or es:BCB_FLAGS[si],BF_DIRTY lea di,BCB_DATA[si] ; ES:DI -> disk buffer mov cx,psecsiz ; CX = byte count for REP STOSB xor ax,ax rep stosb ; zero the whole data buffer pop dx pop cx pop ax add dx,1 ; onto the next block adc ah,0 loop zeroblk10 ; repeat for all sectors in cluster jmp flush_dir fill_buffer: ;----------- ; On Entry: ; ES:SI = address of BCB to be filled ; On Exit: ; ES:SI preserved ; data read into buffer ; test es:BCB_FLAGS[si],BF_ISFAT ; are we reading a FAT sector? jz fill_buf1 ; skip if directory/data mov al,es:BCB_DRV[si] ; get the drive call flush_fat ; write out all dirty buffers mov al,0000$0010b ; reading from FAT area jmps fill_buf3 ; go ahead fill_buf1: mov al,0000$0100b ; else mark as directory test es:BCB_FLAGS[si],BF_ISDIR; test if directory read jnz fill_buf3 ; go ahead fill_buf2: ; neither FAT nor directory => data mov al,0000$0110b ; mark read as data buffer read fill_buf3: mov rwmode,al push cx xor cx,cx cmp al,0000$0010b jne fill_buf4 mov cx,nfats dec cx fill_buf4: mov es:BCB_DRV[si],0FFh ; discard in case of error sub ax,ax ; no offset for 2nd copy yet fill_buf5: push ax call setup_rwx ; compute disk address call read_buff ; read the sector pop ax jns fill_buf6 ; we can end here only if CX was non-zero above and we failed to read a ; FAT copy while there is still another one we could use add ax,nfatrecs dec cx jmps fill_buf5 fill_buf6: pop cx mov al,adrive ; restore the drive mov es:BCB_DRV[si],al ; set the drive # ret read_buff: ;--------- push es push si ; save BCB_ push cur_dma_seg push cur_dma ; save DMA address push cx mov cx,ss:deblock_seg jcxz read_buff10 mov cur_dma_seg,cx mov cur_dma,0 ; xfer via deblocking buffer read_buff10: pop cx call read_block pop cur_dma ; restore DMA address pop cur_dma_seg js read_buff20 ; can happen only on FAT read mov cx,ss:deblock_seg ; if deblocked, copy data jcxz read_buff20 les di,dword ptr cur_dma ; point to destination mov cx,psecsiz ; CX = sector size shr cx,1 ; CX = words per sector push ds mov ds,ss:deblock_seg xor si,si ; DS:SI = source rep movsw ; copy the data pop ds read_buff20: ; SF still indicating error here pop si ; recover BCB_ pop es ret write_buff: ;---------- push es push si push cur_dma_seg push cur_dma mov cx,ss:deblock_seg ; if deblocking we have to jcxz write_buff10 ; copy the data first push ds ; save SYSDAT les si,dword ptr cur_dma ; ES:SI -> source push es ; save source seg mov es,cx xor di,di ; ES:DI -> deblocking buffer mov cur_dma_seg,es mov cur_dma,di ; do xfer via deblocking buffer mov cx,psecsiz ; CX = sector size shr cx,1 ; CX = words per sector pop ds ; DS:SI -> source rep movsw ; copy to deblocking buffer pop ds ; restore SYSDAT write_buff10: call write_block pop cur_dma pop cur_dma_seg pop si pop es ret setup_rwx: ;--------- ; entry: AX = sector offset (multiple FAT writes) ; ES:SI = BCB, BCB_REC filled in ; exit: all values set up for RWXIOSIF mov cur_dma_seg,es ; segment = BCB_SEGMENT lea dx,BCB_DATA[si] mov cur_dma,dx ; offset xor dx,dx add ax,es:BCB_REC[si] adc dl,es:BCB_REC2[si] mov word ptr pblock,ax ; xfer starts at this block mov word ptr pblock+WORD,dx mov mult_sec,1 ; single sector transfer ret END