NetBSD/sys/arch/alpha/a12/if_xb.c

782 lines
21 KiB
C

/* $NetBSD: if_xb.c,v 1.4 2000/12/12 18:00:22 thorpej Exp $ */
/* [Notice revision 2.2]
* Copyright (c) 1997, 1998 Avalon Computer Systems, Inc.
* All rights reserved.
*
* Author: Ross Harvey
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright and
* author notice, this list of conditions, and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Avalon Computer Systems, Inc. nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* 4. This copyright will be assigned to The NetBSD Foundation on
* 1/1/2000 unless these terms (including possibly the assignment
* date) are updated in writing by Avalon prior to the latest specified
* assignment date.
*
* THIS SOFTWARE IS PROVIDED BY AVALON COMPUTER SYSTEMS, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AVALON OR THE CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Notes.
*
* Since the NetBSD build rules force the use of function prototypes, even on
* functions that are defined before they are called, I've taken advantage of
* the opportunity and organized this module in top down fashion, with
* functions generally calling down the page rather than up. It's different.
* I think I'm getting to like it this way.
*
* The crossbar interface is not exactly a peripheral device, and it cannot
* appear on anything other than an alpha-based Avalon A12. The crossbar
* controller is built into the core logic.
*
* If this version of the driver supports MPS transport, it may have some
* large static data declarations. Don't worry about it, as Avalon a12
* support should not appear in a GENERIC or INSTALL kernel.
*
* (Every A12 ever shipped had 512 MB per CPU except one site, which had 256
* MB. Partly has a result of this, it is unlikely that a kernel configured
* for an A12 would be exactly the thing to use on most workstations, so we
* don't really need to worry that we might be configured in a generic or
* site-wide kernel image.)
*
* This preliminary crossbar driver supports IP transport using PIO. Although
* it would be nice to have a DMA driver, do note that the crossbar register
* port is 128 bits wide, so we have 128-bit PIO. (The 21164 write buffer
* will combine two 64-bit stores before they get off-chip.) Also, the rtmon
* driver wasn't DMA either, so at least the NetBSD driver is as good as any
* other that exists now.
*
* We'll do DMA and specialized transport ops later. Given the high speed of
* the PIO mode, no current applications require DMA bandwidth, but everyone
* benefits from low latency. The PIO mode is actually lower in latency
* anyway.
*/
#include "opt_avalon_a12.h" /* Config options headers */
#include <sys/cdefs.h> /* RCS ID & Copyright macro defns */
__KERNEL_RCSID(0, "$NetBSD: if_xb.c,v 1.4 2000/12/12 18:00:22 thorpej Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/socket.h>
#include <sys/mbuf.h>
#include <sys/sockio.h>
#include <uvm/uvm_extern.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <machine/autoconf.h>
#include <machine/rpb.h>
#include <dev/isa/isareg.h>
#include <dev/isa/isavar.h>
#include <dev/dec/clockvar.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <alpha/pci/a12creg.h>
#include <alpha/pci/a12cvar.h>
#include <alpha/pci/pci_a12.h>
#if 1
#define XB_DEBUG xb_debug
#else
#define XB_DEBUG 0
#endif
#undef Static
#if 1
#define Static
#else
#define Static static
#endif
#define IF_XB() /* Generate ctags(1) key */
#define XBAR_MTU (9*1024) /* Quite an arbitrary number */
#define XBAR_MAXFRAMEHDR 48 /* Used to compute if_mtu */
#define XB_DEFAULT_MTU() (XBAR_MTU - XBAR_MAXFRAMEHDR)
#define FIFO_WORDCOUNT 60
static int xb_put_blk __P((struct mbuf *));
static int xb_put __P((struct mbuf *));
static long xb_fifo_empty(void);
int xbmatch __P((struct device *, struct cfdata *, void *));
void xbattach __P((struct device *, struct device *, void *));
struct xb_softc {
struct device d;
} xb_softc;
struct cfattach xb_ca = {
sizeof(struct xb_softc), xbmatch, xbattach,
};
extern struct cfdriver xb_cd;
long *xb_incoming;
int xb_incoming_max = XBAR_MTU;
typedef struct ccode_struct {
int64_t lo64, /* magic channel address s-word, high part*/
hi64; /* magic channel address s-word, low part */
} ccode_type;
/*
* Switch channel codes. Prepending one of these words will get you through
* the switch, which will eat the word, open the addressed channel, and
* forward the rest of the switch frame. Obviously, this helps if the second
* switch word in the frame is the address word for a cascaded switch. (This
* can be repeated for an arbitrary depth of MSN.) The words aren't quite as
* wierd as they look: the switch is really lots of narrow switches in an
* array, and they don't switch an even number of hex digits. Also, there is
* a parity bit on most of the subunits.
*/
ccode_type channel[]={
{ 0x0000000000000000, 0x0000000000000000 },
{ 0x8882108421084210, 0x1104210842108421 },
{ 0x4441084210842108, 0x2208421084210842 },
{ 0xccc318c6318c6318, 0x330c6318c6318c63 },
{ 0x2220842108421084, 0x4410842108421084 },
{ 0xaaa294a5294a5294, 0x5514a5294a5294a5 },
{ 0x66618c6318c6318c, 0x6618c6318c6318c6 },
{ 0xeee39ce739ce739c, 0x771ce739ce739ce7 },
{ 0x1110421084210842, 0x8821084210842108 },
{ 0x99925294a5294a52, 0x9925294a5294a529 },
{ 0x55514a5294a5294a, 0xaa294a5294a5294a },
{ 0xddd35ad6b5ad6b5a, 0xbb2d6b5ad6b5ad6b },
{ 0x3330c6318c6318c6, 0xcc318c6318c6318c },
{ 0xbbb2d6b5ad6b5ad6, 0xdd35ad6b5ad6b5ad }
};
Static enum xb_intr_rcv_state_t {
XBIR_PKTHDR = 0, XBIR_TRANS
} xb_intr_rcv_state;
struct xb_config { int am_i_used; } xb_configuration;
Static struct ifnet xbi;
Static int frame_len;
static int xb_debug;
Static void xb_start __P((struct ifnet *));
Static void xb_mcrp_write __P((long *, int, int));
static __inline void xb_onefree __P((void));
static long set_interrupt_on_fifo_empty(void);
static void xb_init(struct ifnet *);
static int xb_intr __P((void *));
static void xb_intr_rcv __P((void));
Static void quickload __P((volatile long *, long *));
static void xb_init_config __P((struct xb_config *, int));
static int xb_output __P((struct ifnet *, struct mbuf *, struct sockaddr *,
struct rtentry *));
static int xb_ioctl __P((struct ifnet *, u_long, caddr_t));
static void xb_stop __P((void));
static void a12_xbar_setup __P((void));
/* There Can Be Only One */
int xbfound;
int
xbmatch(parent, match, aux)
struct device *parent;
struct cfdata *match;
void *aux;
{
struct pcibus_attach_args *pba = aux;
return cputype == ST_AVALON_A12
&& strcmp(pba->pba_busname, xb_cd.cd_name) == 0
&& !xbfound;
}
void
xbattach(parent, self, aux)
struct device *parent, *self;
void *aux;
{
struct xb_config *ccp;
bcopy(self->dv_xname, xbi.if_xname, IFNAMSIZ);
xbfound = 1;
ccp = &xb_configuration;
xb_init_config(ccp, 1);
printf(": driver %s mtu %d\n", "$Revision: 1.4 $", xbi.if_mtu);
}
static void
xb_init_config(ccp, mallocsafe)
struct xb_config *ccp;
int mallocsafe;
{
/*
* The driver actually only needs about 64 bytes of buffer but with a
* nice contiguous frame we can call m_devget()
*/
if (mallocsafe && xb_incoming == NULL) {
xb_incoming = malloc(xb_incoming_max, M_DEVBUF, M_NOWAIT);
if (xb_incoming == NULL)
DIE();
}
a12_xbar_setup();
a12_intr_register_xb(xb_intr);
}
/*
* From The A12 Theory of Operation. Used with permission.
* --- --- ------ -- ---------
*
* Message Channel Status Register
*
* 31 0
* | |
* 10987654 32109876 54321098 76543210
*
* ........ ........ 0oiefaAr TR...... MCSR
*
* Field Type Name Function
*
* R R,W1C RBC Receive Block Complete
* T R,W1C TBC Transmit Block Complete
* r R IMP Incoming message pending
* A R IMFAE Incoming message fifo almost empty
* a R OMFAF Outgoing message fifo almost full
* f R OMFF Outgoing message fifo full
* e R OMFE Outgoing message fifo empty
* i R DMAin Incoming DMA channel armed
* o R DMAout Outgoing DMA channel armed
*
* Interrupts Generated from MCSR
*
* IMChInt <= (RBC or IMP) and not DMAin
* OMChInt <= ((TBC and not OMFAF) or (OMFE and OMR.E(6))
* ) and not DMAout
*
*/
static int
xb_intr(p)
void *p;
{
int n;
long mcsrval;
/*
* The actual conditions under which this interrupt is generated are
* a bit complicated, and no status flag is available that reads out
* the final values of the interrupt inputs. But, it doesn't really
* matter. Simply check for receive data and transmitter IFF_OACTIVE.
*/
while ((mcsrval = REGVAL(A12_MCSR)) & A12_MCSR_IMP)
for(n = mcsrval & A12_MCSR_IMFAE ? 1 : 5; n; --n)
xb_intr_rcv();
if (xbi.if_flags & IFF_OACTIVE
&& mcsrval & A12_MCSR_OMFE) {
xbi.if_flags &= ~IFF_OACTIVE;
REGVAL(A12_OMR) &= ~A12_OMR_OMF_ENABLE;
alpha_wmb();
xb_start(&xbi);
}
return 0;
}
/*
* The interface logic will shoot us down with MCE (Missing Close Error) or
* ECE (Embedded Close Error) if we aren't in sync with the hardware w.r.t.
* frame boundaries. As those are panic-level errors: Don't Get Them.
*/
static void
xb_intr_rcv()
{
struct mbuf *m;
long frameword[2];
static long *xb_ibp;
int s = 0; /* XXX gcc */
switch (xb_intr_rcv_state) {
case XBIR_PKTHDR:
xb_ibp = xb_incoming;
quickload(REGADDR(A12_FIFO), frameword); /* frame_len >= 16 */
frame_len = frameword[0];
if (!(20 <= frame_len && frame_len+16 <= xb_incoming_max))
DIE();
/*
* The extra word when frames are of an aligned size is due
* to the way the output routines work. After the mbuf is
* sent xb_put_blk(NULL) is called. If there is a leftover
* 127-bit-or-less fragment then the close word rides on it,
* otherwise it gets an entire 128 bits of zeroes.
*/
if (frame_len & 0xf)
frame_len = (frame_len + 0xf) >> 4;
else frame_len = (frame_len >> 4) + 1;
--frame_len; /* we read the frame len + the first packet int64 */
*xb_ibp++ = frameword[1];
xb_intr_rcv_state = XBIR_TRANS;
break;
case XBIR_TRANS:
if (frame_len > 1)
quickload(REGADDR(A12_FIFO), frameword);
else if (frame_len == 1) {
quickload(REGADDR(A12_FIFO_LWE), frameword);
xb_intr_rcv_state = XBIR_PKTHDR;
} else if (XB_DEBUG)
DIE();
--frame_len;
xb_ibp[0] = frameword[0];
xb_ibp[1] = frameword[1];
xb_ibp += 2;
if (xb_intr_rcv_state == XBIR_PKTHDR) {
if (XB_DEBUG) {
s = splimp();
if (s != splimp())
DIE();
}
++xbi.if_ipackets;
if (IF_QFULL(&ipintrq)) {
IF_DROP(&ipintrq);
++xbi.if_iqdrops;
} else {
m = m_devget((caddr_t)xb_incoming,
(caddr_t)xb_ibp - (caddr_t)xb_incoming,
0, &xbi, 0L);
if (m) {
xbi.if_ibytes += m->m_pkthdr.len;
IF_ENQUEUE(&ipintrq, m);
} else
++xbi.if_ierrors;
}
if (XB_DEBUG)
splx(s);
}
break;
default:
DIE();
}
}
/*
* Make it easy for gcc to load a[0..1] without interlocking between
* a[0] and a[1]. (If it did, that would be two external bus cycles.)
*/
Static void
quickload(volatile long *a, long *b)
{
long t1,t2;
t1 = a[0];
t2 = a[1];
b[0] = t1;
b[1] = t2;
}
/*
* Verify during debugging that we have not overflowed the FIFO
*/
static __inline void
xb_onefree()
{
if (XB_DEBUG && REGVAL(A12_MCSR) & A12_MCSR_OMFF)
DIE();
}
static void
xb_init(ifp)
struct ifnet *ifp;
{
ifp->if_flags |= IFF_RUNNING;
}
static void
xb_stop()
{
}
static int
xb_ioctl(ifp, cmd, data)
struct ifnet *ifp;
u_long cmd;
caddr_t data;
{
struct ifaddr *ifa = (struct ifaddr *)data;
int s, error = 0;
s = splimp();
switch (cmd) {
case SIOCSIFADDR:
xbi.if_flags |= IFF_UP;
switch (ifa->ifa_addr->sa_family) {
#ifdef INET
case AF_INET:
xb_init(ifp);
break;
#endif
default:
xb_init(ifp);
}
break;
case SIOCSIFFLAGS:
if ((ifp->if_flags & IFF_UP) == 0 &&
(ifp->if_flags & IFF_RUNNING) != 0) {
xb_stop();
ifp->if_flags &= ~IFF_RUNNING;
} else if ((ifp->if_flags & IFF_UP) != 0 &&
(ifp->if_flags & IFF_RUNNING) == 0) {
xb_start(ifp);
} else
xb_init(ifp);
if (ifp->if_flags & IFF_DEBUG)
xb_debug = 1;
break;
default:
error = EINVAL;
break;
}
splx(s);
return error;
}
/*
* XXX - someday, keep a software copy of A12_OMR. We can execute up to
* 200 or 300 instructions in the time it takes to do the read part of an
* external bus cycle RMW op. (Or 10 - 20 cache cycles.)
*/
static __inline long
xb_fifo_empty(void)
{
return REGVAL(A12_MCSR) & A12_MCSR_OMFE;
}
/*
* rtmon frames
*
* [ (... data) : commid : sourcepid : dstpid : ktype : length : frametype ]
*
* At the moment, NetBSD ip frames are not compatible with rtmon frames:
*
* [ ... data : length ]
*/
static int
xb_output(ifp, m0, dst, rt0)
struct ifnet *ifp;
struct mbuf *m0;
struct sockaddr *dst;
struct rtentry *rt0;
{
int i,s;
struct mbuf *m = m0;
char *lladdr;
caddr_t xbh;
long xbo_framesize;
struct sockaddr_dl *llsa;
int xbaddr;
#ifdef DIAGNOSTIC
if (ifp != &xbi)
DIE();
#endif
if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) {
m_freem(m);
return ENETDOWN;
}
ifp->if_lastchange = time;
/*
* We want an IP packet with a link level route, on a silver platter.
*/
if (rt0 == NULL
|| (rt0->rt_flags & (RTF_GATEWAY | RTF_LLINFO))
|| (llsa = (struct sockaddr_dl *)rt0->rt_gateway) == NULL
|| llsa->sdl_family != AF_LINK
|| llsa->sdl_slen != 0) {
++ifp->if_oerrors;
m_freem(m);
return EHOSTUNREACH;
}
if (dst == NULL
|| dst->sa_family != AF_INET) {
/*
* This is because we give all received packets to ipintrq
* right now.
*/
What();
m_freem(m);
++ifp->if_noproto;
return EAFNOSUPPORT;
}
/*
* The a12MppSwitch is a wormhole routed MSN consisting of a number
* (usually n==1) of 14 channel crossbar switches. Each route through
* a switch requires a 128 bit address word that specifies the channel
* to emerge on. The address word is eaten by the switch and the
* rest of the packet is routed through.
*/
lladdr = LLADDR(llsa);
if (llsa->sdl_alen != 1) /* XXX */
DIE(); /* OK someday, but totally unexpected right now */
/*
* Alternatively, we could lookup the address word and output
* it with PIO when the mbuf is dequeued
*/
xbo_framesize = m->m_pkthdr.len + 8;
M_PREPEND(m, 16 * llsa->sdl_alen + 8, M_DONTWAIT);
if (m == NULL)
return ENOBUFS;
xbh = mtod(m, caddr_t);
for (i=0; i<llsa->sdl_alen; ++i) {
xbaddr = (lladdr[i] & 0xff) - 1;
if (!(0 <= xbaddr && xbaddr <= 11)) /* XXX */
DIE(); /* 12 or 13 will be OK later */
bcopy(&channel[xbaddr].lo64, xbh, 16);
xbh += 16;
}
bcopy(&xbo_framesize, xbh, 8);
s = splimp();
if (IF_QFULL(&ifp->if_snd)) {
IF_DROP(&ifp->if_snd);
++ifp->if_oerrors;
splx(s);
m_freem(m);
return ENOBUFS;
}
ifp->if_obytes += m->m_pkthdr.len;
++ifp->if_opackets;
IF_ENQUEUE(&ifp->if_snd, m);
if ((ifp->if_flags & IFF_OACTIVE) == 0)
xb_start(ifp);
splx(s);
if (m->m_flags & M_MCAST)
ifp->if_omcasts++;
return 0;
}
void
xb_start(ifp)
struct ifnet *ifp;
{
struct mbuf *m;
if ((xbi.if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING)
return;
for (;;) {
IF_DEQUEUE(&xbi.if_snd, m);
if (m == 0)
return;
/*
* XXX The variable-length switch address words cause problems
* for bpf, for now, leave it out. XXX It's not too hard to
* fix, though, as there are lots of techniques that will
* identify the number of switch address words.
*/
if (!xb_put(m)) {
xbi.if_flags |= IFF_OACTIVE;
return;
}
}
}
static int
xb_put(m)
struct mbuf *m;
{
struct mbuf *n;
int len;
if (XB_DEBUG && (alpha_pal_rdps() & 7) < 3)
DIE(); /* this "cannot happen", of course */
for (; m; m = n) {
len = m->m_len;
if (len == 0 || xb_put_blk(m))
MFREE(m, n);
else return 0;
}
xb_put_blk(NULL);
return 1;
}
/*
* Write a single mbuf to the transmit channel fifo. We can only write 128-bit
* words. Right now, we pad at the end. It is possible to pad at the
* beginning, especially since lots of games can be played at the receiver
* with the mbuf data pointer. Padding at the beginning requires a pad-count
* field in a header, but it means you can always DMA the data, regardless of
* alignment. Of course, we don't DMA at all, right now.
*/
static int
xb_put_blk(m)
struct mbuf *m;
{
static long leftover[2]; /* 0-15 bytes from last xb_put_blk() */
static int leftover_len; /* non-aligned amount from last call */
long xfertmp[8]; /* aligned switch word buffer */
int frag_len, /* fifo stream unit */
fifo_len, /* space left in fifo */
fillin, /* amount needed to complete a switch word */
full, /* remember to restart on fifo full */
len; /* amount of mbuf left to do */
caddr_t blk; /* location we are at in mbuf */
static int fifo_free; /* current # of switch words free in fifo */
#define XFERADJ() ((caddr_t)xfertmp+leftover_len)
/* There is always room for the close word */
if (m == NULL) {
if (leftover_len)
leftover_len = 0;
else leftover[0] = leftover[1] = 0;
xb_mcrp_write(leftover, 1, 1);
--fifo_free;
return 1;
}
restart:
if (fifo_free < 2) {
if (!xb_fifo_empty()) {
if(!set_interrupt_on_fifo_empty()) {
/* still empty */
xbi.if_flags |= IFF_OACTIVE;
IF_PREPEND(&xbi.if_snd, m);
return 0;
}
}
fifo_free = FIFO_WORDCOUNT;
}
len = m->m_len;
if (len == 0)
return 1; /* clean finish, nothing left over */
blk = mtod(m, caddr_t);
if (leftover_len) {
/* See function intro comment regarding padding */
if (leftover_len + len < sizeof leftover) {
/* Heh, not even enough to write out */
bcopy(blk, XFERADJ(), len);
leftover_len += len;
return 1;
}
xfertmp[0] = leftover[0];
xfertmp[1] = leftover[1];
fillin = sizeof leftover - leftover_len;
bcopy(blk, XFERADJ(), fillin);
blk += fillin;
len -= fillin;
xb_mcrp_write(xfertmp, 1, 0);
leftover_len = 0;
--fifo_free;
}
/* fifo_free is known to be >= 1 at this point */
while (len >= 16) {
full = 0;
frag_len = sizeof xfertmp;
if (frag_len > len)
frag_len = len;
fifo_len = fifo_free * 16;
if (frag_len > fifo_len) {
frag_len = fifo_len;
full = 1;
}
frag_len &= ~0xf;
bcopy(blk, xfertmp, frag_len);
frag_len >>= 4; /* Round down to switch word size */
xb_mcrp_write(xfertmp, frag_len, 0);
fifo_free -= frag_len;
frag_len <<= 4;
len -= frag_len;
blk += frag_len;
if (full) {
m_adj(m, blk - mtod(m, caddr_t));
goto restart;
}
}
bcopy(blk, leftover, len);
leftover_len = len;
return 1;
}
static long
set_interrupt_on_fifo_empty(void)
{
REGVAL(A12_OMR) |= A12_OMR_OMF_ENABLE;
alpha_mb();
if(xb_fifo_empty()) {
REGVAL(A12_OMR) &= ~A12_OMR_OMF_ENABLE;
alpha_mb();
return 1;
}
return 0;
}
/*
* Write an aligned block of switch words to the FIFO
*/
Static void
xb_mcrp_write(d, n, islast)
long *d;
{
volatile long *xb_fifo = islast ? REGADDR(A12_FIFO_LWE)
: REGADDR(A12_FIFO);
int i;
if (XB_DEBUG && islast && n != 1)
DIE();
n <<= 1;
for (i = 0; i < n; i += 2) {
xb_onefree();
xb_fifo[0] = d[i];
xb_fifo[1] = d[i+1];
alpha_wmb();
}
}
/*
const
int32_t xbar_bc_addr = XBAR_BROADCAST;
*/
static void
a12_xbar_setup()
{
xbi.if_softc = &xb_softc;
xbi.if_start = xb_start;
xbi.if_ioctl = xb_ioctl;
xbi.if_flags = IFF_BROADCAST /* ha ha */
| IFF_SIMPLEX;
if_attach(&xbi);
xbi.if_type = IFT_A12MPPSWITCH;
xbi.if_addrlen = 32;
xbi.if_hdrlen = 32;
xbi.if_mtu = XB_DEFAULT_MTU();
xbi.if_output = xb_output;
/* xbi.if_broadcastaddr = (u_int8_t)&xbar_bc_addr; */
#if NBPFILTER > 0
bpfattach(&xbi, DLT_NULL, 0);
#endif
}