/*
 * $RCSfile: sim_ether.c,v $
 *
 * x-kernel v3.3
 *
 * Original source Copyright (c) 1993,1991,1990,1996  Arizona Board of Regents
 * Modifications Copyright (c) 1993 Massachusetts Board of Regents
 *
 * $Log: sim_ether.c,v $
 * Revision 1.3  1997/07/03 17:19:24  llp
 * c
 * fixed EMAXPAK
 *
 * Revision 1.2  1996/01/31 16:23:05  slm
 * Updated version.
 *
 * Revision 1.1  1995/07/29  02:55:19  slm
 * Initial revision
 *
 * Revision 1.66.1.3  1994/11/26  00:27:10  hkaram
 * msgRefresh replaces msgConstructAllocate
 *
 * Revision 1.66.1.2  1994/11/22  22:50:36  hkaram
 * Converted to new msg tool
 *
 * Revision 1.66.1.1  1994/10/28  17:31:24  hkaram
 * New branch
 *
 * Revision 1.66  1994/05/06  20:28:44  gordon
 * working Solaris version
 *
 * Revision 1.9.1.1  1994/03/14  20:58:08  menze
 *   [ 1994/03/14          umass ]
 *   changes to thread management and statistics
 *
 * Revision 1.9  1994/02/05  00:34:04  menze
 *   [ 1994/02/03          menze ]
 *   Converted to use rom-option library
 *
 * Revision 1.8  1993/12/16  02:02:31  menze
 * Uses SIM_SOCK2ADDR (AZ version) control op instead of SOCK2SIMADDRESS
 * (UMASS version)
 * Other minor changes
 *
 * Revision 1.7  1993/11/13  00:45:26  menze
 * Original version from UMass
 */

#include <stdio.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/resource.h>
/*#include <sys/prctl.h>*/
#include <netdb.h>
#include <syslog.h>
#include <errno.h>
#include <signal.h>
#include <netinet/in.h>
/*#include <ulocks.h>*/

#include "x_stdio.h"
#include "xkernel.h"
#include "eth.h"
#include "eth_i.h"
#include "sim_ether_i.h"
#include "arp.h"
#include "platform.h"	/* For queue and process routines */
#include "mac.h"
#include "sim.h"
/*#include "xk_lock.h"*/

int	tracesimethp;

#ifdef  XK_DEBUG
#define SIMFDDI_STATS
#define PACKET_STATS
#else
#undef  SIMFDDI_STATS
#define PACKET_STATS
#endif

#define PRIVATE static
#define PROCEDURE void
#define PTR void *

#ifdef  PACKET_STATS
PRIVATE int             sentPackets = 0;
PRIVATE int             recvPackets = 0;
PRIVATE int             bcastSentPackets = 0;
PRIVATE int             sendErrorCount = 0;
PRIVATE int             recvNoThreadCount = 0;
/* PRIVATE void *          packetLock;   unused? also for statistics */
static mutex_t packetlock;
#endif  /* PACKET_STATS */

#ifdef SIMETH_STATS
static int		activeThreads;
static int		activeHighWater;
#endif
static ETHhost		ethBcastHost = ETH_BCAST_HOST;
static int		instance;
static sema_t		warm_pool_semaphore;
static sema_t		init_semaphore;

extern int	gethostname( char *, int );
extern char *	inet_ntoa( struct in_addr );
extern int	socket( int, int, int );

void simeth_init(Protl);
static void simEth2sock(ETHhost ethAddr, struct sockaddr_in *sockAddr);
static void sock2simEth(ETHhost *ethAddr, IPhost inAddr, int udpPort);
static int	arpForEachFunc( ArpBinding *, VOID * );
static void 	warm_pool_handler( Event, VOID * );
static void 	init_eth_warm_pool( Protl );
static int	initSocket( int );
static void	internalDemux( Sessn, Msg * );
static int	read_ether( int, char *, int );
static XkReturn	readPortRom( Protl, char **, int, int, VOID * );
static XkReturn	readPortBackRom( Protl, char **, int, int, VOID * );
static void	sendOnSocket( int, ETHhost *, char *, int );
static int	simethControl( Protl, int, char *, int );
static XkReturn	simethOpenEnable( Protl, Protl, Protl, Part * );
static XkHandle	simethPush( Sessn, Msg * );
static void	writeBcast( PState *, char *, int );


static ProtlRomOpt protlOpts[] = {
    { "port", 3, readPortRom },
    { "", 2, readPortBackRom }
};


/* changes to support full internet addressing in rom files */

static void simEth2sock(ETHhost ethAddr, struct sockaddr_in *sockAddr)
{
  bzero((char *)sockAddr, sizeof (struct sockaddr_in));
  sockAddr->sin_family = AF_INET;
  /* 
   * IP address is in the first 4 bytes of the ethernet address
   * UDP port is in the 5th and 6th bytes of the ethernet address
   */
  sockAddr->sin_addr = *(struct in_addr *)&ethAddr;
  sockAddr->sin_port = (*(u_short *)((char *)&ethAddr + 4));
}


static void sock2simEth(ETHhost *ethAddr, IPhost inAddr, int udpPort)
{
    char *cp1, *cp2;
    short tmpshrt;
    int i;
    
    cp1 = (char *)ethAddr;
    cp2 = (char *) &inAddr;		/* passed by value now - cjt */
    for (i=0; i<4; i++) *cp1++ = *cp2++;
    tmpshrt = htons((u_short)udpPort);
    cp2 = (char *) &tmpshrt;
    for (i=0; i<2; i++) *cp1++ = *cp2++;
}

#ifdef PACKET_STATS

PRIVATE PROCEDURE
printPacketStats(Event ev, PTR arg)
{
    /* xLockAcquire(packetLock); */
	mutex_lock(&packetlock);
    printf("Received %d, sent %d, diff %d, bcast %d\n",
           recvPackets, sentPackets,
           (recvPackets-sentPackets), bcastSentPackets);
    /* xLockRelease(packetLock); */
	mutex_unlock(&packetlock);
    evDetach(evSchedule( (EvFunc) printPacketStats, 0, 30*1000*1000 ));
}

#endif /* PACKET_STATS */

static int
arpForEachFunc(ArpBinding *ab, VOID *arg)
{
    EthMsg	*m = (EthMsg *)arg;

    xTrace0(simethp, TR_FUNCTIONAL_TRACE, "arpForEach invoked");
#ifdef PACKET_STATS
    /* xLockAcquire(packetLock); */
	mutex_lock(&packetlock);
    bcastSentPackets ++;
    /* xLockRelease(packetLock); */
	mutex_unlock(&packetlock);
#endif /* PACKET_STATS */
    sendOnSocket(m->sock, (ETHhost *)&ab->hw, m->buf, m->len);
    return TRUE;
}


/* 
 * We simulate broadcast by having ARP perform a callback
 * for every host it has in its table.  We then do a direct send for
 * each ARP entry.
 */
static void
writeBcast(PState *ps, char *buf, int len)
{
    EthMsg	m;
    ArpForEach	afe;
    
    xTrace0(simethp, TR_FUNCTIONAL_TRACE, "writeBcast");

    if ( ! ps->arp ) {
	xError("eth bcast write fails -- no arp protocol");
	return;
    }
    m.buf = buf;
    m.len = len;
    m.sock = ps->sock;
    afe.f = arpForEachFunc;
    afe.v = &m;
    xControlProtl(ps->arp, ARP_FOR_EACH, (char *)&afe, sizeof(ArpForEach));
}	


static void
sendOnSocket(int sock, ETHhost	*dest, char *buf, int len)
{  
/*     extern unsigned long inet_addr(); unused? */
    struct  sockaddr_in	addr;
    int bytesSent;


    simEth2sock(*dest, &addr);
    xTrace3(simethp, TR_FUNCTIONAL_TRACE,
	    "write_ether: sending %d bytes to <%d,%s>",
	    len, ntohs(addr.sin_port), inet_ntoa(addr.sin_addr));

   
        bytesSent = sendto(sock, buf, len, 0,
             (struct sockaddr *)&addr, sizeof(struct sockaddr));


        if (bytesSent != len)
	  {

#ifdef PACKET_STATS
	    /* xLockAcquire(packetLock); */
		mutex_lock(&packetlock);
	    sendErrorCount++ ;
	    /* xLockRelease(packetLock); */
		mutex_unlock(&packetlock);
#endif
	    
            xTrace2(simethp, TR_ERRORS,
                "sendOnSocket: sendto bytesSent %d, errno %d",
                bytesSent, errno);
          }
        else
	  {

#ifdef PACKET_STATS
	    /* xLockAcquire(packetLock); */
		mutex_lock(&packetlock);
	    sentPackets ++;
	    /* xLockRelease(packetLock); */
		mutex_unlock(&packetlock);
#endif

	  }
  }


static int
read_ether( int sock, char *msg, int len )
{
    struct sockaddr_in	from;
    int			size, n;
    
    xTrace1(simethp, TR_FUNCTIONAL_TRACE, "read_ether from sock %d", sock);
    size = sizeof(from);
    if ((n = recvfrom(sock, msg, len, 0, (struct sockaddr *)&from, &size)) < 0)
    {
      xTrace2(simethp, TR_ERRORS, "Got %d return, %d errno from recvfrom", 
        n, errno);
      return -1;
    }
#ifdef PACKET_STATS
    /* xLockAcquire(packetLock); */
	mutex_lock(&packetlock);
    recvPackets++;
    /* xLockRelease(packetLock); */
	mutex_unlock(&packetlock);
#endif /* PACKET_STATS */

    xTrace3(simethp, TR_FUNCTIONAL_TRACE,
	    "read_ether: receiving %d bytes from <%d,%s>",
	    n, ntohs(from.sin_port), inet_ntoa(from.sin_addr));
    return n;
}


static int
initSocket( int port )
{
    int			s, bufSize, bufSizeSize;
    struct sockaddr_in	addr;
    int			on = 1;

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons((u_short)port);
    if ( (s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
	Kabort("init_ether: cannot open socket");
    }	
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof (on));
#ifdef SO_BROADCAST
    setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char *)&on, sizeof (on));
#endif

     /* increase receive buffer sizes from default */

    bufSize = RCVBUFSIZE;
    if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&bufSize,
		   sizeof(bufSize))) {
	xTrace1(simethp, TR_ERRORS,
		"Could not set size of ethernet receive buffer to %d",
		bufSize);
    }

    bufSizeSize = sizeof(bufSize);
    if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&bufSize, &bufSizeSize)) {
	xTrace0(simethp, TR_ERRORS,
		"Could not get size of ethernet receive buffer");
    } else {
	xTrace1(simethp, TR_GROSS_EVENTS,
		"Receive buffer of ethernet socket: %d", bufSize);
    }
    if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&bufSize, &bufSizeSize)) {
	xTrace0(simethp, TR_ERRORS,
		"Could not get size of ethernet send buffer");
    } else {
	xTrace1(simethp, TR_GROSS_EVENTS,
		"Send buffer of ethernet socket: %d", bufSize);
    }
    if (bind(s, (struct sockaddr *)&addr, sizeof(addr))) {
      Kabort("init_ether: cannot bind socket");
    }
    xTrace1(simethp, TR_GROSS_EVENTS,
		"initSocket returning %d", s);
    return s;
}



static XkReturn
readPortRom( self, str, nFields, line, arg )
    Protl	self;
    char	**str;
    int		nFields, line;
    VOID	*arg;
{
    PState	*ps = (PState *)self->state;
    return sscanf(str[2], "%d", &ps->port) >= 1 ? XK_SUCCESS : XK_FAILURE;
}


/* 
 * Backward compatibility ... no opcode field
 */
static XkReturn
readPortBackRom( self, str, nFields, line, arg )
    Protl	self;
    char	**str;
    int		nFields, line;
    VOID	*arg;
{
    PState	*ps = (PState *)self->state;
    return sscanf(str[1], "%d", &ps->port) >= 1 ? XK_SUCCESS : XK_FAILURE;
}


void simeth_init(Protl self)
{
    struct hostent	*h;
    IPhost 		*in;
    char		name[100];
    int			namelen=100;
    PState		*ps;
    
    xTrace0(simethp, TR_MAJOR_EVENTS, "init_ether");
    if ( instance > SIMETH_MAX_INSTANCES ) {
	Kabort("simeth -- too many instances");
    }
    ps = X_NEW(PState);
    bzero((char *)ps, sizeof(PState));
    self->state = (VOID *)ps;
    ps->mtu = ETH_MTU_SIZE;
    ps->port = -1;

    findProtlRomOpts(self, protlOpts, sizeof(protlOpts)/sizeof(ProtlRomOpt), 0);
    if ( ps->port == -1 ) {
	xTrace1(simethp, TR_ERRORS,
		"No port specified for simeth instance %d", instance);
	Kabort("simeth -- port specification error");
    }
    xTrace1(simethp, TR_MAJOR_EVENTS,
	    "init_ether: listening on port %d", ps->port);
    if ( (ps->sock = initSocket(ps->port)) == 0 ) {
	Kabort("init_ether: problems creating socket");
    }

    /* 
     * Determine my host address
     */
    gethostname(name,namelen);
    h = gethostbyname(name);
    in = (IPhost *) h->h_addr;
    sock2simEth(&ps->myHost, *in, ps->port);
    xTrace1(simethp, TR_GROSS_EVENTS,
	    "simeth_init: ethernet started with address %s",
	    ethHostStr(&ps->myHost));

    self->push = simethPush;
    self->controlprotl = simethControl;
    self->openenable  = simethOpenEnable;
    self->up = 0;
    if ( instance == 0 ) {
	init_eth_warm_pool(self);		
    }
    instance++;
#ifdef PACKET_STATS
    /* packetLock = xLockAllocate(); */
	mutex_init(&packetlock,USYNC_THREAD,&packetlock);
#endif /* PACKET_STATS */
}


static void
ethMsgStore( void *hdr, char *netHdr, long len, void *arg )
{
    xAssert(len == sizeof(ETHhdr));
    ((ETHhdr *)hdr)->type = htons(((ETHhdr *)hdr)->type);
    bcopy(hdr, netHdr, sizeof(ETHhdr));
}


static long
ethMsgLoad( void *hdr, char *netHdr, long len, void *arg )
{
    xAssert(len == sizeof(ETHhdr));
    bcopy(netHdr, (char *)hdr, sizeof(ETHhdr));
    ((ETHhdr *)hdr)->type = ntohs(((ETHhdr *)netHdr)->type);
    return sizeof(ETHhdr);
}


static XkHandle
simethPush(Sessn self, Msg *msg)
{
    ETHhdr	*hdr = msgGetAttr(msg, 0);
    char	buffer[EMAXPAK];
    char	*bufPtr = buffer;
    int		len;
    int         dataLen;
    PState	*ps = (PState *)self->state;
    VOID        *buf;
    MsgWalk  mi;
    u_char      *data;

    xTrace0(simethp, TR_EVENTS, "simethPush");
    xAssert(hdr);

    buf = msgPush(msg, sizeof(ETHhdr));
    xAssert(buf);
    ethMsgStore(hdr, buf, sizeof(ETHhdr), 0);

    if ( (len = msgLength(msg)) > EMAXPAK ) {
	xTrace2(simethp, TR_SOFT_ERRORS,
		"sim ether driver: msgLength (%d) is larger than max (%d)",
		len, EMAXPAK);
	return XMSG_ERR_HANDLE;
    }
    /*
     * Place message contents in buffer
     */
    msgWalkInit(&mi, msg);
    while ((data = msgWalkNext(&mi, &dataLen)) != 0) {
      bcopy(data, bufPtr, dataLen);
      bufPtr += dataLen;
    }
    msgWalkDone(&mi);

    if ( ETH_ADS_EQUAL(hdr->dst, ethBcastHost) ) {
	writeBcast(ps, buffer, len);
    } else {
	sendOnSocket(ps->sock, &hdr->dst, buffer, len);
    }
    return XMSG_NULL_HANDLE;
}    


static void
internalDemux(Sessn self, Msg *msg)
{
    ETHhdr	hdr;
    VOID       *buf;
    
    xTrace0(simethp, TR_EVENTS, "in eth internal demux");

    buf = msgPop(msg, sizeof(ETHhdr));
    if (buf == NULL) {
	xTrace0(simethp, TR_SOFT_ERRORS,
		"eth_demux: incoming message too small!");
	return;
    }
    ethMsgLoad(&hdr, buf, sizeof(ETHhdr), 0);

    if ( ! self->up ) {
	xTrace0(simethp, TR_ERRORS, "eth_demux: no upper protocol!");
	return;
    }
    msgSetAttr(msg, 0, &hdr, sizeof(ETHhdr));
    xDemux(xGetUp(self), self, msg);
    xTrace0(simethp, TR_EVENTS, "eth internal demux finished");
}


/*
 * warm_pool_handler:
 * init message struct;
 * forever
 *     allocate new message
 *     release monitor
 *     queue self in warm_pool_semaphore;
 *     go read socket after waking up
 *     after reading, acquire monitor
 *     signal next in warm_pool_semaphore
 *     demux message
 *     init message struct
 */
static void
warm_pool_handler(Event ev, VOID *arg)
{
  Msg		msg;
  char 	*bufPtr;
  int		buflen;
  Sessn 	self = (Sessn) arg;
  PState	*ps = (PState *)self->state;

   xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:initializing", thr_self()); 
  
  /* tell the init thread we're alive */
  sema_post(&init_semaphore);
  
  xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:allocating", thr_self());
  bufPtr = msgConstructAllocate(&msg, EMAXPAK);

  for (;;) {
    
    xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:yielding", thr_self()); 

    MASTER_UNLOCK;
    
    sema_wait(&warm_pool_semaphore);
    
    xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:woke up", thr_self()); 
    
    if ((buflen = read_ether(ps->sock,bufPtr,EMAXPAK)) == -1) {
      Kabort("warm_pool_handler: read_ether failed");
    }
    
    xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:have read ETH", thr_self()); 

    xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:unblocking next", thr_self()); 



    sema_post(&warm_pool_semaphore);


    MASTER_LOCK;

    /* 
     * set the message to reflect the number of bytes received 
     */
    msgTruncate(&msg, buflen);

    xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:got lock", thr_self()); 
    
    xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:calling demux", thr_self()); 
    internalDemux(self, &msg);
    
    xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:allocating", thr_self()); 
    bufPtr = msgRefresh(&msg, EMAXPAK);

    xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:end loop", thr_self()); 
  }
}
	

static void
init_eth_warm_pool(Protl self)
{
    int 	i;
/*     Process	*first_process;   unused? */
    static int	initialized = 0;
    
    if ( initialized ) {
	xTrace0(simethp, TR_EVENTS, "simeth process pool already initialized");
	return;
    }
    xTrace0(simethp, TR_EVENTS, "init_eth_warm_pool:initializing"); 
    initialized = 1;
	sema_init(&init_semaphore,0,USYNC_THREAD,&init_semaphore);
	sema_init(&warm_pool_semaphore,0,USYNC_THREAD,&warm_pool_semaphore);

    /* create warm pool */
    for (i = 0; i < MAX_ETH_WARM_POOL; i++)
        evDetach( evSchedule(warm_pool_handler, (VOID *) self, 0));

    xTrace0(simethp, TR_EVENTS, "init_eth_warm_pool:waiting"); 

    /* wait for all warm pool processes to become ready */
    MASTER_UNLOCK;

    xTrace0(simethp, TR_EVENTS, "init_eth_warm_pool:locking init"); 

    for (i = 0; i < MAX_ETH_WARM_POOL; i++)
    {
      xTrace1(simethp, TR_EVENTS, "init_eth_warm_pool:mutex-lock %d", i); 
      sema_wait(&init_semaphore);
    }

    xTrace0(simethp, TR_EVENTS, "init_eth_warm_pool:finished locking"); 
    MASTER_LOCK;

    xTrace0(simethp, TR_EVENTS, "init_eth_warm_pool:nearly finished"); 

    /* fire off first thread */
    sema_post(&warm_pool_semaphore);

    xTrace0(simethp, TR_EVENTS, "init_eth_warm_pool:finished");
}

static XkReturn
simethOpenEnable( self, hlp, hlpType, p )
    Protl	self, hlp, hlpType;
    Part	*p;
{
    if ( self->up ) {
	xError("simethOpenEnable called multiple times!");
	return XK_FAILURE;
    }
    self->up = hlp;
    return XK_SUCCESS;
}


static int
simethControl(Protl s, int op, char *buf, int len)
{
    PState	*ps = (PState *)s->state;

    switch (op) {

      case MAC_REGISTER_ARP:
      case ETH_REGISTER_ARP:
	/* 
	 * ARP registers itself with us so we can ask it to perform
	 * callbacks in order to simulate hardware broadcast.  
	 */
	checkLen(len, sizeof(Protl));
	ps->arp = *(Protl *)buf;
	return 0;
     
     /*
      * case MAC_IS_REAL_DRIVER:
      *    checkLen(len, sizeof(bool));
      *    (*(bool *)buf) = FALSE;
      *    return sizeof(bool);
      */

      case MAC_DUMP_STATS:
      case ETH_DUMP_STATS:
#ifdef SIMETH_STATS	
	{
	    xTrace0(simethp, TR_ALWAYS, "SIMETH statistics:");
	    xTrace3(simethp, TR_ALWAYS,
		    "\tthreads: %d\tactive: %d\thigh-water: %d",
		    MAX_ETH_WARM_POOL, activeThreads, activeHighWater);
	}
#endif
#ifdef PACKET_STATS
        /* xLockAcquire(packetLock); */
		mutex_lock(&packetlock);
        printf( "\nSIMETH packet statistics:\n\n" );
        printf( "  received   = %d\n", recvPackets );
        printf( "  sent       = %d\n", sentPackets );
        printf( "  difference = %d\n", (recvPackets-sentPackets) );
        printf( "  broadcasts = %d\n", bcastSentPackets );
        printf( "  broken up send errors       = %d\n", sendErrorCount);
        printf( "  no thread on receive errors = %d\n", recvNoThreadCount );
        /* xLockRelease(packetLock); */
		mutex_unlock(&packetlock);
#endif /* PACKET_STATS */
        return 0;

      case GETMAXPACKET:
      case GETOPTPACKET:
        checkLen(len, sizeof(int));
        *(int *) buf = ps->mtu;
        return (sizeof(ps->mtu));

      case GETMYHOST:
	checkLen(len, sizeof(ETHhost));
	bcopy((char *) &ps->myHost, buf, sizeof(ETHhost));
	return (sizeof(ETHhost));

      case SIM_SOCK2ADDR:
	{
	    SimAddrBuf *aBuf = (SimAddrBuf *)buf;

	    checkLen(len, sizeof(SimAddrBuf));
	    sock2simEth((ETHhost *)&aBuf->genericHost, aBuf->ipHost, 
	                aBuf->udpPort); 
	    return sizeof(SimAddrBuf);
	}

      default:
	return -1;

    }
}
