/*
 * ethd.c
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1996,1993,1991,1990  Arizona Board of Regents
 *
 * $Revision: 1.5 $
 * $Date: 1996/06/19 16:25:29 $
 */

#include "xkernel.h"
#include "ethd.h"
#include "ethd_i.h" 
#include "eth_i.h"
#include "simul.h"

int traceethdp;

#ifdef __STDC__
static XkReturn ethdOpenEnable(Protl, Protl, Protl, Part *);
static int      ethdControlProtl(Protl, int, char *, int);
static XkHandle ethdPush(Sessn, Msg *);
#endif

void            xsimSetHostBusy(int);

#ifdef XMEMTRACK

static int ethdTrackId = 0;

extern char *xMallocTrack(unsigned, int);
extern char *xMallocZeroTrack(unsigned, int);
extern int  TrackGetId(char *);

#define xMalloc(s)     xMallocTrack(s, ethdTrackId)
#define xMallocZero(s) xMallocZeroTrack(s, ethdTrackId)

#endif

static XkReturn
ethdOpenEnable(self, hlp, hlpType, p)
Protl self, hlp, hlpType;
Part  *p;
{
    PState *ps = (PState *)self->state;

    if (self->up) {
        DO_TRACE_WT(ps->ts, TRACE_EVENT_OPENEN, TRACE_ERROR, 0);
        xError("ethdOpenEnable called multiple times!");
        return XK_FAILURE;
    }
    DO_TRACE_WT(ps->ts, TRACE_EVENT_OPENEN, TRACE_OK, 0);
    self->up = hlp;
    return XK_SUCCESS;
}

void     
ethdMsgStore(void *hdr, char *netHdr, long len, void *arg)
{   
    ETHhdr tmp;

    xAssert(len == sizeof(ETHhdr));
    bcopy(hdr, (char *)&tmp, sizeof(ETHhdr));
    tmp.type = htons(tmp.type);
    bcopy((char *)&tmp, netHdr, sizeof(ETHhdr));
}

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

static void
ethdSend (Event ev, void *arg)
{
    char   buf[8];
    Sessn  self = (Sessn)arg;
    PState *ps = (PState *)self->state;

    xsimDbg(SED_FLAG, printf("[ethdSend %s]\n", self->myprotl->instName));
    bcopy((char *)&self, buf, sizeof(char *));
    if (xControlSessn(xGetSessnDown(self, 0), SIM_ISBUSY, buf, 1) != 1) {
        printf("ERROR in ethd/%s, xControlSessn fails for SIM_ISBUSY, dropping %s\n",
	       self->myprotl->instName, "msg");
        ps->queueCnt--;
        msgDestroy(&ps->msgQueue[ps->queueBeg]);
        if (++ps->queueBeg >= MAX_QUEUE)
            ps->queueBeg = 0;
    }
    else if (*buf) {
        xsimDbg(SED_FLAG, printf("net is busy, setting notbusy cb\n"));
        bcopy((char *)&self, buf, sizeof(char *));
        if (xControlSessn(xGetSessnDown(self, 0), SIM_NOTBUSY_CB, buf, 0) != 0){
            printf("ERROR in ethd/%s, xControlSessn fails for SIM_NOTBUSY_CB, %s\n",
	           self->myprotl->instName, "dropping msg");
            ps->queueCnt--;
            msgDestroy(&ps->msgQueue[ps->queueBeg]);
            if (++ps->queueBeg >= MAX_QUEUE)
	        ps->queueBeg = 0;
        }
        DO_TRACE_WT(ps->ts, TRACE_EVENT_SEND_BUSY, ps->queueCnt, 0);
    }
    else {
        xsimDbg(SED_FLAG, printf("[ethdSend %s] net is not busy, pushing msg\n",
			         self->myprotl->instName));
        DO_TRACE_WT(ps->ts, TRACE_EVENT_SEND_OKAY, 
		    msgLength(&ps->msgQueue[ps->queueBeg]), 0);
        if (ps->queueCnt <= 0) {
            DO_TRACE_L(ps->ts, TRACE_EVENT_ERROR, ps->queueCnt, 0, 0);
            printf("***ERROR in ethdSend, queueCnt:%d\n", ps->queueCnt);
        }
        else
            xPush(xGetSessnDown(self, 0), &ps->msgQueue[ps->queueBeg]);
    }
}

static void
ethdCbFun (int op, int rv, void *arg)
{
    int    rvar, len, t;
    Sessn  self = (Sessn)arg;
    PState *ps = (PState *)self->state;
    ITrace *itp;

    DO_TRACE_WT(ps->ts, TRACE_EVENT_CB, op, rv);

    switch (op) {
    
        case OP_SENT_CB:
            if (rv == RV_SUCCEED) {
                /* --- sent successfully */
                xsimDbg(SED_FLAG, 
	                printf("[ethdCB %s] Sent successfully, left in queue:%d\n", 
		               self->myprotl->instName, ps->queueCnt-1));
                DO_TRACE_L(ps->ts, TRACE_EVENT_SENT_OKAY,  
		           msgLength(&ps->msgQueue[ps->queueBeg]),
			   ps->queueCnt-1, 0);
                DO_TRACE_L(ps->ts, TRACE_EVENT_DATA, ps->retryCnt,
			   ps->retryDelay, 0);
                if ((itp = ps->traceOut) != NULL) {
                    itp->packetCnt++; 
                    itp->byteCnt += (len = 
			 msgLength(&ps->msgQueue[ps->queueBeg])-sizeof(ETHhdr));
                    if ((t = traceGetTime()) > itp->nextTime) {
                        itp->array[itp->arrayNum] =
			    (itp->sum*itp->m_factor)/itp->d_factor; 
                        while (itp->nextTime <= t)
                            itp->nextTime += itp->timeInc, itp->arrayNum++;
                        itp->sum = len;
                    }
                    else
                        itp->sum += len;
                }

                msgDestroy(&ps->msgQueue[ps->queueBeg]);
                if (++(ps->queueBeg) >= MAX_QUEUE)
	            ps->queueBeg = 0;
                /* --- Check if more msgs waiting to be sent */
                if (--(ps->queueCnt) > 0) {
	            ps->retryCnt = 1;
	            ps->retryDelay = 1;
	            ethdSend(NULL, (void *)self);
		    if (ps->blockCnt > 0)
			semSignal(&ps->waiting);
                }
            }
            else {
                /* --- Doing exp backoff */
                xsimDbg(SPC_FLAG, 
	                printf("[ethdCB %s] Send failed, q:%d, retryCnt:%d, delay:%d\n", 
		               self->myprotl->instName, ps->queueCnt,
			       ps->retryCnt+1, ps->retryDelay*2));
                if (++(ps->retryCnt) > 15) {
	            printf("WARNING in ethd, 15 retries already, dropping msg\n");
	            ps->queueCnt--;
	            DO_TRACE_L(ps->ts, TRACE_EVENT_DROP, 
		               msgLength(&ps->msgQueue[ps->queueBeg]),
			       ps->queueCnt, 0);
	            msgDestroy(&ps->msgQueue[ps->queueBeg]);
	            if (++ps->queueBeg >= MAX_QUEUE)
	                ps->queueBeg = 0;
	            /* --- Check if more msgs waiting to be sent */
	            if (ps->queueCnt > 0) {
	                ps->retryCnt = 1;
	                ps->retryDelay = 1;
	                ethdSend(NULL, (void *)self);
	            }
	            return;
                }
                ps->retryDelay *= 2;
                rvar = lrand48() % ps->retryDelay + 1;
                xsimDbg(SED_FLAG,
			printf("          delaying for:%d slots\n", rvar));
                evDetach(evSchedule(ethdSend, (void *)self, rvar*51));
                DO_TRACE_L(ps->ts, TRACE_EVENT_BACKOFF, rvar*51, ps->queueCnt,
			   0);
                DO_TRACE_L(ps->ts, TRACE_EVENT_DATA, ps->retryCnt,
			   ps->retryDelay, 0);
            }
            break;

        case OP_NOTBUSY_CB:
            xsimDbg(SED_FLAG, 
	            printf("[ethdCB %s] not busy cb, q:%d\n",
			   self->myprotl->instName, ps->queueCnt));
            ethdSend(NULL, (void *)self);
            break;

        default:
            printf("ERROR in ethd/%s, Unknown callback (op: %d)\n",
	           self->myprotl->instName, op);
    }
}
    
static XkHandle
ethdPush(self, msg)
Sessn self;
Msg   *msg; 
{   
    int    len, delay = 0;
    ETHhdr *hdr = msgGetAttr(msg, 0);
    PState *ps = (PState *)self->state; 
    void   *buf;

    xTrace0(ethdp, TR_EVENTS, "ethdPush");
    xAssert(hdr);
    if (ps->delay > 0) {
        if (ps->range > 0)
            delay = ps->delay + 2*(nrand48(ps->randState)%ps->range) -
		    ps->range;
        else
            delay = ps->delay;
        xsimSetHostBusy(delay);
    }
    xsimDbg(SED_FLAG, 
	    printf("[ethdPush %s] hdr = src: %s, dst: %s, type: %x, len: %d\n", 
		   self->myprotl->instName,
		   ethHostStr(&hdr->src), ethHostStr(&hdr->dst), 
		   (int)hdr->type, msgLength(msg)));
    xsimDbg(SED_FLAG,
	    printf("              delay:%d, qCnt:%d, qBeg:%d, qEnd:%d\n", delay,
		  ps->queueCnt,
		   ps->queueBeg, ps->queueEnd));

    DO_TRACE_WT(ps->ts, TRACE_EVENT_PUSH, msgLength(msg), 0);
  
    buf = msgPush(msg, sizeof(ETHhdr));
    xAssert(buf);
    ethdMsgStore(hdr, buf, sizeof(ETHhdr), 0);

    if ((len = msgLength(msg)) > ps->maxPacket) {
        xTrace2(ethdp, TR_SOFT_ERRORS,
	        "ether driver: msgLength (%d) is larger than max (%d)",
	        len, ps->maxPacket);
        DO_TRACE_L(ps->ts, TRACE_EVENT_ERROR, 1, 0, 0);
        DO_TRACE_L(ps->ts, TRACE_EVENT_DROP, len, 0, 0);
        printf("ethd dropping msg, its len (%d) is larger than max(%d)\n",
	       len, ps->maxPacket);
        return XMSG_ERR_HANDLE;
    }
    while (ps->queueCnt >= ps->queueCntMax) {
	if (ps->blockCnt > 4) {
            DO_TRACE_L(ps->ts, TRACE_EVENT_DROP, len, ps->queueCnt, 0);
            xsimDbg(SED_FLAG, printf("[ethdPush %s] queue full, dropping", 
			             self->myprotl->instName));
            printf("[ethdPush %s] ethd queue full, droping msg\n",
	           self->myprotl->instName);
            return XMSG_ERR_HANDLE;
        }
	ps->blockCnt++;
	semWait(&ps->waiting);
	ps->blockCnt--;
    }
    msgConstructCopy(&ps->msgQueue[ps->queueEnd], msg);
    ps->queueCnt++;
    if (++(ps->queueEnd) >= MAX_QUEUE)
        ps->queueEnd = 0;
    if (ps->queueCnt == 1) {
        ps->retryCnt = 1;
        ps->retryDelay = 1;
        DO_TRACE_L(ps->ts, TRACE_EVENT_PUSH_SEND, len, ps->queueCnt, 0);
        ethdSend(NULL, (void *)self);
    }
    return XMSG_NULL_HANDLE;
}       
    
static XkReturn 
ethdDemux(self, llp, msg) 
Protl self;
Sessn llp; 
Msg   *msg;
{
    int    t, len;
    PState *ps = (PState *)self->state; 
    ETHhdr hdr;
    ITrace *itp;
    void   *buf;

    buf = msgPop(msg, sizeof(ETHhdr));
    if (!buf) {
        xTrace0(ethdp, TR_SOFT_ERRORS,
	        "ethdDemux: incoming message too small!");
        return XK_FAILURE; 
    }
    ethdMsgLoad((void *)&hdr, buf, sizeof(ETHhdr), 0);

    if (!self->up) {
        xTrace0(ethdp, TR_ERRORS, "ethdDemux: no upper protocol!");
        return XK_FAILURE;
    }
    xsimDbg(SED_FLAG,
	    printf("[ethdDemux] hdr = src: %s, dst: %s, type: %x, len: %d\n", 
		   ethHostStr(&hdr.src), ethHostStr(&hdr.dst), (int)hdr.type,
		   msgLength(msg)));
    if ((itp = ps->traceIn) != NULL) {
        itp->packetCnt++;
        itp->byteCnt += (len = msgLength(msg));
        if ((t = traceGetTime()) > itp->nextTime) {
            itp->array[itp->arrayNum] = (itp->sum*itp->m_factor)/itp->d_factor;
            while (itp->nextTime <= t)
                itp->nextTime += itp->timeInc, itp->arrayNum++;
            itp->sum = len;
        }
        else
            itp->sum += len;
    }   

    msgSetAttr(msg, 0, &hdr, sizeof(ETHhdr));
    DO_TRACE_WT(ps->ts, TRACE_EVENT_DEMUX, msgLength(msg), 0);
/*  return xDemux(xGetUp(self), self, msg); */
    xsimDbg(SED_FLAG,
	    printf("            demuxing to %s/%s\n", self->up->name, 
		   self->up->instName));
    return self->up->demux(self->up, (Sessn)self, msg);
}

static int
ethdControlProtl(self, opcode, buf, len)
Protl self;
int   opcode, len;
char  *buf;
{
    int    i;
    PState *ps = (PState *)self->state;
  
    DO_TRACE_WT(ps->ts, TRACE_EVENT_CTL_PROTL, opcode, 0);

    switch (opcode) {
        case GETMYHOST:
            checkLen(len, sizeof(ETHhost));
            bcopy((char *) &ps->ethAddr, buf, sizeof(ETHhost));
            return (sizeof(ETHhost));
        case GETMAXPACKET:
        case GETOPTPACKET:
            checkLen(len, sizeof(int));
            i = ps->maxPacket - sizeof(ETHhdr);
            bcopy((char *)&i, buf, sizeof(int));
            return (sizeof(int));
        default:
            return xControlProtl(xGetProtlDown(self,0), opcode, buf, len);
    }
}

extern int  xsimArgc;
extern char **xsimArgv;

void
ethd_init(self)
Protl self;
{
    char   buf[20], *cp;
    int    i, j, k, n = 0, jn = 1, s, timeInc = 100000; 
    PState *ps;
    Protl  llp;
    /* int    time(); */

    xTrace0(ethdp, TR_GROSS_EVENTS, "ethd init");
    xAssert(xIsProtl(self));
#ifdef XMEMTRACK
    if (ethdTrackId == 0)
        ethdTrackId = TrackGetId("ethd");
#endif

    if (!xIsProtl(llp = xGetProtlDown(self,0))) {
        xError("ethd can not get sim protocol obj");
        return;
    }
    if (xOpenEnable(self, self, llp, 0) == XK_FAILURE) {
        xError("ethd can not openenable sim protocol");
        return;
    }
    ps = X_NEW(PState);
    bzero((char *)ps, sizeof(PState));
    ps->blockCnt = 0;
    semInit(&ps->waiting, 0);
    self->state = (VOID *)ps;
    bcopy((char *)&self, buf, sizeof(char *));
    if (xControlProtl(llp, GETMYHOST, buf, sizeof(ETHhost)) <
	(int)sizeof(ETHhost)) {
        xError("ethd_init: can't get my own host");
        ps->ethAddr.high = 0, ps->ethAddr.mid = 0, ps->ethAddr.low = 0;
        return;
    }
    bcopy(buf, (char *)&ps->ethAddr, sizeof(ETHhost));
    bcopy((char *)&self, buf, sizeof(char *));
    if (xControlProtl(llp, GETMAXPACKET, buf, sizeof(int)) < sizeof(int))
        ps->maxPacket = EMAXPAK;
    else {
        bcopy(buf, (char *)&ps->maxPacket, sizeof(int));
        /* ps->maxPacket += sizeof(ETHhdr); */
    }

    /* --- Register callback */
    bcopy((char *)&self, buf, sizeof(char *));
    cp = (char *)ethdCbFun;
    bcopy((char *)&cp, buf+sizeof(char *), sizeof(char *));
    if (xControlProtl(llp, SIM_REGISTER_CB, buf, 2*sizeof(char *)) != 0) {
        xError("ethd_init: can't register callback");
        return;
    }
    if (xControlProtl(xGetProtlDown(self, 0), SIM_GETSEED, (char *)&s,
		      sizeof(int)) != sizeof(int)) {
        printf("[%s-%s] SIM_GETSEED failed!\n", self->name, self->instName);
        s = (int) time(NULL);
    }     
    for (j = k = 0; *(self->instName+k) != '\0'; k++)
        j ^= *(self->instName+k) << 6*k;
    s ^= j;
    ps->randState[0] = 0x531a;
    ps->randState[1] = s & 0xffff;
    ps->randState[2] = (s >> 16) & 0xffff;
    ps->delay = 0;
    ps->queueCntMax = MAX_QUEUE;
    for (i = 0; i < xsimArgc; i++) {
/*      printf("[%s-%s] Argv[%d] = `%s'\n", self->name, self->instName, i,  */
/* 	       xsimArgv[i]); */
        if (!strncmp(xsimArgv[i], "-ethdDelay=", 11)) {
            sscanf(xsimArgv[i]+11, "%d", &ps->delay);
        }
        else if (!strncmp(xsimArgv[i], "-ethdDelayRange=", 16))
            sscanf(xsimArgv[i]+16, "%d", &ps->range);
        else if (!strncmp(xsimArgv[i], "-ethdTrace=", 11)) {
            sscanf(xsimArgv[i]+11, "%d", &n);
	    cp = xsimArgv[i] + 11;
	    while (*cp != ':'  &&  *cp != '\0')
	        cp++;
	    if (*cp == ':')
	        sscanf(cp+1, "%d", &jn);
        }
	else if (!strncmp(xsimArgv[i], "-ethdQueue=", 11))
	    sscanf(xsimArgv[i]+11, "%d", &ps->queueCntMax);
	else if (!strcmp(xsimArgv[i], "-timeInc")  ||
	         !strcmp(xsimArgv[i], "timeInc"))
	    sscanf(xsimArgv[++i], "%d", &timeInc);
	else if (!strncmp(xsimArgv[i], "-timeInc=", 9))
	    sscanf(xsimArgv[i]+9, "%d", &timeInc);
        else if (!strcmp(xsimArgv[i], "-ethdTraceOut")) {
            ps->traceOut = (ITrace *)xMalloc(sizeof(ITrace));
            bzero((char *)ps->traceOut, sizeof(ITrace));
            ps->traceOut->stype = TRACE_STRUCT_I;
            ps->traceOut->nextTime = timeInc;
            ps->traceOut->timeInc = timeInc;
            ps->traceOut->m_factor = 1000000/timeInc;
            ps->traceOut->d_factor = 1024;
            sprintf(ps->traceOut->id, "out-ethd-%s", self->instName);
            ps->traceOut->arrayNum = 0;
        }
        else if (!strcmp(xsimArgv[i], "-ethdTraceIn")) {
            ps->traceIn = (ITrace *)xMalloc(sizeof(ITrace));
            bzero((char *)ps->traceIn, sizeof(ITrace));
            ps->traceIn->stype = TRACE_STRUCT_I;
            ps->traceIn->nextTime = timeInc;
            ps->traceIn->timeInc = timeInc;
            ps->traceIn->m_factor = 1000000/timeInc;
            ps->traceIn->d_factor = 1024;
            sprintf(ps->traceIn->id, "in-ethd-%s", self->instName);
            ps->traceIn->arrayNum = 0;
        }
    }
/*  for (k = 0; k < MAX_QUEUE; k++) */
/*      msgConstructEmpty(&(ps->msgQueue[k])); */
    ps->queueBeg = ps->queueEnd = ps->queueCnt = 0;
    ps->retryCnt = 0;
    ps->retryDelay = 0;
    self->controlprotl = ethdControlProtl;
    self->openenable = ethdOpenEnable;
    self->push = ethdPush;
    self->demux = ethdDemux;
    self->up = 0;
    if (n > 0 || ps->traceIn != NULL || ps->traceOut != NULL) {
        if (n == 0)
	    n = 500;
        TRACE_MALLOC(ps->ts, n, TRACE_PROTL_ETHD, self->name, self->instName,
		     jn);
    }
    if (ps->traceIn != NULL) 
        if (traceRegisterAddBuf(ps->ts.traceObj, (char *)ps->traceIn,
				sizeof(ITrace)))
            printf("\nERROR: traceRegisterAddBuf fails for ethd!\n");
    if (ps->traceOut != NULL) 
        if (traceRegisterAddBuf(ps->ts.traceObj, (char *)ps->traceOut,
				sizeof(ITrace)))
            printf("\nERROR: traceRegisterAddBuf fails for ethd!\n");
}
