/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:buffer.c 12.5$ */
/* $ACIS:buffer.c 12.5$ */
/* $Source: /ibm/acis/usr/sys/afs/RCS/buffer.c,v $ */

#ifndef lint
static char *rcsid = "$Header:buffer.c 12.5$";
#endif

/*
File				buffer.c
Author			Mike Kazar
Date				Now
*/

#ifdef ibm032
#include "../h/param.h"
#endif ibm032
#include "../h/time.h"
#include "../h/kernel.h"

#include "../afs/osi.h"
#include "../afs/lock.h"

#include "../h/buf.h"

/* allocate space from Unix buffer pool */
#define USEBUFFER 0
/* number of pages per Unix buffer, when we're using Unix buffer pool */
#define	NPB 4
/* page size */
#define PAGESIZE 2048
/* log page size */
#define LOGPS 11
/* page hash table size */
#define PHSIZE 32
/* the pHash macro */
#define pHash(fid) ((fid)[0] & (PHSIZE-1))

struct BufferBlock
   {
   struct BufferBlock *next;
   struct buf *addr;
} *BufferList = 0;

struct buffer {
    long fid[5];	/* Unique cache key + i/o addressing */
    long page;
    long accesstime;
    struct buffer *hashNext;
    char *data;
    char lockers;
    char dirty;
    char hashIndex;
} *Buffers;

char *BufferData;

static struct lock afs_bufferLock;
static struct buffer *phTable[PHSIZE];	/* page hash table */
static struct buffer *LastBuffer;
int nbuffers;
int timecounter;
static int calls=0, ios=0;

struct buffer *newslot();

buffer_cleanup() {
    Buffers=0;
    BufferData=0;
    bzero( &afs_bufferLock, sizeof(afs_bufferLock));
    bzero(phTable, sizeof(phTable));
    LastBuffer=0;
    nbuffers=0;
    timecounter=0;
    calls=0;
    ios=0;
}

#ifdef notdef
/* we don't use this one */
int DStat (abuffers, acalls, aios)
    int *abuffers, *acalls, *aios; {
    *abuffers = nbuffers;
    *acalls = calls;
    *aios = ios;
}
#endif

DCleanUp ()
   {
#if USEBUFFER
    struct BufferBlock *tb;
    int freed = 0;
    while (BufferList) {
        tb=BufferList->next;
	brelse( BufferList->addr );
	BufferList = tb;
        freed++;
    }
#else
   osi_Free( BufferData, nbuffers * PAGESIZE ); 
#endif
   osi_Free( Buffers, nbuffers * sizeof(struct buffer) );
   }

int DInit (abuffers)
    int abuffers; {
    /* Initialize the venus buffer system. */
    register int i;
    register struct buffer *tb;
#if USEBUFFER
    struct buf *tub;	    /* unix buffer for allocation */
    struct BufferBlock *bb;
#endif

#if USEBUFFER
    /* round up to next multiple of NPB, since we allocate multiple pages per chunk */
    nbuffers = ((abuffers-1) | (NPB-1)) + 1;
#else
    nbuffers = abuffers;
#endif
    Lock_Init(&afs_bufferLock);
    Buffers = (struct buffer *) osi_Alloc(nbuffers * sizeof(struct buffer));
#if !USEBUFFER
    BufferData = (char *) osi_Alloc(nbuffers * PAGESIZE);
#endif
    timecounter = 0;
    LastBuffer = Buffers;
    for(i=0;i<PHSIZE;i++) phTable[i] = 0;
    for (i=0;i<nbuffers;i++) {
#if USEBUFFER
	if ((i & (NPB-1)) == 0) {
	    /* time to allocate a fresh buffer */
	    tub = geteblk(PAGESIZE*NPB+sizeof (struct BufferBlock));
 	    bb = (struct BufferBlock *) tub->b_un.b_addr;
            bb->next = BufferList;
            bb->addr = tub;
            BufferList = bb;
	    BufferData = (char *) (bb + 1); 
	}
#endif
        /* Fill in each buffer with an empty indication. */
	tb = &Buffers[i];
        dirp_Zap(tb->fid);
        tb->accesstime = tb->lockers = 0;
#if USEBUFFER
	tb->data = &BufferData[PAGESIZE * (i&(NPB-1))];
#else
        tb->data = &BufferData[PAGESIZE*i];
#endif
	tb->hashIndex = 0;
        tb->dirty = 0;
    }
    return 0;
}

char *DRead(fid,page)
    register long *fid;
    register int page; {
    /* Read a page from the disk. */
    register struct buffer *tb;

    ObtainWriteLock(&afs_bufferLock);
    calls++;
    if (LastBuffer->page == page && dirp_Eq(LastBuffer->fid, fid)) {
	tb = LastBuffer;
	tb->accesstime = ++timecounter;
	tb->lockers++;
	ReleaseWriteLock(&afs_bufferLock);
	return tb->data;
    }
    for(tb=phTable[pHash(fid)]; tb; tb=tb->hashNext) {
	if (tb->page == page && dirp_Eq(tb->fid, fid)) {
	    tb->lockers++;
	    tb->accesstime = ++timecounter;
	    LastBuffer = tb;
	    ReleaseWriteLock(&afs_bufferLock);
	    return tb->data;
	}
    }
    /* can't find it */
    tb = newslot(fid, page);
    tb->lockers++;
    if (!dirp_Read(fid,tb->page,tb->data)) {
        dirp_Zap(tb->fid);	/* disaster */
	tb->lockers--;
	ReleaseWriteLock(&afs_bufferLock);
        return 0;
    }
    ios++;
    /* Note that findslot sets the page field in the buffer equal to what it is searching for. */
    ReleaseWriteLock(&afs_bufferLock);
    return tb->data;
}

static FixupBucket(ap)
    register struct buffer *ap; {
    register struct buffer **lp, *tp;
    register int i;
    /* first try to get it out of its current hash bucket, in which it might not be */
    i = ap->hashIndex;
    lp = &phTable[i];
    for(tp = *lp; tp; tp=tp->hashNext) {
	if (tp == ap) {
	    *lp = tp->hashNext;
	    break;
	}
	lp = &tp->hashNext;
    }
    /* now figure the new hash bucket */
    i = pHash(ap->fid);
    ap->hashIndex = i;		/* remember where we are for deletion */
    ap->hashNext = phTable[i];	/* add us to the list */
    phTable[i] = ap;
}

struct buffer *newslot (afid,apage)
    long *afid, apage; {
    /* Find a usable buffer slot */
    register long i;
    long lt,pt;
    register struct buffer *lp, *pp, *tp;

    lp = 0;		/* last non-pure */
    pp = 0;		/* last pure */
    lt = 999999999;
    pt = 999999999;
    tp = Buffers;
    for (i=0;i<nbuffers;i++,tp++) {
	if (tp->lockers == 0) {
	    if (tp->dirty) {
		if (tp->accesstime < lt) {
		    lp = tp;
		    lt = tp->accesstime;
		}
	    }
	    else if (tp->accesstime < pt) {
		pp = tp;
		pt = tp->accesstime;
	    }
	}
    }
    /* If we make it here, the buffer is not in memory.  Find an already-used buffer and trash it.
	If the buffer is dirty, try not to use it.  If it must be used, don't forget to write it out first. */

    if (pp == 0) {
        /* There are no unlocked buffers that don't need to be written to the disk.
	    The variable lx gives the index of the buffer to write out to the disk. */
        if (lp == 0) Die ("all buffers locked");
        if (!dirp_Write(lp->fid,lp->page,lp->data)) Die("writing bogus buffer");
        lp->dirty = 0;
        pp = lp;		/* The buffer to use from now on. */
    }

    /* Now fill in the header. */
    dirp_Cpy(pp->fid, afid);	/* set this */
    pp->page = apage;
    pp->accesstime = ++timecounter;

    FixupBucket(pp);		/* move to the right hash bucket */

    LastBuffer = pp;
    return pp;
}

DRelease (bp,flag)
    register struct buffer *bp;
    int flag; {
    /* Release a buffer, specifying whether or not the buffer has been modified by the locker. */
    register int index;
#if USEBUFFER
    register struct buffer *tp;
#endif

    if (!bp) return;
#if USEBUFFER
    /* look for buffer by scanning Unix buffers for appropriate address */
    tp = Buffers;
    for(index = 0; index < nbuffers; index += NPB, tp += NPB) {
	if ((long)bp >= (long)tp->data && (long)bp < (long)tp->data + PAGESIZE*NPB) {
	    /* we found the right range */
	    index += ((long)bp - (long)tp->data) >> LOGPS;
	    break;
	}
    }
#else
    index = (((char *)bp)-((char *)BufferData))>>LOGPS;
#endif
    bp = &(Buffers[index]);
    ObtainWriteLock(&afs_bufferLock);
    bp->lockers--;
    if (flag) bp->dirty=1;
    ReleaseWriteLock(&afs_bufferLock);
}

DVOffset (ap)
    register struct buffer *ap; {
    /* Return the byte within a file represented by a buffer pointer. */
    register struct buffer *bp;
    register int index;
#if USEBUFFER
    register struct buffer *tp;
#endif
    bp=ap;
#if USEBUFFER
    /* look for buffer by scanning Unix buffers for appropriate address */
    tp = Buffers;
    for(index = 0; index < nbuffers; index += NPB, tp += NPB) {
	if ((long)bp >= (long)tp->data && (long)bp < (long)tp->data + PAGESIZE*NPB) {
	    /* we found the right range */
	    index += ((long)bp - (long)tp->data) >> LOGPS;
	    break;
	}
    }
#else
    index = (((char *)bp)-((char *)BufferData))>>LOGPS;
#endif
    if (index<0 || index >= nbuffers) return -1;
    bp = &(Buffers[index]);
    return PAGESIZE*bp->page+((char *)ap)-bp->data;
}

DZap (fid)
    register long *fid; {
    /* Destroy all buffers pertaining to a particular fid. */
    register struct buffer *tb;
    ObtainWriteLock(&afs_bufferLock);
    for(tb=phTable[pHash(fid)]; tb; tb=tb->hashNext)
        if (dirp_Eq(tb->fid,fid)) {
            dirp_Zap(tb->fid);
            tb->dirty = 0;
	}
    ReleaseWriteLock(&afs_bufferLock);
}

#ifdef notdef
/* we don't seem to use this one */
DFlushEntry (fid)
    register long *fid; {
    /* Flush pages modified by one entry. */
    register struct buffer *tb;
    ObtainWriteLock(&afs_bufferLock);
    for(tb = phTable[pHash(fid)]; tb; tb=tb->hashNext)
        if (tb->dirty && dirp_Eq(tb->fid, fid)) {
            if (dirp_Write(tb->fid, tb->page, tb->data)) tb->dirty = 0;
	}
    ReleaseWriteLock(&afs_bufferLock);
}
#endif

DFlush () {
    /* Flush all the modified buffers. */
    register int i;
    register struct buffer *tb;

    tb = Buffers;
    ObtainWriteLock(&afs_bufferLock);
    for(i=0;i<nbuffers;i++,tb++) {
        if (tb->dirty && dirp_Write(tb->fid, tb->page, tb->data))
            tb->dirty = 0;	/* Clear the dirty flag */
    }
    ReleaseWriteLock(&afs_bufferLock);
}

char *DNew (fid,page)
    register int page;
    register long *fid; {
    /* Same as read, only do *not* even try to read the page, since it probably doesn't exist. */
    register struct buffer *tb;
    ObtainWriteLock(&afs_bufferLock);
    if ((tb = newslot(fid,page)) == 0) {
	ReleaseWriteLock(&afs_bufferLock);
	return 0;
    }
    tb->lockers++;
    ReleaseWriteLock(&afs_bufferLock);
    return tb->data;
}
