/****************************************************************/
/*                                                              */
/*                          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);
}
