
/*
 * EXOS Memory/Download Driver
 *
 * This driver is used to:
 *	download code to board
 *	start up code on board
 */

#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	"../s32/vectors.h"	/* Processor interrupt vector definitions */
#include	"../s32dev/mbvar.h"	/* Multibus definitions (autoconfig) */
#ifdef VALIDnet
#include	"../vnet/vnet.h"	/* Valid Network definitions */
#include	"../conn/conn.h"	/* Valid connection management */
#include	"../rpc/rpc.h"		/* Remote procedure call definitions */
#endif VALIDnet

#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 */

struct buf *ex_getbuf();
struct buf *geteblk();
struct exDLLhdr *xm_findmsg();
ExosSync *xm_send();


/*
 *	Define the Exos Synchronization Buffers
 */
ExosSync exos_sync[NUM_EXOS_SYNC];
bool exos_started = FALSE;


/* A debugging variable */
u_long xmdebug = 0;




xmopen( dev )
dev_t dev;
	{
	dev = minor( dev );
	if( dev >= NEX )
		return( ENXIO );

	if( EXINFO( dev ) == 0 )
		return( ENXIO );
	else
		return( 0 );
	}

xmclose()
	{
	return( 0 );
	}




xmwrite( dev, uio )
dev_t dev;
struct uio *uio;
	{
	struct buf *dlbuf;		/* Pointer to (I/O) buf */
	register caddr_t baddr;	/* Pointer into dlbuf's data buffer */
	register int amount;
	register long dladdr;	/* Down load address */
	register int uiocnt;
	register struct iovec *iov;
	register struct net_load *net_dload;	/* EXOS download structure */
	register ExosSync *xs_p;			/* Sync/Reply buffer */
	u_char reply;				/* Reply code from EXOS */
	struct ex_softc * xs;

	/* Check user's access rights, only SuperUser allowed */
	if( !suser() )
		return( u.u_error = EPERM );

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

#ifdef DEBUG
	if( xmdebug )
		printf( "xmwrite:( %x,%d )\n", dev, u.u_count );
#endif DEBUG

	/* Mask to get only minor number; i.e. select which controller */
	dev = minor( dev );
	xs = &EX_SOFTC( dev );

	/* Allocate a (I/O) buf and setup pointer to data buffer */
	dlbuf = ex_getbuf();
	baddr = dlbuf->b_adrs;

	/* Send until it end of data is reached */
	while( u.u_count )
		{

		/* Current download address is the I/O offset */
		dladdr = uio->uio_offset;

		/* Send the lesser of a buffer full or the remaining data */
		/* Transfer data to kernel space buffer from user space */
		/* Adjust 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 download */
		if( u.u_error )
			break;

#ifdef DEBUG
		if( xmdebug )
			printf( "xmwrite:send( %d,%x,%x )\n", amount, baddr, dladdr );
#endif DEBUG


		/* Send current buffer of data; wait for reply */
		/* Allocate and format a download message */
		/* Note: address conversion from UNIX virtual to Multibus physical */
		net_dload = (struct net_load *)xm_findmsg( xs );
		net_dload->count = amount;
		net_dload->source = (caddr_t)vtop( baddr );
		net_dload->xmbyte = *(char *)baddr;	/* for 1-character writes */
		net_dload->dest = dladdr;

		/* Send request, save reply, release synchronization buffer */
		xs_p = xm_send( NET_DLOAD, dev, net_dload, 0 );
		reply = xs_p->xsy_reply;
		xs_p->xsy_state = XSY_FREE;

		/* Wake up all processes waiting for a sync. buffer */
		if( xs->flags & EX_WAITING )
			wakeup( (caddr_t)&xs->flags );

		/* If request failed, set I/O error for user to see; abort transfer */
		if( reply )
			{
			u.u_error = EIO;
			break;
			}

#ifdef DEBUG3
		if( xmdebug )
			printf( "got to end of loop resid=%d iov_cnt=%d\n",
				uio->uio_resid, uio->uio_iov->iov_len );
#endif DEBUG3
		}

	/* Release I/O buffer */
	/* Adjust I/O counts to reflect the amount of data moved */
	brelse( dlbuf, BSIZE );
	uio->uio_resid = u.u_count;
	uio->uio_iov->iov_len = u.u_count;

	return( u.u_error );
	}





/*
 * Ioctls are used only by netload program,
 * typically in the following sequence:
 * 	
 *	EXIOCRESET
 *	write, write, write...
 *	write, write, write...
 *	EXIOCSTART
 *
 */
xmioctl( dev, cmd, addr, flag )
dev_t dev;
int cmd;
u_char * addr;
int flag;
	{
	register struct net_start *net_start;
	register ExosSync *xs_p;
	register int i;
	u_long staddr;

#ifdef DEBUG
	if( xmdebug )
		printf( "xmioctl:( %x,%x,%x,%x,%x )\n",
			dev, cmd, addr, (caddr_t)( *( (long *)addr ) ), flag );
#endif DEBUG

	dev = minor( dev );

	switch( cmd )
		{

		/* Reset and configure EXOS */
	case EXIOCRESET:
		/* Check user's access rights, only SuperUser is allowed */
		if( !suser() )
			return( u.u_error = EPERM );

		/* Blow away anything waiting for EXOS to complete */
		if( EX_SOFTC( dev ).flags & EX_WAITING )
			wakeup( &( EX_SOFTC( dev ).flags ) );
		for( xs_p = exos_sync; xs_p < &exos_sync[NUM_EXOS_SYNC]; xs_p++ )
			{
			if( xs_p->xsy_state & XSY_WAITING )
				{
				wakeup( (caddr_t)xs_p );
				psignal( xs_p->xsy_proc, SIGKILL );
				}
			xs_p->xsy_state = XSY_FREE;
			}
	
#if	NXD > 0
		/* call the logical link startup routine to reinitialize the links */
		ll_startup();
#endif

		/* Start in FEP download mode */
		if( exConfig( dev, &EX_SOFTC( dev ), EXINFO( dev )->md_addr,
			EX_INTlevel, EX_OPMFEP ) )
			{
			u.u_error = EIO;
			}
		break;

	case EXIOCSTART:
		/* Start program running */
		if( !suser() )
			return( u.u_error = EPERM );

#ifdef DEBUG
		if( xmdebug )
			printf( "xmioctl: start address=%x\n", *(u_long *)addr );
#endif DEBUG

		net_start = (struct net_start *)xm_findmsg( &EX_SOFTC( dev ) );
		net_start->start = *(u_long *)addr;
		xs_p = xm_send( NET_START, dev, net_start, 0 );

		if( xs_p->xsy_reply )
			u.u_error = EIO;
		/*
		 * Ex_klfree deallocates this sync. buffer
		 * (and any others that happen to be tied up).
		 */
		ex_klfree( dev );
		exos_started = TRUE;
		break;

	case EXIOCDEBUG:
		/* what is the value of each of the Sync Buffers */
		printf( "Debugging, sync. buffers %d, XSY_FREEE = %d\n",
				NUM_EXOS_SYNC, XSY_FREE );
		for( i = 0; i < NUM_EXOS_SYNC; ++i )
			{
			printf( "( %d,%d )  ", i, exos_sync[i].xsy_state );
			if( ( i + 1 ) % 5 == 0 )
				printf( "\n" );
			}
		break;
		

	default:
		u.u_error = EINVAL;
		}

	return( u.u_error );
	}





xmintr( xs, mp )
struct ex_softc *xs;
struct exDLLhdr *mp;
	{
	register ExosSync *xs_p;

#ifdef DEBUG
	if( xmdebug )
		printf( "xmintr:( %x,%x,%x )\n", mp->status, mp->reply, mp->request );
#endif

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

	/* Process message based on request sent to EXOS */
	switch( mp->request )
		{

	default:
		printf( "unknown request type %d\n", mp->request );
		if( ( xs_p >= &exos_sync[0] ) || ( xs_p < &exos_sync[NUM_EXOS_SYNC] ) )
			wakeup( (caddr_t)xs_p );
		return( 0 );
		break;

	case NET_DLOAD:
	case NET_START:
		xs_p->xsy_reply = mp->reply;
		if( xs_p->xsy_state & XSY_WAITING )
			{
			xs_p->xsy_state &= ~XSY_WAITING;
			wakeup( (caddr_t)xs_p );
#ifdef DEBUG
			if( xmdebug )
				printf( "xmintr:wakeup( %x )\n", xs_p );
#endif DEBUG
			}
		break;

		}

	if( xs->flags & EX_WAITING )
		wakeup( (caddr_t)&xs->flags );

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




/*
 * xm_findmsg:
 *	 - first find a sync. buffer to hold result of message
 *	 - find next available message on "host to exos" message queue
 *	 - sleep if necessary, to get one
 */
struct exDLLhdr *
xm_findmsg( xs )
struct ex_softc * xs;
	{
	register struct exDLLhdr *mp;
	register ExosSync *xs_p;
	register int s;

	s = splnet();

	while( 1 )
		{

		/* Allocate a sync. buffer; mark it as used */
		/* If none, sleep waiting for one */
		for( xs_p = exos_sync; xs_p < &exos_sync[NUM_EXOS_SYNC]; xs_p++ )
			if( xs_p->xsy_state == XSY_FREE )
			{
			xs_p->xsy_state = XSY_BUSY;
			goto findmsg;
			}

		printf( "findmsg(): no free sync. buffers, sleep!\n" );
		goto nightynight;

findmsg:
		if( mp = (struct exDLLhdr *)exGetCbuf( xs, HSAP_DLL, EXSAP_DLL ) )
			{
			xs_p->xsy_baddr = 0;
			mp->mid = (u_long)xs_p;
			splx( s );
#ifdef DEBUG
			if( xmdebug )
				printf( "xm_findmsg:found( %x,%x )\n", mp, xs_p );
#endif DEBUG
			return( mp );
			}

		printf( "xm_findmsg: no free queue buffers, sleep!\n" );
		xs_p->xsy_state = XSY_FREE;		/* release sync. buf */

nightynight:
		/*
		 * Flag that a process is waiting for either a circular queue buffer
		 * or a sync. buffer.
		 */
		/* !!! GOING TO SLEEP AT splnet !!! */
		xs->flags |= EX_WAITING;
		sleep( (caddr_t)&xs->flags, PRIBIO );
		xs->flags &= ~EX_WAITING;
		}
	}





/*
 * xm_send:
 *	 - send a network message via the message queue's to the exos
 *	 - set up standard header
 *	 - bump board to tell it to go
 *	 - wait for operation to complete and return the pointer to the
 *		result info (kept in the ExosSync structure)
 *	 - must not be called at interrupt level
 */
ExosSync *
xm_send( command, dev, mp, dontint )
int command;
dev_t dev;
register struct exDLLhdr *mp;
int dontint;
	{
	register ExosSync *xs_p;
	register int s;
	label_t lqsav;
	struct ex_softc *xs;
	struct exdevice *exaddr;

	xs_p = (ExosSync *)mp->mid;
	dev = minor( dev );
	xs = &EX_SOFTC( dev );
	exaddr = (struct exdevice *)( EXINFO( dev )->md_addr );

#ifdef DEBUG
	if( xmdebug )
		printf( "xm_send:req( %x, %x, %x, %x, %x )\n",
			command, dev, mp, xs_p, dontint );
#endif DEBUG

	if( !xs->flags & EX_TCInet )
		{
		u.u_error = ENXIO;
		return;
		}

	s = splnet();

	/*
	 * If this proc gets hit by a signal (possibly from arrival of
	 * out-of-band data), free exos resources, THEN do the non-local goto
	 * to exit current system call.  This leaves a message pending
	 * on the board, but since we here clear only XSY_BUSY, no one
	 * else will try to use this sync. buffer until the pending
	 * message comes back from the board and the int. routine clears
	 * XSY_WAITING.
	 */
	lqsav = u.u_qsave;
	if( setjmp( &u.u_qsave ) )
		{
		if( xs->flags & EX_WAITING )
			wakeup( (caddr_t)&xs->flags );
		xs_p->xsy_state &= ~XSY_BUSY;
		splx( s );

#ifdef DEBUG
		if( xmdebug )
			printf( "xm_send:resume( %x, %x )\n", u.u_procp->p_addr, lqsav );
#endif DEBUG

		longjmp( &lqsav );
		}
	else	{
		mp->request = command;
		xs_p->xsy_dev = dev;
		xs_p->xsy_state |= XSY_WAITING;
		xs_p->xsy_proc = u.u_procp;
		mp->status |= MH_EXOS;
		exaddr->portb = 0;

		while( xs_p->xsy_state & XSY_WAITING )
			if( dontint )
				sleep( (caddr_t)xs_p, PRIBIO ); /* not interruptable */
			else
				sleep( (caddr_t)xs_p, PZERO+1 ); /* interruptable */
		}

	u.u_qsave = lqsav;
	splx( s );

#ifdef DEBUG
	if( xs_p->xsy_reply || xmdebug )
#else DEBUG
	if( xs_p->xsy_reply )
#endif DEBUG
		printf( "xm_send:result( %x,%x )\n", command, xs_p->xsy_reply );

	return( xs_p );
	}





/*
 * ex_getbuf:
 *	- get a buffer from the buffer cache which does not cross
 *	  a segment boundary - this is presumed to be in the multibus
 *	  address space
 *	- if we get a buffer which crosses a segment boundary, recurse
 *	  until we get a good one
 *
 *??? Does SEGNO work without doing vtop first? ???
 */

/* Get an 8086 segment number from an address */
#define	SEGNO( x )	( ( (int)( x ) >> 4 ) & 0xF000 )

struct buf *
ex_getbuf()
	{
	register struct buf *nextbp;
	register struct buf *bp;
	register caddr_t p;

	bp = geteblk( BSIZE );
	if( !bp )
		return( (struct buf *)0 );

	p = bp->b_adrs;
	if( SEGNO( p ) != SEGNO( p + ( BSIZE-1 ) ) )
		{
		nextbp = ex_getbuf();
		brelse( bp );
		return( nextbp );
		}

	bzero( bp->b_adrs, BSIZE );
	return( bp );
	}





ex_klfree( dev )
dev_t dev;
	{
	register ExosSync *xs_p;
	struct ex_softc *xs = &EX_SOFTC( minor( dev ) );

	for( xs_p = exos_sync; xs_p < &exos_sync[NUM_EXOS_SYNC]; xs_p++ )
		{
		int s;
		s = splnet();

		if( ( xs_p->xsy_state & XSY_BUSY ) && xs_p->xsy_dev == dev )
			{
#ifdef DEBUG
			if( xmdebug )
				if( xs_p->xsy_state&XSY_WAITING )
					printf( "ex_klfree:( %x,%d )\n", xs_p, xs_p->xsy_count );
#endif DEBUG
			if( xs_p->xsy_baddr )
				{
				brelse( xs_p->xsy_baddr, BSIZE );
				xs_p->xsy_baddr = 0;
				}
			xs_p->xsy_state = XSY_FREE;
			}
		splx( s );
		}

	if( xs->flags & EX_WAITING )
		wakeup( (caddr_t)&xs->flags );
	}
