/*     
 * $RCSfile: hippi_drv.c,v $
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1993,1991,1990,1996  Arizona Board of Regents
 *
 * $Log: hippi_drv.c,v $
 * Revision 1.4  1997/04/15 23:19:58  dorgival
 * Fixed some bugs; moved mach_receive thread to main thread
 *
 * Revision 1.3  1997/03/16 18:24:28  dorgival
 * Changed interface to thread creation
 *
 * Revision 1.2  1997/03/11 20:57:59  dorgival
 * Created xk_thread_t and updated threadSelf()
 *
 * Revision 1.1  1997/03/11 20:47:08  dorgival
 * Initial revision
 *
 *
 */
/*
 * First try to get a Mach out-of-kernel driver for the HiPPI board
 *
 * This version uses the libhippi calls to interface with the in-kernel
 * OSF/1 driver, avoiding making Mach calls directly and simplifying the
 * code. This is not a main restriction, though. 
 *
 * One limitation of this approach is that packet filtering is done
 * solely based on the ULP field in the hippi packet. This will not
 * be enough for an interoperable version of TCP. In that case, we
 * will need to program the packet filter to recognize TCP packets
 * to the specific port used by a given connection. At that point
 * it will necessary to replace at least the call to hippi_bind().
 * We'll leave the decision about replacing all libhippi calls when
 * that change becomes necessary.
 */

/*
 * NEW change to the filtering approach in April 16, 1997:
 *
 * The way it was done before fixed a single ULP for all outgoing
 * packets, and didn't provide for the case of multiple nodes, where
 * each would have to bind to a different ULP.
 *
 * At this point we changed the rom options and the way ULPs are
 * handled to allow multiple nodes to bind to different ULPs using
 * the same rom file. This is done by having each node to add its
 * node number to the IP address in the rom file.
 *
 * So that a sender knows which ULP to use for each outgoing packet
 * we assign different IEEE adresses to each node (paired with the
 * sequential IP addresses created by the scheme above), and extend
 * the IEEE to I-field mapping to include also the ULP associated
 * with each IEEE address.
 *
 * Since so far this implementation is independent of the real TCP/IP
 * implementation for HiPPI, we have the freedom to assing IP and IEEE
 * addresses as needed. This will, of course, have to be corrected for
 * the final implementation with the Washington kernel altered to do
 * user-level protocol processing.
 */

/*
 * If FORCE_DEFRAG is defined, xPush will always copy the message to
 * a new buffer. Otherwise, it will only do that in case the message
 * has more than one fragment.
 */
#define FORCE_DEFRAG 1

#include <mach.h>     /* needed for hippi_deallocator */
#include <fcntl.h>
#include <netinet/in.h>

#include "xk_thread.h"
#include "shepherd_pool.h"
#include "hippi_hdrs.h"
#include "xkernel.h"
#include "eth.h"
#include "eth_i.h"
#include "bind_wrapper.h"

#define PORT  (-1)
#define MAX_MTU       (65280)   /* See RFC 2067 */
#define HIPPI_FP_HDR      (8)
#define HIPPI_LE_HDR     (24)
#define LLC_SNAP_HDR      (8)
#define MAX_PACKET    (HIPPI_FP_HDR+HIPPI_LE_HDR+LLC_SNAP_HDR+MAX_MTU)

#define MIN_SHEPHERDS      2
#define MAX_SHEPHERDS      4
#define MIN_HOLDER         2
#define MAX_HOLDER         4

typedef struct {
    char               devname[128];
    int                ihandle;
    int                mtu;
    int                shepherds;
    Protl              arp;
    ETHhost            myHost;          /* IEEE address for interface */
    Map                iMap;            /* IEEE address to I-Field */
    shepherd_pool_t    shepherd_pool;
    hippi_header_out_t outgoing_header;
} PState;

typedef struct {
    int ifield;
    int ulp;
} imap_t;

typedef struct {
    char* data;
    int   len;
} hippi_packet_t;

int tracehippi_drv;

       void     hippi_drv_init( Protl self );
static XkHandle hippiPush( Sessn self, Msg* msg );
static XkReturn hippiOpenEnable( Protl self, Protl hlp, Protl hlpType, Part* p);
static int      hippiControl( Protl s, int op, char* buf, int len );
static void     receive_mach_msg( Protl self );
static void     hippi_shepherd( Protl self, Msg* msg );

static void     hippi_deallocator( void* buf, int len );

static XkReturn readMTU(       Protl, char**, int, int, VOID* );
static XkReturn readShepherds( Protl, char**, int, int, VOID* );
static XkReturn readDevice(    Protl, char**, int, int, VOID* );
static XkReturn readImap(      Protl, char**, int, int, VOID* );

void hippi_print( hippi_header_any_t* hh, int outgoing );

static ProtlRomOpt protlOpts[] = {
    { "mtu",       3, readMTU },
    { "shepherds", 3, readShepherds },
    { "device",    4, readDevice },       /* device <filename> <ieee802 addr> */
    { "imap",      5, readImap },         /* imap <ieee802 addr><i-field><ULP>*/
};

#define ROMARG1 (2)
#define ROMARG2 (3)
#define ROMARG3 (4)

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

    ret = sscanf(str[ROMARG1],"%d",&ps->mtu) >= 1? XK_SUCCESS: XK_FAILURE;

    xTrace1(hippi_drv,TR_DETAILED,"readMTU: will use mtu %d",
            ps->mtu);
    return ret;
}

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

    ret = sscanf(str[ROMARG1],"%d",&ps->shepherds) >= 1? XK_SUCCESS:XK_FAILURE;

    xTrace1(hippi_drv,TR_DETAILED,"readShepherds: will use %d shepherds",
            ps->shepherds);
    return ret;
}

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

    
    if (sscanf(str[ROMARG1],"%s",ps->devname) < 1) {
	xError("readDevice: error while reading device name");
	return XK_FAILURE;
    }
    if (str2ethHost(&ps->myHost,str[ROMARG2]) != XK_SUCCESS) {
	xError("readDevice: error while reading ieee address of interface");
	return XK_FAILURE;
    }
    xTrace2(hippi_drv,TR_DETAILED,"readDevice: using device %s, address %s\n",
            ps->devname, ethHostStr(&ps->myHost) );
    return XK_SUCCESS;
}

static XkReturn
readImap( Protl self, char** str, int nFields, int line, VOID* arg )
{
    PState* ps = (PState*)self->state;
    ETHhost ieee_addr;
    int     ifield;
    int     ulp;
    imap_t* imap;
    
    if (str2ethHost(&ieee_addr,str[ROMARG1]) != XK_SUCCESS) {
	xError("readImap: error while reading ieee address");
	return XK_FAILURE;
    }
    if (sscanf(str[ROMARG2],"%x",&ifield) < 1) {
	xError("readImap: error while reading I-field");
	return XK_FAILURE;
    }
    if (sscanf(str[ROMARG3],"%x",&ulp) < 1) {
	xError("readImap: error while reading ulp");
	return XK_FAILURE;
    }

    imap = X_NEW(imap_t);
    imap->ifield = ifield;
    imap->ulp    = ulp;

    if ( mapBind(ps->iMap,&ieee_addr,(void*)imap) == ERR_BIND ){
	xError("readImap: unable to add new mapping");
	return XK_FAILURE;
    }
    xTrace3(hippi_drv,TR_DETAILED,"readImap: bound %s to ifield 0x%x, ulp 0x%x",
            ethHostStr(&ieee_addr), ifield, ulp );
    return XK_SUCCESS;
}


static void
receive_mach_msg( Protl self )
{
    PState*           ps =     (PState*) self->state;
    hippi_packet_t    pkt;
    msg_holder_t*     holder;
    imap_t*           imap;

    if ( mapResolve( ps->iMap, &(ps->myHost), (void**)&imap ) != XK_SUCCESS){
	xTrace1(hippi_drv,TR_ERRORS,
	        "receive_mach_msg: unable to map my ieee address (%s)",
		ethHostStr(&(ps->myHost)) );
	return;
    }

    xTrace1(hippi_drv,TR_DETAILED, "receive_mach_msg: listening to port %d",
            imap->ulp );
    
    if ( hippi_bind( ps->ihandle, imap->ulp, PORT ) < 0 ) {
	xError("receive_mach_msg: hippi_bind failed");
	exit(1);
    }

    while (1) 
    {
	pkt.len = hippi_read( ps->ihandle, &(pkt.data), MAX_PACKET );

	if ( ( pkt.len > 0 ) && ( pkt.len <= MAX_PACKET ) ) {
	    xTrace2(hippi_drv,TR_EVENTS,"receive_mach_msg: msg 0x%x, %d bytes",
	            pkt.data, pkt.len );
	} else {
	    xTrace0(hippi_drv,TR_ERRORS,"receive_mach_msg: hippi_read failed");
	    if ( pkt.len > 0 ) {
		hippi_memfree( ps->ihandle, pkt.data, pkt.len, TRUE );
	    }
	    continue;
	}

	holder = get_free_holder( &ps->shepherd_pool );

	if ( !holder ) {
	    xTrace0(hippi_drv,TR_EVENTS,"receive_mach_msg: no holders");
	    hippi_memfree( ps->ihandle, pkt.data, pkt.len, TRUE );
	    continue;
	}

	xTrace1(hippi_drv,TR_FULL_TRACE,
	        "receive_mach_msg: destroying old msg in 0x%x",&holder->msg);
	msgDestroy( &holder->msg );
	xTrace0(hippi_drv,TR_FULL_TRACE,
	        "receive_mach_msg: constructing new msg");
	msgConstructInplace( &holder->msg,pkt.data,pkt.len,hippi_deallocator );
	xTrace0(hippi_drv,TR_FULL_TRACE,
		"calling trigger_shepherd to schedule msg handling");
	trigger_shepherd( &ps->shepherd_pool, holder );
    }
}

/* 
 * hippi_deallocator is passed to msgContructInplace to be used as the
 * deallocator for the msg buffer allocated by hippi_read. It does not
 * get the ihandle when it is called, so we cannot call hippi_memfree
 * from it, but that is not a big problem. We just call vm_deallocate
 * directly, since that's the only important action hippi_memfree does
 * in the case of hippi_read-allocated buffers.
 */

static void
hippi_deallocator( void* ptr, int size )
{
    vm_deallocate(mach_task_self(), (vm_offset_t)ptr, size );
}

static void
hippi_shepherd( Protl self, Msg* msg )
{
    ETHhdr             eth_hdr;
    hippi_header_in_t* hippi_header_in;
    xk_thread_t*       my_thread = threadSelf();

    xTrace3(hippi_drv,TR_FULL_TRACE,"hippi_shepherd(0x%x): msg 0x%x, %d bytes",
	    my_thread, msg, msgLength(msg) );
    if ( ! self->up ) {
	xTrace0(hippi_drv, TR_ERRORS, "hippi_shepherd: no upper protocol!");
	return;
    }

    /*
     * Build xkernel messages from the mach message, throwing away
     * the HiPPI headers
     */
    hippi_header_in = msgPop(msg, HIPPI_FP_HDR+HIPPI_LE_HDR+LLC_SNAP_HDR );
    if ( hippi_header_in == NULL ) {
	xTrace0(hippi_drv, TR_SOFT_ERRORS,
		"hippi_shepherd: incoming message too small!");
	return;
    }

    xIfTrace(hippi_drv,TR_GROSS_EVENTS){
	xIfTrace(hippi_drv,TR_MAJOR_EVENTS) {
	} else {
	    hippi_print( (hippi_header_any_t*) hippi_header_in, 0 );
	}
    }

    bcopy(&hippi_header_in->le.src_ieee_address,&eth_hdr.src, sizeof(ETHhost));
    bcopy(&hippi_header_in->le.dst_ieee_address,&eth_hdr.dst, sizeof(ETHhost));
    eth_hdr.type = hippi_header_in->llc.ether_type;

    xTrace3(hippi_drv,TR_FULL_TRACE,
            "hippi_shepherd: building attr (src=%s,dst=%s,type=0x%x)",
	    ethHostStr(&eth_hdr.src), ethHostStr(&eth_hdr.dst), eth_hdr.type );

    msgSetAttr(msg, 0, &eth_hdr, sizeof(ETHhdr) );
    /*
     * find the self and upper protocols from some known structure
     */
    
    xTrace0(hippi_drv,TR_FULL_TRACE,"hippi_shepherd: calling xDemux");
    xDemux( xGetUp(self), (Sessn)self, msg );

}

void
hippi_drv_init( Protl self )
{
    PState* ps;

    ps = X_NEW(PState);
    bzero((char *)ps, sizeof(PState));
    self->state = (VOID *)ps;

    ps->mtu        = MAX_MTU;
    ps->shepherds  = MIN_SHEPHERDS;
    ps->devname[0] = '0';
    ps->iMap       = mapCreate(47, sizeof(ETHhost));

    self->push         = hippiPush;
    self->controlprotl = hippiControl;
    self->openenable   = hippiOpenEnable;
    self->up           = NULL;

    xTrace1(hippi_drv,TR_FULL_TRACE,"hippi_drv_init(0x%x) entered",self);
    findProtlRomOpts(self, protlOpts, sizeof(protlOpts)/sizeof(ProtlRomOpt),0);

    /*
     * At this point we have the myHost filled from the rom file, we must
     * adapt it to the real address based on this node's number:
     */

    ps->myHost.low  = ntohs(ps->myHost.low);
    ps->myHost.low += mynode();
    ps->myHost.low  = htons(ps->myHost.low);

    shepherd_pool_init( &ps->shepherd_pool,
                        MIN_HOLDER, MAX_HOLDER,
			(shepherd_func_t)hippi_shepherd, self,
			ps->shepherds, MAX_SHEPHERDS );

    if ( ! ps->devname[0] ) {
	xError("hippi_drv_init: devname must be defined in rom file");
	exit(1);
    }

    if ( ( ps->ihandle = hippi_open( ps->devname, HIPPI_RAW, O_RDWR ) ) < 0 ) {
	xError("hippi_drv_init: unable to open device");
	exit(1);
    }

    bzero(&ps->outgoing_header,sizeof(hippi_header_out_t));
    ps->outgoing_header.fp.fields.ulp     = 0;   /* Will be determined later */
    ps->outgoing_header.fp.fields.p       = 1;
    ps->outgoing_header.fp.fields.b       = 0;
    ps->outgoing_header.fp.fields.d1_size = 3;
    ps->outgoing_header.fp.fields.d2_off  = 0;
    ps->outgoing_header.le.fc             = 0;
    ps->outgoing_header.le.w              = 0;
    ps->outgoing_header.le.m_type         = 0;
    ps->outgoing_header.le.dst_sw_address = 0;
    ps->outgoing_header.le.dat            = 0;
    ps->outgoing_header.le.sat            = 0;
    ps->outgoing_header.le.src_sw_address = 0;
    ps->outgoing_header.llc.dsap          = 0xAA;
    ps->outgoing_header.llc.ssap          = 0xAA;
    ps->outgoing_header.llc.control       = 03;
    ps->outgoing_header.llc.ether_type    = 0x0800;

    /*
     * Now, start the thread to receive mach messages with incoming packets.
     * It used to create a new thread, but now we re-use the main thread,
     * which was locked forever, doing nothing, anyway.
     * Old call:
     *            xkThreadCreate((ThreadFunc)receive_mach_msg,self,XK_DRIVER);
     */
    PTHREAD_MUTEX_LOCK( &main_thread_desc.mutex );
    main_thread_desc.func = (void(*)(void*))receive_mach_msg;
    main_thread_desc.arg  = self;
    PTHREAD_MUTEX_UNLOCK( &main_thread_desc.mutex );
    PTHREAD_COND_SIGNAL( &main_thread_desc.cond );
}

static XkHandle
hippiPush( Sessn self, Msg* msg )
{
    MsgWalk mw;
    char*   data;
    int     datalen;
    int     fragments;
    char*   defrag_buffer;
    char*   ptr;
    imap_t* imap;
    int     hippi_msg_len;
    ETHhost ethBcastHost;
    hippi_header_out_t   hippi_header_out;

    ETHhdr* hdr;
    PState* ps;
    int     hlp_msg_len;

    ps          = (PState*) (self->state);
    hdr         = msgGetAttr(msg, 0);
    hlp_msg_len = msgLength(msg);

    xTrace2(hippi_drv,TR_FUNCTIONAL_TRACE,"hippiPush(0x%x,0x%x) called",
            self, msg );

    ethBcastHost.high = 0xffff;
    ethBcastHost.mid  = 0xffff;
    ethBcastHost.low  = 0xffff;

    if ( ETH_ADS_EQUAL( (hdr->dst), ethBcastHost ) ) {
	xError("hippiPush: not capable of broadcasts, yet!");
	return XMSG_NULL_HANDLE;
    }

    xTrace2(hippi_drv,TR_FUNCTIONAL_TRACE,"hippiPush: msgLength(0x%x) = %d",
            msg, hlp_msg_len );
    /*
     * Fill in predefined fields in header, and then go on with 
     * the variable ones (i-field, ulp, src/dest addresses, length)
     */

    if ( mapResolve( ps->iMap, &(hdr->dst), (void**)&imap ) != XK_SUCCESS){
	xTrace1(hippi_drv,TR_ERRORS,
	        "hippiPush: unable to map ieee address %s to (i-field,ulp)",
		ethHostStr(&(hdr->dst)) );
	return XMSG_NULL_HANDLE;
    } else {
	xTrace3(hippi_drv,TR_FULL_TRACE,
	        "hippiPush: ieee address %s mapped to i-field 0x%x, ulp 0x%x",
		ethHostStr(&(hdr->dst)), imap->ifield, imap->ulp );
    }

    bcopy(&(ps->outgoing_header),&hippi_header_out,sizeof(hippi_header_out_t));

    hippi_header_out.cci.Ifield         = htonl(imap->ifield);
    hippi_header_out.fp.fields.ulp      = imap->ulp;
    hippi_header_out.fp.words.w1        = htonl(hippi_header_out.fp.words.w1);
    hippi_header_out.fp.fields.d2_size  = htonl(hlp_msg_len+8);

    hippi_header_out.llc.ether_type     = hdr->type;
    bcopy(&(hdr->src),&hippi_header_out.le.src_ieee_address,sizeof(ETHhost));
    bcopy(&(hdr->dst),&hippi_header_out.le.dst_ieee_address,sizeof(ETHhost));

    ptr = msgPush(msg, sizeof(hippi_header_out_t));
    xAssert(ptr);
    bcopy(&hippi_header_out,ptr,sizeof(hippi_header_out_t));

    hippi_msg_len = LEN_ROUND8(msgLength(msg));

#ifndef FORCE_DEFRAG
    /*
     * Check for the common case: all message is in a single buffer,
     * so we don't need to consolidate it. Just call hippi_write.
     * There is still the problem that hippi_msg_len may indicate some
     * bytes AFTER the end of the buffer, which may cause a SEGV error.
     *
     * If FORCE_DEFRAG is defined, only the else part of the if will
     * be compiled, becoming the default operation.
     */
    defrag_buffer = hippi_memget(ps->ihandle,hippi_msg_len);
    msgWalkInit(&mw, msg);
    fragments = 0;
    ptr       = defrag_buffer;
    while ( ( data = msgWalkNext( &mw, &datalen ) ) != 0 ) {
	fragments++;
    }
    msgWalkDone(&mw);

    if ( fragments == 1 ) {
	if ( hippi_write( ps->ihandle, ptr, hippi_msg_len ) < 0 ) {
	    xError("hippiPush: hippi_write failed");
	}

    } else
#endif /* USE_DEFRAG */
    {
	xTrace1(hippi_drv,TR_FULL_TRACE,
	        "hippiPush: allocating buffer (%d bytes)", hippi_msg_len );

	defrag_buffer = hippi_memget(ps->ihandle,hippi_msg_len);
	xTrace2(hippi_drv,TR_FULL_TRACE,"hippiPush: defrag_buffer 0x%x, %d bytes",
		defrag_buffer, hippi_msg_len );
		
	msgWalkInit(&mw, msg);
	fragments = 0;
	ptr       = defrag_buffer;
	while ( ( data = msgWalkNext( &mw, &datalen ) ) != 0 ) {
	    bcopy(data,ptr,datalen);
	    ptr += datalen;
	    fragments++;
	}
	msgWalkDone(&mw);

	if ( fragments > 1 ) {
	    xTrace1(hippi_drv,TR_FULL_TRACE,
		    "hippiPush: %d fragments in msg",fragments);
	}

	xIfTrace(hippi_drv,TR_GROSS_EVENTS) {
	    xIfTrace(hippi_drv,TR_MAJOR_EVENTS) {
	    } else {
		hippi_print( (hippi_header_any_t*)defrag_buffer, 1 );
	    }
	}

	if ( hippi_write( ps->ihandle, defrag_buffer, hippi_msg_len ) < 0 ) {
	    xError("hippiPush: hippi_write failed");
	}

	hippi_memfree(ps->ihandle,defrag_buffer, hippi_msg_len, 0 );
    }

    return XMSG_NULL_HANDLE;
}

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

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

    switch (op) {
	/* NOTE: must also implement ARP registration. */

      case GETMYHOST:
	checkLen(len, sizeof(ETHhost));
	bcopy((char *) &ps->myHost, buf, sizeof(ETHhost));
	return (sizeof(ETHhost));
    
      case ETH_REGISTER_ARP:
        /*
         * ARP registers itself with us so we can ask it to perform
         * callbacks in order to simulate hardware broadcast.
         * (Not used at this point)
         */
        checkLen(len, sizeof(Protl));
        ps->arp = *(Protl *)buf;
        return 0;


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

      default:
        return -1;
    }
}

void
hippi_print( hippi_header_any_t* hh, int outgoing )
{
    char                    copy[64*1024];
    union  hippi_fp_header* fpptr;
    struct hippi_le_header* leptr;
    struct llc*             llcptr;
    char*                   msg;
    int                     size;

    if ( outgoing ) {
	size = ntohl(hh->out.fp.fields.d2_size)+sizeof(hippi_header_out_t);
    } else {
	size = ntohl(hh->in.fp.fields.d2_size)+sizeof(hippi_header_in_t);
    }

    memcpy(&copy,hh,size);
    hh = (hippi_header_any_t*)copy;

    if ( outgoing ) {
	fpptr  = &(hh->out.fp);
	leptr  = &(hh->out.le);
	llcptr = &(hh->out.llc);
	msg    = copy+sizeof(hippi_header_out_t);
    } else {
	fpptr  = &(hh->in.fp);
	leptr  = &(hh->in.le);
	llcptr = &(hh->in.llc);
	msg    = copy+sizeof(hippi_header_in_t);
    }
    

    fpptr->words.w1 = ntohl(fpptr->words.w1);
    fpptr->words.w2 = ntohl(fpptr->words.w2);

    fprintf(stderr,
    "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
    fprintf(stderr,
    "|      %02x       |%d|%d|        %03x          |      %02x       |  %x  |\n",
    fpptr->fields.ulp&0xFF, fpptr->fields.p&0x01, fpptr->fields.b&0x01, fpptr->fields.res, fpptr->fields.d1_size, fpptr->fields.d2_off);
    fprintf(stderr,
    "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
    fprintf(stderr,
    "|                     d2_size = %3d                             |\n",
    fpptr->fields.d2_size);
    fprintf(stderr,
    "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
    fprintf(stderr,
    "|  %x  |%x|   %x   |                  %06x                       |\n",
    leptr->fc, leptr->w, leptr->m_type, leptr->dst_sw_address);
    fprintf(stderr,
    "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
    fprintf(stderr,
    "|   %x   |   %x   |                  %06x                       |\n",
    leptr->dat, leptr->sat, leptr->src_sw_address);
    fprintf(stderr,
    "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
    fprintf(stderr,
    "|              %x                |             %04x              |\n",
    leptr->res, leptr->dst_ieee_address.high);
    fprintf(stderr,
    "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
    fprintf(stderr,
    "|              %04x                           %04x              |\n",
    leptr->dst_ieee_address.mid, leptr->dst_ieee_address.low);
    fprintf(stderr,
    "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
    fprintf(stderr,
    "|              %x                |             %04x              |\n",
    leptr->le_local_admin, leptr->src_ieee_address.high);
    fprintf(stderr,
    "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
    fprintf(stderr,
    "|              %04x                           %04x              |\n",
    leptr->src_ieee_address.mid, leptr->src_ieee_address.low);
    fprintf(stderr,
    "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
    fprintf(stderr,
    "|       %02x      |       %02x      |       %02x      |       %02x      |\n",
    llcptr->ssap, llcptr->dsap, llcptr->control, llcptr->org_code[3]);
    fprintf(stderr,
    "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
    fprintf(stderr,
    "|       %02x      |       %02x      |             %04x              |\n",
    llcptr->org_code[2], llcptr->org_code[0], llcptr->ether_type);
    fprintf(stderr,
    "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
}
