+/* if_ec.c 4.32 82/12/17 */
+
+#include "ec.h"
+
+/*
+ * 3Com Ethernet Controller interface
+ */
+#include "../machine/pte.h"
+
+#include "../h/param.h"
+#include "../h/systm.h"
+#include "../h/mbuf.h"
+#include "../h/buf.h"
+#include "../h/protosw.h"
+#include "../h/socket.h"
+#include "../h/vmmac.h"
+#include <errno.h>
+
+#include "../net/if.h"
+#include "../net/netisr.h"
+#include "../net/route.h"
+#include "../netinet/in.h"
+#include "../netinet/in_systm.h"
+#include "../netinet/ip.h"
+#include "../netinet/ip_var.h"
+#include "../netpup/pup.h"
+
+#include "../vax/cpu.h"
+#include "../vax/mtpr.h"
+#include "../vaxif/if_ether.h"
+#include "../vaxif/if_ecreg.h"
+#include "../vaxif/if_uba.h"
+#include "../vaxuba/ubareg.h"
+#include "../vaxuba/ubavar.h"
+
+#define ECMEM 0000000
+
+int ecprobe(), ecattach(), ecrint(), ecxint(), eccollide();
+struct uba_device *ecinfo[NEC];
+u_short ecstd[] = { 0 };
+struct uba_driver ecdriver =
+ { ecprobe, 0, ecattach, 0, ecstd, "ec", ecinfo };
+u_char ec_iltop[3] = { 0x02, 0x07, 0x01 };
+u_char ecbroadcastaddr[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+#define ECUNIT(x) minor(x)
+
+int ecinit(),ecoutput(),ecreset();
+struct mbuf *ecget();
+
+extern struct ifnet loif;
+
+/*
+ * Ethernet software status per interface.
+ *
+ * Each interface is referenced by a network interface structure,
+ * es_if, which the routing code uses to locate the interface.
+ * This structure contains the output queue for the interface, its address, ...
+ * We also have, for each interface, a UBA interface structure, which
+ * contains information about the UNIBUS resources held by the interface:
+ * map registers, buffered data paths, etc. Information is cached in this
+ * structure for use by the if_uba.c routines in running the interface
+ * efficiently.
+ */
+struct ec_softc {
+ struct ifnet es_if; /* network-visible interface */
+ struct ifuba es_ifuba; /* UNIBUS resources */
+ short es_mask; /* mask for current output delay */
+ short es_oactive; /* is output active? */
+ u_char *es_buf[16]; /* virtual addresses of buffers */
+ u_char es_enaddr[6]; /* board's ethernet address */
+} ec_softc[NEC];
+
+/*
+ * Do output DMA to determine interface presence and
+ * interrupt vector. DMA is too short to disturb other hosts.
+ */
+ecprobe(reg)
+ caddr_t reg;
+{
+ register int br, cvec; /* r11, r10 value-result */
+ register struct ecdevice *addr = (struct ecdevice *)reg;
+ register caddr_t ecbuf = (caddr_t) &umem[numuba][ECMEM];
+
+#ifdef lint
+ br = 0; cvec = br; br = cvec;
+ ecrint(0); ecxint(0); eccollide(0);
+#endif
+ /*
+ * Make sure memory is turned on
+ */
+ addr->ec_rcr = EC_AROM;
+ /*
+ * Disable map registers for ec unibus space,
+ * but don't allocate yet.
+ */
+ (void) ubamem(numuba, ECMEM, 32*2, 0);
+ /*
+ * Check for existence of buffers on Unibus.
+ */
+ if (badaddr((caddr_t)ecbuf, 2)) {
+ bad1:
+ printf("ec: buffer mem not found\n");
+ bad2:
+ (void) ubamem(numuba, 0, 0, 0); /* reenable map (780 only) */
+ addr->ec_rcr = EC_MDISAB; /* disable memory */
+ return (0);
+ }
+#if VAX780
+ if (cpu == VAX_780 && uba_hd[numuba].uh_uba->uba_sr) {
+ uba_hd[numuba].uh_uba->uba_sr = uba_hd[numuba].uh_uba->uba_sr;
+ goto bad1;
+ }
+#endif
+
+ /*
+ * Tell the system that the board has memory here, so it won't
+ * attempt to allocate the addresses later.
+ */
+ if (ubamem(numuba, ECMEM, 32*2, 1) == 0) {
+ printf("ecprobe: cannot reserve uba addresses\n");
+ goto bad2;
+ }
+
+ /*
+ * Make a one byte packet in what should be buffer #0.
+ * Submit it for sending. This whould cause an xmit interrupt.
+ * The xmit interrupt vector is 8 bytes after the receive vector,
+ * so adjust for this before returning.
+ */
+ *(u_short *)ecbuf = (u_short) 03777;
+ ecbuf[03777] = '\0';
+ addr->ec_xcr = EC_XINTEN|EC_XWBN;
+ DELAY(100000);
+ addr->ec_xcr = EC_XCLR;
+ if (cvec > 0 && cvec != 0x200) {
+ if (cvec & 04) { /* collision interrupt */
+ cvec -= 04;
+ br += 1; /* rcv is collision + 1 */
+ } else { /* xmit interrupt */
+ cvec -= 010;
+ br += 2; /* rcv is xmit + 2 */
+ }
+ }
+ return (1);
+}
+
+/*
+ * Interface exists: make available by filling in network interface
+ * record. System will initialize the interface when it is ready
+ * to accept packets.
+ */
+ecattach(ui)
+ struct uba_device *ui;
+{
+ struct ec_softc *es = &ec_softc[ui->ui_unit];
+ register struct ifnet *ifp = &es->es_if;
+ register struct ecdevice *addr = (struct ecdevice *)ui->ui_addr;
+ struct sockaddr_in *sin;
+ int i, j;
+ u_char *cp;
+
+ ifp->if_unit = ui->ui_unit;
+ ifp->if_name = "ec";
+ ifp->if_mtu = ETHERMTU;
+ ifp->if_net = ui->ui_flags;
+
+ /*
+ * Read the ethernet address off the board, one nibble at a time.
+ */
+ addr->ec_xcr = EC_UECLR;
+ addr->ec_rcr = EC_AROM;
+ cp = es->es_enaddr;
+#define NEXTBIT addr->ec_rcr = EC_AROM|EC_ASTEP; addr->ec_rcr = EC_AROM
+ for (i=0; i<6; i++) {
+ *cp = 0;
+ for (j=0; j<=4; j+=4) {
+ *cp |= ((addr->ec_rcr >> 8) & 0xf) << j;
+ NEXTBIT; NEXTBIT; NEXTBIT; NEXTBIT;
+ }
+ cp++;
+ }
+#ifdef notdef
+ printf("ec%d: addr=%x:%x:%x:%x:%x:%x\n", ui->ui_unit,
+ es->es_enaddr[0]&0xff, es->es_enaddr[1]&0xff,
+ es->es_enaddr[2]&0xff, es->es_enaddr[3]&0xff,
+ es->es_enaddr[4]&0xff, es->es_enaddr[5]&0xff);
+#endif
+ ifp->if_host[0] = ((es->es_enaddr[3]&0xff)<<16) |
+ ((es->es_enaddr[4]&0xff)<<8) | (es->es_enaddr[5]&0xff);
+ sin = (struct sockaddr_in *)&es->es_if.if_addr;
+ sin->sin_family = AF_INET;
+ sin->sin_addr = if_makeaddr(ifp->if_net, ifp->if_host[0]);
+
+ sin = (struct sockaddr_in *)&ifp->if_broadaddr;
+ sin->sin_family = AF_INET;
+ sin->sin_addr = if_makeaddr(ifp->if_net, INADDR_ANY);
+ ifp->if_flags = IFF_BROADCAST;
+
+ ifp->if_init = ecinit;
+ ifp->if_output = ecoutput;
+ ifp->if_reset = ecreset;
+ for (i=0; i<16; i++)
+ es->es_buf[i] = (u_char *)&umem[ui->ui_ubanum][ECMEM+2048*i];
+ if_attach(ifp);
+}
+
+/*
+ * Reset of interface after UNIBUS reset.
+ * If interface is on specified uba, reset its state.
+ */
+ecreset(unit, uban)
+ int unit, uban;
+{
+ register struct uba_device *ui;
+
+ if (unit >= NEC || (ui = ecinfo[unit]) == 0 || ui->ui_alive == 0 ||
+ ui->ui_ubanum != uban)
+ return;
+ printf(" ec%d", unit);
+ (void) ubamem(uban, ECMEM, 32*2, 0); /* mr disable (no alloc) */
+ ecinit(unit);
+}
+
+/*
+ * Initialization of interface; clear recorded pending
+ * operations, and reinitialize UNIBUS usage.
+ */
+ecinit(unit)
+ int unit;
+{
+ struct ec_softc *es = &ec_softc[unit];
+ struct ecdevice *addr;
+ int i, s;
+
+ /*
+ * Hang receive buffers and start any pending writes.
+ * Writing into the rcr also makes sure the memory
+ * is turned on.
+ */
+ addr = (struct ecdevice *)ecinfo[unit]->ui_addr;
+ s = splimp();
+ for (i=ECRHBF; i>=ECRLBF; i--)
+ addr->ec_rcr = EC_READ|i;
+ es->es_oactive = 0;
+ es->es_mask = ~0;
+ es->es_if.if_flags |= IFF_UP;
+ if (es->es_if.if_snd.ifq_head)
+ ecstart(unit);
+ splx(s);
+ if_rtinit(&es->es_if, RTF_UP);
+}
+
+/*
+ * Start or restart output on interface.
+ * If interface is already active, then this is a retransmit
+ * after a collision, and just restuff registers.
+ * If interface is not already active, get another datagram
+ * to send off of the interface queue, and map it to the interface
+ * before starting the output.
+ */
+ecstart(dev)
+ dev_t dev;
+{
+ int unit = ECUNIT(dev);
+ struct ec_softc *es = &ec_softc[unit];
+ struct ecdevice *addr;
+ struct mbuf *m;
+
+ if (es->es_oactive)
+ goto restart;
+
+ IF_DEQUEUE(&es->es_if.if_snd, m);
+ if (m == 0) {
+ es->es_oactive = 0;
+ return;
+ }
+ ecput(es->es_buf[ECTBF], m);
+
+restart:
+ addr = (struct ecdevice *)ecinfo[unit]->ui_addr;
+ addr->ec_xcr = EC_WRITE|ECTBF;
+ es->es_oactive = 1;
+}
+
+/*
+ * Ethernet interface transmitter interrupt.
+ * Start another output if more data to send.
+ */
+ecxint(unit)
+ int unit;
+{
+ register struct ec_softc *es = &ec_softc[unit];
+ register struct ecdevice *addr =
+ (struct ecdevice *)ecinfo[unit]->ui_addr;
+
+ if (es->es_oactive == 0)
+ return;
+ if ((addr->ec_xcr&EC_XDONE) == 0 || (addr->ec_xcr&EC_XBN) != ECTBF) {
+ printf("ec%d: stray xmit interrupt, xcr=%b\n", unit,
+ addr->ec_xcr, EC_XBITS);
+ es->es_oactive = 0;
+ addr->ec_xcr = EC_XCLR;
+ return;
+ }
+ es->es_if.if_opackets++;
+ es->es_oactive = 0;
+ es->es_mask = ~0;
+ addr->ec_xcr = EC_XCLR;
+ if (es->es_if.if_snd.ifq_head)
+ ecstart(unit);
+}
+
+/*
+ * Collision on ethernet interface. Do exponential
+ * backoff, and retransmit. If have backed off all
+ * the way print warning diagnostic, and drop packet.
+ */
+eccollide(unit)
+ int unit;
+{
+ struct ec_softc *es = &ec_softc[unit];
+
+ es->es_if.if_collisions++;
+ if (es->es_oactive)
+ ecdocoll(unit);
+}
+
+ecdocoll(unit)
+ int unit;
+{
+ register struct ec_softc *es = &ec_softc[unit];
+ register struct ecdevice *addr =
+ (struct ecdevice *)ecinfo[unit]->ui_addr;
+ register i;
+ int delay;
+
+ /*
+ * Es_mask is a 16 bit number with n low zero bits, with
+ * n the number of backoffs. When es_mask is 0 we have
+ * backed off 16 times, and give up.
+ */
+ if (es->es_mask == 0) {
+ es->es_if.if_oerrors++;
+ printf("ec%d: send error\n", unit);
+ /*
+ * Reset interface, then requeue rcv buffers.
+ * Some incoming packets may be lost, but that
+ * can't be helped.
+ */
+ addr->ec_xcr = EC_UECLR;
+ for (i=ECRHBF; i>=ECRLBF; i--)
+ addr->ec_rcr = EC_READ|i;
+ /*
+ * Reset and transmit next packet (if any).
+ */
+ es->es_oactive = 0;
+ es->es_mask = ~0;
+ if (es->es_if.if_snd.ifq_head)
+ ecstart(unit);
+ return;
+ }
+ /*
+ * Do exponential backoff. Compute delay based on low bits
+ * of the interval timer. Then delay for that number of
+ * slot times. A slot time is 51.2 microseconds (rounded to 51).
+ * This does not take into account the time already used to
+ * process the interrupt.
+ */
+ es->es_mask <<= 1;
+ delay = mfpr(ICR) &~ es->es_mask;
+ DELAY(delay * 51);
+ /*
+ * Clear the controller's collision flag, thus enabling retransmit.
+ */
+ addr->ec_xcr = EC_CLEAR;
+}
+
+/*
+ * Ethernet interface receiver interrupt.
+ * If input error just drop packet.
+ * Otherwise purge input buffered data path and examine
+ * packet to determine type. If can't determine length
+ * from type, then have to drop packet. Othewise decapsulate
+ * packet based on type and pass to type specific higher-level
+ * input routine.
+ */
+ecrint(unit)
+ int unit;
+{
+ struct ecdevice *addr = (struct ecdevice *)ecinfo[unit]->ui_addr;
+
+ while (addr->ec_rcr & EC_RDONE)
+ ecread(unit);
+}
+
+ecread(unit)
+ int unit;
+{
+ register struct ec_softc *es = &ec_softc[unit];
+ struct ecdevice *addr = (struct ecdevice *)ecinfo[unit]->ui_addr;
+ register struct ether_header *ec;
+ struct mbuf *m;
+ int len, off, resid, ecoff, rbuf;
+ register struct ifqueue *inq;
+ u_char *ecbuf;
+
+ es->es_if.if_ipackets++;
+ rbuf = addr->ec_rcr & EC_RBN;
+ if (rbuf < ECRLBF || rbuf > ECRHBF)
+ panic("ecrint");
+ ecbuf = es->es_buf[rbuf];
+ ecoff = *(short *)ecbuf;
+ if (ecoff <= ECRDOFF || ecoff > 2046) {
+ es->es_if.if_ierrors++;
+#ifdef notdef
+ if (es->es_if.if_ierrors % 100 == 0)
+ printf("ec%d: += 100 input errors\n", unit);
+#endif
+ goto setup;
+ }
+
+ /*
+ * Get input data length.
+ * Get pointer to ethernet header (in input buffer).
+ * Deal with trailer protocol: if type is PUP trailer
+ * get true type from first 16-bit word past data.
+ * Remember that type was trailer by setting off.
+ */
+ len = ecoff - ECRDOFF - sizeof (struct ether_header);
+ ec = (struct ether_header *)(ecbuf + ECRDOFF);
+ ec->ether_type = ntohs((u_short)ec->ether_type);
+#define ecdataaddr(ec, off, type) ((type)(((caddr_t)((ec)+1)+(off))))
+ if (ec->ether_type >= ETHERPUP_TRAIL &&
+ ec->ether_type < ETHERPUP_TRAIL+ETHERPUP_NTRAILER) {
+ off = (ec->ether_type - ETHERPUP_TRAIL) * 512;
+ if (off >= ETHERMTU)
+ goto setup; /* sanity */
+ ec->ether_type = ntohs(*ecdataaddr(ec, off, u_short *));
+ resid = ntohs(*(ecdataaddr(ec, off+2, u_short *)));
+ if (off + resid > len)
+ goto setup; /* sanity */
+ len = off + resid;
+ } else
+ off = 0;
+ if (len == 0)
+ goto setup;
+
+ /*
+ * Pull packet off interface. Off is nonzero if packet
+ * has trailing header; ecget will then force this header
+ * information to be at the front, but we still have to drop
+ * the type and length which are at the front of any trailer data.
+ */
+ m = ecget(ecbuf, len, off);
+ if (m == 0)
+ goto setup;
+ if (off) {
+ m->m_off += 2 * sizeof (u_short);
+ m->m_len -= 2 * sizeof (u_short);
+ }
+ switch (ec->ether_type) {
+
+#ifdef INET
+ case ETHERPUP_IPTYPE:
+ schednetisr(NETISR_IP);
+ inq = &ipintrq;
+ break;
+#endif
+ default:
+ m_freem(m);
+ goto setup;
+ }
+
+ if (IF_QFULL(inq)) {
+ IF_DROP(inq);
+ m_freem(m);
+ goto setup;
+ }
+ IF_ENQUEUE(inq, m);
+
+setup:
+ /*
+ * Reset for next packet.
+ */
+ addr->ec_rcr = EC_READ|EC_RCLR|rbuf;
+}
+
+/*
+ * Ethernet output routine.
+ * Encapsulate a packet of type family for the local net.
+ * Use trailer local net encapsulation if enough data in first
+ * packet leaves a multiple of 512 bytes of data in remainder.
+ * If destination is this address or broadcast, send packet to
+ * loop device to kludge around the fact that 3com interfaces can't
+ * talk to themselves.
+ */
+ecoutput(ifp, m0, dst)
+ struct ifnet *ifp;
+ struct mbuf *m0;
+ struct sockaddr *dst;
+{
+ int type, dest, s, error;
+ register struct ec_softc *es = &ec_softc[ifp->if_unit];
+ register struct mbuf *m = m0;
+ register struct ether_header *ec;
+ register int off, i;
+ struct mbuf *mcopy = (struct mbuf *) 0; /* Null */
+
+ switch (dst->sa_family) {
+
+#ifdef INET
+ case AF_INET:
+ dest = ((struct sockaddr_in *)dst)->sin_addr.s_addr;
+ if ((dest &~ 0xff) == 0)
+ mcopy = m_copy(m, 0, (int)M_COPYALL);
+ else if (dest == ((struct sockaddr_in *)&es->es_if.if_addr)->
+ sin_addr.s_addr) {
+ mcopy = m;
+ goto gotlocal;
+ }
+ off = ntohs((u_short)mtod(m, struct ip *)->ip_len) - m->m_len;
+ if (off > 0 && (off & 0x1ff) == 0 &&
+ m->m_off >= MMINOFF + 2 * sizeof (u_short)) {
+ type = ETHERPUP_TRAIL + (off>>9);
+ m->m_off -= 2 * sizeof (u_short);
+ m->m_len += 2 * sizeof (u_short);
+ *mtod(m, u_short *) = ntohs((u_short)ETHERPUP_IPTYPE);
+ *(mtod(m, u_short *) + 1) = ntohs((u_short)m->m_len);
+ goto gottrailertype;
+ }
+ type = ETHERPUP_IPTYPE;
+ off = 0;
+ goto gottype;
+#endif
+
+ default:
+ printf("ec%d: can't handle af%d\n", ifp->if_unit,
+ dst->sa_family);
+ error = EAFNOSUPPORT;
+ goto bad;
+ }
+
+gottrailertype:
+ /*
+ * Packet to be sent as trailer: move first packet
+ * (control information) to end of chain.
+ */
+ while (m->m_next)
+ m = m->m_next;
+ m->m_next = m0;
+ m = m0->m_next;
+ m0->m_next = 0;
+ m0 = m;
+
+gottype:
+ /*
+ * Add local net header. If no space in first mbuf,
+ * allocate another.
+ */
+ if (m->m_off > MMAXOFF ||
+ MMINOFF + sizeof (struct ether_header) > m->m_off) {
+ m = m_get(M_DONTWAIT, MT_HEADER);
+ if (m == 0) {
+ error = ENOBUFS;
+ goto bad;
+ }
+ m->m_next = m0;
+ m->m_off = MMINOFF;
+ m->m_len = sizeof (struct ether_header);
+ } else {
+ m->m_off -= sizeof (struct ether_header);
+ m->m_len += sizeof (struct ether_header);
+ }
+ ec = mtod(m, struct ether_header *);
+ bcopy((caddr_t)es->es_enaddr, (caddr_t)ec->ether_shost, 6);
+ if ((dest &~ 0xff) == 0)
+ bcopy((caddr_t)ecbroadcastaddr, (caddr_t)ec->ether_dhost, 6);
+ else {
+ u_char *to = dest & 0x8000 ? ec_iltop : es->es_enaddr;
+
+ bcopy((caddr_t)to, (caddr_t)ec->ether_dhost, 3);
+ ec->ether_dhost[3] = (dest>>8) & 0x7f;
+ ec->ether_dhost[4] = (dest>>16) & 0xff;
+ ec->ether_dhost[5] = (dest>>24) & 0xff;
+ }
+ ec->ether_type = htons((u_short)type);
+
+ /*
+ * Queue message on interface, and start output if interface
+ * not yet active.
+ */
+ s = splimp();
+ if (IF_QFULL(&ifp->if_snd)) {
+ IF_DROP(&ifp->if_snd);
+ error = ENOBUFS;
+ goto qfull;
+ }
+ IF_ENQUEUE(&ifp->if_snd, m);
+ if (es->es_oactive == 0)
+ ecstart(ifp->if_unit);
+ splx(s);
+
+gotlocal:
+ return(mcopy ? looutput(&loif, mcopy, dst) : 0);
+
+qfull:
+ m0 = m;
+ splx(s);
+bad:
+ m_freem(m0);
+ return(error);
+}
+
+/*
+ * Routine to copy from mbuf chain to transmit
+ * buffer in UNIBUS memory.
+ * If packet size is less than the minimum legal size,
+ * the buffer is expanded. We probably should zero out the extra
+ * bytes for security, but that would slow things down.
+ */
+ecput(ecbuf, m)
+ u_char *ecbuf;
+ struct mbuf *m;
+{
+ register struct mbuf *mp;
+ register int off;
+ u_char *bp;
+
+ for (off = 2048, mp = m; mp; mp = mp->m_next)
+ off -= mp->m_len;
+ if (2048 - off < ETHERMIN + sizeof (struct ether_header))
+ off = 2048 - ETHERMIN - sizeof (struct ether_header);
+ *(u_short *)ecbuf = off;
+ bp = (u_char *)(ecbuf + off);
+ for (mp = m; mp; mp = mp->m_next) {
+ register unsigned len = mp->m_len;
+ u_char *mcp;
+
+ if (len == 0)
+ continue;
+ mcp = mtod(mp, u_char *);
+ if ((unsigned)bp & 01) {
+ *bp++ = *mcp++;
+ len--;
+ }
+ if (off = (len >> 1)) {
+ register u_short *to, *from;
+
+ to = (u_short *)bp;
+ from = (u_short *)mcp;
+ do
+ *to++ = *from++;
+ while (--off > 0);
+ bp = (u_char *)to,
+ mcp = (u_char *)from;
+ }
+ if (len & 01)
+ *bp++ = *mcp++;
+ }
+ m_freem(m);
+}
+
+/*
+ * Routine to copy from UNIBUS memory into mbufs.
+ * Similar in spirit to if_rubaget.
+ *
+ * Warning: This makes the fairly safe assumption that
+ * mbufs have even lengths.
+ */
+struct mbuf *
+ecget(ecbuf, totlen, off0)
+ u_char *ecbuf;
+ int totlen, off0;
+{
+ register struct mbuf *m;
+ struct mbuf *top = 0, **mp = ⊤
+ register int off = off0, len;
+ u_char *cp;
+
+ cp = ecbuf + ECRDOFF + sizeof (struct ether_header);
+ while (totlen > 0) {
+ register int words;
+ u_char *mcp;
+
+ MGET(m, M_DONTWAIT, MT_DATA);
+ if (m == 0)
+ goto bad;
+ if (off) {
+ len = totlen - off;
+ cp = ecbuf + ECRDOFF +
+ sizeof (struct ether_header) + off;
+ } else
+ len = totlen;
+ if (len >= CLBYTES) {
+ struct mbuf *p;
+
+ MCLGET(p, 1);
+ if (p != 0) {
+ m->m_len = len = CLBYTES;
+ m->m_off = (int)p - (int)m;
+ } else {
+ m->m_len = len = MIN(MLEN, len);
+ m->m_off = MMINOFF;
+ }
+ } else {
+ m->m_len = len = MIN(MLEN, len);
+ m->m_off = MMINOFF;
+ }
+ mcp = mtod(m, u_char *);
+ if (words = (len >> 1)) {
+ register u_short *to, *from;
+
+ to = (u_short *)mcp;
+ from = (u_short *)cp;
+ do
+ *to++ = *from++;
+ while (--words > 0);
+ mcp = (u_char *)to;
+ cp = (u_char *)from;
+ }
+ if (len & 01)
+ *mcp++ = *cp++;
+ *mp = m;
+ mp = &m->m_next;
+ if (off == 0) {
+ totlen -= len;
+ continue;
+ }
+ off += len;
+ if (off == totlen) {
+ cp = ecbuf + ECRDOFF + sizeof (struct ether_header);
+ off = 0;
+ totlen = off0;
+ }
+ }
+ return (top);
+bad:
+ m_freem(top);
+ return (0);
+}