/* 
 * $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.2  1996/01/30 20:49:43  slm
 * Updated copyright and version.
 *
 * Revision 1.1  1995/07/29  02:38:26  slm
 * Initial revision
 *
 * Revision 1.9.1.1.1.3  1994/11/26  00:22:41  hkaram
 *  msgRefresh replaces msgConstructAllocate
 *
 * Revision 1.9.1.1.1.2  1994/11/22  22:03:59  hkaram
 * Converted to new msg tool
 *
 * Revision 1.9.1.1.1.1  1994/11/12  19:07:55  hkaram
 * New branch
 *
 * 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

#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;   /* also for statistics */
#endif  /* PACKET_STATS */

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

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

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

void simEth2sock(ethAddr, sockAddr)
    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(ethAddr, inAddr, udpPort)
     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(ev, arg)
    Event        ev;
    PTR          arg;
{
    xLockAcquire(packetLock);
    printf("Received %d, sent %d, diff %d, bcast %d\n",
           recvPackets, sentPackets,
           (recvPackets-sentPackets), bcastSentPackets);
    xLockRelease(packetLock);
    evDetach(evSchedule( printPacketStats, 0, 30*1000*1000 ));
}

#endif /* PACKET_STATS */

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

    xTrace0(simethp, TR_FUNCTIONAL_TRACE, "arpForEach invoked");
#ifdef PACKET_STATS
    xLockAcquire(packetLock);
    bcastSentPackets ++;
    xLockRelease(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( ps, buf, len )
    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( sock, dest, buf, len )
    int		sock;
    ETHhost	*dest;
    char	*buf;
    int		len;
{  
    extern unsigned long inet_addr();
    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);
	    sendErrorCount++ ;
	    xLockRelease(packetLock);
#endif
	    
            xTrace2(simethp, TR_ERRORS,
                "sendOnSocket: sendto bytesSent %d, errno %d",
                bytesSent, errno);
          }
        else
	  {

#ifdef PACKET_STATS
	    xLockAcquire(packetLock);
	    sentPackets ++;
	    xLockRelease(packetLock);
#endif

	  }
  }


static int
read_ether( int sock, char *msg, int len )
{
    struct sockaddr_in	from;
    int			size, n;
    
    xTrace1(simethp, TR_EVENTS, "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);
    recvPackets++;
    xLockRelease(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( port )
    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( self )
    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();
#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( self, msg )
    Sessn	self;
    Msg 	*msg;
{
    ETHhdr	*hdr = msgGetAttr(msg, 0);
    char	buffer[MAX_ETH_PACKET_SIZE];
    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)) > MAX_ETH_PACKET_SIZE ) {
	xTrace2(simethp, TR_SOFT_ERRORS,
		"sim ether driver: msgLength (%d) is larger than max (%d)",
		len, MAX_ETH_PACKET_SIZE);
	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(self, msg)
    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(ev, arg)
    Event	ev;
    VOID 	*arg;
{
    Msg		msg;
    char 	*bufPtr;
    int		buflen;
    Process     *my_process, *next_process;
    Sessn 	self = (Sessn) arg;
    PState	*ps = (PState *)self->state;

    my_process = process_self();
    xAssert(my_process);
    xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:initializing",
            my_process->pid);

    /* tell the init thread we're alive */
    usvsema(init_semaphore);

    xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:allocating",
            my_process->pid);
    bufPtr = msgConstructAllocate(&msg, MAX_ETH_PACKET_SIZE);

    for (;;) {

        xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:yielding",
            my_process->pid);
        xk_master_unlock();

	uspsema(warm_pool_semaphore);

        xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:woke up",
            my_process->pid);

        if ((buflen = read_ether(ps->sock,bufPtr,MAX_ETH_PACKET_SIZE))
            == -1) {
            Kabort("warm_pool_handler: read_ether failed");
        }

        xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:have read ETH",
            my_process->pid);

        xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:unblocking next",
            my_process->pid);
        usvsema(warm_pool_semaphore);

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

	xk_master_lock();

        xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:got lock",
            my_process->pid);

        xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:calling demux",
            my_process->pid);
	internalDemux(self, &msg);

        xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:re-allocating",
            my_process->pid);
        bufPtr = msgRefresh(&msg, MAX_ETH_PACKET_SIZE);

        xTrace1(simethp, TR_EVENTS, "warm_pool_handler %d:end loop",
            my_process->pid);
    }
}
	

static void
init_eth_warm_pool(self)
Protl self;
{
    int 	i;
    Process	*first_process;
    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;
    init_semaphore = usnewsema(xkernel_arena, 0);
    warm_pool_semaphore = usnewsema(xkernel_arena, 0);

    /* 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 */
    xk_master_unlock();

    for (i = 0; i < MAX_ETH_WARM_POOL; i++)
    {
	uspsema(init_semaphore);
    }
    xk_master_lock();

    /* fire off first thread */
    usvsema(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( s, op, buf, len )
    Protl	s;
    int 	op, len;
    char 	*buf;
{
    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);
        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);
#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;

    }
}
