/*
 * $RCSfile: fddi.c,v $
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1993,1991,1990,1996  Arizona Board of Regents
 *
 * $Log: fddi.c,v $
 * Revision 1.2  1996/01/29 22:14:37  slm
 * Updated copyright and version.
 *
 * Revision 1.1  1995/07/28  22:13:01  slm
 * Initial revision
 *
 * Revision 1.5.1.4  1994/12/02  18:13:18  hkaram
 * Changed to new mapResolve interface
 *
 * Revision 1.5.1.3  1994/11/26  00:59:40  hkaram
 * msgDestroy removed off from Demux operation because it is
 * taken care of in the buffer pool handler
 *
 * Revision 1.5.1.2  1994/11/22  20:58:20  hkaram
 * Added casts to mapResolve calls
 *
 * Revision 1.5.1.1  1994/11/12  19:40:10  hkaram
 * New branch
 *
 * Revision 1.5  1994/02/05  00:05:29  menze
 *   [ 1994/01/30          menze ]
 *   Using xk_u_intN types
 *
 * Revision 1.4  1994/01/10  17:50:25  menze
 *   [ 1993/12/23          menze ]
 *   Revised rom parsing code to use library routines
 *
 *   [ 1993/12/23          menze ]
 *   Removed irix-platform-specific coding conventions so this protocol can
 *   be built everywhere.
 *
 * Revision 1.3  1993/12/16  01:27:51  menze
 * includes mac.h
 *
 * Revision 1.2  1993/11/13  00:52:47  menze
 * Original version from UMass
 *
 * Based on xkernel/protocols/eth/eth.c (DJY)
 */

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

#include "xkernel.h"
#include "romopt.h"
#include "fddi.h"
#include "fddi_i.h"
#include "mac.h"

typedef struct {
    FDDIhdr hdr;
} SState;

typedef struct {
    FDDIhost host;          /* remote host MAC address */
    ETHtype  type;          /* Ethernet type field in 802.2 header */
} ActiveId;

typedef struct {
    Sessn      prmSessn;
    FDDIhost   myHost;        /* our local MAC address */
    xk_u_int8  dsap;          /* SNAP destination service access point */
    xk_u_int8  ssap;          /* SNAP source service access point */
    xk_u_int8  control;       /* SNAP control field */
    xk_u_int8  OrgCodePart1;  /* SNAP Organization code (3 bytes) ... */
    xk_u_int16 OrgCodePart2;
    Map        activeMap;
    Map        passiveMap;
    int        mtu;
} PState;

typedef ETHtype  PassiveId;  /* Ethernet type field in 802.2 header */

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

/* some nice prime numbers for hashing */
#define FDDI_ACTIVE_MAP_SIZE  257
#define FDDI_PASSIVE_MAP_SIZE 13

int tracefddip;

#ifdef XK_DEBUG

static FDDIhost fddiBroadcastHost = FDDI_BCAST_HOST;

#endif

#ifdef __STDC__

static void     demuxStub(Event, VOID *);
static int      dispActiveMap(VOID *, VOID *, VOID *);
static int      dispPassiveMap(VOID *, VOID *, VOID *);
static Sessn    fddiCreateSessn(Protl, Protl, Protl, ActiveId *);
static void     fddiSessnInit(Sessn);
static int      fddiControlProtl(Protl, int, char *, int);
static XkReturn fddiDemux(Protl, Sessn, Msg *);
static Sessn    fddiOpen(Protl, Protl, Protl, Part *);
static XkReturn fddiOpenEnable(Protl, Protl, Protl, Part *);
static XkReturn fddiOpenDisable(Protl, Protl, Protl, Part *);
static XkReturn fddiOpenDisableAll(Protl, Protl);
static XkReturn fddiClose(Sessn);
static XkHandle fddiPush(Sessn, Msg *);
static XkHandle fddiLoopPush(Sessn, Msg *);
static XkReturn fddiPop(Sessn, Sessn, Msg *, VOID *);
static int      fddiControlSessn(Sessn, int, char *, int);
static Part     *fddiGetParticipants(Sessn);
static long     getRelProtNum(Protl, Protl, char *);
static XkReturn readMtu(Protl, char **, int, int, VOID *);

#else

static void     demuxStub();
static int      dispActiveMap();
static int      dispPassiveMap();
static Sessn    fddiCreateSessn();
static void     fddiSessnInit();
static int      fddiControlProtl();
static XkReturn fddiDemux();
static Sessn    fddiOpen();
static XkReturn fddiOpenEnable();
static XkReturn fddiOpenDisable();
static XkReturn fddiOpenDisableAll();
static XkReturn fddiClose();
static XkHandle fddiPush();
static XkHandle fddiLoopPush();
static XkReturn fddiPop();
static int      fddiControlSessn();
static Part     *fddiGetParticipants();
static long     getRelProtNum();
static XkReturn readMtu();

#endif /* __STDC__ */

#define FDDI_ADDRESS_ISBROAD(A) (FDDI_ADDRS_EQUAL(A,fddiBroadcastHost))
#define FDDI_ADDRESS_ISMULTI(A) (FDDI_ADDRESS_ISGROUP(A) && \
                                 !FDDI_ADDRESS_ISBROAD(A))

static ProtlRomOpt fddiOpt[] = {
    { "mtu", 3, readMtu }
};

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

    n = relProtNum(hlp, llp);
    if (n == -1) {
	xTrace3(fddip, TR_ERRORS,
		"fddi %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;
}

static XkReturn
readMtu(self, str, nFields, line, arg)
Protl self;
int   line, nFields;
char  **str;
VOID  *arg;
{
    PState *ps = (PState *)self->state;

#ifdef XKMACHKERNEL
    return sscanf1(str[2], "%d", &ps->mtu) < 1  ? XK_FAILURE : XK_SUCCESS;
#else
    return sscanf(str[2], "%d", &ps->mtu) < 1  ? XK_FAILURE : XK_SUCCESS;
#endif
}

void
fddi_init(self)   /* Changed from fddi_init for first build */
Protl self;
{
    PState *ps;
    Protl  llp;

    xTrace0(fddip, TR_EVENTS, "fddi_init");
    if (!xIsProtl(llp = xGetProtlDown(self, 0))) {
	xError("FDDI can not get driver protocol object");
	return;
    }
    if (xOpenEnable(self, self, llp, 0) == XK_FAILURE) {
	xError("FDDI can not openenable driver protocol");
	return;
    }
    ps = X_NEW(PState);
    self->state = (VOID *)ps;
    ps->activeMap = mapCreate(FDDI_ACTIVE_MAP_SIZE, sizeof(ActiveId));
    ps->passiveMap = mapCreate(FDDI_PASSIVE_MAP_SIZE, sizeof(PassiveId));
    ps->prmSessn = 0;
    ps->dsap = FDDI_SNAP_DSAP;
    ps->ssap = FDDI_SNAP_SSAP;
    ps->control = FDDI_SNAP_CONTROL;
    ps->OrgCodePart1 = FDDI_SNAP_ORGCODEPART1;
    ps->OrgCodePart2 = FDDI_SNAP_ORGCODEPART2;
    if (xControlProtl(llp, GETMAXPACKET, (char *)&ps->mtu, sizeof(ps->mtu)) !=
            sizeof(ps->mtu)) {
        xTrace0(fddip, TR_ERRORS, "FDDI can't get MTU from driver protocol");
        Kabort("fddi_init: out of here");
    }
    findProtlRomOpts(self, fddiOpt, sizeof(fddiOpt)/sizeof(ProtlRomOpt), 0);
    xTrace1(fddip, TR_MAJOR_EVENTS, "FDDI using mtu %d", ps->mtu);
    /*
     * NO ...
     * if (xControlProtl(llp,MAC_REGISTER_ULP,(char *)&self,sizeof(Protl)) < 0){
     *     xError("fddi_init: can't get register myself with driver protocol");
     *     return;
     * }
     */
    if (xControlProtl(llp, GETMYHOST, (char *)&ps->myHost, sizeof(FDDIhost)) <
	    (int)sizeof(FDDIhost)) {
	xError("fddi_init: can't get my own host");
	return;
    }
    self->controlprotl   = fddiControlProtl;
    self->open           = fddiOpen;
    self->openenable     = fddiOpenEnable;
    self->opendisable    = fddiOpenDisable;
    self->demux          = fddiDemux;
    self->opendisableall = fddiOpenDisableAll;
}

static Sessn
fddiOpen(self, hlp, hlpType, part)
Protl self, hlp, hlpType;
Part  *part;
{
    PState   *ps = (PState *)self->state;
    ActiveId key;
    Sessn    fddiSessn;
    FDDIhost *remoteHost;
    long     protNum;

    if (part == 0 || partLength(part) < 1) {
	xTrace0(fddip, TR_SOFT_ERRORS, "fddiOpen - bad participants");
	return ERR_SESSN;
    }
    remoteHost = (FDDIhost *)partPop(*part);
    xAssert(remoteHost);
    key.host = *remoteHost;
    if ((protNum = getRelProtNum(hlpType, self, "open")) == -1)
	return ERR_SESSN;
    key.type = protNum;
    xTrace2(fddip, TR_MAJOR_EVENTS, "fddi_open: destination address = %s:%4x",
	    fddiHostStr(&key.host), key.type);
    key.type = htons(key.type);
    if (mapResolve(ps->activeMap, &key, (void **)&fddiSessn) == XK_FAILURE)
	fddiSessn = fddiCreateSessn(self, hlp, hlpType, &key);
    xTrace1(fddip, TR_MAJOR_EVENTS, "fddi_open: returning %X", fddiSessn);
    return (fddiSessn);

}

static Sessn
fddiCreateSessn(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(fddiSessnInit, hlp, hlpType, self, 1, (Sessn *)&llp);
    if (FDDI_ADDRS_EQUAL(key->host, ps->myHost)) {
	xTrace0(fddip, TR_MAJOR_EVENTS,
		"fddiCreateSessn -- creating loopback session");
	s->push = fddiLoopPush;
    }
    s->binding = mapBind(ps->activeMap, key, s);
    if (s->binding == ERR_BIND) {
	xTrace0(fddip, TR_ERRORS, "error binding in fddiCreateSessn");
	xDestroySessn(s);
	return ERR_SESSN;
    }
    ss                       = X_NEW(SState);
    ss->hdr.dst              = key->host;
    ss->hdr.src              = ps->myHost;
    ss->hdr.llc.dsap         = ps->dsap;
    ss->hdr.llc.ssap         = ps->ssap;
    ss->hdr.llc.control      = ps->control;
    ss->hdr.llc.OrgCodePart1 = ps->OrgCodePart1;
    ss->hdr.llc.OrgCodePart2 = ps->OrgCodePart2;
    ss->hdr.llc.EtherType    = key->type;
    s->state                 = (VOID *)ss;
    return s;
}

static XkReturn
fddiOpenEnable(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(fddip, TR_GROSS_EVENTS, "fddiOpenEnable: hlp=%x, protlNum=%x",
	    hlp, protNum);
    key = protNum;
    key = htons(key);
    return defaultOpenEnable(ps->passiveMap, hlp, hlpType, (VOID *)&key);
}

static XkReturn
fddiOpenDisable(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(fddip, TR_GROSS_EVENTS, "fddiOpenDisable: hlp=%x, protlNum=%x",
	    hlp, protNum);
    key = protNum;
    key = htons(key);
    return defaultOpenDisable(ps->passiveMap, hlp, hlpType, (VOID *)&key);
}

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

    xPrintSessn(s);
    return MFE_CONTINUE;
}

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

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

    xTrace0(fddip, TR_MAJOR_EVENTS, "fddiOpenDisableAll called");

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

static XkReturn
fddiDemux(self, llp, msg)
Protl self;
Sessn llp;
Msg   *msg;
{
    PState    *ps = (PState *)self->state;
    ActiveId  actKey;
    PassiveId pasKey;
    Sessn     s = 0;
    Enable    *e;
    FDDIhdr   *hdr = msgGetAttr(msg, 0);

    xTrace0(fddip, TR_EVENTS, "fddiDemux");
    xTrace1(fddip, TR_FUNCTIONAL_TRACE, "FDDI - EtherType: %x",
	    hdr->llc.EtherType);
    xTrace2(fddip, TR_FUNCTIONAL_TRACE, "src: %s  dst: %s",
	    fddiHostStr(&hdr->src), fddiHostStr(&hdr->dst));
    xTrace4(fddip, TR_FUNCTIONAL_TRACE,
            "dsap: %d  ssap: %d  control: %d  org-code: %d",
            hdr->llc.dsap, hdr->llc.ssap, hdr->llc.control,
            (hdr->llc.OrgCodePart1 << (8*sizeof(hdr->llc.OrgCodePart2))) +
             hdr->llc.OrgCodePart2);
    xIfTrace(fddip, TR_DETAILED)
	msgShow(msg);
    xAssert(hdr);
    if (ps->prmSessn) {
	Msg pMsg;

	xTrace0(fddip, TR_EVENTS,
		"fddiDemux: 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 (!(FDDI_ADDRS_EQUAL(hdr->dst, ps->myHost) ||
          FDDI_ADDRESS_ISBROAD(hdr->dst))) {
	xError("fddiDemux: 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("fddiDemux delays packet");
	    Delay(4 * 1000);
	    xError("fddiDemux delay returns");
	}
	else
	    xTrace1(fddip, TR_EVENTS, "fddiDemux does not delay (%d)", count);
    }
#endif
#endif
    actKey.host = hdr->src;
    actKey.type = hdr->llc.EtherType;
    if (mapResolve(ps->activeMap, &actKey, (void **)&s) == XK_FAILURE) {
	pasKey = actKey.type;
	if (mapResolve(ps->passiveMap, &pasKey, (void **)&e) == XK_SUCCESS) {
	    xTrace1(fddip, TR_EVENTS,
		    "fddiDemux: openenable exists for msg type %x",
		    ntohs(pasKey));
	    xAssert(ntohs(hdr->llc.EtherType) == relProtNum(e->hlpType,self));
	    s = fddiCreateSessn(self, e->hlp, e->hlpType, &actKey);
	    if (s != ERR_SESSN) {
		xOpenDone(e->hlp, self, s);
		xTrace0(fddip, TR_EVENTS,
			"fddiDemux: sending message to new session");
	    }
	}
	else {
	    xTrace1(fddip, TR_EVENTS,
		    "fddiDemux: openenable does not exist for msg type %x",
		    ntohs(pasKey));
	}
    }
    if (xIsSessn(s))
	xPop(s, llp, msg, 0);
    return XK_SUCCESS;
}

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

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

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

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

static XkHandle
fddiLoopPush(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
fddiPush(s, msg)
Sessn s;
Msg   *msg;
{
    xTrace0(fddip, TR_EVENTS, "entered fddiPush");
    msgSetAttr(msg, 0, &((SState *)s->state)->hdr, sizeof(FDDIhdr));
    xPush(xGetSessnDown(s, 0), msg);
    xTrace0(fddip, TR_EVENTS, "leaving fddiPush");
    return XMSG_NULL_HANDLE;
}

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

static int
fddiControlSessn(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 fddiControlProtl(xMyProtl(s), op, buf, len);

        case GETPEERHOST:
	    checkLen(len, sizeof(FDDIhost));
	    bcopy((char *)&ss->hdr.dst, buf, sizeof(FDDIhost));
	    return (sizeof(FDDIhost));

        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.llc.EtherType;
	    return sizeof(long);

        case MAC_SETPROMISCUOUS:
	{
            /*
             * This doesn't seem right (what happens if simfddi is
             * our down protocol?) - fix it LATER!!!
             */
	    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);
	}

        case MAC_CLEARPROMISCUOUS:
            xError("MAC_CLEARPROMISCUOUS not yet supported in FDDI");
            return -1;

        default:
	    return -1;
    }
}

static Part *
fddiGetParticipants(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(FDDIhost));
    return p;
}

static int
fddiControlProtl(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(FDDIhost));
	    bcopy((char *)&ps->myHost, buf, sizeof(FDDIhost));
	    return (sizeof(FDDIhost));

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

static void
fddiSessnInit(s)
Sessn s;
{
    s->push            = fddiPush;
    s->pop             = fddiPop;
    s->close           = fddiClose;
    s->controlsessn    = fddiControlSessn;
    s->getparticipants = fddiGetParticipants;
}
