/* 
 * ethProtl.c 
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1993,1991,1990,1996  Arizona Board of Regents
 *
 * $Revision: 1.5 $
 * $Date: 1996/06/14 21:36:14 $
 */

/*
 * The xkernel ethernet driver is structured in two layers.
 *
 * The ethernet protocol layer (this file) is independent of any
 * particular ethernet controller hardware.
 * It comprises the usual xkernel protocol functions,
 * e.g. eth_open, eth_push, etc.).
 * It knows about ethernet addresses and "types,"
 * but nothing about any particular ethernet controller.
 *
 * The device driver, which exports an xkernel interface, sits below
 * this protocol
 */

#include "xkernel.h"
#include "./eth.h"
#include "./eth_i.h"
#include "rom.h"

typedef struct {
    ETHhdr	hdr;
} SState;

typedef struct {
    ETHhost	host;
    ETHtype	type;
} ActiveId;

typedef struct {
    Sessn    	prmSessn;
    ETHhost 	myHost;
    Map		actMap;
    Map 	pasMap;
    int		mtu;
} PState;

typedef ETHtype  PassiveId;

typedef struct {
    Msg		msg;
    Protl	self;
    Protl	llp;
    ETHhdr	hdr;
} RetBlock;

#define	ETH_ACTIVE_MAP_SZ	257
#define	ETH_PASSIVE_MAP_SZ	13

int 	traceethp;

#ifdef XK_DEBUG

static ETHhost	ethBcastHost = BCAST_ETH_AD;

#endif


#ifdef __STDC__

static void	demuxStub( Event, VOID * );
static Sessn	ethCreateSessn( Protl, Protl, Protl, ActiveId * );
static void	ethSessnInit( Sessn );
static int	ethControlProtl( Protl, int, char *, int );
static XkReturn	ethDemux( Protl, Sessn, Msg * );
static Sessn	ethOpen( Protl, Protl, Protl, Part * );
static XkReturn	ethOpenEnable( Protl, Protl, Protl, Part * );
static XkReturn	ethOpenDisable( Protl, Protl, Protl, Part * );
static XkReturn	ethOpenDisableAll( Protl, Protl );
static XkReturn	ethClose( Sessn );
static XkHandle	ethPush( Sessn, Msg * );
static XkHandle	ethLoopPush( Sessn, Msg * );
static XkReturn	ethPop( Sessn, Sessn, Msg *, VOID * );
static int	ethControlSessn( Sessn, int, char *, int );
static Part     *ethGetParticipants(Sessn);
static long	getRelProtNum( Protl, Protl, char * );

#else

static void	demuxStub();
static Sessn	ethCreateSessn();
static void	ethSessnInit();
static int	ethControlProtl();
static XkReturn	ethDemux();
static Sessn	ethOpen();
static XkReturn	ethOpenEnable();
static XkReturn	ethOpenDisable();
static XkReturn	ethOpenDisableAll();
static XkReturn	ethClose();
static XkHandle	ethPush();
static XkHandle	ethLoopPush();
static XkReturn	ethPop();
static int	ethControlSessn();
static Part     *ethGetParticipants();
static long	getRelProtNum();

#endif __STDC__


static long
getRelProtNum( hlp, llp, s )
    Protl	hlp, llp;
    char	*s;
{
    long	n;

    n = relProtNum(hlp, llp);
    if ( n == -1 ) {
	xTrace3(ethp, TR_ERRORS,
		"eth %s could not get prot num of %s relative to %s",
		s, hlp->name, llp->name);
    }
    if ( n < 0 || n > 0xffff ) {
	return -1;
    }
    return n;
}


#define ERROR \
		{ sprintf(errBuf, \
			       "ETH format error in line %d of the rom file",\
			       i + 1);	\
		  xError(errBuf); }

/* 
 * May set the 'mtu' field of the protocol state
 */
static void
processRomFile( self )
    Protl	self;
{
    PState	*ps = (PState *)self->state;
    int 	i;
    char	instStr[80];

    strcpy(instStr, self->name);
    if ( strlen(self->instName) > 0 ) {
	strcat(instStr, "/");
	strcat(instStr, self->instName);
    }
    for ( i=0; rom[i][0]; i++ ) {
	if ( ! strcmp(rom[i][0], instStr) ) {
	    if ( ! rom[i][1] ) {
		ERROR;
		break;
	    }
	    if ( ! strcmp(rom[i][1], "mtu") ) {
		if ( ! rom[i][2] || 
#ifdef XKMACHKERNEL
				    sscanf1
#else
				    sscanf
#endif
					  (rom[i][2], "%d", &ps->mtu) < 1 ) {
		    ERROR;
		    break;
		}
	    }
	}
    }
}
#undef ERROR


void
eth_init( self )
     Protl self;
{
    PState	*ps;
    Protl	llp;

    xTrace2(ethp, TR_EVENTS, "eth_init for %s/%s", self->name, self->instName);
    if (!xIsProtl(llp = xGetProtlDown(self, 0))) {
	xError("eth can not get driver protocol object");
	return;
    }
    if ( xOpenEnable(self, self, llp, 0) == XK_FAILURE ) {
	xError("eth can not openenable driver protocol");
	return;
    }
    ps = X_NEW(PState);
    self->state = (VOID *)ps;
    ps->actMap = mapCreate(ETH_ACTIVE_MAP_SZ, sizeof(ActiveId));
    ps->pasMap = mapCreate(ETH_PASSIVE_MAP_SZ, sizeof(PassiveId));
    ps->prmSessn = 0;
    ps->mtu = MAX_ETH_DATA_SZ;
    if (xControlProtl(llp, GETOPTPACKET, (char *)&ps->mtu, sizeof(int)) <
	(int)sizeof(int))
      ps->mtu = MAX_ETH_DATA_SZ;
    processRomFile( self );
    xTrace1(ethp, TR_MAJOR_EVENTS, "eth using mtu %d", ps->mtu);
    if ( xControlProtl(llp, GETMYHOST, (char *)&ps->myHost, sizeof(ETHhost))
			< (int)sizeof(ETHhost) ) {
	xError("eth_init: can't get my own host");
	return;
    }
    self->controlprotl = ethControlProtl;
    self->open = ethOpen;
    self->openenable = ethOpenEnable;
    self->opendisable = ethOpenDisable;
    self->demux = ethDemux;
    self->opendisableall = ethOpenDisableAll;
}


static Sessn
ethOpen( self, hlp, hlpType, part )
    Protl	self, hlp, hlpType;
    Part 	*part;
{
    PState	*ps = (PState *)self->state;
    ActiveId  	key;
    Sessn 	ethSessn;
    ETHhost	*remoteHost;
    long	protNum;
    
    xTrace2(ethp, TR_MAJOR_EVENTS, "eth_open: %s/%s", self->name, 
	    self->instName);
    if ( part == 0 || partLength(part) < 1 ) {
	xTrace0(ethp, TR_SOFT_ERRORS, "ethOpen -- bad participants");
	return ERR_SESSN;
    }
    remoteHost = (ETHhost *)partPop(*part);
    xAssert(remoteHost);
    bzero((char *)&key, sizeof(ActiveId));
    key.host = *remoteHost;
    if ( (protNum = getRelProtNum(hlpType, self, "open")) == -1 ) {
	return ERR_SESSN;
    }
    key.type = protNum;
    xTrace2(ethp, TR_MAJOR_EVENTS, "eth_open: destination address = %s:%4x",
	    ethHostStr(&key.host), key.type);
    key.type = htons(key.type);
    if (mapResolve(ps->actMap, &key, (void **)&ethSessn) == XK_FAILURE)
	ethSessn = ethCreateSessn(self, hlp, hlpType, &key);
    xTrace1(ethp, TR_MAJOR_EVENTS, "eth_open: returning %X", ethSessn);
    return (ethSessn);

}


static Sessn
ethCreateSessn( self, hlp, hlpType, key )
    Protl	self, hlp, hlpType;
    ActiveId	*key;
{
    Sessn	s;
    Protl	llp = xGetProtlDown(self, 0);
    SState	*ss;
    PState	*ps = (PState *)self->state;

    s = xCreateSessn(ethSessnInit, hlp, hlpType, self, 1, (Sessn *)&llp);
    if ( ETH_ADS_EQUAL(key->host, ps->myHost) ) {
	xTrace0(ethp, TR_MAJOR_EVENTS,
		"ethCreateSessn -- creating loopback session");
	s->push = ethLoopPush;
    }
    s->binding = mapBind(ps->actMap, (char *)key, s);
    if ( s->binding == ERR_BIND ) {
	xTrace0(ethp, TR_ERRORS, "error binding in ethCreateSessn");
	xDestroySessn(s);
	return ERR_SESSN;
    }
    ss = X_NEW(SState);
    ss->hdr.dst = key->host;
    ss->hdr.type = key->type;
    ss->hdr.src = ps->myHost;
    s->state = (VOID *)ss;
    return s;
}


static XkReturn
ethOpenEnable(self, hlp, hlpType, part)
    Protl self, hlp, hlpType;
    Part *part;
{
    PState	*ps = (PState *)self->state;
    PassiveId	key;
    long	protNum;
    
    if ( (protNum = getRelProtNum(hlpType, self, "openEnable")) == -1 ) {
	return XK_FAILURE;
    }
    xTrace2(ethp, TR_GROSS_EVENTS, "eth_openenable: %s/%s", self->name,
	    self->instName);
    xTrace2(ethp, TR_GROSS_EVENTS, "eth_openenable: hlp=%x, protlNum=%x",
	    hlp, protNum);
    key = protNum;
    key = htons(key);
    return defaultOpenEnable(ps->pasMap, hlp, hlpType, (VOID *)&key);
} 


static XkReturn
ethOpenDisable(self, hlp, hlpType, part)
    Protl self, hlp, hlpType;
    Part *part;
{
    PState	*ps = (PState *)self->state;
    long	protNum;
    PassiveId	key;
    
    if ( (protNum = getRelProtNum(hlpType, self, "opendisable")) == -1 ) {
	return XK_FAILURE;
    }
    xTrace2(ethp, TR_GROSS_EVENTS, "eth_opendisable: hlp=%x, protlNum=%x",
	    hlp, protNum);
    key = protNum;
    key = htons(key);
    return defaultOpenDisable(ps->pasMap, hlp, hlpType, (VOID *)&key);
}


static int
dispActiveMap( key, val, arg )
    VOID	*key, *val, *arg;
{
    Sessn	s = (Sessn)val;
    xPrintSessn(s);
    return MFE_CONTINUE;
}


static int
dispPassiveMap( key, val, arg )
    VOID	*key, *val, *arg;
{
#ifdef XK_DEBUG
    Enable	*e = (Enable *)val;
#endif
    xTrace2(ethp, TR_ALWAYS, "Enable object, hlp == %s, hlpType = %s",
	    e->hlp->fullName, e->hlpType->fullName);
    return MFE_CONTINUE;
}


static XkReturn
ethOpenDisableAll( self, hlp )
    Protl	self, hlp;
{
    XkReturn	xkr;
    PState		*ps = (PState *)self->state;

    xTrace0(ethp, TR_MAJOR_EVENTS, "eth openDisableAll called");

    xTrace0(ethp, TR_ALWAYS, "before passive map contents:");
    mapForEach(ps->pasMap, dispPassiveMap, 0);
    xkr = defaultOpenDisableAll(((PState *)self->state)->pasMap, hlp, 0);
    xTrace0(ethp, TR_ALWAYS, "after passive map contents:");
    mapForEach(ps->pasMap, dispPassiveMap, 0);
    xTrace0(ethp, TR_ALWAYS, "active map contents:");
    mapForEach(ps->actMap, dispActiveMap, 0);
    return XK_SUCCESS;
}


static XkReturn
ethDemux( self, llp, msg )
    Protl	self;
    Sessn	llp;
    Msg		*msg;
{
    PState	*ps = (PState *)self->state;
    ActiveId 	actKey;
    PassiveId 	pasKey;
    Sessn 	s = 0;
    Enable 	*e;
    ETHhdr	*hdr = msgGetAttr(msg, 0);
    
    xTrace2(ethp, TR_EVENTS, "eth_demux: %s/%s", self->name, self->instName);
    xTrace1(ethp, TR_FUNCTIONAL_TRACE, "eth type: %x", hdr->type);
    xTrace2(ethp, TR_FUNCTIONAL_TRACE, "src: %s  dst: %s",
	    ethHostStr(&hdr->src), ethHostStr(&hdr->dst));
    xIfTrace(ethp, TR_DETAILED) msgShow(msg);
    xAssert(hdr);
    if ( ps->prmSessn ) {
	Msg	pMsg;
	
	xTrace0(ethp, TR_EVENTS,
		"eth_demux: passing msg to promiscuous session");
	msgConstructCopy(&pMsg, msg);
	xDemux(xGetUp(ps->prmSessn), ps->prmSessn, &pMsg);
	msgDestroy(&pMsg);
    }
#ifdef XK_DEBUG
    /*
     * verify that msg is for this host
     */
    if ( ! (ETH_ADS_EQUAL(hdr->dst, ps->myHost) ||
	    ETH_ADS_EQUAL(hdr->dst, ethBcastHost))) {
	xError("eth_demux: msg is not for this host");
	return XK_FAILURE;
    }

#  if 0
    /* 
     * Temporary for testing
     */
    {
	static int	count = 0;

	/* 
	 * Every 30 packets there is a burst for 10 packets during
	 * which every other packet is delayed.
	 */
	count++;
	if ( ((count / 10) % 3) &&  ! (count % 2) ) {
	    xError("ethDemux delays packet");
	    Delay(4 * 1000);
	    xError("ethDemux delay returns");
	} else {
	    xTrace1(ethp, TR_EVENTS, "ethDemux does not delay (%d)", count);
	}
    }
#  endif
#endif
    bzero((char *)&actKey, sizeof(ActiveId));
    actKey.host = hdr->src;
    actKey.type = hdr->type;
    if (mapResolve(ps->actMap, &actKey, (void **)&s) == XK_FAILURE) {
	pasKey = actKey.type;
	if (mapResolve(ps->pasMap, &pasKey, (void **)&e) == XK_SUCCESS) {
	    xTrace1(ethp, TR_EVENTS,
		    "eth_demux: openenable exists for msg type %x",
		    ntohs(pasKey));
	    xAssert( ntohs(hdr->type) == relProtNum(e->hlpType, self) );
	    s = ethCreateSessn(self, e->hlp, e->hlpType, &actKey);
	    if ( s != ERR_SESSN ) {
		xOpenDone(e->hlp, self, s);
		xTrace0(ethp, TR_EVENTS,
			"eth_demux: sending message to new session");
	    }
	} else {
	    xTrace1(ethp, TR_EVENTS,
		    "eth_demux: openenable does not exist for msg type %x",
		    ntohs(pasKey));
	}
    }
    if (xIsSessn(s)) {
	xPop(s, llp, msg, 0);
    }
    msgDestroy(msg);
    return XK_SUCCESS;
}



static XkReturn
ethClose( s )
    Sessn	s;
{
    PState	*ps = (PState *)xMyProtl(s)->state;

    xTrace1(ethp, TR_MAJOR_EVENTS, "eth closing session %x", s);
    xAssert(xIsSessn(s));
    xAssert( s->rcnt <= 0 );
    mapRemoveBinding( ps->actMap, s->binding );
    xDestroySessn( s );
    return XK_SUCCESS;
}


static void
demuxStub(ev, arg)
    Event	ev;
    VOID 	*arg;
{
    RetBlock	*b = (RetBlock *)arg;

    ethDemux(b->self, (Sessn)b->llp, &b->msg);
    xFree((char *)arg);
}


static XkHandle
ethLoopPush( s, m )
    Sessn	s;
    Msg		*m;
{
    RetBlock	*b;

    b = X_NEW(RetBlock);
    msgConstructCopy(&b->msg, m);
    b->hdr = ((SState *)s->state)->hdr;
    msgSetAttr(&b->msg, 0, (VOID *)&b->hdr, sizeof(b->hdr));
    b->self = s->myprotl;
    b->llp = xGetProtlDown(s->myprotl, 0);
    evDetach( evSchedule(demuxStub, b, 0) );
    return XMSG_NULL_HANDLE;
}


static XkHandle
ethPush( s, msg )
    Sessn	s;
    Msg 	*msg;
{
    xTrace2(ethp, TR_EVENTS, "eth_push: %s/%s",
	    s->myprotl->name, s->myprotl->instName);
    msgSetAttr(msg, 0, &((SState *)s->state)->hdr, sizeof(ETHhdr));
    xPush(xGetSessnDown(s, 0), msg);
    return XMSG_NULL_HANDLE;
}


static XkReturn
ethPop( s, llp, m, h)
    Sessn	s, llp;
    Msg 	*m;
    VOID	*h;
{
    return xDemux(xGetUp(s), s, m);
} 


static int
ethControlSessn(s, op, buf, len)
    Sessn	s;
    int 	op, len;
    char 	*buf;
{
    SState	*ss = (SState *)s->state;
    
    xAssert(xIsSessn(s));
    switch (op) {

      case GETMYHOST:
      case GETMAXPACKET:
      case GETOPTPACKET:
	return ethControlProtl(xMyProtl(s), op, buf, len);
	
      case GETPEERHOST:
	checkLen(len, sizeof(ETHhost));
	bcopy((char *) &ss->hdr.dst, buf, sizeof(ETHhost));
	return (sizeof(ETHhost));
	
      case GETMYHOSTCOUNT:
      case GETPEERHOSTCOUNT:
	checkLen(len, sizeof(int));
	*(int *)buf = 1;
	return sizeof(int);
	
      case GETMYPROTO:
      case GETPEERPROTO:
	checkLen(len, sizeof(long));
	*(long *) buf = ss->hdr.type;
	return sizeof(long);
	
      case ETH_SETPROMISCUOUS:
	{
	    PState	*ps = (PState *)xMyProtl(s)->state;
	    checkLen(len, sizeof(int));
	    ps->prmSessn = s;
	    /* 
	     * Tell the device driver to go into promiscuous mode
	     */
	    return xControlSessn(xGetSessnDown(s, 0), op, buf, len);
	}

      default:
	return -1;
    }
}

static Part *
ethGetParticipants(s)
Sessn s;
{
    Part   *p;
    SState *ss = (SState *)s->state;

    p = (Part *)xMalloc(sizeof(Part));
    partInit(p, 1);
    /* remote host */
    partPush(p[0], &ss->hdr.dst, sizeof(ETHhost));
    return p;
}

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

    xAssert(xIsProtl(self));
    switch (op) {

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

      default:
	return xControlProtl(xGetProtlDown(self, 0), op, buf, len);
  }
  
}


static void
ethSessnInit(s)
    Sessn s;
{
  s->push = ethPush;
  s->pop = ethPop;
  s->close = ethClose;
  s->controlsessn = ethControlSessn;
  s->getparticipants = ethGetParticipants;
}
