/*- * Copyright (c) 1990 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * Tim L. Tucker. * * 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 * 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. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 THE REGENTS OR 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. * * $Id: if_ec.c,v 1.10 1993/06/14 16:45:25 mycroft Exp $ */ /* * A driver for the 3Com 3C503 (Etherlink II) ethernet adaptor. * * Written by Herb Peyerl (hpeyerl@novatel.cuc.ab.ca) on 04/25/92. * (This is my first ever device driver and I couldn't have done * it without the consumption of many "Brock Gummy Bears" so a * big thanx to the "Brock Candy Company" of Chattanooga TN) * * This driver uses the Western Digital 8003 driver for a template * since the two cards use the DP8390 chip by National. Everything * is fairly similar except that the nic on the wd8003 appears to * to see shared memory at 0x0000 and on the 3com, the nic sees it * at 0x2000. Also, the 3c503 has it's own ASIC for controlling * things like the IRQ level in software and whether to use the * onboard xceiver or not. Since the Clarkson drivers do a very * good rendition of a 3c503, I also scavenged a lot of ideas from * there. */ #include "param.h" #include "mbuf.h" #include "socket.h" #include "ioctl.h" #include "errno.h" #include "syslog.h" #include "net/if.h" #include "net/netisr.h" #ifdef INET #include "netinet/in.h" #include "netinet/in_systm.h" #include "netinet/in_var.h" #include "netinet/ip.h" #include "netinet/if_ether.h" #endif #ifdef NS #include "netns/ns.h" #include "netns/ns_if.h" #endif #include "i386/isa/isa_device.h" #include "i386/isa/if_ec.h" #include "ec.h" /* * Ethernet software status per interface. * * Each interface is referenced by a network interface structure, * qe_if, which the routing code uses to locate the interface. * This structure contains the output queue for the interface, its address, ... */ struct ec_softc { struct arpcom ec_ac; /* Ethernet common part */ #define ec_if ec_ac.ac_if /* network-visible interface */ #define ec_addr ec_ac.ac_enaddr /* hardware Ethernet address */ #define ns_addrp ec_ac.ac_enaddr /* hardware Ethernet address (for ns)*/ u_char ec_flags; /* software state */ #define EC_RUNNING 0x01 #define EC_TXBUSY 0x02 u_char ec_type; /* interface type code */ u_short ec_vector; /* interrupt vector */ short ec_io_ctl_addr; /* i/o bus address, control */ short ec_io_nic_addr; /* i/o bus address, DS8390 */ caddr_t ec_vmem_addr; /* card RAM virtual memory base */ u_long ec_vmem_size; /* card RAM bytes */ caddr_t ec_vmem_ring; /* receive ring RAM vaddress */ caddr_t ec_vmem_end; /* receive ring RAM end */ } ec_softc[NEC]; #define PAGE0 outb(sc->ec_io_nic_addr + EN_CCMD, ENC_NODMA|ENC_PAGE0); #define PAGE1 outb(sc->ec_io_nic_addr + EN_CCMD, ENC_NODMA|ENC_PAGE1); static Bdry; int ether_output(), ecprobe(), ecattach(), ecintr(), ec_init(), ec_ioctl(), ec_reset(), ec_watchdog(), ec_start_output(); struct isa_driver ecdriver = { ecprobe, ecattach, "ec", }; ecprobe(is) struct isa_device *is; { register struct ec_softc *sc = &ec_softc[is->id_unit&127]; int i, sum; /* * Set up the softc structure with card specific info. * The 3Com asic is at base+0x400 */ sc->ec_io_ctl_addr = is->id_iobase + 0x400; sc->ec_io_nic_addr = is->id_iobase; sc->ec_vector = is->id_irq; sc->ec_vmem_addr = (caddr_t)is->id_maddr; sc->ec_vmem_size = is->id_msize; sc->ec_vmem_ring = sc->ec_vmem_addr + (EC_PAGE_SIZE * EC_TXBUF_SIZE); sc->ec_vmem_end = sc->ec_vmem_addr + is->id_msize; /* * Now we get the MAC address. Assume thin ethernet unless told otherwise later. */ /* Toggle reset bit on*/ outb(sc->ec_io_ctl_addr + E33G_CNTRL, ECNTRL_RESET|ECNTRL_ONBOARD); DELAY(100); /* Toggle reset bit off */ outb(sc->ec_io_ctl_addr + E33G_CNTRL, ECNTRL_ONBOARD); DELAY(100); /* Map SA_PROM */ outb(sc->ec_io_ctl_addr + E33G_CNTRL, ECNTRL_SAPROM|ECNTRL_ONBOARD); for (i=0;iec_addr[i] = inb(sc->ec_io_nic_addr + i); /* Disable SA_PROM */ outb(sc->ec_io_ctl_addr + E33G_CNTRL, ECNTRL_ONBOARD); /* tcm, rsel, mbs0, nim */ outb(sc->ec_io_ctl_addr + E33G_GACFR, EGACFR_IRQOFF); /* * Stop the chip just in case. */ DELAY(1000); PAGE0 outb(sc->ec_io_nic_addr + EN_CCMD, ENC_NODMA|ENC_STOP); DELAY(1000); /* Check cmd reg and fail if not right */ if ((i=inb(sc->ec_io_nic_addr + EN_CCMD)) != (ENC_NODMA|ENC_STOP)) return(0); /* * Test the shared memory. */ for(i = 0 ; i < sc->ec_vmem_size ; ++i) sc->ec_vmem_addr[i] = 0x0; for(sum=0, i=0; iec_vmem_size; ++i) sum += sc->ec_vmem_addr[i]; if(sum) { printf("ec%d: shared memory error (device conflict?)\n", is->id_unit); return(0); } /* * All done. */ return(16); } ecattach(is) struct isa_device *is; { register struct ec_softc *sc = &ec_softc[is->id_unit]; register struct ifnet *ifp = &sc->ec_if; /** ** Initialize the ASIC in same order as Clarkson driver. **/ /* * Point vector pointer registers off into boonies. */ outb(sc->ec_io_ctl_addr + E33G_VP2, 0xff); outb(sc->ec_io_ctl_addr + E33G_VP1, 0xff); outb(sc->ec_io_ctl_addr + E33G_VP0, 0x0); /* * Set up control of shared memory, buffer ring, etc. */ outb(sc->ec_io_ctl_addr + E33G_STARTPG, EC_RXBUF_OFFSET); outb(sc->ec_io_ctl_addr + E33G_STOPPG, EC_RXBUF_END); /* * Set up the IRQ and NBURST on the board. * ( Not sure why we set up NBURST since we don't use DMA.) * * Normally we would is->id_irq<<2 but IRQ2 is defined as 0x200 * in icu.h so it's a special case. */ if(is->id_irq == 0x200) outb(sc->ec_io_ctl_addr + E33G_IDCFR, 0x10); else outb(sc->ec_io_ctl_addr + E33G_IDCFR, is->id_irq << 2); outb(sc->ec_io_ctl_addr + E33G_NBURST, 0x08); /* Set Burst to 8 */ outb(sc->ec_io_ctl_addr + E33G_DMAAH, 0x20); outb(sc->ec_io_ctl_addr + E33G_DMAAL, 0x0); /* * Fill in the ifnet structure. */ ifp->if_unit = is->id_unit; ifp->if_name = "ec" ; ifp->if_mtu = ETHERMTU; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS; ifp->if_init = ec_init; ifp->if_output = ether_output; ifp->if_start = ec_start_output; ifp->if_ioctl = ec_ioctl; ifp->if_reset = ec_reset; ifp->if_watchdog = ec_watchdog; /* * Attach the interface to something. Have to figure this out later. */ if_attach(ifp); /* * Weeee.. We get to tell people we exist... */ printf("ec%d: ethernet address %s\n", is->id_unit, ether_sprintf(sc->ec_addr)); } ec_init(unit) int unit; { register struct ec_softc *sc = &ec_softc[unit]; register struct ifnet *ifp = &sc->ec_if; int i, s; u_short ax, cx; Bdry=0; /* * Address not known. */ if(ifp->if_addrlist == (struct ifaddr *) 0) return; /* * select thick (e.g. AUI connector) if LLC0 bit is set */ outb(sc->ec_io_ctl_addr + E33G_CNTRL, (ifp->if_flags & IFF_LLC0) ? 0 : ECNTRL_ONBOARD); /* * Set up the 8390 chip. * (Use sequence recommended by 3Com. ) */ s=splhigh(); PAGE0 outb(sc->ec_io_nic_addr + EN_CCMD, ENC_NODMA|ENC_PAGE0|ENC_STOP); outb(sc->ec_io_nic_addr + EN0_DCFG, ENDCFG_BM8); outb(sc->ec_io_nic_addr + EN0_RCNTLO, 0x0); outb(sc->ec_io_nic_addr + EN0_RCNTHI, 0x0); outb(sc->ec_io_nic_addr + EN0_RXCR, ENRXCR_MON ); outb(sc->ec_io_nic_addr + EN0_TXCR, 0x02); outb(sc->ec_io_nic_addr + EN0_BOUNDARY, EC_RXBUF_OFFSET); outb(sc->ec_io_nic_addr + EN0_TPSR, 0x20); outb(sc->ec_io_nic_addr + EN0_STARTPG, EC_RXBUF_OFFSET); outb(sc->ec_io_nic_addr + EN0_STOPPG, EC_RXBUF_END); outb(sc->ec_io_nic_addr + EN0_ISR, 0xff); outb(sc->ec_io_nic_addr + EN0_IMR, 0x3f); /* * Copy Ethernet address from SA_PROM into 8390 chip registers. */ PAGE1 for(i=0;i<6;i++) outb(sc->ec_io_nic_addr + EN1_PHYS+i, sc->ec_addr[i]); /* * Set multicast filter mask bits in case promiscuous rcv wanted (???) * (set to 0xff as in if_we.c) */ for(i=0;i<8;i++) outb(sc->ec_io_nic_addr + EN1_MULT+i, 0xff); /* * Set current shared page for RX to work on. */ outb(sc->ec_io_nic_addr + EN1_CURPAG, EC_RXBUF_OFFSET); /* * Start the 8390. Clear Interrupt Status reg, and accept Broadcast * packets. */ outb(sc->ec_io_nic_addr + EN_CCMD, ENC_START|ENC_PAGE0|ENC_NODMA); outb(sc->ec_io_nic_addr + EN0_ISR, 0xff); outb(sc->ec_io_nic_addr + EN0_TXCR, 0x0); outb(sc->ec_io_nic_addr + EN0_RXCR, ENRXCR_BCST); /* * Take interface out of reset, program the vector, * enable interrupts, and tell the world we are up. */ ifp->if_flags |= IFF_RUNNING; outb(sc->ec_io_ctl_addr + E33G_GACFR, EGACFR_NORM); /* tcm, rsel, mbs0 */ (void) splx(s); sc->ec_flags &= ~EC_TXBUSY; ec_start_output(ifp); } ec_start_output(ifp) struct ifnet *ifp; { register struct ec_softc *sc = &ec_softc[ifp->if_unit]; struct mbuf *m0, *m; register caddr_t buffer; int len, s; int ec_cmd_reg; /* * The DS8390 only has one transmit buffer, if it is busy we * must wait until the transmit interrupt completes. */ s=splhigh(); if(sc->ec_flags & EC_TXBUSY) { (void) splx(s); return; } IF_DEQUEUE(&sc->ec_if.if_snd, m); if(m == 0) { (void) splx(s); return; } sc->ec_flags |= EC_TXBUSY; (void) splx(s); /* * Copy the mbuf chain into the transmit buffer */ buffer = sc->ec_vmem_addr; len = 0; for(m0 = m; m!= 0; m = m->m_next) { bcopy(mtod(m, caddr_t), buffer, m->m_len); buffer += m->m_len; len += m->m_len; } m_freem(m0); /* * Init transmit length registers and set transmit start flag. */ s=splhigh(); len = MAX(len, ETHER_MIN_LEN); PAGE0 outb(sc->ec_io_nic_addr + EN0_TCNTLO, len & 0xff); outb(sc->ec_io_nic_addr + EN0_TCNTHI, len >> 8); ec_cmd_reg = inb(sc->ec_io_nic_addr + EN_CCMD); outb(sc->ec_io_nic_addr + EN_CCMD, ec_cmd_reg|ENC_TRANS); (void) splx(s); } int ec_cmd_reg, ec_sts_reg; /* * Interrupt handler. */ ecintr(unit) int unit; { register struct ec_softc *sc = &ec_softc[unit]; unit = 0; /* * Get current command register and interrupt status. * Turn off interrupts while we take care of things. */ ec_cmd_reg = inb(sc->ec_io_nic_addr + EN_CCMD); PAGE0 ec_sts_reg = inb(sc->ec_io_nic_addr + EN0_ISR); outb(sc->ec_io_nic_addr + EN0_IMR, 0x0); loop: outb(sc->ec_io_nic_addr + EN0_ISR, ec_sts_reg); /* * have we lost ourselves somewhere? */ if (ec_sts_reg == 0xff) ec_watchdog(unit); /* * Transmit error */ if(ec_sts_reg & ENISR_TX_ERR) { sc->ec_if.if_collisions += inb(sc->ec_io_nic_addr + EN0_TCNTLO); ++sc->ec_if.if_oerrors; } /* * Receive Error */ if(ec_sts_reg & ENISR_RX_ERR) { (void) inb(sc->ec_io_nic_addr + 0xD); (void) inb(sc->ec_io_nic_addr + 0xE); (void) inb(sc->ec_io_nic_addr + 0xF); ++sc->ec_if.if_ierrors; } /* * Normal transmit complete. */ if(ec_sts_reg&ENISR_TX || ec_sts_reg&ENISR_TX_ERR) ectint(unit); /* * Normal receive notification */ if(ec_sts_reg&(ENISR_RX|ENISR_RX_ERR)) ecrint(unit); /* * Try to start transmit */ ec_start_output(&sc->ec_if); /* * Reenable onboard interrupts. */ /*PAGE0*/ outb(sc->ec_io_nic_addr + EN_CCMD, ec_cmd_reg); outb(sc->ec_io_nic_addr + EN0_IMR, 0x3f); if(ec_sts_reg=inb(sc->ec_io_nic_addr + EN0_ISR)) goto loop; } /* * Transmit interrupt */ ectint(unit) int unit; { register struct ec_softc *sc = &ec_softc[unit]; /* * Do some statistics. */ PAGE0 sc->ec_flags &= ~EC_TXBUSY; sc->ec_if.if_timer = 0; ++sc->ec_if.if_opackets; sc->ec_if.if_collisions += inb(sc->ec_io_nic_addr + EN0_TCNTLO); } /* * Receive interrupt. * (This gave me the most trouble so excuse the mess.) */ ecrint(unit) int unit; { register struct ec_softc *sc = &ec_softc[unit]; u_char bnry, curr; int len; struct ec_ring *ecr; /* * Traverse the receive ring looking for packets to pass back. * The search is complete when we find a descriptor not in use. */ PAGE0 bnry = inb(sc->ec_io_nic_addr + EN0_BOUNDARY); PAGE1 curr = inb(sc->ec_io_nic_addr + EN1_CURPAG); if(Bdry) bnry =Bdry; while (bnry != curr) { /* get pointer to this buffer header structure */ ecr = (struct ec_ring *)(sc->ec_vmem_addr + ((bnry-EC_VMEM_OFFSET) << 8)); /* count includes CRC */ len = ecr->ec_count - 4; /*if (len > 30 && len <= ETHERMTU+100) */ ecread(sc, (caddr_t)(ecr + 1), len); /*else printf("reject:%x bnry:%x curr:%x", len, bnry, curr);*/ outofbufs: PAGE0 /* advance on chip Boundry register */ if((caddr_t) ecr + EC_PAGE_SIZE - 1 > sc->ec_vmem_end) { bnry = EC_RXBUF_OFFSET; outb(sc->ec_io_nic_addr + EN0_BOUNDARY, (sc->ec_vmem_size / EC_PAGE_SIZE) - 1 - EC_VMEM_OFFSET); } else { if (len > 30 && len <= ETHERMTU+100) bnry = ecr->ec_next_packet; else bnry = curr; /* watch out for NIC overflow, reset Boundry if invalid */ if ((bnry - 1) < EC_RXBUF_OFFSET) { outb(sc->ec_io_nic_addr + EN0_BOUNDARY, (sc->ec_vmem_size / EC_PAGE_SIZE) - 1 - EC_VMEM_OFFSET); bnry = EC_RXBUF_OFFSET; } else outb(sc->ec_io_nic_addr + EN0_BOUNDARY, bnry-1); } /* refresh our copy of CURR */ PAGE1 curr = inb(sc->ec_io_nic_addr + EN1_CURPAG); } Bdry = bnry; PAGE0 } #define ecdataaddr(sc, eh, off, type) \ ((type) ((caddr_t)((eh)+1)+(off) >= (sc)->ec_vmem_end) ? \ (((caddr_t)((eh)+1)+(off))) - (sc)->ec_vmem_end \ + (sc)->ec_vmem_ring: \ ((caddr_t)((eh)+1)+(off))) ecread(sc, buf, len) register struct ec_softc *sc; char *buf; int len; { register struct ether_header *eh; struct mbuf *m, *ecget(); int off, resid; ++sc->ec_if.if_ipackets; /* * Deal with trailer protocol: if type is trailer type * get true type from first 16-bit word past data. * Remember that type was trailer by setting off. */ eh = (struct ether_header *)buf; eh->ether_type = ntohs((u_short)eh->ether_type); if (eh->ether_type >= ETHERTYPE_TRAIL && eh->ether_type < ETHERTYPE_TRAIL+ETHERTYPE_NTRAILER) { off = (eh->ether_type - ETHERTYPE_TRAIL) * 512; if (off >= ETHERMTU) return; /* sanity */ eh->ether_type = ntohs(*ecdataaddr(sc, eh, off, u_short *)); resid = ntohs(*(ecdataaddr(sc, eh, off+2, u_short *))); if (off + resid > len) return; /* sanity */ len = off + resid; } else off = 0; len -= sizeof(struct ether_header); if (len <= 0) return; /* * Pull packet off interface. Off is nonzero if packet * has trailing header; neget 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(buf, len, off, &sc->ec_if, sc); if (m == 0) return; ether_input(&sc->ec_if, eh, m); } ec_ioctl(ifp, cmd, data) register struct ifnet *ifp; int cmd; caddr_t data; { register struct ifaddr *ifa = (struct ifaddr *)data; struct ec_softc *sc = &ec_softc[ifp->if_unit]; struct ifreq *ifr = (struct ifreq *)data; int s=splimp(), error=0; switch(cmd){ case SIOCSIFADDR: ifp->if_flags |= IFF_UP; switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: ec_init(ifp->if_unit); /* before arpwhohas */ ((struct arpcom *)ifp)->ac_ipaddr = IA_SIN(ifa)->sin_addr; arpwhohas((struct arpcom *)ifp, &IA_SIN(ifa)->sin_addr); break; #endif #ifdef NS case AF_NS: { register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); if (ns_nullhost(*ina)) ina->x_host = *(union ns_host *)(sc->ns_addrp); else { /* * The manual says we can't change the address * while the receiver is armed, * so reset everything */ ifp->if_flags &= ~IFF_RUNNING; bcopy((caddr_t)ina->x_host.c_host, (caddr_t)sc->ns_addrp, sizeof(sc->ns_addrp)); } ec_init(ifp->if_unit); /* does ne_setaddr() */ break; } #endif default: ec_init(ifp->if_unit); break; } break; case SIOCSIFFLAGS: if ((ifp->if_flags & IFF_UP) == 0 && ifp->if_flags & IFF_RUNNING) { ifp->if_flags &= ~IFF_RUNNING; ec_stop(ifp->if_unit); } else if (ifp->if_flags & IFF_UP && (ifp->if_flags & IFF_RUNNING) == 0) ec_init(ifp->if_unit); break; #ifdef notdef case SIOCGHWADDR: bcopy((caddr_t)sc->sc_addr, (caddr_t) &ifr->ifr_data, sizeof(sc->sc_addr)); break; #endif default: error = EINVAL; } splx(s); return (error); } ec_reset(unit) int unit; { if(unit >= NEC) return; printf("ec%d: reset\n", unit); ec_init(unit); } ec_watchdog(unit) int unit; { log(LOG_WARNING, "ec%d: soft reset\n", unit); ec_stop(unit); ec_init(unit); } ec_stop(unit) int unit; { register struct ec_softc *sc = &ec_softc[unit]; int s; s=splimp(); PAGE0 outb(sc->ec_io_nic_addr + EN_CCMD, ENC_NODMA|ENC_STOP); outb(sc->ec_io_nic_addr + EN0_IMR, 0x0); sc->ec_flags &= ~EC_RUNNING; /* * Shutdown the 8390. */ (void) splx(s); } /* * Pull read data off a interface. * Len is length of data, with local net header stripped. * Off is non-zero if a trailer protocol was used, and * gives the offset of the trailer information. * We copy the trailer information and then all the normal * data into mbufs. When full cluster sized units are present * we copy into clusters. */ struct mbuf * ecget(buf, totlen, off0, ifp, sc) caddr_t buf; int totlen, off0; struct ifnet *ifp; struct ec_softc *sc; { struct mbuf *top, **mp, *m, *p; int off = off0, len; register caddr_t cp = buf; char *epkt; int tc =totlen; buf += sizeof(struct ether_header); cp = buf; epkt = cp + totlen; if (off) { cp += off + 2 * sizeof(u_short); totlen -= 2 * sizeof(u_short); } MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == 0) return (0); m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = totlen; m->m_len = MHLEN; top = 0; mp = ⊤ while (totlen > 0) { if (top) { MGET(m, M_DONTWAIT, MT_DATA); if (m == 0) { m_freem(top); return (0); } m->m_len = MLEN; } len = min(totlen, epkt - cp); if (len >= MINCLSIZE) { MCLGET(m, M_DONTWAIT); if (m->m_flags & M_EXT) m->m_len = len = min(len, MCLBYTES); else len = m->m_len; } else { /* * Place initial small packet/header at end of mbuf. */ if (len < m->m_len) { if (top == 0 && len + max_linkhdr <= m->m_len) m->m_data += max_linkhdr; m->m_len = len; } else len = m->m_len; } totlen -= len; /* only do up to end of buffer */ if (cp+len > sc->ec_vmem_end) { unsigned toend = sc->ec_vmem_end - cp; bcopy(cp, mtod(m, caddr_t), toend); cp = sc->ec_vmem_ring; bcopy(cp, mtod(m, caddr_t)+toend, len - toend); cp += len - toend; epkt = cp + totlen; } else { bcopy(cp, mtod(m, caddr_t), (unsigned)len); cp += len; } *mp = m; mp = &m->m_next; if (cp == epkt) { cp = buf; epkt = cp + tc; } } return (top); }