
/*
 * hypersubs.c: version 3.3 of 6/4/85
 *
 *  hypersubs.c -- Hyper-Exec utility subroutines
 *  copyright (c) American Information Systems Corporation
 *	Daniel Steinberg  November, 1984
 */
#ifdef SCCS
static char *sccsid = "@(#)hypersubs.c	3.3";
#endif

#include "vinc/viosconf.h"
#include "vinc/iopacket.h"
#include "vinc/viostatus.h"
#include "vinc/pktfuncs.h"
#include "vinc/poolfuncs.h"

#include "INC5/3200mmu.h"

#define PGSZ sizeof(PAGE_)		/* size of a page of memory */

					/* throw: V_BAD_BUFFER/V_ALL_FAILURE */
map_buffer (pkt)
    PKT_PTR pkt;	/* i/o packet */

/* map_buffer (pkt) -- Verify (and optionally re-map) an i/o buffer
 *
 *	in:	pkt		I/O Packet bufptr
 *	return:	(int)		FALSE if buffer is physically mapped
 *				TRUE if buffer remapped and a new I/O pkt queued
 *	thrown:	V_BAD_BUFFER	buffer is not available at all
 *		V_ALL_FAILURE	allocation failed for packet duplication
 *
 *	If the buffer spec in 'pkt' is physically-mapped, returns FALSE.
 *
 *	If buffer is a Mapped-Buffer (Ubufmap(*pkt) == Segment Table Address),
 *	re-map it as follows:
 *		1) Check that the Segment Table is in valid memory.
 *		2) Allocate, from pool, a mapping table large enough to
 *		   fit mapping ptrs and lengths for the entire request.
 *		3) Convert the buffer Virtual Address and size to a series
 *		   of mapping entries (Physical Address and size), ending with
 *		   an entry with zero size.  (Check each Page Table address
 *		   before accessing it.)
 *		4) Duplicate the current I/O packet (free the mapping table, if
 *		   allocation error), set the child to return status to the
 *		   parent, copy the device spec, set the child buffer spec to
 *		   the address of the mapping buffer, and set the parent Aux.
 *		   Param to the mapping buffer so that the newly allocated
 *		   buffer will be released when the I/O completes.
 *		5) Increment the child Stnum, queue it, and return TRUE.
 *
 *	Throws V_BAD_BUFFER if the buffer is a VIOS buffer (map_buffer() should
 *		only be called for initial packet processing).
 *	Throws V_BAD_BUFFER if the user page table is not valid.
 *	Throws V_ALL_FAILURE if the buffer mapping table or child packet could
 *	not be allocated.
 */
{
    register IO_PACKET *pk;
    int cnt;
    register int size;
    register unsigned addr;
    register unsigned ptb;
    register unsigned ptr;
    MAP_LIST *map;
    MAP_LIST **mptr;
    PKT_PTR npkt;

    switch (ptb = (unsigned) Ubufmap(pk = *pkt))
	{
    case POOL_BUFFER:			/* buffer is in VIOS pool */
    case MAPPED_BUFFER:			/* buffer is already re-mapped */
badbuf:	throw(V_BAD_BUFFER);		/* These are illegal from OS */

    case PHYS_BUFFER:			/* buffer address is physical */
	return(FALSE);

    default:				/* buffer Segment Table provided */
	if (chk_bitmap(ptb, sizeof(SEGMENT_TABLE)) EQ 0)
	    goto badbuf;		/* bad segment table address */

	cnt = Ubufsize(pk);		/* get buffer length */

	/* Allocate a buffer mapping table long enough for any buffer.
	 * Last entry has zero size, therefore smallest table has 2 entries.
	 * A buffer of 2 bytes may span a page boundary, therefore the
	 *  expression  (PGSZ -2)/PGSZ  guarantees an extra table entry.
	 */
	map = *(mptr = (MAP_LIST**)
		    valloc((((cnt + PGSZ - 2)/PGSZ) + 2) * sizeof(MAP_LIST)));
	
	if (catch() NE 0)	/* if any errors thrown from below */
	    {
	    free(mptr);		/* release buffer map table */
	    rethrow();		/* and send error back up */
	    }
	
	addr = (unsigned) Ubufaddr(pk = *pkt);	/* get Virtual Address */

	while (cnt GT 0)		/* loop until entire buffer remapped */
	    {
	    /* get Segment Table entry for this Virtual Address */
	    ptr = * ((unsigned*)(ptb + ((addr & INDEX1) >> ISHFT1)));

	    /* NOTE THAT THE FOLLOWING CODE CHECKS THE PAGE TABLE TOO OFTEN */
	    /* AND SHOULD BE CHANGED TO DETECT PAGE TABLE BOUNDARIES */

	    /* verify that the Page Table is valid */
	    if (
					/* (!(ptr & VALID)) OR */
			(chk_bitmap((ptr &= PFN), sizeof(PAGE_TABLE)) EQ 0) )
		goto badbuf;

	    /* get Page Table entry for this Virtual Address */
	    ptr = * ((unsigned*)(ptr + ((addr & INDEX2) >> ISHFT2)));

	    /* verify that the Page is valid */
					/* if (!(ptr & VALID))
					    goto badbuf; */
	    
	    /* calculate Physical address and store in map table */
	    /* ('size' gets byte offset in page) */
	    Maddr(map) = (char*) ((ptr & PFN) + (size = addr & OFFSET));

	    /* figure out how many bytes were just mapped */
	    if ((size = PGSZ - size) GT cnt)
		size = cnt;		/* reached end of buffer */
	    
	    Msize(map++) = size;	/* save map size in table */

	    cnt -= size;		/* update buffer byte count */
	    addr += size;		/* get next Virtual Address to map */
	    } /*while-cnt*/
	
	Msize(map) = 0;		/* zero last entry */

	/* At this point, the entire buffer has been mapped.
	 * Now allocate a child I/O packet and set it up to take over.
	 */
	pk = *(npkt = duppkt(pkt));	/* V_ALL_FAILURE could be thrown */

	resetdev(npkt, VDevice(*pkt));	/* duplicate the primary device */
	Ubufmap(pk) = (char*) MAPPED_BUFFER;	/* flag special map table */
	Ubufaddr(pk) = (char*) mptr;	/* set bufptr to map table */
	Parentstat(pk) = (int**) pkt;	/* return status to parent pkt */
	Stnum(pk)++;			/* child starts at next state number */
	q_virtual(npkt);		/* queue the child packet */

	Auxparam(*pkt) = (char**) mptr;	/* parent will release the map table */
	uncatch();			/* end of protected code */

	return(TRUE);			/* buffer successfully mapped */

	} /*switch-Ubufmap*/
}

						/* throw: V_BAD_BUFFER */
chk_buffer (pkt)
    PKT_PTR pkt;	/* i/o packet */

/* chk_buffer (pkt) -- Verify that an i/o buffer is legal
 *
 *	in:	pkt		I/O Packet bufptr
 *	return:	(int)		TRUE if buffer is legal and read/write
 *				FALSE if buffer is read-only
 *	thrown:	V_BAD_BUFFER	buffer is not available at all
 *
 *	Returns if the i/o buffer in 'pkt'  (Ubuffer(*pkt))
 *	is at a valid memory address, according to the memory bitmap.
 *	Returns TRUE if buffer is read/write; FALSE if read-only.
 *
 *	Throws V_BAD_BUFFER if the buffer is not available at all.
 *
 */
{
    register int i;
    register IO_PACKET *pk;
    register int cnt;
    register MAP_LIST *map;

    switch (Ubufmap(pk = *pkt))
	{
    case POOL_BUFFER:		/* if buffer is in VIOS pool */
	return(TRUE);		/* it is always r/w */

    case PHYS_BUFFER:		/* if buffer address is a physical address */
	/* Check memory bitmap, throw V_BAD_BUFFER if memory not available */
	if ( (i = chk_bitmap(Ubufaddr(pk), Ubufsize(pk))) EQ 0)
	    throw(V_BAD_BUFFER);	/* memory not available at all */
	break;

    case MAPPED_BUFFER:		/* if buffer is now a mapping table */
	i = 1;		/* assume buffer is read/write */

	map = * ((MAP_LIST**)Ubufaddr(pk));	/* get address of map table */

	while ((cnt = Msize(map)) GT 0)	/* loop while valid entries */
	    {
	    if ((cnt = chk_bitmap(Maddr(map++), cnt)) EQ 0)
		throw(V_BAD_BUFFER);		/* bad physical address range */
	    
	    if (cnt LT 0)			/* if read-only */
		i = -1;				/* then entire buffer is r-o */
	    } /*while-map entries*/
	
	break;

#ifdef DEBUG3   /*************************************************************/
    default:
	throw(V_BAD_BUFFER);		/* buffer wasn't already remapped */
#endif /* DEBUG3 *************************************************************/

	} /*switch-Ubufmap*/

    if (i LT 0)
	return(FALSE);		/* read-only */
    else
	return(TRUE);		/* read-write */
}

#ifdef V_OS /************ VIRTUAL OPERATING SYSTEM SUPPORT *************/
						/* throw: V_SYS_WAIT */
lock_buffer (pkt)
    register PKT_PTR pkt;	/* i/o packet */

/* lock_buffer (pkt) -- Lock an i/o buffer in memory
 *
 *	in:	pkt		I/O Packet bufptr
 *	return:	(NONE)		returns if i/o buffer page is in memory
 *	thrown:	V_SYS_WAIT	if buffer is not yet locked
 *
 *	Returns if the i/o buffer in 'pkt'  (Ubuffer(*pkt))
 *	is resident and locked in memory.
 *	Throws V_SYS_WAIT if the page was locked out and is now scheduled
 *	for loading.  When the page is loaded and locked, the Hyper-Exec
 *	will requeue the i/o packet and reschedule the VIOS.
 *
 *	**** NOT YET IMPLEMENTED ****
 */
{
    return(TRUE);			/* always true for now */
}


unlock_buffer (pkt)
    register PKT_PTR pkt;

/* unlock_buffer (pkt) -- Allow an i/o buffer to be paged out of memory
 *
 *	in:	pkt		I/O Packet bufptr
 *	return:	(NONE)
 *	thrown:	(NONE)
 *
 *	Signals the Hyper-Exec that the i/o request at 'pkt' no longer
 *	requires that its i/o buffer (Ubuffer(*pkt)) be resident in memory,
 *	presumably because the data has been copied to the host memory.
 *
 *	**** NOT YET IMPLEMENTED ****
 */
{
}

    BOOLEAN
buf_io_alloc (pkt)
    register PKT_PTR pkt;

/* buf_io_alloc (pkt) -- Initiate buffered i/o, if feasible
 *
 *	in:	pkt		I/O packet bufptr
 *	return:	(BOOLEAN)	TRUE if i/o was buffered, & child pkt requeued
 *				NULL if i/o could not be buffered
 *	thrown:	(NONE)
 *
 *	If the i/o buffer size of 'pkt' is less than or equal to BUFIOSIZE,
 *	and the i/o/ buffer is mapped (Ubufmap offset <> 0), and a pool
 *	buffer of the same size as the i/o buffer may be allocated, attempt
 *	to buffer the i/o.  Duplicate the i/o packet (if allocation failure,
 *	release the buffer and return FALSE) and reset the child packet to
 *	use the newly allocated buffer, returning status to (parent) 'pkt'.
 *	Increment the state number of the child packet.
 *	The child pkt is requeued, and the new buffer address is stored in
 *	Auxparam(*pkt) so that the buffer may be located for data copy.
 */
{
     register PKT_PTR npkt;
     register IO_PACKET *pk;
     register IO_PACKET *npk;
     register unsigned size;

    pk = *pkt;

    /* determine if request should be buffered in local pool */
    /* and attempt to allocate a buffer for it */

    if ( ((size = Ubufsize(pk)) LE BUFIOSIZE) AND (Ubufmap(pk) NE 0) AND
		    ((size = Auxparam(pk) = alloc(size)) NE NULL) )
	{
	if (catch() EQ 0)
	    {
	    npk = *(npkt = duppkt(pkt));	/* copy i/o packet */
					    /* (or throw V_ALL_FAILURE) */
	    uncatch();
	    resetdev (npkt, VDevice(*pkt));	/* duplicate device */
	    Ubufmap(npk) = 0;			/* no map offset to new buf */
	    Ubufaddr(npk) = (char*) size;	/* bufptr to new buf */
	    Auxparam(npk) = NULL;		/* don't dealloc twice */
	    Parentstat(npk) = (int**) pkt;	/* copy i/o status */
	    Stnum(npk)++;			/* increase state number */
	    q_virtual(npkt);			/* requeue (handler is same) */
	    return(TRUE);
	    }
	else
	    {
	    Auxparam(*pkt) = NULL;		/* pkt alloc failed... */
	    vfree(size);			/* free buf (once only) */
	    }
	} /*if-bufferable*/
    
    return(FALSE);		/* request could not be buffered */
}
#endif /***************** VIRTUAL OPERATING SYSTEM SUPPORT *************/


cp_from_buffer (buf, to, offset, cnt, cprout)
    PKT_UBUFFER *buf;
    char *to;
    int offset;
    register int cnt;
    int (*cprout)();

/* cp_from_buffer (buf, to, offfset, cnt, cprout) -- Copy I/O buffer somewhere
 *
 *	in:	buf		address of an i/o packet buffer descriptor
 *	in:	to		address of destination
 *	in:	offset		number of bytes of buffer to skip before copy
 *	in:	cnt		number of bytes to copy
 *	in:	cprout		address of copy routine to call
 *	return:	(NONE)
 *	thrown:	(NONE)
 *
 *	Call 'cprout' to copy data from an i/o buffer (usually the Ubuffer of an
 *	i/o packet) to the destination specified by 'to'.
 *	The function 'cprout()' is called (possibly repeatedly) with the
 *	arguments:	from, to, cnt
 *	where 'from' and 'to' are physical addresses.
 *
 *	This routine handles mapped buffers, assuming they have been remapped.
 *
 */
{
    register MAP_LIST *map;
    register int size;
    register char *addr;

    switch (Ubmap(buf))
	{
    case PHYS_BUFFER:			/* physical address of buffer */
	(*cprout)(Ubaddr(buf)+offset, to, cnt);	/* simple copy */
	break;

    case POOL_BUFFER:			/* pool buffer */
	(*cprout)(*((char**)Ubaddr(buf))+offset, to, cnt);	/* from pool */
	break;

    case MAPPED_BUFFER:			/* mapping list */
	map = * ((MAP_LIST**)Ubaddr(buf));	/* get map table address */

	while (cnt GT 0)		/* while more to copy */
	    {
	    size = Msize(map);		/* get size of this segment */
	    addr = Maddr(map++);	/* get physcal address of segment */

#ifdef DEBUG3   /*************************************************************/
	    if (size LE 0)
		error("Buffer map @%x too short", map);
#endif /* DEBUG3 *************************************************************/

	    if (offset GT 0)		/* if skipping initial offset */
		{
		if (size LE offset)	/* skip this entire segment? */
		    {
		    offset -= size;	/* yes...subtract its size */
		    continue;		/* and repeat the while loop */
		    }
		else
		    {
		    addr += offset;	/* offset into the current segment */
		    size -= offset;	/* and subtract skipped size */
		    offset = 0;		/* finished offset */
		    }			/* fall thru to copy */
		}

	    if (size GT cnt)
		size = cnt;		/* quit before end of buffer */

	    (*cprout)(addr, to, size);	/* copy from mapped addr */

	    to += size;			/* update dest ptr */
	    cnt -= size;		/* update remaining byte count */
	    } /*while-cnt*/
	break;
	} /*switch-Ubmap*/
}


cp_to_buffer (from, buf, offset, cnt, cprout)
    char *from;
    PKT_UBUFFER *buf;
    int offset;
    register int cnt;
    int (*cprout)();

/* cp_to_buffer (from, buf, offset, cnt, cprout) -- Copy somewhere to I/O buffer
 *
 *	in:	from		address of source
 *	in:	buf		address of an i/o packet buffer descriptor
 *	in:	offset		number of bytes of buffer to skip before copy
 *	in:	cnt		number of bytes to copy
 *	in:	cprout		address of copy routine to call
 *	return:	(NONE)
 *	thrown:	(NONE)
 *
 *	Call 'cprout' to copy data from a source specified by 'from' to an
 *	i/o buffer (usually the Ubuffer of an i/o packet).
 *	The function 'cprout()' is called (possibly repeatedly) with the
 *	arguments:	from, to, cnt
 *	where 'from' and 'to' are physical addresses.
 *
 *	This routine handles mapped buffers, assuming they have been remapped.
 *
 */
{
    register MAP_LIST *map;
    register int size;
    register char *addr;

    switch (Ubmap(buf))
	{
    case PHYS_BUFFER:			/* physical address of buffer */
	(*cprout)(from, Ubaddr(buf)+offset, cnt);	/* simple copy */
	break;

    case POOL_BUFFER:			/* pool buffer */
	(*cprout)(from, *((char**)Ubaddr(buf))+offset, cnt);	/* to pool */
	break;

    case MAPPED_BUFFER:			/* mapping list */
	map = * ((MAP_LIST**)Ubaddr(buf));	/* get map table address */

	while (cnt GT 0)		/* while more to copy */
	    {
	    size = Msize(map);		/* get size of this segment */
	    addr = Maddr(map++);	/* get physcal address of segment */

#ifdef DEBUG3   /*************************************************************/
	    if (size LE 0)
		error("Buffer map @%x too short", map);
#endif /* DEBUG3 *************************************************************/

	    if (offset GT 0)		/* if skipping initial offset */
		{
		if (size LE offset)	/* skip this entire segment? */
		    {
		    offset -= size;	/* yes...subtract its size */
		    continue;		/* and repeat the while loop */
		    }
		else
		    {
		    addr += offset;	/* offset into the current segment */
		    size -= offset;	/* and subtract skipped size */
		    offset = 0;		/* finished offset */
		    }			/* fall thru to copy */
		}

	    if (size GT cnt)
		size = cnt;		/* quit before end of buffer */

	    (*cprout)(from, addr, size);	/* copy to mapped buf */

	    from += size;		/* update source ptr */
	    cnt -= size;		/* update remaining byte count */
	    } /*while-cnt*/
	break;
	} /*switch-Ubmap*/
}

    char
get_chr (pkt)
    PKT_PTR pkt;

/* get_chr (pkt) -- Get next character from an i/o buffer
 *
 *	in:	pkt		Target I/O packet
 *	return:	(char)		Next character in Ubuffer(*pkt)
 *	thrown:	(NONE)
 *
 *	Locate the i/o buffer of the i/o packet (might be mapped into
 *	a target O.S. virtual address space) and return the next character
 *	in the buffer.  The next character location is determined by the
 *	buffer address/mapping and current count (Statbcount(*pkt)).
 *	After fetching the byte, the count is incremented.
 *
 *	Note that no checking is performed to see if any bytes remain;
 *	i/o completion must be detected at an outer layer.
 *
 *	The buffer must be resident and locked in memory.
 *
 */	
{
    register IO_PACKET *pk;
    register MAP_LIST *map;
    register int cnt;
    register int size;

    cnt = Statbcount(pk = *pkt)++;	/* get offset and increment ctr */

    switch (Ubufmap(pk))
	{
    case PHYS_BUFFER:			/* physical address */
	return (*(Ubufaddr(pk) + cnt));		/* return the char */

    case POOL_BUFFER:			/* buffer in VIOS pool */
	return (* (*((char**)Ubufaddr(pk)) + cnt) );	/* return the char */
    
    case MAPPED_BUFFER:			/* buffer mapping list */
	map = * ((MAP_LIST**) Ubufaddr(pk));	/* get map table ptr */

	while ((size = Msize(map)) GT 0)	/* while map is valid */
	    {
	    if (cnt LT size)		/* if next address is in this segment */
		return(*(Maddr(map) + cnt));	/* return the char */
	    
	    cnt -= size;		/* subtract this segment size */
	    map++;			/* point to next entry */
	    } /*while*/

    default:
#ifdef DEBUG3   /*************************************************************/
	error("Bad buffer map @%x", map);	/* this should never happen */
#endif /* DEBUG3 *************************************************************/

	} /*switch-Ubufmap*/
}

    char
put_chr (chr, pkt)
    int chr;
    PKT_PTR pkt;

/* put_chr (chr, pkt) -- Put a character into an i/o buffer
 *
 *	in:	chr		Character to insert
 *	in:	pkt		Target I/O packet
 *	return:	(char)		character inserted ('chr')
 *	thrown:	(NONE)
 *
 *	Locate the i/o buffer of the i/o packet (might be mapped into
 *	a target O.S. virtual address space) and insert 'chr'
 *	in the buffer.  The next character location is determined by the
 *	buffer address/mapping and current count (Statbcount(*pkt)).
 *	After loading the byte, the count is incremented.
 *
 *	Note that no checking is performed to see if there is room for 'chr';
 *	i/o completion must be detected at an outer layer.
 *
 *	The buffer must be resident and locked in memory.
 *
 */	
{
    register IO_PACKET *pk;
    register MAP_LIST *map;
    register int cnt;
    register int size;

    cnt = Statbcount(pk = *pkt)++;	/* get offset and increment ctr */

    switch (Ubufmap(pk))
	{
    case PHYS_BUFFER:			/* physical address */
	return (*(Ubufaddr(pk) + cnt) = chr);		/* return the char */

    case POOL_BUFFER:			/* buffer in VIOS pool */
	return (* (*((char**)Ubufaddr(pk)) + cnt) = chr );
    
    case MAPPED_BUFFER:			/* buffer mapping list */
	map = * ((MAP_LIST**) Ubufaddr(pk));	/* get map table ptr */

	while ((size = Msize(map)) GT 0)	/* while map is valid */
	    {
	    if (cnt LT size)		/* if next address is in this segment */
		return(*(Maddr(map) + cnt) = chr);	/* return the char */
	    
	    cnt -= size;		/* subtract this segment size */
	    map++;			/* point to next entry */
	    } /*while*/

    default:
#ifdef DEBUG3   /*************************************************************/
	error("Bad buffer map @%x", map);	/* this should never happen */
#endif /* DEBUG3 *************************************************************/

	} /*switch-Ubufmap*/
}


put_val (val, pkt)
    unsigned val;
    register PKT_PTR pkt;

/* put_val (val, pkt) -- Put a value into an i/o buffer
 *
 *	in:	val		Value to insert
 *	in:	pkt		Target I/O packet
 *	return:	(NONE)
 *	thrown:	(NONE)
 *
 *	Locate the i/o buffer of the i/o packet (might be mapped into
 *	a target O.S. virtual address space) and insert 'val'
 *	in the buffer.  The next character location is determined by the
 *	buffer address/mapping and current count (Statbcount(*pkt)).
 *	After loading (sizeof(unsigned)) bytes, the count is incremented.
 *
 *	Note that no checking is performed to see if there is room for 'val';
 *	i/o completion must be detected at an outer layer.
 *
 *	The buffer must be resident and locked in memory.
 *
 */	
{
    register char *ptr;
    register int i;

    ptr = (char*) &val;		/* get address of value to insert */

    for (i=0; i LT sizeof(unsigned); i++)
	put_chr(*ptr++, pkt);	/* put next byte of value into buffer */
}


return_stat (pkt)
    PKT_PTR pkt;

/* return_stat (pkt) -- Return I/O status to a target O.S.
 *	in:	pkt		target i/o packet
 *	return:	(NONE)
 *	thrown:	(NONE)
 *
 *	If the return status address (Cstataddr) of 'pkt' is non-null,
 *	lock and map the status buffer and copy the i/o packet status
 *	to it.
 *
 *	NOTE that the lock/map/unlock code is unimplemented.
 */
{
    register char *f;
    register char *t;
    register int i;

    if ((t = Cstataddr(*pkt)) NE NULL)		/* if non-NULL return block */
	{
			/* lock and map status block */

	f = (char*) &Status(*pkt);		/* get source address */
	for (i=0; i<sizeof(PKT_IOSTATUS); i++)
	    {
	    *t++ = *f++;			/* copy bytes of status */
	    }
			/* unlock status block */
	}
}


vio_reschedule ()

/* vio_reschedule() -- Reschedule the VIOS for execution before Hyper-Exec exit
 *
 *	return:	(NONE)
 *	thrown:	(NONE)
 *
 *	If not interrupt state and the VIOS is not running, initiate the VIOS
 *	at its main entry.
 *	Otherwise, schedule the Hyper-Exec to re-enter the VIOS before
 *	returning to any target O.S.
 */
{
    if (Intstate OR Vioexecuting OR ints_disabled())
	Vioscheduled = TRUE;	/* schedule VIOS to run again */

    else
	vios_entry(NULL);	/* go execute the vios */
}
