
/*
 * Copyright 1987 Alan Kent
 *
 * Permission is granted to redistribute this code as long
 * as this message is retained in the code and the code is
 * not sold without written permission from the author.
 *
 * UUCP: {seismo,hplabs,mcvax,ukc,nttlab}!munnari!goanna.oz!ajk
 * ACSnet: ajk@goanna.oz
 * ARPA: munnari!goanna.oz!ajk@SEISMO.ARPA
 */


#include "hd.h"


#define CACHE_SIZE		40

/* Cache Node types */

#define NOT_IN_CACHE	0
#define CN_SPECIAL		1
#define CN_HISTORY		2
#define CN_DIRTY		3


#define HASH_SIZE		(CACHE_SIZE*3)
#define HASH(block)		(((block)&0x7fff)%HASH_SIZE)

static struct cache *	hash_table[ HASH_SIZE ];


static struct cache {

	struct Node node;
	struct cache *collision;
	struct posn posn;
	int			type;
	UBYTE		buf[ HD_SECTOR ];

} cache[ CACHE_SIZE ];


#define DIRTY_LIMIT		10
#define SPECIAL_LIMIT	( CACHE_SIZE - DIRTY_LIMIT - 7 )
#define MAX_BASES		10


extern struct cache *	cache_search ();
extern struct Node *	RemHead ();
extern struct Node *	RemTail ();
extern LONG				log_to_phys ();


static struct List	history;
static struct List	dirty;
static int			num_dirty;
static struct List	special;
static int			special_count;
static int			num_bases = 0;
static LONG			bases[ MAX_BASES ];


UBYTE *
read_cache ( posn )
struct posn *posn;
{
	register struct cache *c;
	int i;
	struct posn next_posn;


	/* see if sector is in special cache list */

	c = cache_search ( posn );
	if ( c != NULL ) {

		switch ( c->type ) {

		case CN_SPECIAL :

			/* special list still good for a bit longer! */
			
			special_count = 5;
			Remove ( c );
			AddHead ( &history , c );
			c->type = CN_HISTORY;
			return ( c->buf );

		case CN_DIRTY :

			/* yes, well have to leave it there! */

			return ( c->buf );

		case CN_HISTORY :
			Remove ( c );
			AddHead ( &history , c );	/* move to head of history */
			c->type = CN_HISTORY;
			return ( c->buf );
		}
	}

	/* see if the special_case is really being used for anything */

	if ( --special_count <= 0 ) {

		/* move everything left in the special cache over to the normal */
		/* cache - it does not seem to be needed anymore */

		while ( ( c = (struct cache *)special.lh_Head )->node.ln_Succ != NULL ) {
			Remove ( c );
			AddTail ( &history , c );
			c->type = CN_HISTORY;
		}
	}

	/* ok, well we have to actually go and read the sector! */

	/* if the special cache list is not empty, just read a sector */

	if ( special.lh_Head->ln_Succ != NULL ) {

		c = (struct cache *) RemTail ( &history );
		new_posn ( c , posn );
		flush_seek ( &c->posn );
		read_sector ( &c->posn , c->buf );
		analyse ( c , &next_posn );
		AddHead ( &history , c );
		c->type = CN_HISTORY;
		return ( c->buf );
	}

	/* ok!! now, the special list is EMPTY! Is the disk access we want */
	/* to do the NEXT of a recently accessed page? */

	for ( i = 0, c = (struct cache *) history.lh_Head;
	i < 5  &&  c->node.ln_Succ != NULL;
	i++, c = (struct cache *)c->node.ln_Succ ) {
		if ( is_next ( posn , c ) ) {

			/* GOODY! Looks like we have got something to pre-read! */

			/* first special entry will be what we asked for */
			next_posn = *posn;

			for ( i = 0; i < SPECIAL_LIMIT; i++ ) {

				/* if in any of the caches, dont re-read! */

				if ( cache_search ( &next_posn ) != NULL )
					break;

				c = (struct cache *) RemTail ( &history );
				new_posn ( c , &next_posn );
				flush_seek ( &c->posn );
				read_sector ( &c->posn , c->buf );
				analyse ( c , &next_posn );
				AddTail ( &special , c );
				c->type = CN_SPECIAL;

				/* end of list will set -ve values */

				if ( next_posn.cylinder < 0 )
					break;
			}
			c = (struct cache *) special.lh_Head;
			Remove ( c );
			AddHead ( &history , c );
			c->type = CN_HISTORY;
			special_count = 5;
			return ( c->buf );
		}
	}

	/* Oh well, its just a boring read then */

	c = (struct cache *) RemTail ( &history );
	new_posn ( c , posn );
	flush_seek ( &c->posn );
	read_sector ( &c->posn , c->buf );
	analyse ( c , &next_posn );
	AddHead ( &history , c );
	c->type = CN_HISTORY;
	return ( c->buf );
}


void
write_cache ( posn , buf )
struct posn *posn;
char *buf;
{
	register struct cache *c , *scan;


	/* is sector already in a cache? */

	c = cache_search ( posn );
	if ( c != NULL ) {

		if ( c->type == CN_DIRTY ) {

			/* dirty sectors can be left were they are */

			copy_sector ( buf , c->buf );
			return;
		}

		/* otherwise, fall through - it will have to be put in the */
		/* dirty list */

	}
	else {

		/* get a cache for the sector. */

		c = (struct cache *) history.lh_TailPred;
	}

	/* Ok, we have got a sector to put everything in */

	Remove ( c );
	new_posn ( c , posn );
	copy_sector ( buf , c->buf );

	/* now, we need a sorted insertion to keep dirty list sorted by cyl */

	if ( dirty.lh_Head->ln_Succ != NULL ) {
		for ( scan = (struct cache *)dirty.lh_Head;
		scan->node.ln_Succ != NULL;
		scan = (struct cache *) scan->node.ln_Succ ) {
			if ( scan->posn.cylinder > posn->cylinder )
				break;
		}
		Insert ( &dirty , c , scan->node.ln_Pred );
	}
	else {		/* only node in list */
		AddHead ( &dirty , c );
	}
	c->type = CN_DIRTY;
	num_dirty++;

	/* if too many dirty pages, probably should write some out */

	if ( num_dirty > DIRTY_LIMIT ) {

		/* try and find a close cylinder */

		for ( scan = (struct cache *)dirty.lh_Head;
		scan->node.ln_Succ != NULL;
		scan = (struct cache *)scan->node.ln_Succ ) {
			if ( scan->posn.cylinder >= cur_posn.cylinder )
				break;
		}

		/* if at end of list, just use last entry */

		if ( scan->node.ln_Succ == NULL )
			scan = (struct cache *) scan->node.ln_Pred;
		write_dirty ( scan );
	}
}



void
flush_all ()
{
	while ( dirty.lh_Head->ln_Succ != NULL )
		write_dirty ( dirty.lh_Head );
}


write_dirty ( c )
register struct cache *c;
{
	Remove ( c );
	num_dirty--;
	write_sector ( &c->posn , c->buf );
	AddTail ( &history , c );
	c->type = CN_HISTORY;
}


void
clear_all ()
{
	register int i;
	register struct cache *c;

	NewList ( &special );
	NewList ( &dirty );
	NewList ( &history );
	special_count = 0;
	num_dirty = 0;
	for ( i = 0; i < HASH_SIZE; i++ )
		hash_table[i] = NULL;
	for ( i = 0; i < CACHE_SIZE; i++ ) {
		c = &cache[i];
		c->posn.cylinder = -3;
		c->posn.sector = 0;
		c->posn.surface = 0;
		c->posn.block = -3 * first.sectors * first.heads;
		AddTail ( &history , c );
		c->type = CN_HISTORY;
		c->collision = hash_table[ HASH(c->posn.block) ];
		hash_table[ HASH(c->posn.block) ] = c;
	}
}


int
init_cache ()
{
	register int i;

	clear_all ();
	return ( 0 );
}


void
free_cache ()
{
	register int i;

	flush_all ();
}


struct cache *
cache_search ( posn )
register struct posn *posn;
{
	register struct cache *c;

	c = hash_table[ HASH(posn->block) ];
	while ( c != NULL ) {
		if ( posn->block == c->posn.block )
			return ( c );
		c = c->collision;
	}
	return ( NULL );
}


new_posn ( c , posn )
register struct cache *c;
struct posn *posn;
{
	register struct cache **old;
	register struct cache **h;

	/* first, remove from existing hash table position */

	old = &hash_table[ HASH(c->posn.block) ];
	while ( *old != NULL ) {
		if ( *old == c ) {				/* found pointer to c */
			*old = (*old)->collision;	/* unlink node */
			break;
		}
		old = &(*old)->collision;
	}

	/* now update the position */

	c->posn = *posn;

	/* now insert into hash table at new position */

	h = &hash_table[ HASH(c->posn.block) ];
	c->collision = *h;
	*h = c;
}



flush_seek ( posn ) /* write out all dirty tracks on way to posn */
struct posn *posn;
{
	register struct cache *c;

	if ( posn->cylinder > cur_posn.cylinder ) {
		for ( c = (struct cache *)dirty.lh_Head;
		c->node.ln_Succ != NULL;
		c = (struct cache *)c->node.ln_Succ ) {
			if ( c->posn.cylinder >= cur_posn.cylinder
			&&  c->posn.cylinder <= posn->cylinder ) {
				c = (struct cache *)c->node.ln_Pred;
				write_dirty ( c->node.ln_Succ );
			}
		}
	}
	else {
		for ( c = (struct cache *)dirty.lh_TailPred;
		c->node.ln_Pred != NULL;
		c = (struct cache *)c->node.ln_Pred ) {
			if ( c->posn.cylinder <= cur_posn.cylinder + 1
			&&  c->posn.cylinder >= posn->cylinder ) {
				c = (struct cache *)c->node.ln_Succ;
				write_dirty ( c->node.ln_Pred );
			}
		}
	}
}


/* have a look, check block # to see if any new partitions */
analyse ( c , next_posn )
register struct cache *c;
register struct posn *next_posn;
{
	LONG logical_block , base;
	LONG type , subtype;
	ULONG *buf;


	buf = (ULONG*) c->buf;
	type = buf[0];
	subtype = buf[127];

	if ( type == 2  &&  subtype == 2		/* File Header */
	||   type == 2  &&  subtype == -3 ) {	/* User Directory */

		/* these blocks point to themselves, so we can use block # */
		/* to work out where partitions start */

		base = c->posn.block - buf[1];

		/* see if we know about this base yet, if not add it */

		new_base ( base );
	}

	/* ok, now look for a next pointer */

	get_next ( c , next_posn );
}


get_next ( c , next_posn )
register struct cache *c;
register struct posn *next_posn;
{
	LONG type , subtype , next , block;
	ULONG *buf;

	buf = (ULONG *) c->buf;
	type = buf[0];
	subtype = buf[127];
	next = buf[4];

	next_posn->block = -1;
	next_posn->sector = -1;
	next_posn->surface = -1;
	next_posn->cylinder = -1;

	if ( type == 2  &&  subtype == -3		/* file header */
	||   type == 16 &&  subtype == -3		/* file extension */
	||   type == 8 ) {						/* data block */
		block = log_to_phys ( c->posn.block , next );
		if ( block >= 0
		&&  block < first.sectors * first.heads * first.cylinders ) {
			next_posn->block = block;
			next_posn->sector = block % first.sectors;
			next_posn->surface = ( block / first.sectors ) % first.heads;
			next_posn->cylinder = block / ( first.sectors * first.heads );
		}
	}
}


LONG
log_to_phys ( real , logical )
LONG real , logical;
{
	register int i;

	for ( i = num_bases - 1; i >= 0; i-- )
		if ( real >= bases[i] )
			return ( logical + bases[i] );
	return ( -1 );
}


new_base ( base )
LONG base;
{
	register int i , j;

	for ( i = num_bases - 1; i >= 0; i-- ) {
		if ( bases[i] == base )
			return;
		if ( bases[i] < base )
			break;
	}
	i++;
	for ( j = num_bases; j > i; j-- )
		bases[j] = bases[j-1];
	bases[i] = base;
	num_bases++;
}


is_next ( posn , c )
struct posn *posn;
struct cache *c;
{
	struct posn next_posn;

	get_next ( c , &next_posn );
	return ( posn->block == next_posn.block );
}
