/**************************************************
UZI (Unix Z80 Implementation) Kernel:  devio.c
***************************************************/


int ok(), nogood();
#define DEVIO

#include <sys/unix.h>
#include <sys/extern.h>

/* Buffer pool management */

/********************************************************
The high-level interface is through bread() and bfree().
Bread() is given a device and block number, and a rewrite flag.
If rewrite is 0, the block is actually read if it is not already
in the buffer pool. If rewrite is set, it is assumed that the caller
plans to rewrite the entire contents of the block, and it will
not be read in, but only have a buffer named after it.

Bfree() is given a buffer pointer and a dirty flag.
If the dirty flag is 0, the buffer is made available for further 
use.  If the flag is 1, the buffer is marked "dirty", and
it will eventually be written out to disk.  If the flag is 2,
it will be immediately written out.

Zerobuf() returns a buffer of zeroes not belonging to any
device.  It must be bfree'd after use, and must not be
dirty. It is used when a read() wants to read an unallocated
block of a file.

Bufsync() write outs all dirty blocks.

Note that a pointer to a buffer structure is the same as a
pointer to the data.  This is very important.

********************************************************/


unsigned bufclock = 0;  /* Time-stamp counter for LRU */


 /*
 |  Reads a block and returns a pointer to it
*/
char *bread( dev, blk, rewrite )
int	dev;
blkno_t	blk;
int	rewrite;
{
	bufptr	bp;
	bufptr	bfind();
	bufptr	freebuf();

	if( bp = bfind(dev,blk) )
	{
		if( bp->bf_busy )
			panic( "want busy block" );
		goto done;
	}
	bp = freebuf();

	/* If rewrite is set, we are about to write over the entire block,
	so we don't need the previous contents */

	bp->bf_dev = dev;
	bp->bf_blk = blk;

	ifnot( rewrite )
		if( bdread(bp) == -1 )
		{
			udata.u_error = EIO;
			return( NULL );
		}

	if( rewrite == 2 )
		bzero( bp->bf_data, 512 );

	done:

	bp->bf_dev = dev;
	bp->bf_blk = blk;

	bp->bf_busy = 1;
	bp->bf_time = ++bufclock;  /* Time stamp it */
	return( bp->bf_data );
}


 /*
 |  Realeases a buffer, discarding contents
*/
int brelse(bp)
bufptr	bp;
{
	bfree( bp, 0 );
}


 /*
 |  Writes a buffer to disc, then releases it
*/
int bawrite(bp)
bufptr	bp;
{
    bfree( bp, 1 );
}


 /*
 |  Frees a buffer, either writing or discarding contents
*/
int bfree( bp, dirty )
bufptr	bp;
int	dirty;
{
    bp->bf_dirty |= dirty;
    bp->bf_busy   = 0;

    if( dirty == 2 )   /* Extra dirty */
    {
        if( bdwrite(bp) == -1 )
            udata.u_error = EIO;
        bp->bf_dirty = 0;
        return( -1 );
    }
    return( 0 );
}


 /*
 |  Returns a zeroed empty buffer
*/
char *zerobuf()
{
	bufptr	bp;
	bufptr	freebuf();

	bp = freebuf();
	bp->bf_dev = -1;
	bzero( bp->bf_data, 512 );
	return( bp->bf_data );
}


 /*
 |  Writes out all dirty blocks
*/
int bufsync()
{
	bufptr bp;

	for( bp=bufpool; bp < bufpool+NBUFS; ++bp )
	{
		if( bp->bf_dev != -1 && bp->bf_dirty )
		bdwrite( bp );
	}
}


 /*
 |  Looks through the buffers in the pool for a dev/block
 |  conbination that has already been read in
*/
bufptr bfind( dev, blk )
int	dev;
blkno_t	blk;
{
	bufptr bp;

	for( bp=bufpool; bp < bufpool+NBUFS; ++bp )
	{
		if( bp->bf_dev == dev && bp->bf_blk == blk )
			return( bp );
	}

	return( NULL );
}


 /*
 |  Get a buffer by throwing out the oldest non-busy one
*/
bufptr freebuf()
{
	bufptr	bp;
	bufptr	oldest;
	int	oldtime;

	/* Try to find a non-busy buffer 
	and write out the data if it is dirty */

	oldest = NULL;
	oldtime = 0;
	for( bp=bufpool; bp < bufpool+NBUFS; ++bp )
	{
		if( ( bufclock-bp->bf_time >= oldtime ) && !bp->bf_busy )
		{
			oldest = bp;
			oldtime = bufclock - bp->bf_time;
		}
	}

	ifnot( oldest )
		panic( "no free buffers" );

	/* If it's busy, write it out */
	if( oldest->bf_dirty )
	{
		if( bdwrite(oldest) == -1 )
			udata.u_error = EIO;
		oldest->bf_dirty = 0;
	}
	return( oldest );
}


 /*
 |  Initialise the buffer pool by setting all owners to -1
*/
int bufinit()
{
	bufptr bp;

	for( bp=bufpool; bp < &bufpool[NBUFS]; bp++ )
		bp->bf_dev = -1;
}


 /*
 |  Output information about the buffer pool
 |  Looks like debugging stuff to me
*/
int bufdump()
{
	bufptr j;

	kprintf( "\ndev\tblock\tdirty\tbusy\ttime clock %d\n", bufclock );
	for( j=bufpool; j < &bufpool[NBUFS]; j++ )
		kprintf("%d\t%u\t%d\t%d\t%u\n", j->bf_dev,j->bf_blk,j->bf_dirty,j->bf_busy,j->bf_time);
}


/***************************************************
Bdread() and bdwrite() are the block device interface routines.
they are given a buffer pointer, which contains the device, block number,
and data location.
They basically validate the device and vector the call.

Cdread() and cdwrite are the same for character (or "raw") devices,
and are handed a device number.
Udata.u_base, count, and offset have the rest of the data.
****************************************************/

 /*
 |  Read a buffer from disc by calling the dev_read function
 |  from the device switch
*/
int bdread( bp )
bufptr	bp;
{
	int	foo;
	int	(*dud)();

	ifnot( validdev(bp->bf_dev) )
		panic( "bdread: invalid dev" );

	udata.u_buf = bp;
	
	dud = dev_tab[bp->bf_dev].dev_read;
	foo = ( (*dud)( dev_tab[bp->bf_dev].minor, 0 ) );

	ifnot( validdev(bp->bf_dev) )
		panic( "bdread: invalid dev (after read)" );

	return foo;
}


 /*
 |  Compliment to above:  write a buffer to disc using the
 |  dev_write function from the switch table
*/
int bdwrite( bp )
bufptr	bp;
{
	int	(*dud)();

	ifnot( validdev(bp->bf_dev) )
	{
		panic( "bdwrite: invalid dev" );
	}
	
	udata.u_buf = bp;

	dud = dev_tab[bp->bf_dev].dev_write;
	return( (*dud)( dev_tab[bp->bf_dev].minor, 0 ) );
}


 /*
 |  Performs a read from a character device, using the switch.
*/
int cdread( dev )
int	dev;
{
	int	(*dud)();
	
	ifnot( validdev(dev) )
		panic("cdread: invalid dev");
		
	dud = dev_tab[dev].dev_read;
	return( (*dud)( dev_tab[dev].minor, 1 ) );
}


 /*
 |  Write to a character device.
*/
int cdwrite( dev )
int	dev;
{
	int	(*dud)();

	ifnot( validdev(dev) )
		panic( "cdwrite: invalid dev" );

	dud = dev_tab[dev].dev_write;
	return( (*dud)( dev_tab[dev].minor, 1 ) );
}


 /*
 |  Not entirely sure.  Looks like it reads from the swap
 |  device, communicating with the swapping subsystem via
 |  some global variables
*/
int swapread( dev, blkno, nbytes, buf )
int		dev;
blkno_t		blkno;
unsigned	nbytes;
char		*buf;
{
	int	(*dud)();
	
	swapbase = buf;
	swapcnt  = nbytes;
	swapblk  = blkno;
	
	dud = dev_tab[dev].dev_read;
	return( (*dud)( dev_tab[dev].minor, 2 ) );
}


 /*
 |  Compliment to the above.  Writes to the swap device?
*/
int swapwrite( dev, blkno, nbytes, buf )
int		dev;
blkno_t		blkno;
unsigned	nbytes;
char		*buf;
{
	int	(*dud)();
	
	swapbase = buf;
	swapcnt = nbytes;
	swapblk = blkno;
	
	dud = dev_tab[dev].dev_write;
	return( (*dud)( dev_tab[dev].minor, 2 ) );
}


/**************************************************
The device driver read and write routines now have
only two arguments, minor and rawflag.  If rawflag is
zero, a single block is desired, and the necessary data
can be found in udata.u_buf.
Otherwise, a "raw" or character read is desired, and
udata.u_offset, udata.u_count, and udata.u_base
should be consulted instead.
Any device other than a disk will have only raw access.
*****************************************************/


 /*
 |  Opens a device from the switch
*/
int d_open( dev )
int	dev;
{
	int	(*dud)();
	
	ifnot( validdev(dev) )
		return( -1 );

	dud = dev_tab[dev].dev_open;
	return( (*dud)( dev_tab[dev].minor ) );
}


 /*
 |  Closes a device.  No real return value
*/
int d_close( dev )
int	dev;
{
	int	(*dud)();
	
	ifnot (validdev(dev))
		panic( "d_close: bad device" );
	dud = dev_tab[dev].dev_close;
	(*dud)( dev_tab[dev].minor );
}


 /*
 |  Performs an ioctl request on a device
*/
int d_ioctl( dev, request, data )
int	dev;
int	request;
char	*data;
{
	int	(*dud)();
	
	ifnot( validdev(dev) )
	{
		udata.u_error = ENXIO;
		return( -1 );
	}
	
	dud = dev_tab[dev].dev_ioctl;
	if( (*dud)( dev_tab[dev].minor, request, data) )
	{
		udata.u_error = EINVAL;
		return( -1 );
	}
	
	return( 0 );
}


 /*
 |  Odd.
*/
static int ok()
{
	return( 0 );
}


 /*
 |  Equally strange
*/
static int nogood()
{
	return( -1 );
}


 /*
 |  Tries to decide if a device number is value, by:
 |	 (i) Is it greater than or equal to zero?
 |	(ii) Is it less than the size of the table?
*/
int validdev( dev )
int	dev;
{
	return( dev >= 0 && dev < (sizeof(dev_tab)/sizeof(struct devsw)) );
}


/*************************************************************
Character queue management routines
************************************************************/


 /*
 |  Add something to the tail of a clist (s_queue)
 |  Unlike real V7 this seems not to move between blocks
 |  but rather be limited to one block per list. 
*/
int insq( q, c )
struct s_queue	*q;
char			c;
{
	di();

	/* Check maximum size, and maybe fail */
	if( q->q_count == q->q_size )
	{
		ei();
		return(0);
	}

	/* Write and wrap the tail around if necessary */
	*(q->q_tail) = c;
	++q->q_count;
	if( ++q->q_tail >= q->q_base + q->q_size )
		q->q_tail = q->q_base;

	ei();
	return( 1 );
}


 /*
 |  Remove something from the head of a queue
*/
int remq( q, cp )
struct s_queue	*q;
char			*cp;
{
	di();

	/* Fail if the count is zero */
	ifnot( q->q_count )
	{
		ei();
		return( 0 );
	}

	/* Read and wrap the point round if necessary */
	*cp = *(q->q_head);
	--q->q_count;
	if( ++q->q_head >= q->q_base + q->q_size )
		q->q_head = q->q_base;

	ei();
	return( 1 );
}


 /*
 |  Remove something from the tail; the most recently added char.
*/
int uninsq( q, cp )
struct s_queue	*q;
char			*cp;
{
	di();

	/* Can't remove from an empty queue! */
	ifnot( q->q_count )
	{
		ei();
		return(0);
	}

	/* Remove and wrap around */
	--q->q_count;
	if (--q->q_tail <= q->q_base)
		q->q_tail = q->q_base + q->q_size - 1;
	*cp = *(q->q_tail);
	
	ei();
	return( 1 );
}


 /*
 |  Returns true if the queue has more characters than its wakeup number
*/
int fullq( q )
struct s_queue	*q;
{
	di();
	
	if( q->q_count > q->q_wakeup )
	{
		ei();
		return( 1 );
	}
	
	ei();
	return( 0 );
}


 /*
 |  Output a character on the emergency channel
 |  This should be in appli.c, but only devio has access to dev_tab[]
*/
#include <sys/devtty.h>
extern oneterm vterms[NOTERMS];
int kputchar( c )
char    c;
{
	termPrint( &(vterms[dev_tab[TTYDEV].minor]), c );
}

