FreeDOS/kernel/blockio.c

518 lines
15 KiB
C
Raw Normal View History

2012-10-15 02:08:06 +02:00
/****************************************************************/
/* */
/* blockio.c */
/* DOS-C */
/* */
/* Block cache functions and device driver interface */
/* */
/* Copyright (c) 1995 */
/* Pasquale J. Villani */
/* All Rights Reserved */
/* */
/* This file is part of DOS-C. */
/* */
/* DOS-C is free software; you can redistribute it and/or */
/* modify it under the terms of the GNU General Public License */
/* as published by the Free Software Foundation; either version */
/* 2, or (at your option) any later version. */
/* */
/* DOS-C is distributed in the hope that it will be useful, but */
/* WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See */
/* the GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public */
/* License along with DOS-C; see the file COPYING. If not, */
/* write to the Free Software Foundation, 675 Mass Ave, */
/* Cambridge, MA 02139, USA. */
/* */
/****************************************************************/
#include "portab.h"
#include "globals.h"
#ifdef VERSION_STRINGS
static BYTE *blockioRcsId =
"$Id: blockio.c 1702 2012-02-04 08:46:16Z perditionc $";
#endif
#define b_next(bp) ((struct buffer FAR *)(MK_FP(FP_SEG(bp), bp->b_next)))
#define b_prev(bp) ((struct buffer FAR *)(MK_FP(FP_SEG(bp), bp->b_prev)))
#define bufptr(fbp) ((struct buffer FAR *)(MK_FP(FP_SEG(bp), fbp)))
/************************************************************************/
/* */
/* block cache routines */
/* */
/************************************************************************/
/* #define DISPLAY_GETBLOCK */
STATIC BOOL flush1(struct buffer FAR * bp);
/*
this searches the buffer list for the given disk/block.
returns:
a far pointer to the buffer.
If the buffer is found the UNCACHE bit is not set and else it is set.
new:
upper layer may set UNCACHE attribute
UNCACHE buffers are recycled first.
intended to be used for full sector reads into application buffer
resets UNCACHE upon a "HIT" -- so then this buffer will not be
recycled anymore.
*/
STATIC void move_buffer(struct buffer FAR *bp, size_t firstbp)
{
/* connect bp->b_prev and bp->b_next */
b_next(bp)->b_prev = bp->b_prev;
b_prev(bp)->b_next = bp->b_next;
/* insert bp between firstbp and firstbp->b_prev */
bp->b_prev = bufptr(firstbp)->b_prev;
bp->b_next = firstbp;
b_next(bp)->b_prev = FP_OFF(bp);
b_prev(bp)->b_next = FP_OFF(bp);
}
STATIC struct buffer FAR *searchblock(ULONG blkno, COUNT dsk)
{
int fat_count = 0;
struct buffer FAR *bp;
size_t lastNonFat = 0;
size_t uncacheBuf = 0;
seg bufseg = FP_SEG(firstbuf);
size_t firstbp = FP_OFF(firstbuf);
#ifdef DISPLAY_GETBLOCK
printf("[searchblock %d, blk %ld, buf ", dsk, blkno);
#endif
/* Search through buffers to see if the required block */
/* is already in a buffer */
bp = MK_FP(bufseg, firstbp);
do
{
if ((bp->b_blkno == blkno) &&
(bp->b_flag & BFR_VALID) && (bp->b_unit == dsk))
{
/* found it -- rearrange LRU links */
#ifdef DISPLAY_GETBLOCK
printf("HIT %04x:%04x]\n", FP_SEG(bp), FP_OFF(bp));
#endif
bp->b_flag &= ~BFR_UNCACHE; /* reset uncache attribute */
if (FP_OFF(bp) != firstbp)
{
*(UWORD *)&firstbuf = FP_OFF(bp);
move_buffer(bp, firstbp);
}
return bp;
}
if (bp->b_flag & BFR_UNCACHE)
uncacheBuf = FP_OFF(bp);
if (bp->b_flag & BFR_FAT)
fat_count++;
else
lastNonFat = FP_OFF(bp);
bp = b_next(bp);
} while (FP_OFF(bp) != firstbp);
/*
now take either the last buffer in chain (not used recently)
or, if we are low on FAT buffers, the last non FAT buffer
*/
if (uncacheBuf)
{
bp = bufptr(uncacheBuf);
}
else if (bp->b_flag & BFR_FAT && fat_count < 3 && lastNonFat)
{
bp = bufptr(lastNonFat);
}
else
{
bp = b_prev(bufptr(firstbp));
}
bp->b_flag |= BFR_UNCACHE; /* set uncache attribute */
#ifdef DISPLAY_GETBLOCK
printf("MISS, replace %04x:%04x]\n", FP_SEG(bp), FP_OFF(bp));
#endif
if (FP_OFF(bp) != firstbp) /* move to front */
{
move_buffer(bp, firstbp);
*(UWORD *)&firstbuf = FP_OFF(bp);
}
return bp;
}
BOOL DeleteBlockInBufferCache(ULONG blknolow, ULONG blknohigh, COUNT dsk, int mode)
{
struct buffer FAR *bp = firstbuf;
/* Search through buffers to see if the required block */
/* is already in a buffer */
do
{
if (blknolow <= bp->b_blkno &&
bp->b_blkno <= blknohigh &&
(bp->b_flag & BFR_VALID) && (bp->b_unit == dsk))
{
if (mode == XFR_READ)
flush1(bp);
else
bp->b_flag = 0;
}
bp = b_next(bp);
}
while (FP_OFF(bp) != FP_OFF(firstbuf));
return FALSE;
}
#if TOM
void dumpBufferCache(void)
{
struct buffer FAR *bp = firstbuf;
int printed = 0;
/* Search through buffers to see if the required block */
/* is already in a buffer */
do
{
printf("%8lx %02x ", bp->b_blkno, bp->b_flag);
if (++printed % 6 == 0)
printf("\n");
bp = b_next(bp);
}
while (FP_OFF(bp) != FP_OFF(firstbuf));
printf("\n");
}
#endif
/* */
/* Return the address of a buffer structure containing the */
/* requested block. */
/* if overwrite is set, then no need to read first */
/* */
/* returns: */
/* requested block with data */
/* failure: */
/* returns NULL */
/* */
struct buffer FAR *getblk(ULONG blkno, COUNT dsk, BOOL overwrite)
{
/* Search through buffers to see if the required block */
/* is already in a buffer */
struct buffer FAR *bp = searchblock(blkno, dsk);
if (!(bp->b_flag & BFR_UNCACHE))
{
return bp;
}
/* The block we need is not in a buffer, we must make a buffer */
/* available, and fill it with the desired block */
/* take the buffer that lbp points to and flush it, then read new block. */
if (!flush1(bp))
return NULL;
/* Fill the indicated disk buffer with the current track and sector */
if (!overwrite && dskxfer(dsk, blkno, bp->b_buffer, 1, DSKREAD))
{
return NULL;
}
bp->b_flag = BFR_VALID | BFR_DATA;
bp->b_unit = dsk;
bp->b_blkno = blkno;
return bp;
}
/* */
/* Mark all buffers for a disk as not valid */
/* */
VOID setinvld(REG COUNT dsk)
{
struct buffer FAR *bp = firstbuf;
do
{
if (bp->b_unit == dsk)
bp->b_flag = 0;
bp = b_next(bp);
}
while (FP_OFF(bp) != FP_OFF(firstbuf));
}
/* Check if there is at least one dirty buffer */
/* */
BOOL dirty_buffers(REG COUNT dsk)
{
struct buffer FAR *bp = firstbuf;
do
{
if (bp->b_unit == dsk &&
(bp->b_flag & (BFR_VALID | BFR_DIRTY)) == (BFR_VALID | BFR_DIRTY))
return TRUE;
bp = b_next(bp);
}
while (FP_OFF(bp) != FP_OFF(firstbuf));
return FALSE;
}
/* */
/* */
/* Flush all buffers for a disk */
/* */
/* returns: */
/* TRUE on success */
/* */
BOOL flush_buffers(REG COUNT dsk)
{
struct buffer FAR *bp = firstbuf;
REG BOOL ok = TRUE;
bp = firstbuf;
do
{
if (bp->b_unit == dsk)
if (!flush1(bp))
ok = FALSE;
bp = b_next(bp);
}
while (FP_OFF(bp) != FP_OFF(firstbuf));
return ok;
}
/* */
/* Write one disk buffer */
/* */
STATIC BOOL flush1(struct buffer FAR * bp)
{
BOOL ok = TRUE;
if ((bp->b_flag & (BFR_VALID | BFR_DIRTY)) == (BFR_VALID | BFR_DIRTY))
{
#ifdef WITHFAT32
ULONG b_offset = 0;
#else
UWORD b_offset = 0;
#endif
UBYTE b_copies = 1;
ULONG blkno = bp->b_blkno;
if (bp->b_flag & BFR_FAT)
{
b_copies = bp->b_copies;
b_offset = bp->b_offset;
#ifdef WITHFAT32
if (b_offset == 0) /* FAT32 FS */
b_offset = bp->b_dpbp->dpb_xfatsize;
#endif
}
while (b_copies--)
{
if (dskxfer(bp->b_unit, blkno, bp->b_buffer, 1, DSKWRITE))
ok = FALSE;
blkno += b_offset;
}
}
bp->b_flag &= ~BFR_DIRTY; /* even if error, mark not dirty */
if (!ok) /* otherwise system has trouble */
bp->b_flag &= ~BFR_VALID; /* continuing. */
return ok;
}
/* */
/* Write all disk buffers */
/* */
BOOL flush(void)
{
REG struct buffer FAR *bp = firstbuf;
REG BOOL ok;
ok = TRUE;
do
{
if (!flush1(bp))
ok = FALSE;
bp->b_flag &= ~BFR_VALID;
bp = b_next(bp);
}
while (FP_OFF(bp) != FP_OFF(firstbuf));
network_redirector(REM_FLUSHALL);
return (ok);
}
/************************************************************************/
/* */
/* Device Driver Interface Functions */
/* */
/************************************************************************/
/* */
/* Transfer one or more blocks to/from disk */
/* */
UWORD dskxfer(COUNT dsk, ULONG blkno, VOID FAR * buf, UWORD numblocks,
COUNT mode)
{
register struct dpb FAR *dpbp = get_dpb(dsk);
if (dpbp == NULL)
{
return 0x0201; /* illegal command */
}
#if TOM
#define KeyboardShiftState() (*(BYTE FAR *)(MK_FP(0x40,0x17)))
if (KeyboardShiftState() & 0x01)
{
printf("dskxfer:%s %x - %lx %u\n", mode == DSKWRITE ? "write" : "read",
dsk, blkno, numblocks);
if ((KeyboardShiftState() & 0x03) == 3)
dumpBufferCache();
}
#endif
for (;;)
{
IoReqHdr.r_length = sizeof(request);
IoReqHdr.r_unit = dpbp->dpb_subunit;
switch (mode)
{
case DSKWRITE:
if (verify_ena)
{
IoReqHdr.r_command = C_OUTVFY;
break;
}
/* else fall through */
case DSKWRITEINT26:
IoReqHdr.r_command = C_OUTPUT;
break;
case DSKREADINT25:
case DSKREAD:
IoReqHdr.r_command = C_INPUT;
break;
default:
return 0x0100; /* illegal command */
}
IoReqHdr.r_status = 0;
IoReqHdr.r_meddesc = dpbp->dpb_mdb;
IoReqHdr.r_count = numblocks;
if ((dpbp->dpb_device->dh_attr & ATTR_HUGE) || blkno >= MAXSHORT)
{
IoReqHdr.r_start = HUGECOUNT;
IoReqHdr.r_huge = blkno;
}
else
IoReqHdr.r_start = (UWORD)blkno;
/*
* Some drivers normalise transfer address so HMA transfers are disastrous!
* Then transfer block through xferbuf (DiskTransferBuffer doesn't work!)
* (But this won't work for multi-block HMA transfers... are there any?)
*/
if (FP_SEG(buf) >= 0xa000 && numblocks == 1 && bufloc != LOC_CONV)
{
IoReqHdr.r_trans = deblock_buf;
if (mode == DSKWRITE)
fmemcpy(deblock_buf, buf, dpbp->dpb_secsize);
execrh((request FAR *) & IoReqHdr, dpbp->dpb_device);
if (mode == DSKREAD)
fmemcpy(buf, deblock_buf, dpbp->dpb_secsize);
}
else
{
IoReqHdr.r_trans = (BYTE FAR *) buf;
execrh((request FAR *) & IoReqHdr, dpbp->dpb_device);
}
if ((IoReqHdr.r_status & (S_ERROR | S_DONE)) == S_DONE)
break;
/* INT25/26 (_SEEMS_ TO) return immediately with 0x8002,
if drive is not online,...
normal operations (DIR) wait for ABORT/RETRY
other condition codes not tested
*/
if (mode >= DSKWRITEINT26)
return (IoReqHdr.r_status);
loop:
switch (block_error(&IoReqHdr, dpbp->dpb_unit, dpbp->dpb_device, mode))
{
case ABORT:
case FAIL:
return (IoReqHdr.r_status);
case RETRY:
continue;
case CONTINUE:
break;
default:
goto loop;
}
break;
} /* retry loop */
/* *** Changed 9/4/00 BER */
return 0; /* Success! Return 0 for a successful operation. */
/* End of change */
}
/*
this removes any (additionally allocated) buffers
from the HMA buffer chain, because they get allocated to the 'user'
*/
void AllocateHMASpace (size_t lowbuffer, size_t highbuffer)
{
REG struct buffer FAR *bp = firstbuf;
int n;
if (FP_SEG(bp) != 0xffff)
return;
n = LoL_nbuffers;
do
{
/* check if buffer intersects with requested area */
if (FP_OFF(bp) < highbuffer && FP_OFF(bp+1) > lowbuffer)
{
flush1(bp);
/* unlink bp from buffer chain */
b_prev(bp)->b_next = bp->b_next;
b_next(bp)->b_prev = bp->b_prev;
if (FP_OFF(bp) == FP_OFF(firstbuf))
firstbuf = b_next(bp);
LoL_nbuffers--;
}
bp = b_next(bp);
}
while (--n);
}