static char sccsid[] = "%W% %Y% %Q% %G%";
static char rcsid[] = "$Header: xd_ll.c,v 800.1 85/10/21 16:21:46 root Exp $";
#include "ex.h"			/* Configuration constants */
#include "xm.h"			/* Defines download driver */
#include "xd.h"			/* Defines (TCI/DECnet) logical link driver */

#include	"../h/types.h"		/* Commonly used data types */

#include	"../machine/pte.h"	/* VM page table definitions */
#include	"../h/param.h"		/* Process, memory and file sys parameters */
#include	"../h/systm.h"		/* Kernel global variables */
#include	"../h/mbuf.h"		/* Net/Comm buffer definitions */
#include	"../h/socket.h"	/* Socket interface definitions */
#include	"../h/vmmac.h"		/* Virtual memory address conversion macros */
#include	"../h/map.h"		/* Resource allocation maps */
#include	"../h/ioctl.h"		/* System wide IOCTL definitions */
#include	"../h/dir.h"		/* File directory definitions */
#include	"../h/buf.h"
#include	"../h/inode.h"
#include	"../h/uio.h"
#include	"../h/user.h"		/* U Page definitions (per user blocks) */
				/*  ../h/user.h includes 
					../machine/pch.h
					../h/dmap.h
					../h/time.h
					../h/resource.h
					../h/errno.h
				 */
#include	"../h/proc.h"
#include	"../h/kernel.h"	/* Kernel variables */
#include "../net/if.h"		/* Network interface definitions */
#include "../netinet/if_ether.h"	/* Standard Ethernet definitions */

#include	"../s32dev/exreg.h"	/* EXOS 201 definitions */
#include	"../s32dev/exdef.h"	/* Standard EXOS definitions */
#include 	"../s32dev/exioctl.h"	/* Definition of ioctl's */
#include	"../tci/tci.h"		/* TCI standard definitions */
#include	"../tci/exos_decnet.h"	/* Decnet Exos interface */
#include	"../tci/community.h"	/* TCI/Community User Definitions */
#include	"../tci/exos_sync.h"	/* Sync/Kludge buffer defines */

#define	BSIZE CLBYTES
extern struct ex_softc EX_SOFTC(NEX);	/* Controller/driver state and data */
extern struct mb_device *EXINFO(NEX);	/* Multibus device descriptor */
extern ExosSync exos_sync[];		/* Kludge (sync/reply) pool */
extern bool exos_started;

extern struct buf *ex_getbuf();
extern struct buf *geteblk();

ExosPacket *ll_findmsg();
ExosSync *ll_send();

	/* this is bad, but a quick hack since they don't have sprintf */
static char *ll_name[] =
	{
	"/dev/ll",
	"/dev/ll1", "/dev/ll2", "/dev/ll3", "/dev/ll4", "/dev/ll5", "/dev/ll6",
	"/dev/ll7", "/dev/ll8", "/dev/ll9", "/dev/ll10", "/dev/ll11", "/dev/ll12"
	};

u_long ll_debug = 0;



LinkInfo llinfo[NUMFDS] =
	{
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 },
	{ NULL, 0, LI_FREE, 0, 0, 0, NULL, 0 }
	};




/*
 * Routine:	ll_startup()
 *
 * Access type:	Public (to the Kernel)
 *
 * Abstract:	This routine is called whenever the EXOS board is reset.  It
 *			will initialize anything that is needed to be initialized for
 *			the logical link driver.
 *
 * Inputs:	None.
 *
 * Outputs:	None.
 *
 * Returns:	None.
 *
 * Side Effects:	Fields of the ll_info array are updated.
 *
 * Assumptions:	Unknown
 */
ll_startup()
	{
	register int i;

	/* ?!? We want to kill off any processes hanging around sleeping
	 * ?!? that are attached to any taken links 
	 */

	/* free up all of the logical links */
	for( i = 0; i < NUMFDS; ++i )
		llinfo[i].li_state = LI_FREE;
	}




/*
 * Routine:
 *
 * Access type:
 *
 * Abstract:
 *
 * Inputs:
 *
 * Outputs:
 *
 * Returns:
 *
 * Side Effects:
 *
 * Assumptions:
 */
llopen( dev )
dev_t dev;
	{
	int min_dev = minor( dev );		/* Index into link table */
	register int free_ll;
	char device_name[20];

	/* check if the exos board is started yet */
	if( !exos_started )
		{
		DPRINT( ll_debug, 0, ( "Board has not been started\n" ) );
		return( u.u_error = LL_UNINIT );
		}

	/* find a free logical link device */
	for( free_ll = 1; free_ll < NUMFDS; ++free_ll )
		if( llinfo[free_ll].li_state == LI_FREE )
			break;

	/* check for no free open links */
	if( free_ll == NUMFDS )
		{
		DPRINT( ll_debug, 0, ( "There are no free logical links\n" ) );
		return( u.u_error = ETXTBSY );
		}

	/* setup new pointers to the free logical link */
	/* sprintf( device_name, "/dev/ll%d", free_ll ); */
	/**** HACK WARNING ***/
	strcpy( device_name, ll_name[free_ll] );

	if( xd_reopen( "/dev/ll", device_name ) == FAIL )
		{
		DPRINT( ll_debug, 0, ( "Couldn't open the new device!\n" ) );
		return( u.u_error = EIO );
		}

	/* Allocate link */
	llinfo[free_ll].li_minor = min_dev;
	llinfo[free_ll].li_state = LI_TAKEN;
	llinfo[free_ll].li_proc = u.u_procp;
	llinfo[free_ll].li_pid = u.u_procp->p_pid;
	llinfo[free_ll].li_ctrlr = 0;
	llinfo[free_ll].li_link = -1;		/* "Very Important" */

	return( 0 );
	}



llclose( dev ) 
dev_t dev;
	{
	int min_dev = minor( dev );
	register struct ll_info *ll;
	register caddr_t baddr;	/* Pointer into clbuf's data buffer */
	register ExosPacket *mp;	/* EXOS DECnet message */
	register ExosSync *xsy_p;		/* Sync/Reply buffer */
	struct ex_softc *xs;
	int ret;

	ll = &llinfo[min_dev];

	/* If link not allocated, I/O error */
	if( ( ll->li_state & LI_TAKEN ) == 0 )
		{
		return( u.u_error = EIO );
		}

	/* If we've issued the close once, don't do it again */
	if( ( ll->li_state & LI_CLOSED ) != 0 )
		{
		return( 0 );
		}

	if( ( ll->li_state & LI_FAILED ) != 0 )
		{
		ll->li_state = LI_FREE;
		return( 0 );
		}

	/* Send link close request, if link is really open */
	if( ( ll->li_state & LI_OPEN ) || ( ll->li_state & LI_ACTIVE ) )
		{
		ll = &llinfo[min_dev];
		xs = &EX_SOFTC( ll->li_ctrlr );

		/*
		 * Clear all sync. buffers waiting for this process!
		 */
		for( xsy_p = exos_sync; xsy_p < &exos_sync[NUM_EXOS_SYNC]; ++xsy_p )
			if( xsy_p->xsy_proc == u.u_procp )
				xsy_p->xsy_state = XSY_FREE;

		/*
		 * Allocate the EXOS message
		 * This should block and never return a NULL.  But we'll
		 * check anyway!
		 */
		if( ( mp = ll_findmsg( xs, DNclose ) ) == NULL )
			{
			DPRINT( ll_debug, 0, ( "llclose: no EXOS packet!\n" ) );
			return( u.u_error = EIO );
			}

		/* fill in the EXOS information */
		mp->ex_lcn = ll->li_link;
		mp->ex_dev_type = DEV_LL;
		mp->ex_fd = min_dev;
		mp->ex_bd.eb_addr = NULL;
		mp->ex_bd.eb_len = 0;


		/* mark that we are sending the close */
		ll->li_state |= LI_CLOSED;

		/* Send request to EXOS land */
		xsy_p = ll_send( xs, 0, mp, 0, FALSE );
		free_sync_buffer( xsy_p, xs );
		}
	
	return( u.u_error = 0 );
	}






/*
 * llread() -
 *
 */
llread( dev, uio ) 
dev_t dev;
struct uio *uio;
	{
	struct buf *rdbuf;		/* Pointer to (I/O) buf */
	register caddr_t baddr;	/* Pointer into rdbuf's data buffer */
	register int amount;
	register int uiocnt;
	register struct iovec *iov;
	register ExosPacket *mp;	/* EXOS DECnet message */
	register ExosSync *xsy_p;		/* Sync/Reply buffer */
	struct ex_softc *xs;
	register short min_dev;
	short ret_value;

	/* Minor number is index into LLINFO; i.e. which logical link */
	min_dev = minor( dev );
	if( ( llinfo[min_dev].li_state & LI_OPEN ) == 0 )
		{
		printf( "llread: logical link is not open\n" );
		return( u.u_error = EIO );
		}

	xs = &EX_SOFTC( llinfo[min_dev].li_ctrlr );

	/* Calculate u.u_count (relic from before scatter-gather I/O) */
	u.u_count = 0;
	iov = uio->uio_iov;
	for( uiocnt = 0; uiocnt < uio->uio_iovcnt; ++uiocnt )
		{
		u.u_count += iov->iov_len;
		++iov;
		}

	DPRINT( ll_debug, 0, ( "llread:(%x,%d)\n", min_dev, u.u_count ) );

	/* Allocate a (I/O) buf and setup pointer to data buffer */
	if( ( rdbuf = ex_getbuf() ) == NULL )
		{
		panic( "llread: Out of UNIX bio buffers\n" );
		return( u.u_error = EIO );
		}
	baddr = rdbuf->b_adrs;

	/*
	 *	The amount that we want to receive is either a FULL buffer,
	 *	i.e. BSIZE, or u.u_count which ever is less.
	 *	?!? What is we are receiving less?  Do we want to flag it?
	 */
	amount = MIN( BSIZE, u.u_count );


	/*
	 * Allocate and format a message
	 * Note the address given in the message block is a Multibus
	 * physical address rather than a virtual address (vtop() call)
	 */
	if( ( mp = ll_findmsg( xs, DNread ) ) == NULL )
		{
		printf( "llread: no EXOS packet!\n" );
		return( u.u_error = EIO );
		}

	mp->ex_lcn = llinfo[min_dev].li_link;
	mp->ex_dev_type = DEV_LL;
	mp->ex_fd = min_dev;
	mp->ex_bd.eb_addr = (caddr_t)vtop( baddr );
	mp->ex_bd.eb_len = amount;

	/* Send the request to EXOS land.  This will block! */
	xsy_p = ll_send( xs, 0, mp, 0, TRUE );

	/* get the data we need and free the sync. buffer */
	amount = xsy_p->xsy_bd.eb_len;
	ret_value = xsy_p->xsy_ret_status;;
	free_sync_buffer( xsy_p, xs );

	DPRINT( ll_debug, 1, ( "llread: receive %d at = %x ret = %d\n", amount,
		baddr, ret_value ) );
	/*
	 * Process the return value
	 */
	if( ret_value == 0 )
		{
		/* copy the data to user space */
		uiomove( baddr, amount, UIO_READ, uio );
		u.u_count -= amount;
		u.u_error = 0;
		}
	else
		u.u_error = ret_value;

	/* Release I/O buffer */
	brelse( rdbuf, BSIZE );

	/* Adjust I/O counts to reflect the amount of data moved */
	uio->uio_resid = u.u_count;
	uio->uio_iov->iov_len = u.u_count;

	return( u.u_error );
	}




/*
 *	llwrite
 *		called by UNIX kernel when a write occurs to /dev/ll
 */
llwrite( dev, uio )
dev_t dev;
struct uio *uio;
	{
	register short min_dev;
	struct buf *wrbuf;		/* Pointer to (I/O) buf */
	register caddr_t baddr;	/* Pointer into wrbuf's data buffer */
	register int amount;
	register int uiocnt;
	register struct iovec *iov;

	register ExosPacket *mp;	/* EXOS DECnet message */
	register ExosSync *xsy_p;		/* Sync/Reply buffer */
	register short return_value;
	struct ex_softc * xs;


	/* Minor number is index into LLINFO; i.e. which logical link */
	min_dev = minor( dev );
	if( ( llinfo[min_dev].li_state & LI_OPEN ) == 0 )
		{
		printf( "the logical link is not open\n" );
		return( u.u_error = EIO );
		}

	xs = &EX_SOFTC( llinfo[min_dev].li_ctrlr );

	/* Calculate u.u_count (relic from before scatter-gather I/O) */
	u.u_count = 0;
	iov = uio->uio_iov;
	for( uiocnt = 0; uiocnt < uio->uio_iovcnt; ++uiocnt )
		{
		u.u_count += iov->iov_len;
		++iov;
		}

	DPRINT( ll_debug, 1, ( "llwrite:( %x,%d )\n", min_dev, u.u_count ) );

	/* Allocate a (I/O) buf and setup pointer to data buffer */
	if( ( wrbuf = ex_getbuf() ) == NULL )
		{
		panic( "llwrite: no more UNIX buffers" );
		return( u.u_error = EIO );
		}
	baddr = wrbuf->b_adrs;

	/* Send until end of data is reached */
	u.u_error = 0;
	while( u.u_count )
		{

		/*
		 * We want to send either a full buffer or the remaining data;
		 * whichever is less.
		 * To do this we transfer the data to kernel space from
		 * user space then adjust the number of bytes left to move.
		 */
		amount = MIN( BSIZE, u.u_count );
		uiomove( baddr, amount, UIO_WRITE, uio );
		u.u_count -= amount;

		/* If failure during data transfer, abort transfer */
		if( u.u_error )
			break;

		DPRINT( ll_debug, 1, ( "llwrite:send(%d,%x)\n", amount, baddr ) );

		/*
		 * Allocate and format a message
		 * Note the address conversion from UNIX virtual
		 * to Multibus physical, in the vtop function call.
		 */
		mp = ll_findmsg( xs, DNwrite );
		mp->ex_bd.eb_addr = (caddr_t)vtop( baddr );
		mp->ex_bd.eb_len = amount;
		mp->ex_lcn = llinfo[min_dev].li_link;
		mp->ex_dev_type = DEV_LL;
		mp->ex_fd = min_dev;

		/* Send the request to EXOS land. */
		xsy_p = ll_send( xs, 0, mp, 0, TRUE );

		/* get the information we need and free the sync. buffer */
		return_value = xsy_p->xsy_ret_status;
		free_sync_buffer( xsy_p, xs );

		DPRINT( ll_debug, 1, ( "llwrite: ret_status = %x\n", return_value ) );

		/*
		 * If the request failed, set an I/O error for user to see;
		 * then abort this transfer
		 */
		if( return_value != 0 )
			{
			u.u_error = return_value;;
			break;
			}
		}	/* end while loop */


	/* Release I/O buffer */
	brelse( wrbuf, BSIZE );

	/* Adjust I/O counts to reflect the amount of data moved */
	uio->uio_resid = u.u_count;
	uio->uio_iov->iov_len = u.u_count;

	return( u.u_error );
	}







/*
 *	llioctl() -
 *
 */
llioctl( dev, cmd, addr, flag )
dev_t dev;
int cmd;
u_char *addr;
int flag;
	{
	register ExosPacket *mp;
	register ExosSync *xsy_p;
	register struct buf *opn;		/* Pointer to open block I/O buf */
	register struct ex_softc *xs;
	register short datasize = IOCTLDATASIZE( cmd );
	register short returnvalue;		/* Returned from on-board LL driver */
	register short min_dev;
	bool startup = FALSE;

	DPRINT( ll_debug, 1, ( "llioctl:(%x,%x,%x,%x,%x)\n",
		dev, cmd, addr, (caddr_t)( *( (long * )addr ) ), flag ) );

	min_dev = minor( dev );
	xs = &EX_SOFTC( llinfo[min_dev].li_ctrlr );

	/* get a UNIX buffer for the data */
	if( ( opn = ex_getbuf() ) == NULL )
		{
		panic( "llioctl: no more UNIX buffs" );
		return( u.u_error = EIO );
		}
	
	/* Get EXOS message buffer.  This will block until one is available */
	if( ( mp = ll_findmsg( xs, DNopen ) ) == NULL )
		{
		/* this should not happen; ll_findmsg() blocks */
		panic( "llioctl: no more EXOS buffs" );
		return( u.u_error = EIO );
		}

	DPRINT( ll_debug, 0, ( "llioctl: command 0x%x\n", IOCTLNUM( cmd ) ) );

	if( cmd == SES_LINK_ACCESS || cmd == SES_NUM_SERVER ||
		cmd == SES_NAME_SERVER )
		{
		llinfo[min_dev].li_state |= LI_ACTIVE;
		startup = TRUE;
		}

	/* 
	 * Fill in EXOS buff with address and length of the data
	 * Note that EXOS gets a physical address!
	 */
	mp->ex_bd.eb_addr = (caddr_t)vtop( opn->b_adrs );
	mp->ex_bd.eb_len = datasize;


	/* Fill in other information into the EXOS driver */
	mp->ex_lcn = llinfo[min_dev].li_link;
	mp->ex_dev_type = DEV_LL;
	mp->ex_fd = min_dev;
	mp->ex_cmd_type = DNioctl;		/* Command for on-board LL driver */
	mp->ex_mdata.ioctl_func = IOCTLNUM( cmd );

	/* copy the data from the ioctl block into System space */
	if( cmd == SES_NUM_SERVER )		/* only want the short part */
		addr += 2;
	bcopy( addr, opn->b_adrs, datasize );

	/* for SES_NUM_SERVER, slide it over a bit */

	/*
	 * Send message buffer to on-board LL driver.
	 * This will block until the board responds!
	 */
	xsy_p = ll_send( xs, 0, mp, 0, TRUE );

	/*
 	 * The ioctl has been processed!
	 * In the case that this gives us a new lcn, save the lcn.
	 */
	llinfo[min_dev].li_link = xsy_p->xsy_lcn;

	/* Check the return value and set the file's state accordingly */
	returnvalue = xsy_p->xsy_ret_status;

	if( startup )
		{
		llinfo[min_dev].li_state &= ~LI_ACTIVE;
		if( returnvalue == 0 )
			llinfo[min_dev].li_state |= LI_OPEN;
		else
			llinfo[min_dev].li_state |= LI_FAILED;
		}

	/*
	 * Copy the EXOS-generated data back to the user
	 * And free Unix buffer
	 */
	bcopy( opn->b_adrs, addr, datasize );
	brelse( opn, BSIZE );

	/* get rid of the sync. buffer */
	free_sync_buffer( xsy_p, xs );

	/*
	 * The mdata.ret_status field is returned from the on-board LL driver
	 * and saved in the kuldge buffer in llintr.
	 * We inspect the value and then return a number to the
	 * user program.
	 */
	return( u.u_error = returnvalue );
	}



llintr( xs, mp )
struct ex_softc *xs;
ExosPacket *mp;
	{
	register ExosSync *xsy_p;

	DPRINT( ll_debug, 1, ("llintr:(%x,%x)\n", mp->ex_status, mp->ex_cmd_type));

	/*
	 * Release buffers of io calls that got hit by a signal
	 * - note unsolicited messages don't have meaningful
	 *	sync. pointers, thus the complicated test clause.
	 */
	xsy_p = (ExosSync *)mp->ex_recp;
	if( ( xsy_p >= &exos_sync[0] ) && ( xsy_p < &exos_sync[NUM_EXOS_SYNC] ) &&
		( ( xsy_p->xsy_state & XSY_BUSY ) == 0 ) &&
		( xsy_p->xsy_state & XSY_WAITING ) )
		{
		if( xsy_p->xsy_baddr )
			{
			brelse( xsy_p->xsy_baddr, BSIZE );
			xsy_p->xsy_baddr = 0;
			}
		}

	/* Process message based on request sent to EXOS */
	switch( mp->ex_cmd_type )
		{
	case DNclose:
		DPRINT( ll_debug, 1, ( "close response\n" ) );
		/* release this link */
		llinfo[mp->ex_fd].li_state = LI_FREE;
		break;

	case DNopen:
	case DNwrite:
	case DNread:
	case DNioctl:
		break;

	default:
		DPRINT( ll_debug, 0, ("unknown request type %x\n", mp->ex_cmd_type));
		if( ( xsy_p >= &exos_sync[0] ) || ( xsy_p < &exos_sync[NUM_EXOS_SYNC] ) )
			wakeup( (caddr_t)xsy_p );
		if( xsy_p->xsy_baddr )
			{
			brelse( xsy_p->xsy_baddr, BSIZE );
			xsy_p->xsy_baddr = 0;
			}
		return( 0 );
		break;
		}

	/*
	 * Save data from reply buffer
	 */
	xsy_p->xsy_dev_type = mp->ex_dev_type;
	xsy_p->xsy_fd = mp->ex_fd;
	xsy_p->xsy_lcn = mp->ex_lcn;
	xsy_p->xsy_ret_status = mp->ex_mdata.ret_status;
	xsy_p->xsy_bd = mp->ex_bd;

	if( xsy_p->xsy_state & XSY_WAITING )
		{
		xsy_p->xsy_state &= ~XSY_WAITING;
		wakeup( (caddr_t)xsy_p );
		}
	else	{
		/* if no process is waiting on this buffer, release it */
		if( xsy_p->xsy_baddr )
			{
			brelse( xsy_p->xsy_baddr, BSIZE );
			xsy_p->xsy_baddr = 0;
			}
		free_sync_buffer( xsy_p, xs );
		}

	return( 1 );		/* serviced message */
	}





llselect()
	{
	return( 0 );
	}

