/**********************************************************************/
/*                                                                    */
/*      IBM Confidential                                              */
/*                                                                    */
/*      Copyright (c) IBM Corporation 1996                            */
/*                                                                    */
/*      All Rights Reserved                                           */
/*                                                                    */
/*====================================================================*/
/*  READAHD.C - CDFS Asynchronous read-ahead Source File              */
/* -------------------------------------------------------------------*/
/*  DESCRIPTION:                                                      */
/*                                                                    */
/*   This module contains the code that performs the base asynch      */
/*   read-ahead functions for the CDROM File System                   */
/*                                                                    */
/*  FUNCTIONS DEFINED: CDReadAhead()                                  */
/*                     ReadAheadDaemon()                              */
/*                                                                    */
/*====================================================================*/
/*  REVISION HISTORY (in reverse chronological order)                 */
/*  ------------------------------------------------------------------*/
/*  01/22/96  Chris Monahan (CJM)   Added CDReadAhead() function,     */
/*                                  Moved structs/defines to .H file  */
/*  01/18/96  Steven Pratt  (SLP)   Created Module                    */
/**********************************************************************/
#define INCL_DOSERRORS
#define INCL_NOPMAPI

#include <os2.h>
#include <fsh.h>
#include <fsd.h>

#include "cdfs.h"
#include "sem.h"
#include "path.h"
#include "vol.h"
#include "heap.h"
#include "mem.h"
#include "buffer.h"

#ifdef STATS_H
#include "stats.h"
#endif

#include "message.h"
#include "fsdata.h"
#include "stdlib.h"
#include "cache.h"

#ifdef DEBUG
#include "pr_debug.h"
#endif

#include "pr_cache.h"
#include "pr_memin.h"
#include "readahd.h"
/*--------------------------------------------------------------------*/
/* globals for readahead                                              */
/*--------------------------------------------------------------------*/
USHORT     num_readahead_bufs = 4;     /* max concurrent read-aheads  */
UCHAR      readahead_mode;             /* on or off for now           */
SEM        readahead_pending;          /* read-ahead rqst has been q'd*/
BOOL       readahead_shutdown = FALSE; /* stop readahead flag         */
USHORT     ra_open_sqntl_value = 2;    /* # of sqntl. reads value on  */
                                       /*  initial open               */
USHORT     ra_trigger_sqntl = 2;       /* # of sqntl. reads trigs ra  */
                                       /*  subsequently (after random)*/
ULONG      ra_max_size = 32;           /* Biggest size the read-ahead */
                                       /*  will attempt (in sectors)  */
ra_buffer far *ra_buf = NULL;          /* ptr to read-ahead buf array */


/**********************************************************************/
/* Function: CDReadAhead()  -  Check/Queue for CD read-ahead Daemon   */
/*                                                                    */
/* Description:   This function determines if a read-ahead action is  */
/*                merited using the read history stored in the        */
/*                sffsd structure.  If read-ahead is called for, it   */
/*                queues the read-ahead data to be processed by the   */
/*                read-ahead daemon function (ReadAheadDaemon()).     */
/*                                                                    */
/* Input Parms:   PVOL_DEP_DATA   VolData;                            */
/*                USHORT          VPB;                                */
/*                BOOL            BLOwns;                             */
/*                SECT_NUM        StartSector;                        */
/*                SECT_NUM        LastSector;                         */
/*                SECT_NUM        ExtentEnd;                          */
/*                PSFFSD          psffsd;                             */
/*                                                                    */
/* Output Parms:  None                                                */
/*                                                                    */
/* Globals used:  num_readahead_bufs                                  */
/*                readahead_mode                                      */
/*                first_readahead                                     */
/*                readahead_pending                                   */
/*                readahead_shutdown                                  */
/*                                                                    */
/**********************************************************************/
void CDReadAhead(PVOL_DEP_DATA   VolData,
                 USHORT          VPB,
                 BOOL            BLOwns,
                 SECT_NUM        StartSector,
                 SECT_NUM        LastSector,
                 SECT_NUM        ExtentEnd,
                 PSFFSI          psffsi,
                 PFILE_DEP_DATA  psffsd)
{
  unsigned int CurrBuf;
  SECT_NUM     RASize = 0;
  SECT_NUM     TempSector;
  BOOL         RAReset = FALSE;

#ifdef DEBUG
    dprintf ("CDReadAhead : Entering... \n");
    dprintf ("CDReadAhead : startsec = %lx ", StartSector );
    dprintf (" lastsec = %lx",LastSector );
    dprintf (" endextent = %lx\n",ExtentEnd   );
#endif
  /*------------------------------------------------------------------*/
  /* Don't even consider it if read-ahead is not enabled              */
  /*------------------------------------------------------------------*/
  if ((readahead_mode == RA_OFF) || /* if read-ahead is off then exit */
      (readahead_shutdown))
  {
#ifdef DEBUG
    dprintf ("CDReadAhead : Leaving mode=off or shutdown ... \n");
#endif
    return;
  }

  /*------------------------------------------------------------------*/
  /* Check if we are at the end of the extent and disregard the       */
  /*  read-ahead if so                                                */
  /*------------------------------------------------------------------*/
  if (LastSector == ExtentEnd)
  {
#ifdef DEBUG
    dprintf ("CDReadAhead : Leaving - lastsector = extent end \n");
#endif
    return;
  }

  /*------------------------------------------------------------------*/
  /* Set the read-ahead conditions in the sffsd - first deal with     */
  /*  the first access after the OPEN                                 */
  /*------------------------------------------------------------------*/
  if (psffsd->usSqntlCount == 0xFFFF)
  {
    psffsd->usSqntlCount = ra_open_sqntl_value;
    psffsd->snNextSqntl = LastSector+1;
    psffsd->snNextReadAhead = StartSector;
    while (psffsd->snNextReadAhead <= LastSector)
    {
      psffsd->snNextReadAhead += cFileSectors+1;
    }
    RAReset = TRUE;
#ifdef DEBUG
    dprintf ("CDReadAhead : First read of file \n");
#endif
  }

  /*------------------------------------------------------------------*/
  /* Then deal with a standard sequential read - treat any read that  */
  /* encompasses the sequential marker as sequential.                 */
  /*------------------------------------------------------------------*/
  else if ((StartSector <= psffsd->snNextSqntl) && /* sequential access*/
           (LastSector >= psffsd->snNextSqntl))
  {
    if (LastSector >= psffsd->snNextReadAhead)
    {
      while (psffsd->snNextReadAhead <= LastSector)
      {
        psffsd->snNextReadAhead += cFileSectors+1;
      }
      RAReset = TRUE;
    }
    psffsd->snNextSqntl = LastSector+1;
    ++(psffsd->usSqntlCount);
  }

  /*------------------------------------------------------------------*/
  /* Now deal with an access away from the last known localities      */
  /*------------------------------------------------------------------*/
  else                                      /* random access          */
  {
    psffsd->usSqntlCount = 0;
    psffsd->snNextSqntl = LastSector+1;
    psffsd->snNextReadAhead = StartSector;
    while (psffsd->snNextReadAhead <= LastSector)
    {
      psffsd->snNextReadAhead += cFileSectors+1;
    }
    RAReset = TRUE;
  }

  /*--------------------------------------------------------------*/
  /* Do nothing if we haven't hit the sqntl trigger yet           */
  /*--------------------------------------------------------------*/
  if (psffsd->usSqntlCount < ra_trigger_sqntl)
  {
#ifdef DEBUG
    dprintf ("CDReadAhead : Leaving - no trigger=%d \n",ra_trigger_sqntl);
    dprintf ("CDReadAhead : sqcount = %d  nextsq = %d  nextreadahe= %d  \n",
             psffsd->usSqntlCount,psffsd->snNextSqntl,psffsd->snNextReadAhead);
#endif
    return;
  }

  /*--------------------------------------------------------------*/
  /* Do nothing if we already read-ahead the next region          */
  /*--------------------------------------------------------------*/
  if ((LastSector+ra_max_size < psffsd->snNextReadAhead) && (!RAReset))
  {
#ifdef DEBUG
    dprintf ("CDReadAhead : Leaving - already read ahead these sectors \n");
#endif
    return;
  }

  /*--------------------------------------------------------------*/
  /* Now set the size of the read-ahead to perform - this is the  */
  /* minimum of three variables as follows:                       */
  /*  - What's left in the extent                                 */
  /*  - The max read-ahead size                                   */
  /*  - 1/8 of the current cache size                             */
  /* We further will abort the read-ahead if the current read is  */
  /* larger than our calculated read-ahead size - this is due to  */
  /* the fact that performing insufficient read-ahead for a large */
  /* region reader will actually adversely impact performance.    */
  /* We do not abort the read-ahead if the size of the extent     */
  /* (and hopefully the file) is the limiting factor, since we    */
  /* expect the reader to come back with the exact remaining size */
  /* as the next read.                                            */
  /*--------------------------------------------------------------*/
  if (ExtentEnd > psffsd->snNextReadAhead)
  {
    RASize = min(ExtentEnd - psffsd->snNextReadAhead, ra_max_size);
    RASize = min(RASize, (cBufSegs * SEC_PER_SEG)>>2);
  }
  else
  {
    RASize = 0;
  }
  if ((LastSector - StartSector > RASize) &&
      (RASize != ExtentEnd - psffsd->snNextReadAhead))
  {
#ifdef DEBUG
    dprintf ("CDReadAhead : Leaving - read larger than max \n");
#endif
    return;
  }

  /*------------------------------------------------------------------*/
  /* Now check if we have anything to read-ahead - return if not      */
  /*------------------------------------------------------------------*/
  if (RASize == 0)
  {
#ifdef DEBUG
    dprintf ("CDReadAhead : Leaving - readahead = 0 \n");
#endif
    return;
  }

  /*------------------------------------------------------------------*/
  /* Now check if we have buffer pointer is valid                     */
  /*------------------------------------------------------------------*/
  if (!ra_buf)
  {
#ifdef DEBUG
    dprintf ("CDReadAhead : Leaving - ra_buf invalid \n");
#endif
    return;
  }

  /*------------------------------------------------------------------*/
  /* Try to find a free read-ahead data buffer - bail if none         */
  /*------------------------------------------------------------------*/
  for (CurrBuf = 0; CurrBuf < num_readahead_bufs; ++CurrBuf)
  {
#ifdef DEBUG
    dprintf ("CDReadAhead : look for buffer \n");
#endif
    if ((ra_buf[CurrBuf].state == RA_FREE) ||
        ((ra_buf[CurrBuf].handle == psffsi->sfi_selfsfn) &&
         (ra_buf[CurrBuf].state != RA_BUSY)))
    {
      FSH_SEMREQUEST(&(ra_buf[CurrBuf].lock), 0xFFFFFFFF);
      if ((ra_buf[CurrBuf].state != RA_FREE) &&
          ((ra_buf[CurrBuf].handle != psffsi->sfi_selfsfn) ||
           (ra_buf[CurrBuf].state == RA_BUSY)))
      {
        FSH_SEMCLEAR(&(ra_buf[CurrBuf].lock));
      }

      /*--------------------------------------------------------------*/
      /* We have found an appropriate buffer to use - fill it in and  */
      /*  update the sffsd to reflect the read-ahead.                 */
      /*--------------------------------------------------------------*/
      else
      {
#ifdef DEBUG
    dprintf ("CDReadAhead : Do a read ahead \n");
#endif
        ra_buf[CurrBuf].VolData = VolData;
        ra_buf[CurrBuf].VPB = VPB;
        ra_buf[CurrBuf].BLOwns = BLOwns;
        ra_buf[CurrBuf].StartSector = psffsd->snNextReadAhead;
        ra_buf[CurrBuf].LastSector = psffsd->snNextReadAhead + RASize - 1;
        ra_buf[CurrBuf].state = RA_PENDING;
        ra_buf[CurrBuf].handle = psffsi->sfi_selfsfn;
        psffsd->snNextReadAhead += RASize;
        FSH_SEMCLEAR(&(ra_buf[CurrBuf].lock));
        FSH_SEMCLEAR(&(readahead_pending));
        break;
      }
    }
  }

  /*------------------------------------------------------------------*/
  /* return whether we read-ahead or not . . .                        */
  /*------------------------------------------------------------------*/
#ifdef DEBUG
    dprintf ("CDReadAhead : Leaving - bottom \n");
#endif
  return;
}

/******************************************************************************/
/* Function: ReadAheadDaemon  -  Do Async Read-Ahead for CD ROM               */
/*                                                                            */
/* Input Parms: None                                                          */
/*                                                                            */
/* Output Parms: None                                                         */
/*                                                                            */
/* Globals accessed:                                                          */
/*   num_readahead_bufs                   might be calculated in RAdaemon     */
/*   readahead_mode                       on or off for now                   */
/*   first_readahead                      ptr to first read-ahead buffer      */
/*   readahead_pending                    read-ahead request has been queued  */
/*   readahead_shutdown                   stop readahead flag                 */
/*                                                                            */
/*                                                                            */
/* Description:                                                               */
/*   This function is started at IPL time and runs as a daemon processing     */
/* Async read-ahead requests from CDROM reads.  The behavior of this function */
/* is controled through parms on the CDROM IFS Statement in CONFIG.SYS.       */
/*                                                                            */
/******************************************************************************/
void ReadAheadDaemon()
{
  ra_buffer far *current_readahead;            /* current buffer              */
  USHORT i;                                    /* loop count var              */
  PBUF  buf;                                   /* ptr to hold return buf addr,*/
                                               /*  throwaway data             */
  SECT_NUM read_sector;                        /* sector to read              */

#ifdef DEBUG
    dprintf ("ReadAheadDaemon : Entering...\n");
#endif

  if (!readahead_mode)                 /* if read-ahead is not on then exit */
  {
#ifdef DEBUG
    dprintf ("ReadAheadDaemon : Leaving 1 ...\n");
#endif
    return;
  }

  /* Set max read ahead size as a multiple of cFileSectors */
  ra_max_size = cFileSectors + 1;
  while ((ra_max_size + cFileSectors + 1) <= MAX_RA_SIZE)
    ra_max_size = ra_max_size + cFileSectors + 1;

  /* Allocate space for the array of buffers based on max number allowed      */
  if (GetHeapChunk((char far * far *)&ra_buf,
      (sizeof(ra_buffer) * num_readahead_bufs)))
  {
#ifdef DEBUG
    dprintf ("ReadAheadDaemon : Leaving No Memory ...\n");
#endif
    readahead_shutdown = TRUE;
    return;
  }

    for (i=1, current_readahead=ra_buf; i<=num_readahead_bufs;
        i++, current_readahead++)
    {
      current_readahead->state = RA_FREE;
      FSH_SEMCLEAR(&(current_readahead->lock));
    }

  /* Start the sem out as SET                                                 */
  FSH_SEMSET(&readahead_pending);                            /* Init the Sem  */

  while (readahead_shutdown == FALSE)  /* loop forever unless shutdown        */
  {
    FSH_SEMWAIT(&readahead_pending, 0xFFFFFFFF);             /* wait for work */
    FSH_SEMSET(&readahead_pending);                          /* Reset the Sem */

#ifdef DEBUG
    dprintf ("ReadAheadDaemon : Woke up...\n");
#endif
    /* Loop through the read-ahead queue looking for the entry(s) which have  */
    /* a request pending. When found fill in FindBuf parms from queue entry   */
    /* fields and call FindBuf to have the data added to the cache.           */
    /* Don't worry about return codes from FindBuf, if it fails we just go on.*/
    for (i=1, current_readahead=ra_buf; i<=num_readahead_bufs;
        i++, current_readahead++)
    {
      if (current_readahead->state == RA_PENDING)
      {
#ifdef DEBUG
    dprintf ("ReadAheadDaemon : Doing Read Ahead\n");
#endif
        FSH_SEMREQUEST(&(current_readahead->lock), 0xFFFFFFFF);
        current_readahead->state = RA_BUSY;            /* Mark this guy busy  */
        FSH_SEMCLEAR(&(current_readahead->lock));

        /* If FindBuf has the first requested sector in cache, then it will    */
        /* not do any reads from the CD.  In order to be sure that we get every*/
        /* sector in the requested region read into memory we call FindBuf     */
        /* once for each sector.  Most of these calls will not cause CD access */
        /* as the data will be in cache.                                       */

        for (read_sector = current_readahead->StartSector;
             read_sector <= current_readahead->LastSector;
             read_sector++)
        {
          FindBuf (current_readahead->VolData,         /* Do the read-ahead   */
                   current_readahead->BLOwns,
                   read_sector,
                   current_readahead->VPB,
                   (PPBUF) &buf,
                   current_readahead->LastSector
#ifdef STATS_H
                   , DATA_SEC
#endif
                );
           RELEASE_BUF(buf);                        /* release the buffer */
        } /* end for */

        current_readahead->state = RA_FREE;         /* free up this buffer */
      } /* end if pending */
    } /* end for loop */
  } /* end while */
#ifdef DEBUG
    dprintf ("ReadAheadDaemon : Leaving 2 ...\n");
#endif
  return;
}

