/*static char *SCCSID = "@(#)cachinit.c	6.2 92/01/31";*/
#define INIT2 INIT2
#define INCL_DOSERRORS
#include <os2def.h>
#include <bse.h>
#include <fsd.h>
#include <fsh.h>

#include "message.h"
#include "heap.h"
#include "chain.h"
#include "const.h"
#include "misc.h"
#include "buf.h"
#include "dir.h"
#include "storage.h"
#include "fnode.h"
#include "volume.h"
#include "tables.h"
#include "pb_ifs.h"
#include "superb.h"
#include "lw.h"
#include "frame.h"
#include "fsdata.h"
#include "pbmacros.h"
#include "pbmsg.h"

#include "pr_FSINI.h"
#include "pr_HEAP.h"
#include "pr_MISC.h"
#include "pr_debug.h"

/* the percent dirty that the dirty chain has to get before it gets flushed */
#define PERCENT_DIRTY_THRESH	80

/* put these secondary initialization routines in their own segment */

#ifdef MINIFSD
extern USHORT BootStage;
#endif

#define MIN_CACHE   0xffff  /* 64k = minimum size of reasonable cache */

#define CB_CACHE_SEG	0x10000 /* count of bytes in a cache segment */


/*  Message for when cannot get enough memory	*/
extern MESSAGE _TXT_MSG_HPFS_NOTENOUGH_MEM;

/*  If get less, than requested cache . . . */
extern MESSAGE _TXT_MSG_HPFS_WANT_MEM;

/*
 ***	CacheInit - Initialize the cache.
 *
 *	This routine allocates and initializes the Pinball cache.
 *	It gets called exactly once from FS_MOUNT.
 *
 *	ENTRY	uses global cBufs = number of buffers to allocate,
 *			fs_init has ensured that cBufs is such that
 *			all of the bufnodes will fit in one 64K segment.
 *
 *	EXIT	Returns an error code iff trouble from FSH_xxx routines.
 *
 *	HISTORY
 *	    01-Nov-89 by JR (John Rogers, JohnRo@Microsoft)
 *		- PTR B786561: Handle failed mem realloc.
 */

INT far
CacheInit (PFRAME pfrm)
{
    SEL sel;
    ULONG ulTotal;		/* total size of space needed */
    ULONG ulTotalNeeded;	/* goal amount to check if can get all */
    ULONG ulSegSize;	/* size of current segment being allocated */
    USHORT usSegNum;
    USHORT usBufIndex;
    USHORT usMin;
    USHORT usMax;
    USHORT usTableSize;
    USHORT us;
    USHORT usRet = NO_ERROR;
    PUSHORT ausSelList;

    /*
     * CACHE INITIALIZATION
     */

    /*
     * Resize the global data segment to match the size of the cache.
     *
     * NewSize = adr of end of seg - size of unused cache headers
     */

    {
	USHORT usAllButCache;
	USHORT usNewSize;

	usAllButCache = (USHORT) (&(abufCache [0]));
	usNewSize = usAllButCache + cBufs * sizeof (BUFNODE);

#ifdef DEBUG
	/* Check for overflow in calculation of new data segment size */
	if (usAllButCache > (0xffff - cBufs * sizeof (BUFNODE))) {
	    usNewSize = 0xffff;
	}
#endif


#ifdef DEBUG
	dprintf ("CacheInit: resizing data seg from %d to %d\n",
		 usAllButCache + sizeof (BUFNODE), usNewSize);
#endif

	/*
	 * Use cBufs to get the selector of the global data segment.
	 * Resize it to be exactly as big as we need it so it holds
	 * the right amount of buffer headers.
	 */
#ifdef MINIFSD
	usRet = MFSH_SEGREALLOC ((USHORT)((ULONG)((PVOID)(&cBufs)) >> 16),
				(ULONG) usNewSize);
#else
	usRet = FSH_SEGREALLOC ((USHORT)((ULONG)((PVOID)(&cBufs)) >> 16),
				(ULONG) usNewSize);
#endif

	if (usRet != NO_ERROR) {
#ifdef DEBUG
	    dprintf ("CachInit: FSH_SEGREALLOC returned %d, ", usRet);
	    dprintf ("usNewSize=%d\n", usNewSize);
#endif
	    return (usRet);
	}
    }


/* #ifdef DEBUG */
    memset (abufCache, 0, cBufs * sizeof (BUFNODE));
/* #endif */

    InitBufNodeList ();

    /*
     ******* Allocate the segment(s) for the data blocks.
     */

    /* get the total number of bytes for cache */
    ulTotal = (ULONG) ( ((ULONG) cBufs) * (ULONG) SECSIZE * (ULONG) SPB);
    ulTotalNeeded = ulTotal;   /* Make copy of this total */

    /* find the number of segments needed */
    usTableSize = ((USHORT) (ulTotal / CB_CACHE_SEG));
    /* Add in, if remainder */
    if (ulTotal % CB_CACHE_SEG) {
	usTableSize++;
    }

    /*
     *	Previously assigned table size is quadrupled to allow for smaller
     *	than CB_CACHE_SEG size segments.  No significance to number 4.
     *	Know the table will not overflow since the biggest it could get
     *	is 128.
     */
    usTableSize *= 4;

    if ((ausSelList = GHS(usTableSize * sizeof (USHORT),pfrm)) == NULL) {
	INTERR (PBMSG_CACHINIT_TOOLARGE);
    }

    usBufIndex = 0;
    usSegNum = 0;

    while (ulTotal) {

	/* allocate a segment */
	ulSegSize = MIN ((ULONG)CB_CACHE_SEG, ulTotal);

#ifdef MINIFSD
	if (BootStage == 1) {
	    /*
	     *	BUGBUG - if at boot time, fail if find some 64K blocks,
	     *	 but not all. Not likely to occur.
	     */

	    if ((usRet = MFSH_SEGALLOC (1, ulSegSize, &sel))) {
         goto FSInitExit;
	    }
	    if (usSegNum >= usTableSize) {
		break;
	    } else {
		ausSelList [usSegNum++] = sel;
	    }

	    LinkUp (&(abufCache [usBufIndex]),
		    MAKEP (sel, 0),
		    ulSegSize / (SPB * SECSIZE));

	    usBufIndex += (USHORT) (ulSegSize / (SPB * SECSIZE));


#ifdef DEBUG
	    dprintf (
	    "CacheInit: mini stg 1 cache alloc, sel=0x%x, size=0x%lx\n",
		     sel, ulSegSize);
#endif /* DEBUG */

	} else	/* continued out of #endif */
#endif /* MINI_FSD */
	{
	    ULONG ulThisTry = ulSegSize;    /* Block trying to get next */
	    ULONG ulCutOff = ulSegSize >> 3;  /* Blocks too small */

	    /* Current amount shooting for */
	    ULONG ulTotalThisSeg = ulSegSize;

	 while (ulTotalThisSeg &&
			(ulThisTry >= ulCutOff) &&
			(usSegNum < usTableSize)) {

		if (((usRet = FSH_SEGALLOC (SA_FRING0,
						ulThisTry,
						& sel))) == NO_ERROR) {
#ifdef DEBUG
		    dprintf ("Got segment of size 0x%lx\n", ulThisTry);
#endif
		    /* Add selector to the table */
		    ausSelList [usSegNum++] = sel;

		    /*
		     * Link with bufnodes, the blocks in the seg
		     * just allocated.
		     */
		    LinkUp (&(abufCache [usBufIndex]),
				MAKEP (sel, 0),
				ulThisTry / (SPB * SECSIZE));

		    usBufIndex += (USHORT) (ulThisTry / (SPB * SECSIZE));


		    /* found new block, so decrement amount looking for */
		    ulTotalThisSeg -= ulThisTry;


		} else {   /* Couldn't allocate space, try smaller */
#ifdef DEBUG
		    dprintf ("Did not get segment of size 0x%lx\n", ulThisTry);
#endif
		    ulThisTry >>= 1;
		}
	 }

	    /*
	     * If can't find smaller blocks now, won't find later,
	     * or if have no room in table to record blocks,
	     * stop allocating
	     */
	    if ((ulThisTry < ulCutOff) || (usSegNum >= usTableSize)) {
		break;
	    }


	    /* Set seg size to amount actually allocated */
	    ulSegSize -= ulTotalThisSeg;

#ifdef DEBUG
	    dprintf ("CacheInit: cache alloc, sel=0x%x, size=0x%lx\n",
		     sel, ulSegSize);
#endif

	    }

	/* Decrement amount total left to get */
	ulTotal -= ulSegSize;


    }  /* Ends search for space, while (ultotal) . . . */

    /* When done getting as much as possible, make sure got at least 64k */
    if (ulTotal != 0) {
	if ((ulTotalNeeded - ulTotal) < (ULONG)MIN_CACHE) {

	    MAYBLOCK (
		FSH_CRITERROR (_TXT_MSG_HPFS_NOTENOUGH_MEM.cchMessage,
			       _TXT_MSG_HPFS_NOTENOUGH_MEM.pszMessage,
			       0, NULL, CE_ALLFAIL)
	    );

	    /* BUGBUG - free table, but should really free allocated
	     *	 cache segments, as well.	Exits with error since 64K
	     *	 minimum was not found.
	     */
	    usRet = ERROR_NOT_ENOUGH_MEMORY;
	    goto FSInitExit;

	} else {   /* Did not get what use wanted, but at least 64K */

	    /* Convert parameter into ascii string */
	    CHAR ach [12];
	    INT i;

	    /* find the number of total Kbytes we are getting */
	    USHORT usTotalK = USSHIFTRUL (ulTotalNeeded - ulTotal, 10);

	    for (i = 10; usTotalK && (i > 0); usTotalK /= 10) {
		ach[--i] = ((CHAR)(usTotalK % 10)) + '0';
	    }
	    ach[10] = '\0';

	    MAYBLOCK (
		FSH_CRITERROR(_TXT_MSG_HPFS_WANT_MEM.cchMessage,
			      _TXT_MSG_HPFS_WANT_MEM.pszMessage,
			      1,
			      ach + i, CE_ALLACK)
	    );
	}
    }

    /*
     * Allocate the table which maps a block's selector index to the
     * bufnode array index of the bufnode corresponding to
     * the 0th block in this block segment.  The table holds USHORTs
     * and is (max selector index value - min selector index value) + 2
     * entries long.  The 0th element is the min selector index + 1 to
     * give us a convenient way to remember the min.  The rest of the
     * elements are indices into the bufnode array.
     */

    usMin = 0xffff;
    usMax = 0;
    for (us = 0; us < usSegNum; us++) {
	    usMin = MIN (usMin, SEL_INDEX (ausSelList [us]));
	    usMax = MAX (usMax, SEL_INDEX (ausSelList [us]));
#ifdef DEBUG
	    if (SEL_INDEX (ausSelList [us]) == 0) {
		    dprintf ("CacheInit: SEL_INDEX(ausSelList[%d]) is 0!\n",
			      us);
		    Int3 ();
	    }
#endif
    }

#ifdef MINIFSD
    if (BootStage == 1) {

	if ((usRet = MFSH_SEGALLOC (1,
				(ULONG) ((usMax-usMin+2) * sizeof (USHORT)),
				&sel))) {
			goto FSInitExit;
	}
#ifdef DEBUG
	dprintf (
	"CacheInit: mini stg 1 blocktab alloc, sel=0x%x, size=0x%lx\n",
		 sel,
		 (ULONG) ((usMax - usMin + 2) * sizeof (USHORT)));
#endif
    }
    else {
	if ((usRet = FSH_SEGALLOC (SA_FRING0,
				  (ULONG) ((usMax-usMin+2) *
				  sizeof (USHORT)),
				  &sel))) {
		goto FSInitExit;
	}
#ifdef DEBUG
	dprintf (
	"CacheInit: mini blocktab allocation, sel=0x%x, size=0x%lx\n",
		 sel,
		 (ULONG) ((usMax - usMin + 2) * sizeof (USHORT)));
#endif
    }
#else
    if ((usRet = FSH_SEGALLOC (SA_FRING0,
			      (ULONG) ((usMax-usMin+2) * sizeof (USHORT)),
			      &sel))) {
	    goto FSInitExit;
    }
#ifdef DEBUG
    dprintf ("CacheInit: blocktab allocation, sel=0x%x, size=0x%lx\n",
	     sel,
	     (ULONG) ((usMax - usMin + 2) * sizeof (USHORT)));
#endif
#endif

    pusBlockMap = MAKEP (sel, 0);	/* make a pointer */

#ifdef DEBUG
    /* zero fill this guy */
    memset (pusBlockMap, 0xff, (usMax - usMin + 2) * sizeof (USHORT));

    if (usMin == 0)
	    dprintf ("CacheInit: usMin is wrapping\n");
#endif
    /* usMin could be 0 */
    pusBlockMap [0] = usMin - 1;	/* save the min - 1 */


    /*
     * Every time thru the loop, except for the last, we know that we're
     * dealing with a segment of size CB_CACHE_SEG bytes.
     */
    usBufIndex = 0;
    for (us = 0; us < usSegNum; us++) {
	    pusBlockMap [SEL_INDEX (ausSelList [us]) - pusBlockMap [0]] =
		    usBufIndex;
#ifdef DEBUG2
	    {
	    USHORT usTemp;

	    usTemp = SEL_INDEX (ausSelList [us]) - pusBlockMap [0];
	    dprintf ("CacheInit: pusBlockMap[%d]=%x\n",
		     usTemp,
		     usBufIndex);
	    }
#endif
	    usBufIndex += CB_CACHE_SEG / SPB / SECSIZE;
    }

    /* set the LW threshold for number of buffs dirty before writing */
    LWData.cbufDirtyMax = (cBufs * (PERCENT_DIRTY_THRESH / 10)) /
			  (100 / 10);

#ifdef DEBUG
    dprintf ("CacheInit: cbufDirtyMax=%d\n", LWData.cbufDirtyMax);
#endif

FSInitExit:

    /* done with temp table */
    RetHeap (ausSelList);

#ifdef DEBUG
    if (usRet != NO_ERROR) {
	dprintf ("CacheInit: usRet=%d\n", usRet);
    }
#endif

    return (usRet);
}

#define BUF_ORDINAL(pbuf)	(((USHORT)pbuf - (USHORT)abufCache) / sizeof (BUFNODE))

STATIC void
InitBufNodeList ()
{
    USHORT us;		/* counter */

    /*	Start out with a 0-length lists */

    bufLRUHead.dcbpLru.pbufFwd = bufLRUHead.dcbpLru.pbufBak = &bufLRUHead;
    bufLWHead.dcbpDirt.pbufFwd = bufLWHead.dcbpDirt.pbufBak = &bufLWHead;
//test DCF2     @76575
    bufLWHeadVDU.dcbpDirt.pbufFwd = bufLWHeadVDU.dcbpDirt.pbufBak = &bufLWHeadVDU;

    /* For each buffer, put it in the chain and initialize it's fields. */
    for (us = 0; us < cBufs; us++) {

#ifdef DEBUG
	    if (BUF_ORDINAL (abufCache + us) > cBufs) {

		dprintf ("InitBufNodeList: us out of bounds? us=%d, ", us);
		dprintf ("abufCache=%x, cBufs=%d\n", abufCache, cBufs);
	    }
#endif
	    /* set forward and backward lru pointers */
	    DCADDF (abufCache + us, dcbpLru, bufLRUHead);

	    /* set forward and backward hash pointers and hash dummy */
	    abufCache [us].dcbpHash.pbufBak = &(abufCache [us - 1]);
	    abufCache [us].dcbpHash.pbufFwd = &(abufCache [us + 1]);
	    abufCache [us].ppbufHTA = &pbufHashDummy;

	    /* make lsn invalid */
	    abufCache [us].lsnSec = 0xffffffff;
    }

    /* set us to the index of the last buffer (0 thru cBufs-1) */
    us = cBufs - 1;
    /*
     * Now, bak hash pointer of first points to last, and fwd hash
     * pointer of last points to first.
     */
    abufCache [0].dcbpHash.pbufBak = &(abufCache [us]);
    abufCache [us].dcbpHash.pbufFwd = &(abufCache [0]);

}

STATIC void
LinkUp (pbuf, pblk, culBlocks)
PBUFNODE pbuf;
PBLOCK pblk;
ULONG culBlocks;
{
   while (culBlocks--) {

      pbuf->pblkData = pblk;

#ifdef MINIFSD
      if (BootStage != 1) {
#endif
	 VirtToPhys(pblk,&(pbuf->blkDataPhysAddr));
#ifdef MINIFSD
      }
#endif

      pbuf++;
      pblk++;
   }
}
