diff options
author | Theo de Raadt <deraadt@cvs.openbsd.org> | 1995-10-18 10:44:53 +0000 |
---|---|---|
committer | Theo de Raadt <deraadt@cvs.openbsd.org> | 1995-10-18 10:44:53 +0000 |
commit | 49235ceee0c25492d4ca35194d7c72838a9ec284 (patch) | |
tree | 14f77677934d4f4cb9baa428691ace792fd0deed /sys/arch/mvme68k/dev/if_ie.c | |
parent | c9328c850e70436131e06a34f73c14cc230c18f9 (diff) |
mvme68k port by me. some parts by dale rahn.
Diffstat (limited to 'sys/arch/mvme68k/dev/if_ie.c')
-rw-r--r-- | sys/arch/mvme68k/dev/if_ie.c | 1958 |
1 files changed, 1958 insertions, 0 deletions
diff --git a/sys/arch/mvme68k/dev/if_ie.c b/sys/arch/mvme68k/dev/if_ie.c new file mode 100644 index 00000000000..e24a121d0e9 --- /dev/null +++ b/sys/arch/mvme68k/dev/if_ie.c @@ -0,0 +1,1958 @@ +/* $NetBSD: if_ie.c,v 1.15 1995/04/11 09:18:09 pk Exp $ */ + +/*- + * Copyright (c) 1995 Theo de Raadt + * Copyright (c) 1993, 1994, 1995 Charles Hannum. + * Copyright (c) 1992, 1993, University of Vermont and State + * Agricultural College. + * Copyright (c) 1992, 1993, Garrett A. Wollman. + * + * Portions: + * Copyright (c) 1994, 1995, Rafal K. Boni + * Copyright (c) 1990, 1991, William F. Jolitz + * Copyright (c) 1990, The Regents of the University of California + * + * All rights reserved. + * + * 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 Charles Hannum, by the + * University of Vermont and State Agricultural College and Garrett A. + * Wollman, by William F. Jolitz, and by the University of California, + * Berkeley, Lawrence Berkeley Laboratory, and its contributors. + * 4. Neither the names of the Universities nor the names of the authors + * 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 UNIVERSITY OR AUTHORS 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. + */ + +/* + * Intel 82586 Ethernet chip + * Register, bit, and structure definitions. + * + * Original StarLAN driver written by Garrett Wollman with reference to the + * Clarkson Packet Driver code for this chip written by Russ Nelson and others. + * + * BPF support code taken from hpdev/if_le.c, supplied with tcpdump. + * + * 3C507 support is loosely based on code donated to NetBSD by Rafal Boni. + * + * Majorly cleaned up and 3C507 code merged by Charles Hannum. + * + * Converted to SUN ie driver by Charles D. Cranor, + * October 1994, January 1995. + * This sun version based on i386 version 1.30. + */ + +extern void *etherbuf; +extern int etherlen; + +/* +Mode of operation: + + We run the 82586 in a standard Ethernet mode. We keep NFRAMES + received frame descriptors around for the receiver to use, and + NRXBUF associated receive buffer descriptors, both in a circular + list. Whenever a frame is received, we rotate both lists as + necessary. (The 586 treats both lists as a simple queue.) We also + keep a transmit command around so that packets can be sent off + quickly. + + We configure the adapter in AL-LOC = 1 mode, which means that the + Ethernet/802.3 MAC header is placed at the beginning of the receive + buffer rather than being split off into various fields in the RFD. + This also means that we must include this header in the transmit + buffer as well. + + By convention, all transmit commands, and only transmit commands, + shall have the I (IE_CMD_INTR) bit set in the command. This way, + when an interrupt arrives at ieintr(), it is immediately possible + to tell what precisely caused it. ANY OTHER command-sending + routines should run at splimp(), and should post an acknowledgement + to every interrupt they generate. + +*/ + +#include "bpfilter.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/buf.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/errno.h> +#include <sys/syslog.h> +#include <sys/device.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/if_dl.h> +#include <net/netisr.h> +#include <net/route.h> + +#if NBPFILTER > 0 +#include <net/bpf.h> +#include <net/bpfdesc.h> +#endif + +#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 <vm/vm.h> + +/* + * ugly byte-order hack for SUNs + */ + +#define SWAP(x) (x) + +#include <machine/autoconf.h> +#include <machine/cpu.h> +#include <machine/pmap.h> + +#include "mc.h" +#include "pcctwo.h" + +#if NMC > 0 +#include <mvme68k/dev/mcreg.h> +#endif +#if NPCCTWO > 0 +#include <mvme68k/dev/pcctworeg.h> +#endif + +#include <mvme68k/dev/if_ie.h> +#include <mvme68k/dev/i82586.h> + +static struct mbuf *last_not_for_us; +vm_map_t ie_map; /* for obio */ + +#define IED_RINT 0x01 +#define IED_TINT 0x02 +#define IED_RNR 0x04 +#define IED_CNA 0x08 +#define IED_READFRAME 0x10 +#define IED_ALL 0x1f + +#define ETHER_MIN_LEN 64 +#define ETHER_MAX_LEN 1518 +#define ETHER_ADDR_LEN 6 + +#define B_PER_F 3 /* recv buffers per frame */ +#define MXFRAMES 300 /* max number of recv frames */ +#define MXRXBUF (MXFRAMES*B_PER_F) /* number of buffers to allocate */ +#define IE_RBUF_SIZE 256 /* size of each receive buffer; + MUST BE POWER OF TWO */ +#define NTXBUF 2 /* number of transmit commands */ +#define IE_TBUF_SIZE ETHER_MAX_LEN /* length of transmit buffer */ + + +/* + * Ethernet status, per interface. + * + * hardware addresses/sizes to know (all KVA): + * sc_iobase = base of chip's 24 bit address space + * sc_maddr = base address of chip RAM as stored in ie_base of iscp + * sc_msize = size of chip's RAM + * sc_reg = address of card dependent registers + * + * the chip uses two types of pointers: 16 bit and 24 bit + * 16 bit pointers are offsets from sc_maddr/ie_base + * KVA(16 bit offset) = offset + sc_maddr + * 24 bit pointers are offset from sc_iobase in KVA + * KVA(24 bit address) = address + sc_iobase + * + * on the vme/multibus we have the page map to control where ram appears + * in the address space. we choose to have RAM start at 0 in the + * 24 bit address space. this means that sc_iobase == sc_maddr! + * to get the phyiscal address of the board's RAM you must take the + * top 12 bits of the physical address of the register address + * and or in the 4 bits from the status word as bits 17-20 (remember that + * the board ignores the chip's top 4 address lines). + * For example: + * if the register is @ 0xffe88000, then the top 12 bits are 0xffe00000. + * to get the 4 bits from the the status word just do status & IEVME_HADDR. + * suppose the value is "4". Then just shift it left 16 bits to get + * it into bits 17-20 (e.g. 0x40000). Then or it to get the + * address of RAM (in our example: 0xffe40000). see the attach routine! + * + * on the onboard ie interface the 24 bit address space is hardwired + * to be 0xff000000 -> 0xffffffff of KVA. this means that sc_iobase + * will be 0xff000000. sc_maddr will be where ever we allocate RAM + * in KVA. note that since the SCP is at a fixed address it means + * that we have to allocate a fixed KVA for the SCP. + */ + +struct ie_softc { + struct device sc_dev; /* device structure */ + struct intrhand sc_ih, sc_failih; /* interrupt info */ + struct evcnt sc_intrcnt; /* # of interrupts, per ie */ + + caddr_t sc_iobase; /* KVA of base of 24 bit addr space */ + caddr_t sc_maddr; /* KVA of base of chip's RAM (16bit addr sp.)*/ + u_int sc_msize; /* how much RAM we have/use */ + caddr_t sc_reg; /* KVA of car's register */ + int sc_bustype; + + struct arpcom sc_arpcom;/* system arpcom structure */ + + void (*reset_586)(); /* card dependent reset function */ + void (*chan_attn)(); /* card dependent attn function */ + void (*run_586)(); /* card depenent "go on-line" function */ + void (*memcopy) __P((const void *, void *, u_int)); + /* card dependent memory copy function */ + void (*memzero) __P((void *, u_int)); + /* card dependent memory zero function */ + + + int want_mcsetup; /* mcsetup flag */ + int promisc; /* are we in promisc mode? */ + + /* + * pointers to the 3 major control structures + */ + + volatile struct ie_sys_conf_ptr *scp; + volatile struct ie_int_sys_conf_ptr *iscp; + volatile struct ie_sys_ctl_block *scb; + + /* + * pointer and size of a block of KVA where the buffers + * are to be allocated from + */ + + caddr_t buf_area; + int buf_area_sz; + + /* + * the actual buffers (recv and xmit) + */ + + volatile struct ie_recv_frame_desc *rframes[MXFRAMES]; + volatile struct ie_recv_buf_desc *rbuffs[MXRXBUF]; + volatile char *cbuffs[MXRXBUF]; + int rfhead, rftail, rbhead, rbtail; + + volatile struct ie_xmit_cmd *xmit_cmds[NTXBUF]; + volatile struct ie_xmit_buf *xmit_buffs[NTXBUF]; + u_char *xmit_cbuffs[NTXBUF]; + int xmit_busy; + int xmit_free; + int xchead, xctail; + + struct ie_en_addr mcast_addrs[MAXMCAST + 1]; + int mcast_count; + + int nframes; /* number of frames in use */ + int nrxbuf; /* number of recv buffs in use */ + +#ifdef IEDEBUG + int sc_debug; +#endif +#if NMC > 0 + struct mcreg *sc_mc; +#endif +#if NPCCTWO > 0 + struct pcctworeg *sc_pcc2; +#endif +}; + +static void ie_obreset __P((struct ie_softc *)); +static void ie_obattend __P((struct ie_softc *)); +static void ie_obrun __P((struct ie_softc *)); + +void iewatchdog __P((/* short */)); +int ieintr __P((void *)); +int iefailintr __P((void *)); +int ieinit __P((struct ie_softc *)); +int ieioctl __P((struct ifnet *, u_long, caddr_t)); +void iestart __P((struct ifnet *)); +void iereset __P((struct ie_softc *)); +static void ie_readframe __P((struct ie_softc *, int)); +static void ie_drop_packet_buffer __P((struct ie_softc *)); +static int command_and_wait __P((struct ie_softc *, int, + void volatile *, int)); +/*static*/ void ierint __P((struct ie_softc *)); +/*static*/ void ietint __P((struct ie_softc *)); +static int ieget __P((struct ie_softc *, struct mbuf **, + struct ether_header *, int *)); +static void setup_bufs __P((struct ie_softc *)); +static int mc_setup __P((struct ie_softc *, void *)); +static void mc_reset __P((struct ie_softc *)); + +#ifdef IEDEBUG +void print_rbd __P((volatile struct ie_recv_buf_desc *)); + +int in_ierint = 0; +int in_ietint = 0; +#endif + +int iematch(); +void ieattach(); + +struct cfdriver iecd = { + NULL, "ie", iematch, ieattach, DV_IFNET, sizeof(struct ie_softc) +}; + +/* + * address generation macros + * MK_24 = KVA -> 24 bit address in SUN byte order + * MK_16 = KVA -> 16 bit address in INTEL byte order + * ST_24 = store a 24 bit address in SUN byte order to INTEL byte order + */ +#define MK_24(base, ptr) ((caddr_t)((u_long)ptr)) +#define MK_16(base, ptr) SWAP((u_short)( ((u_long)(ptr)) - ((u_long)(base)) )) +#define ST_24(to, from) { \ + u_long fval = (u_long)(from); \ + u_char *t = (u_char *)&(to), *f = (u_char *)&fval; \ + t[0] = f[2]; t[1] = f[3]; /*t[2] = f[0]*/; t[3] = f[1]; \ + } +/* + * Here are a few useful functions. We could have done these as macros, but + * since we have the inline facility, it makes sense to use that instead. + */ +static inline void +ie_setup_config(cmd, promiscuous, manchester) + volatile struct ie_config_cmd *cmd; + int promiscuous, manchester; +{ + + cmd->ie_config_count = 0x0c; + cmd->ie_fifo = 8; + cmd->ie_save_bad = 0x40; + cmd->ie_addr_len = 0x2e; + cmd->ie_priority = 0; + cmd->ie_ifs = 0x60; + cmd->ie_slot_low = 0; + cmd->ie_slot_high = 0xf2; + cmd->ie_promisc = !!promiscuous | manchester << 2; + cmd->ie_crs_cdt = 0; + cmd->ie_min_len = 64; + cmd->ie_junk = 0xff; +} + +static inline void +ie_ack(sc, mask) + struct ie_softc *sc; + u_int mask; +{ + volatile struct ie_sys_ctl_block *scb = sc->scb; + + command_and_wait(sc, scb->ie_status & mask, 0, 0); +} + +int +iematch(parent, vcf, args) + struct device *parent; + void *vcf, *args; +{ + struct cfdata *cf = vcf; + struct confargs *ca = args; + + return (!badvaddr(ca->ca_vaddr, 4)); +} + +/* + * Deep Magic: reset it, then set SCP address again. Pray. + */ +void +ie_obreset(sc) + struct ie_softc *sc; +{ + volatile struct ieob *ieo = (struct ieob *) sc->sc_reg; + volatile int t; + u_long a; + + a = IE_PORT_RESET; + ieo->porthigh = a & 0xffff; + t = 0; t = 1; + ieo->portlow = a >> 16; + delay(1000); + + a = (u_long)pmap_extract(pmap_kernel(), (vm_offset_t)sc->scp) | + IE_PORT_NEWSCPADDR; + ieo->porthigh = a & 0xffff; + t = 0; t = 1; + ieo->portlow = a >> 16; + delay(1000); +} + +void +ie_obattend(sc) + struct ie_softc *sc; +{ + volatile struct ieob *ieo = (struct ieob *) sc->sc_reg; + + ieo->attn = 1; +} + +void +ie_obrun(sc) + struct ie_softc *sc; +{ +} + +/* + * Taken almost exactly from Bill's if_is.c, then modified beyond recognition. + */ +void +ieattach(parent, self, aux) + struct device *parent, *self; + void *aux; +{ + struct ie_softc *sc = (void *) self; + struct confargs *ca = aux; + struct ifnet *ifp = &sc->sc_arpcom.ac_if; + extern void myetheraddr(u_char *); /* should be elsewhere */ + register struct bootpath *bp; + int pri = ca->ca_ipl; + volatile struct ieob *ieo; + vm_offset_t pa; + + sc->reset_586 = ie_obreset; + sc->chan_attn = ie_obattend; + sc->run_586 = ie_obrun; + sc->memcopy = bcopy; + sc->memzero = bzero; + sc->sc_msize = etherlen; + sc->sc_reg = ca->ca_vaddr; + ieo = (volatile struct ieob *) sc->sc_reg; + + sc->sc_maddr = etherbuf; /* maddr = vaddr */ + pa = pmap_extract(pmap_kernel(), (vm_offset_t)sc->sc_maddr); + if (pa == 0) panic("ie pmap_extract"); + sc->sc_iobase = (caddr_t)pa; /* iobase = paddr (24 bit) */ + + /*printf("maddrP %x iobaseV %x\n", sc->sc_maddr, sc->sc_iobase);*/ + + (sc->memzero)(sc->sc_maddr, sc->sc_msize); + sc->iscp = (volatile struct ie_int_sys_conf_ptr *) + sc->sc_maddr; /* @ location zero */ + sc->scb = (volatile struct ie_sys_ctl_block *) + roundup((int)sc->iscp + sizeof(struct ie_int_sys_conf_ptr), 16); + sc->scp = (struct ie_sys_conf_ptr *) + roundup((int)sc->scb + sizeof(struct ie_sys_ctl_block), 16); + /*printf("scpV %x iscpV %x scbV %x\n", sc->scp, sc->iscp, sc->scb);*/ + + sc->scp->ie_bus_use = 0; /* 16-bit */ + ST_24(sc->scp->ie_iscp_ptr, + pmap_extract(pmap_kernel(), (vm_offset_t)sc->iscp)); + + /*printf("iscpV(%x) = iscpP(%x) -> scp.ptr@%x = val:%x\n", + sc->iscp, pmap_extract(pmap_kernel(), (vm_offset_t)sc->iscp), + &sc->scp->ie_iscp_ptr, sc->scp->ie_iscp_ptr);*/ + + /* + * rest of first page is unused (wasted!), rest of ram + * for buffers + */ + sc->buf_area = sc->sc_maddr + NBPG; + sc->buf_area_sz = sc->sc_msize - NBPG; + myetheraddr(sc->sc_arpcom.ac_enaddr); + + if (ie_setupram(sc) == 0) { + printf(": RAM CONFIG FAILED!\n"); + /* XXX should reclaim resources? */ + return; + } + ifp->if_unit = sc->sc_dev.dv_unit; + ifp->if_name = iecd.cd_name; + ifp->if_start = iestart; + ifp->if_ioctl = ieioctl; + ifp->if_watchdog = iewatchdog; + ifp->if_flags = + IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS | IFF_MULTICAST; + + /* Attach the interface. */ + if_attach(ifp); + ether_ifattach(ifp); + + printf(": address %s\n", ether_sprintf(sc->sc_arpcom.ac_enaddr)); + +#if NBPFILTER > 0 + bpfattach(&ifp->if_bpf, ifp, DLT_EN10MB, sizeof(struct ether_header)); +#endif + + sc->sc_bustype = ca->ca_bustype; + + sc->sc_ih.ih_fn = ieintr; + sc->sc_ih.ih_arg = sc; + sc->sc_ih.ih_ipl = pri; + sc->sc_failih.ih_fn = iefailintr; + sc->sc_failih.ih_arg = sc; + sc->sc_failih.ih_ipl = pri; + + switch (sc->sc_bustype) { +#if NMC > 0 + case BUS_MC: + mcintr_establish(MCV_IE, &sc->sc_ih); + sc->sc_mc = (struct mcreg *)ca->ca_master; + sc->sc_mc->mc_ieirq = pri | MC_SC_SNOOP | MC_IRQ_IEN | + MC_IRQ_ICLR; + mcintr_establish(MCV_IEFAIL, &sc->sc_failih); + sc->sc_mc->mc_iefailirq = pri | MC_IRQ_IEN | MC_IRQ_ICLR; + break; +#endif +#if NPCCTWO > 0 + case BUS_PCCTWO: + pcctwointr_establish(PCC2V_IE, &sc->sc_ih); + sc->sc_pcc2 = (struct pcctworeg *)ca->ca_master; + sc->sc_pcc2->pcc2_ieirq = pri | PCC2_SC_SNOOP | + PCC2_IRQ_IEN | PCC2_IRQ_ICLR; + pcctwointr_establish(PCC2V_IEFAIL, &sc->sc_failih); + sc->sc_pcc2->pcc2_iefailirq = pri | PCC2_IRQ_IEN | + PCC2_IRQ_ICLR; + break; +#endif + } + + evcnt_attach(&sc->sc_dev, "intr", &sc->sc_intrcnt); +} + +/* + * Device timeout/watchdog routine. Entered if the device neglects to generate + * an interrupt after a transmit has been started on it. + */ +void +iewatchdog(unit) + short unit; +{ + struct ie_softc *sc = iecd.cd_devs[unit]; + + log(LOG_ERR, "%s: device timeout\n", sc->sc_dev.dv_xname); + ++sc->sc_arpcom.ac_if.if_oerrors; + + iereset(sc); +} + +int +iefailintr(v) +void *v; +{ + struct ie_softc *sc = v; + + switch (sc->sc_bustype) { +#if NMC > 0 + case BUS_MC: + sc->sc_mc->mc_ieirq |= MC_IRQ_ICLR; /* safe: clear irq */ + sc->sc_mc->mc_iefailirq |= MC_IRQ_ICLR; /* clear failure */ + sc->sc_mc->mc_ieerr = MC_IEERR_SCLR; /* reset error */ + break; +#endif +#if NPCCTWO > 0 + case BUS_PCCTWO: + sc->sc_pcc2->pcc2_ieirq |= PCC2_IRQ_ICLR; /* safe: clear irq */ + sc->sc_pcc2->pcc2_iefailirq |= PCC2_IRQ_ICLR; /* clear failure */ + sc->sc_pcc2->pcc2_ieerr = PCC2_IEERR_SCLR; /* reset error */ + break; +#endif + } + + iereset(sc); + return (1); +} + +/* + * What to do upon receipt of an interrupt. + */ +int +ieintr(v) +void *v; +{ + struct ie_softc *sc = v; + register u_short status; + + status = sc->scb->ie_status; +/*printf("I");*/ + +loop: + /* Ack interrupts FIRST in case we receive more during the ISR. */ + ie_ack(sc, IE_ST_WHENCE & status); + switch (sc->sc_bustype) { +#if NMC > 0 + case BUS_MC: + sc->sc_mc->mc_ieirq |= MC_IRQ_ICLR; /* clear irq */ + break; +#endif +#if NPCCTWO > 0 + case BUS_PCCTWO: + sc->sc_pcc2->pcc2_ieirq |= PCC2_IRQ_ICLR; /* clear irq */ + break; +#endif + } + + if (status & (IE_ST_RECV | IE_ST_RNR)) { +#ifdef IEDEBUG + in_ierint++; + if (sc->sc_debug & IED_RINT) + printf("%s: rint\n", sc->sc_dev.dv_xname); +#endif + ierint(sc); +#ifdef IEDEBUG + in_ierint--; +#endif + } + + if (status & IE_ST_DONE) { +#ifdef IEDEBUG + in_ietint++; + if (sc->sc_debug & IED_TINT) + printf("%s: tint\n", sc->sc_dev.dv_xname); +#endif + ietint(sc); +#ifdef IEDEBUG + in_ietint--; +#endif + } + + if (status & IE_ST_RNR) { + printf("%s: receiver not ready\n", sc->sc_dev.dv_xname); + sc->sc_arpcom.ac_if.if_ierrors++; + iereset(sc); + } + +#ifdef IEDEBUG + if ((status & IE_ST_ALLDONE) && (sc->sc_debug & IED_CNA)) + printf("%s: cna\n", sc->sc_dev.dv_xname); +#endif + + if ((status = sc->scb->ie_status) & IE_ST_WHENCE) + goto loop; + + sc->sc_intrcnt.ev_count++; + return 1; +} + +/* + * Process a received-frame interrupt. + */ +void +ierint(sc) + struct ie_softc *sc; +{ + volatile struct ie_sys_ctl_block *scb = sc->scb; + int i, status; + static int timesthru = 1024; + + i = sc->rfhead; + for (;;) { + status = sc->rframes[i]->ie_fd_status; + + if ((status & IE_FD_COMPLETE) && (status & IE_FD_OK)) { + sc->sc_arpcom.ac_if.if_ipackets++; + if (!--timesthru) { + sc->sc_arpcom.ac_if.if_ierrors += + SWAP(scb->ie_err_crc) + + SWAP(scb->ie_err_align) + + SWAP(scb->ie_err_resource) + + SWAP(scb->ie_err_overrun); + scb->ie_err_crc = scb->ie_err_align = + scb->ie_err_resource = scb->ie_err_overrun = + 0; + timesthru = 1024; + } + ie_readframe(sc, i); + } else { + if ((status & IE_FD_RNR) != 0 && + (scb->ie_status & IE_RU_READY) == 0) { + sc->rframes[0]->ie_fd_buf_desc = + MK_16(sc->sc_maddr, sc->rbuffs[0]); + scb->ie_recv_list = + MK_16(sc->sc_maddr, sc->rframes[0]); + command_and_wait(sc, IE_RU_START, 0, 0); + } + break; + } + i = (i + 1) % sc->nframes; + } +} + +/* + * Process a command-complete interrupt. These are only generated by the + * transmission of frames. This routine is deceptively simple, since most of + * the real work is done by iestart(). + */ +void +ietint(sc) + struct ie_softc *sc; +{ + int status; + + sc->sc_arpcom.ac_if.if_timer = 0; + sc->sc_arpcom.ac_if.if_flags &= ~IFF_OACTIVE; + + status = sc->xmit_cmds[sc->xctail]->ie_xmit_status; + + if (!(status & IE_STAT_COMPL) || (status & IE_STAT_BUSY)) + printf("ietint: command still busy!\n"); + + if (status & IE_STAT_OK) { + sc->sc_arpcom.ac_if.if_opackets++; + sc->sc_arpcom.ac_if.if_collisions += + SWAP(status & IE_XS_MAXCOLL); + } else if (status & IE_STAT_ABORT) { + printf("%s: send aborted\n", sc->sc_dev.dv_xname); + sc->sc_arpcom.ac_if.if_oerrors++; + } else if (status & IE_XS_NOCARRIER) { + printf("%s: no carrier\n", sc->sc_dev.dv_xname); + sc->sc_arpcom.ac_if.if_oerrors++; + } else if (status & IE_XS_LOSTCTS) { + printf("%s: lost CTS\n", sc->sc_dev.dv_xname); + sc->sc_arpcom.ac_if.if_oerrors++; + } else if (status & IE_XS_UNDERRUN) { + printf("%s: DMA underrun\n", sc->sc_dev.dv_xname); + sc->sc_arpcom.ac_if.if_oerrors++; + } else if (status & IE_XS_EXCMAX) { + printf("%s: too many collisions\n", sc->sc_dev.dv_xname); + sc->sc_arpcom.ac_if.if_collisions += 16; + sc->sc_arpcom.ac_if.if_oerrors++; + } + + /* + * If multicast addresses were added or deleted while transmitting, + * mc_reset() set the want_mcsetup flag indicating that we should do + * it. + */ + if (sc->want_mcsetup) { + mc_setup(sc, (caddr_t)sc->xmit_cbuffs[sc->xctail]); + sc->want_mcsetup = 0; + } + + /* Done with the buffer. */ + sc->xmit_free++; + sc->xmit_busy = 0; + sc->xctail = (sc->xctail + 1) % NTXBUF; + + iestart(&sc->sc_arpcom.ac_if); +} + +/* + * Compare two Ether/802 addresses for equality, inlined and unrolled for + * speed. I'd love to have an inline assembler version of this... + */ +static inline int +ether_equal(one, two) + u_char *one, *two; +{ + + if (one[0] != two[0] || one[1] != two[1] || one[2] != two[2] || + one[3] != two[3] || one[4] != two[4] || one[5] != two[5]) + return 0; + return 1; +} + +/* + * Check for a valid address. to_bpf is filled in with one of the following: + * 0 -> BPF doesn't get this packet + * 1 -> BPF does get this packet + * 2 -> BPF does get this packet, but we don't + * Return value is true if the packet is for us, and false otherwise. + * + * This routine is a mess, but it's also critical that it be as fast + * as possible. It could be made cleaner if we can assume that the + * only client which will fiddle with IFF_PROMISC is BPF. This is + * probably a good assumption, but we do not make it here. (Yet.) + */ +static inline int +check_eh(sc, eh, to_bpf) + struct ie_softc *sc; + struct ether_header *eh; + int *to_bpf; +{ + int i; + + switch(sc->promisc) { + case IFF_ALLMULTI: + /* + * Receiving all multicasts, but no unicasts except those + * destined for us. + */ +#if NBPFILTER > 0 + *to_bpf = (sc->sc_arpcom.ac_if.if_bpf != 0); /* BPF gets this packet if anybody cares */ +#endif + if (eh->ether_dhost[0] & 1) + return 1; + if (ether_equal(eh->ether_dhost, sc->sc_arpcom.ac_enaddr)) return 1; + return 0; + + case IFF_PROMISC: + /* + * Receiving all packets. These need to be passed on to BPF. + */ +#if NBPFILTER > 0 + *to_bpf = (sc->sc_arpcom.ac_if.if_bpf != 0); +#endif + /* If for us, accept and hand up to BPF */ + if (ether_equal(eh->ether_dhost, sc->sc_arpcom.ac_enaddr)) return 1; + +#if NBPFILTER > 0 + if (*to_bpf) + *to_bpf = 2; /* we don't need to see it */ +#endif + + /* + * Not a multicast, so BPF wants to see it but we don't. + */ + if (!(eh->ether_dhost[0] & 1)) + return 1; + + /* + * If it's one of our multicast groups, accept it and pass it + * up. + */ + for (i = 0; i < sc->mcast_count; i++) { + if (ether_equal(eh->ether_dhost, (u_char *)&sc->mcast_addrs[i])) { +#if NBPFILTER > 0 + if (*to_bpf) + *to_bpf = 1; +#endif + return 1; + } + } + return 1; + + case IFF_ALLMULTI | IFF_PROMISC: + /* + * Acting as a multicast router, and BPF running at the same + * time. Whew! (Hope this is a fast machine...) + */ +#if NBPFILTER > 0 + *to_bpf = (sc->sc_arpcom.ac_if.if_bpf != 0); +#endif + /* We want to see multicasts. */ + if (eh->ether_dhost[0] & 1) + return 1; + + /* We want to see our own packets */ + if (ether_equal(eh->ether_dhost, sc->sc_arpcom.ac_enaddr)) + return 1; + + /* Anything else goes to BPF but nothing else. */ +#if NBPFILTER > 0 + if (*to_bpf) + *to_bpf = 2; +#endif + return 1; + + default: + /* + * Only accept unicast packets destined for us, or multicasts + * for groups that we belong to. For now, we assume that the + * '586 will only return packets that we asked it for. This + * isn't strictly true (it uses hashing for the multicast + * filter), but it will do in this case, and we want to get out + * of here as quickly as possible. + */ +#if NBPFILTER > 0 + *to_bpf = (sc->sc_arpcom.ac_if.if_bpf != 0); +#endif + return 1; + } + return 0; +} + +/* + * We want to isolate the bits that have meaning... This assumes that + * IE_RBUF_SIZE is an even power of two. If somehow the act_len exceeds + * the size of the buffer, then we are screwed anyway. + */ +static inline int +ie_buflen(sc, head) + struct ie_softc *sc; + int head; +{ + + return (SWAP(sc->rbuffs[head]->ie_rbd_actual) + & (IE_RBUF_SIZE | (IE_RBUF_SIZE - 1))); +} + +static inline int +ie_packet_len(sc) + struct ie_softc *sc; +{ + int i; + int head = sc->rbhead; + int acc = 0; + + do { + if (!(sc->rbuffs[sc->rbhead]->ie_rbd_actual & IE_RBD_USED)) { +#ifdef IEDEBUG + print_rbd(sc->rbuffs[sc->rbhead]); +#endif + log(LOG_ERR, "%s: receive descriptors out of sync at %d\n", + sc->sc_dev.dv_xname, sc->rbhead); + iereset(sc); + return -1; + } + + i = sc->rbuffs[head]->ie_rbd_actual & IE_RBD_LAST; + + acc += ie_buflen(sc, head); + head = (head + 1) % sc->nrxbuf; + } while (!i); + + return acc; +} + +/* + * Setup all necessary artifacts for an XMIT command, and then pass the XMIT + * command to the chip to be executed. On the way, if we have a BPF listener + * also give him a copy. + */ +inline static void +iexmit(sc) + struct ie_softc *sc; +{ + +#if NBPFILTER > 0 + /* + * If BPF is listening on this interface, let it see the packet before + * we push it on the wire. + */ + if (sc->sc_arpcom.ac_if.if_bpf) + bpf_tap(sc->sc_arpcom.ac_if.if_bpf, + sc->xmit_cbuffs[sc->xctail], + SWAP(sc->xmit_buffs[sc->xctail]->ie_xmit_flags)); +#endif + +/*printf("iexmit base %x cmd %x bfd %x to %x\n", +sc->sc_maddr, +sc->xmit_cmds[sc->xctail], +sc->xmit_buffs[sc->xctail], +sc->xmit_cbuffs[sc->xctail]);*/ + sc->xmit_buffs[sc->xctail]->ie_xmit_flags |= IE_XMIT_LAST; + sc->xmit_buffs[sc->xctail]->ie_xmit_next = SWAP(0xffff); + ST_24(sc->xmit_buffs[sc->xctail]->ie_xmit_buf, + MK_24(sc->sc_iobase, sc->xmit_cbuffs[sc->xctail])); + + sc->xmit_cmds[sc->xctail]->com.ie_cmd_link = SWAP(0xffff); + sc->xmit_cmds[sc->xctail]->com.ie_cmd_cmd = + IE_CMD_XMIT | IE_CMD_INTR | IE_CMD_LAST; + + sc->xmit_cmds[sc->xctail]->ie_xmit_status = SWAP(0); + sc->xmit_cmds[sc->xctail]->ie_xmit_desc = + MK_16(sc->sc_maddr, sc->xmit_buffs[sc->xctail]); + + sc->scb->ie_command_list = + MK_16(sc->sc_maddr, sc->xmit_cmds[sc->xctail]); + command_and_wait(sc, IE_CU_START, 0, 0); + + sc->xmit_busy = 1; + sc->sc_arpcom.ac_if.if_timer = 5; +} + +/* + * Read data off the interface, and turn it into an mbuf chain. + * + * This code is DRAMATICALLY different from the previous version; this + * version tries to allocate the entire mbuf chain up front, given the + * length of the data available. This enables us to allocate mbuf + * clusters in many situations where before we would have had a long + * chain of partially-full mbufs. This should help to speed up the + * operation considerably. (Provided that it works, of course.) + */ +static inline int +ieget(sc, mp, ehp, to_bpf) + struct ie_softc *sc; + struct mbuf **mp; + struct ether_header *ehp; + int *to_bpf; +{ + struct mbuf *m, *top, **mymp; + int i; + int offset; + int totlen, resid; + int thismboff; + int head; + + totlen = ie_packet_len(sc); + if (totlen <= 0) + return -1; + + i = sc->rbhead; + + /* + * Snarf the Ethernet header. + */ + (sc->memcopy)((caddr_t)sc->cbuffs[i], (caddr_t)ehp, sizeof *ehp); + + /* + * As quickly as possible, check if this packet is for us. + * If not, don't waste a single cycle copying the rest of the + * packet in. + * This is only a consideration when FILTER is defined; i.e., when + * we are either running BPF or doing multicasting. + */ + if (!check_eh(sc, ehp, to_bpf)) { + ie_drop_packet_buffer(sc); + sc->sc_arpcom.ac_if.if_ierrors--; /* just this case, it's not an error */ + return -1; + } + totlen -= (offset = sizeof *ehp); + + MGETHDR(*mp, M_DONTWAIT, MT_DATA); + if (!*mp) { + ie_drop_packet_buffer(sc); + return -1; + } + + m = *mp; + m->m_pkthdr.rcvif = &sc->sc_arpcom.ac_if; + m->m_len = MHLEN; + resid = m->m_pkthdr.len = totlen; + top = 0; + mymp = ⊤ + + /* + * This loop goes through and allocates mbufs for all the data we will + * be copying in. It does not actually do the copying yet. + */ + do { /* while (resid > 0) */ + /* + * Try to allocate an mbuf to hold the data that we have. If + * we already allocated one, just get another one and stick it + * on the end (eventually). If we don't already have one, try + * to allocate an mbuf cluster big enough to hold the whole + * packet, if we think it's reasonable, or a single mbuf which + * may or may not be big enough. + * Got that? + */ + if (top) { + MGET(m, M_DONTWAIT, MT_DATA); + if (!m) { + m_freem(top); + ie_drop_packet_buffer(sc); + return -1; + } + m->m_len = MLEN; + } + + if (resid >= MINCLSIZE) { + MCLGET(m, M_DONTWAIT); + if (m->m_flags & M_EXT) + m->m_len = min(resid, MCLBYTES); + } else { + if (resid < m->m_len) { + if (!top && resid + max_linkhdr <= m->m_len) + m->m_data += max_linkhdr; + m->m_len = resid; + } + } + resid -= m->m_len; + *mymp = m; + mymp = &m->m_next; + } while (resid > 0); + + resid = totlen; + m = top; + thismboff = 0; + head = sc->rbhead; + + /* + * Now we take the mbuf chain (hopefully only one mbuf most of the + * time) and stuff the data into it. There are no possible failures at + * or after this point. + */ + while (resid > 0) { /* while there's stuff left */ + int thislen = ie_buflen(sc, head) - offset; + + /* + * If too much data for the current mbuf, then fill the current + * one up, go to the next one, and try again. + */ + if (thislen > m->m_len - thismboff) { + int newlen = m->m_len - thismboff; + (sc->memcopy)((caddr_t)(sc->cbuffs[head] + offset), + mtod(m, caddr_t) + thismboff, (u_int)newlen); + m = m->m_next; + thismboff = 0; /* new mbuf, so no offset */ + offset += newlen; /* we are now this far + into the packet */ + resid -= newlen; /* so there is this much + left to get */ + continue; + } + + /* + * If there is more than enough space in the mbuf to hold the + * contents of this buffer, copy everything in, advance + * pointers and so on. + */ + if (thislen < m->m_len - thismboff) { + (sc->memcopy)((caddr_t)(sc->cbuffs[head] + offset), + mtod(m, caddr_t) + thismboff, (u_int)thislen); + thismboff += thislen; /* we are this far into the mbuf */ + resid -= thislen; /* and this much is left */ + goto nextbuf; + } + + /* + * Otherwise, there is exactly enough space to put this + * buffer's contents into the current mbuf. Do the combination + * of the above actions. + */ + (sc->memcopy)((caddr_t)(sc->cbuffs[head] + offset), + mtod(m, caddr_t) + thismboff, (u_int)thislen); + m = m->m_next; + thismboff = 0; /* new mbuf, start at the beginning */ + resid -= thislen; /* and we are this far through */ + + /* + * Advance all the pointers. We can get here from either of + * the last two cases, but never the first. + */ + nextbuf: + offset = 0; + sc->rbuffs[head]->ie_rbd_actual = SWAP(0); + sc->rbuffs[head]->ie_rbd_length |= IE_RBD_LAST; + sc->rbhead = head = (head + 1) % sc->nrxbuf; + sc->rbuffs[sc->rbtail]->ie_rbd_length &= ~IE_RBD_LAST; + sc->rbtail = (sc->rbtail + 1) % sc->nrxbuf; + } + + /* + * Unless something changed strangely while we were doing the copy, we + * have now copied everything in from the shared memory. + * This means that we are done. + */ + return 0; +} + +/* + * Read frame NUM from unit UNIT (pre-cached as IE). + * + * This routine reads the RFD at NUM, and copies in the buffers from the list + * of RBD, then rotates the RBD and RFD lists so that the receiver doesn't + * start complaining. Trailers are DROPPED---there's no point in wasting time + * on confusing code to deal with them. Hopefully, this machine will never ARP + * for trailers anyway. + */ +static void +ie_readframe(sc, num) + struct ie_softc *sc; + int num; /* frame number to read */ +{ + int status; + struct mbuf *m = 0; + struct ether_header eh; +#if NBPFILTER > 0 + int bpf_gets_it = 0; +#endif + + status = sc->rframes[num]->ie_fd_status; + + /* Immediately advance the RFD list, since we have copied ours now. */ + sc->rframes[num]->ie_fd_status = SWAP(0); + sc->rframes[num]->ie_fd_last |= IE_FD_LAST; + sc->rframes[sc->rftail]->ie_fd_last &= ~IE_FD_LAST; + sc->rftail = (sc->rftail + 1) % sc->nframes; + sc->rfhead = (sc->rfhead + 1) % sc->nframes; + + if (status & IE_FD_OK) { +#if NBPFILTER > 0 + if (ieget(sc, &m, &eh, &bpf_gets_it)) { +#else + if (ieget(sc, &m, &eh, 0)) { +#endif + sc->sc_arpcom.ac_if.if_ierrors++; + return; + } + } + +#ifdef IEDEBUG + if (sc->sc_debug & IED_READFRAME) + printf("%s: frame from ether %s type %x\n", sc->sc_dev.dv_xname, + ether_sprintf(eh.ether_shost), (u_int)eh.ether_type); +#endif + + if (!m) + return; + + if (last_not_for_us) { + m_freem(last_not_for_us); + last_not_for_us = 0; + } + +#if NBPFILTER > 0 + /* + * Check for a BPF filter; if so, hand it up. + * Note that we have to stick an extra mbuf up front, because bpf_mtap + * expects to have the ether header at the front. + * It doesn't matter that this results in an ill-formatted mbuf chain, + * since BPF just looks at the data. (It doesn't try to free the mbuf, + * tho' it will make a copy for tcpdump.) + */ + if (bpf_gets_it) { + struct mbuf m0; + m0.m_len = sizeof eh; + m0.m_data = (caddr_t)&eh; + m0.m_next = m; + + /* Pass it up. */ + bpf_mtap(sc->sc_arpcom.ac_if.if_bpf, &m0); + } + /* + * A signal passed up from the filtering code indicating that the + * packet is intended for BPF but not for the protocol machinery. + * We can save a few cycles by not handing it off to them. + */ + if (bpf_gets_it == 2) { + last_not_for_us = m; + return; + } +#endif /* NBPFILTER > 0 */ + + /* + * In here there used to be code to check destination addresses upon + * receipt of a packet. We have deleted that code, and replaced it + * with code to check the address much earlier in the cycle, before + * copying the data in; this saves us valuable cycles when operating + * as a multicast router or when using BPF. + */ + + /* + * Finally pass this packet up to higher layers. + */ + ether_input(&sc->sc_arpcom.ac_if, &eh, m); +} + +static void +ie_drop_packet_buffer(sc) + struct ie_softc *sc; +{ + int i; + + do { + /* + * This means we are somehow out of sync. So, we reset the + * adapter. + */ + if (!(sc->rbuffs[sc->rbhead]->ie_rbd_actual & IE_RBD_USED)) { +#ifdef IEDEBUG + print_rbd(sc->rbuffs[sc->rbhead]); +#endif + log(LOG_ERR, "%s: receive descriptors out of sync at %d\n", + sc->sc_dev.dv_xname, sc->rbhead); + iereset(sc); + return; + } + + i = sc->rbuffs[sc->rbhead]->ie_rbd_actual & IE_RBD_LAST; + + sc->rbuffs[sc->rbhead]->ie_rbd_length |= IE_RBD_LAST; + sc->rbuffs[sc->rbhead]->ie_rbd_actual = SWAP(0); + sc->rbhead = (sc->rbhead + 1) % sc->nrxbuf; + sc->rbuffs[sc->rbtail]->ie_rbd_length &= ~IE_RBD_LAST; + sc->rbtail = (sc->rbtail + 1) % sc->nrxbuf; + } while (!i); +} + + +/* + * Start transmission on an interface. + */ +void +iestart(ifp) + struct ifnet *ifp; +{ + struct ie_softc *sc = iecd.cd_devs[ifp->if_unit]; + struct mbuf *m0, *m; + u_char *buffer; + u_short len; + +/*printf("iestart\n");*/ + if ((ifp->if_flags & IFF_RUNNING) == 0) + return; + + if (sc->xmit_free == 0) { + ifp->if_flags |= IFF_OACTIVE; + if (!sc->xmit_busy) + iexmit(sc); + return; + } + + do { + IF_DEQUEUE(&sc->sc_arpcom.ac_if.if_snd, m); + if (!m) + break; + + len = 0; + buffer = sc->xmit_cbuffs[sc->xchead]; + + for (m0 = m; m && (len +m->m_len) < IE_TBUF_SIZE; + m = m->m_next) { + bcopy(mtod(m, caddr_t), buffer, m->m_len); + buffer += m->m_len; + len += m->m_len; + } + if (m) + printf("%s: tbuf overflow\n", sc->sc_dev.dv_xname); + + m_freem(m0); + len = max(len, ETHER_MIN_LEN); + sc->xmit_buffs[sc->xchead]->ie_xmit_flags = SWAP(len); + + sc->xmit_free--; + sc->xchead = (sc->xchead + 1) % NTXBUF; + } while (sc->xmit_free > 0); + + /* If we stuffed any packets into the card's memory, send now. */ + if ((sc->xmit_free < NTXBUF) && (!sc->xmit_busy)) + iexmit(sc); + + return; +} + +/* + * set up IE's ram space + */ +int +ie_setupram(sc) + struct ie_softc *sc; +{ + volatile struct ie_int_sys_conf_ptr *iscp; + volatile struct ie_sys_ctl_block *scb; + int s; + + s = splimp(); + + iscp = sc->iscp; + (sc->memzero)((char *) iscp, sizeof *iscp); + + scb = sc->scb; + (sc->memzero)((char *) scb, sizeof *scb); + + iscp->ie_busy = 1; /* ie_busy == char */ + iscp->ie_scb_offset = MK_16(sc->sc_maddr, scb); + ST_24(iscp->ie_base, sc->sc_iobase); + + (sc->reset_586) (sc); + (sc->chan_attn) (sc); + + delay(100); /* wait a while... */ + + if (iscp->ie_busy) { + splx(s); + return 0; + } + /* + * Acknowledge any interrupts we may have caused... + */ + ie_ack(sc, IE_ST_WHENCE); + splx(s); + + return 1; +} + +void +iereset(sc) + struct ie_softc *sc; +{ + int s = splimp(); + + printf("%s: reset\n", sc->sc_dev.dv_xname); + + /* Clear OACTIVE in case we're called from watchdog (frozen xmit). */ + sc->sc_arpcom.ac_if.if_flags &= ~(IFF_UP | IFF_OACTIVE); + ieioctl(&sc->sc_arpcom.ac_if, SIOCSIFFLAGS, 0); + + /* + * Stop i82586 dead in its tracks. + */ + if (command_and_wait(sc, IE_RU_ABORT | IE_CU_ABORT, 0, 0)) + printf("%s: abort commands timed out\n", sc->sc_dev.dv_xname); + + if (command_and_wait(sc, IE_RU_DISABLE | IE_CU_STOP, 0, 0)) + printf("%s: disable commands timed out\n", sc->sc_dev.dv_xname); + +#ifdef notdef + if (!check_ie_present(sc, sc->sc_maddr, sc->sc_msize)) + panic("ie disappeared!\n"); +#endif + + sc->sc_arpcom.ac_if.if_flags |= IFF_UP; + ieioctl(&sc->sc_arpcom.ac_if, SIOCSIFFLAGS, 0); + + splx(s); +} + +/* + * This is called if we time out. + */ +static void +chan_attn_timeout(rock) + caddr_t rock; +{ + + *(int *)rock = 1; +} + +/* + * Send a command to the controller and wait for it to either complete + * or be accepted, depending on the command. If the command pointer + * is null, then pretend that the command is not an action command. + * If the command pointer is not null, and the command is an action + * command, wait for + * ((volatile struct ie_cmd_common *)pcmd)->ie_cmd_status & MASK + * to become true. + */ +static int +command_and_wait(sc, cmd, pcmd, mask) + struct ie_softc *sc; + int cmd; + volatile void *pcmd; + int mask; +{ + volatile struct ie_cmd_common *cc = pcmd; + volatile struct ie_sys_ctl_block *scb = sc->scb; + volatile int timedout = 0; + extern int hz; + + scb->ie_command = (u_short)cmd; + + if (IE_ACTION_COMMAND(cmd) && pcmd) { + (sc->chan_attn)(sc); + +#if 0 + /* + * XXX + * I don't think this timeout works on suns. + * we are at splimp() in the loop, and the timeout + * stuff runs at software spl (so it is masked off?). + */ + + /* + * According to the packet driver, the minimum timeout should + * be .369 seconds, which we round up to .4. + */ + timeout(chan_attn_timeout, (caddr_t)&timedout, 2 * hz / 5); +#endif + + /* + * Now spin-lock waiting for status. This is not a very nice + * thing to do, but I haven't figured out how, or indeed if, we + * can put the process waiting for action to sleep. (We may + * be getting called through some other timeout running in the + * kernel.) + */ + for (;;) + if ((cc->ie_cmd_status & mask) || timedout) + break; +#if 0 + untimeout(chan_attn_timeout, (caddr_t)&timedout); +#endif + + return timedout; + } else { + /* + * Otherwise, just wait for the command to be accepted. + */ + (sc->chan_attn)(sc); + + while (scb->ie_command) + ; /* XXX spin lock */ + + return 0; + } +} + +/* + * Run the time-domain reflectometer. + */ +static void +run_tdr(sc, cmd) + struct ie_softc *sc; + struct ie_tdr_cmd *cmd; +{ + int result; + + cmd->com.ie_cmd_status = SWAP(0); + cmd->com.ie_cmd_cmd = IE_CMD_TDR | IE_CMD_LAST; + cmd->com.ie_cmd_link = SWAP(0xffff); + + sc->scb->ie_command_list = MK_16(sc->sc_maddr, cmd); + cmd->ie_tdr_time = SWAP(0); + + if (command_and_wait(sc, IE_CU_START, cmd, IE_STAT_COMPL) || + !(cmd->com.ie_cmd_status & IE_STAT_OK)) + result = 0x10000; /* XXX */ + else + result = cmd->ie_tdr_time; + + ie_ack(sc, IE_ST_WHENCE); + + if (result & IE_TDR_SUCCESS) + return; + + if (result & 0x10000) + printf("%s: TDR command failed\n", sc->sc_dev.dv_xname); + else if (result & IE_TDR_XCVR) + printf("%s: transceiver problem\n", sc->sc_dev.dv_xname); + else if (result & IE_TDR_OPEN) + printf("%s: TDR detected an open %d clocks away\n", + sc->sc_dev.dv_xname, result & IE_TDR_TIME); + else if (result & IE_TDR_SHORT) + printf("%s: TDR detected a short %d clocks away\n", + sc->sc_dev.dv_xname, result & IE_TDR_TIME); + else + printf("%s: TDR returned unknown status %x\n", + sc->sc_dev.dv_xname, result); +} + +#ifdef notdef +/* ALIGN works on 8 byte boundaries.... but 4 byte boundaries are ok for sun */ +#define _ALLOC(p, n) (bzero(p, n), p += n, p - n) +#define ALLOC(p, n) _ALLOC(p, ALIGN(n)) /* XXX convert to this? */ +#endif + +static inline caddr_t +Align(ptr) + caddr_t ptr; +{ + u_long l = (u_long)ptr; + + l = (l + 3) & ~3L; + return (caddr_t)l; +} + +/* + * setup_bufs: set up the buffers + * + * we have a block of KVA at sc->buf_area which is of size sc->buf_area_sz. + * this is to be used for the buffers. the chip indexs its control data + * structures with 16 bit offsets, and it indexes actual buffers with + * 24 bit addresses. so we should allocate control buffers first so that + * we don't overflow the 16 bit offset field. The number of transmit + * buffers is fixed at compile time. + * + * note: this function was written to be easy to understand, rather than + * highly efficient (it isn't in the critical path). + */ +static void +setup_bufs(sc) + struct ie_softc *sc; +{ + caddr_t ptr = sc->buf_area; /* memory pool */ + volatile struct ie_recv_frame_desc *rfd = (void *) ptr; + volatile struct ie_recv_buf_desc *rbd; + int n, r; + + /* + * step 0: zero memory and figure out how many recv buffers and + * frames we can have. XXX CURRENTLY HARDWIRED AT MAX + */ + (sc->memzero)(ptr, sc->buf_area_sz); + ptr = Align(ptr); /* set alignment and stick with it */ + + n = (int)Align(sizeof(struct ie_xmit_cmd)) + + (int)Align(sizeof(struct ie_xmit_buf)) + IE_TBUF_SIZE; + n *= NTXBUF; /* n = total size of xmit area */ + + n = sc->buf_area_sz - n;/* n = free space for recv stuff */ + + r = (int)Align(sizeof(struct ie_recv_frame_desc)) + + (((int)Align(sizeof(struct ie_recv_buf_desc)) + IE_RBUF_SIZE) * B_PER_F); + + /* r = size of one R frame */ + + sc->nframes = n / r; + if (sc->nframes <= 0) + panic("ie: bogus buffer calc\n"); + if (sc->nframes > MXFRAMES) + sc->nframes = MXFRAMES; + + sc->nrxbuf = sc->nframes * B_PER_F; + +#ifdef IEDEBUG + printf("IEDEBUG: %d frames %d bufs\n", sc->nframes, sc->nrxbuf); +#endif + + /* + * step 1a: lay out and zero frame data structures for transmit and recv + */ + for (n = 0; n < NTXBUF; n++) { + sc->xmit_cmds[n] = (volatile struct ie_xmit_cmd *) ptr; + ptr = Align(ptr + sizeof(struct ie_xmit_cmd)); + } + + for (n = 0; n < sc->nframes; n++) { + sc->rframes[n] = (volatile struct ie_recv_frame_desc *) ptr; + ptr = Align(ptr + sizeof(struct ie_recv_frame_desc)); + } + + /* + * step 1b: link together the recv frames and set EOL on last one + */ + for (n = 0; n < sc->nframes; n++) { + sc->rframes[n]->ie_fd_next = + MK_16(sc->sc_maddr, sc->rframes[(n + 1) % sc->nframes]); + } + sc->rframes[sc->nframes - 1]->ie_fd_last |= IE_FD_LAST; + + /* + * step 2a: lay out and zero frame buffer structures for xmit and recv + */ + for (n = 0; n < NTXBUF; n++) { + sc->xmit_buffs[n] = (volatile struct ie_xmit_buf *) ptr; + ptr = Align(ptr + sizeof(struct ie_xmit_buf)); + } + + for (n = 0; n < sc->nrxbuf; n++) { + sc->rbuffs[n] = (volatile struct ie_recv_buf_desc *) ptr; + ptr = Align(ptr + sizeof(struct ie_recv_buf_desc)); + } + + /* + * step 2b: link together recv bufs and set EOL on last one + */ + for (n = 0; n < sc->nrxbuf; n++) { + sc->rbuffs[n]->ie_rbd_next = + MK_16(sc->sc_maddr, sc->rbuffs[(n + 1) % sc->nrxbuf]); + } + sc->rbuffs[sc->nrxbuf - 1]->ie_rbd_length |= IE_RBD_LAST; + + /* + * step 3: allocate the actual data buffers for xmit and recv + * recv buffer gets linked into recv_buf_desc list here + */ + for (n = 0; n < NTXBUF; n++) { + sc->xmit_cbuffs[n] = (u_char *) ptr; + ptr = Align(ptr + IE_TBUF_SIZE); + } + + /* Pointers to last packet sent and next available transmit buffer. */ + sc->xchead = sc->xctail = 0; + + /* Clear transmit-busy flag and set number of free transmit buffers. */ + sc->xmit_busy = 0; + sc->xmit_free = NTXBUF; + + for (n = 0; n < sc->nrxbuf; n++) { + sc->cbuffs[n] = (char *) ptr; /* XXX why char vs uchar? */ + sc->rbuffs[n]->ie_rbd_length = SWAP(IE_RBUF_SIZE); + ST_24(sc->rbuffs[n]->ie_rbd_buffer, MK_24(sc->sc_iobase, ptr)); + ptr = Align(ptr + IE_RBUF_SIZE); + } + + /* + * step 4: set the head and tail pointers on receive to keep track of + * the order in which RFDs and RBDs are used. link in recv frames + * and buffer into the scb. + */ + + sc->rfhead = 0; + sc->rftail = sc->nframes - 1; + sc->rbhead = 0; + sc->rbtail = sc->nrxbuf - 1; + + sc->scb->ie_recv_list = MK_16(sc->sc_maddr, sc->rframes[0]); + sc->rframes[0]->ie_fd_buf_desc = MK_16(sc->sc_maddr, sc->rbuffs[0]); + +#ifdef IEDEBUG + printf("IE_DEBUG: reserved %d bytes\n", ptr - sc->buf_area); +#endif +} + +/* + * Run the multicast setup command. + * Called at splimp(). + */ +static int +mc_setup(sc, ptr) + struct ie_softc *sc; + void *ptr; +{ + volatile struct ie_mcast_cmd *cmd = ptr; + + cmd->com.ie_cmd_status = SWAP(0); + cmd->com.ie_cmd_cmd = IE_CMD_MCAST | IE_CMD_LAST; + cmd->com.ie_cmd_link = SWAP(0xffff); + + (sc->memcopy)((caddr_t)sc->mcast_addrs, (caddr_t)cmd->ie_mcast_addrs, + sc->mcast_count * sizeof *sc->mcast_addrs); + + cmd->ie_mcast_bytes = + SWAP(sc->mcast_count * ETHER_ADDR_LEN); /* grrr... */ + + sc->scb->ie_command_list = MK_16(sc->sc_maddr, cmd); + if (command_and_wait(sc, IE_CU_START, cmd, IE_STAT_COMPL) || + !(cmd->com.ie_cmd_status & IE_STAT_OK)) { + printf("%s: multicast address setup command failed\n", + sc->sc_dev.dv_xname); + return 0; + } + return 1; +} + +/* + * This routine takes the environment generated by check_ie_present() and adds + * to it all the other structures we need to operate the adapter. This + * includes executing the CONFIGURE, IA-SETUP, and MC-SETUP commands, starting + * the receiver unit, and clearing interrupts. + * + * THIS ROUTINE MUST BE CALLED AT splimp() OR HIGHER. + */ +int +ieinit(sc) + struct ie_softc *sc; +{ + volatile struct ie_sys_ctl_block *scb = sc->scb; + void *ptr; + int n; + + ptr = sc->buf_area; + + /* + * Send the configure command first. + */ + { + volatile struct ie_config_cmd *cmd = ptr; + + scb->ie_command_list = MK_16(sc->sc_maddr, cmd); + cmd->com.ie_cmd_status = SWAP(0); + cmd->com.ie_cmd_cmd = IE_CMD_CONFIG | IE_CMD_LAST; + cmd->com.ie_cmd_link = SWAP(0xffff); + + ie_setup_config(cmd, sc->promisc, 0); + + if (command_and_wait(sc, IE_CU_START, cmd, IE_STAT_COMPL) || + !(cmd->com.ie_cmd_status & IE_STAT_OK)) { + printf("%s: configure command failed\n", + sc->sc_dev.dv_xname); + return 0; + } + } + + /* + * Now send the Individual Address Setup command. + */ + { + volatile struct ie_iasetup_cmd *cmd = ptr; + + scb->ie_command_list = MK_16(sc->sc_maddr, cmd); + cmd->com.ie_cmd_status = SWAP(0); + cmd->com.ie_cmd_cmd = IE_CMD_IASETUP | IE_CMD_LAST; + cmd->com.ie_cmd_link = SWAP(0xffff); + + (sc->memcopy)(sc->sc_arpcom.ac_enaddr, + (caddr_t)&cmd->ie_address, sizeof cmd->ie_address); + + if (command_and_wait(sc, IE_CU_START, cmd, IE_STAT_COMPL) || + !(cmd->com.ie_cmd_status & IE_STAT_OK)) { + printf("%s: individual address setup command failed\n", + sc->sc_dev.dv_xname); + return 0; + } + } + + /* + * Now run the time-domain reflectometer. + */ + run_tdr(sc, ptr); + + /* + * Acknowledge any interrupts we have generated thus far. + */ + ie_ack(sc, IE_ST_WHENCE); + + /* + * Set up the transmit and recv buffers. + */ + setup_bufs(sc); + + sc->sc_arpcom.ac_if.if_flags |= IFF_RUNNING; /* tell higher levels that we are here */ + + sc->scb->ie_recv_list = MK_16(sc->sc_maddr, sc->rframes[0]); + command_and_wait(sc, IE_RU_START, 0, 0); + + ie_ack(sc, IE_ST_WHENCE); + + if (sc->run_586) + (sc->run_586)(sc); + + return 0; +} + +static void +iestop(sc) + struct ie_softc *sc; +{ + + command_and_wait(sc, IE_RU_DISABLE, 0, 0); +} + +int +ieioctl(ifp, cmd, data) + register struct ifnet *ifp; + u_long cmd; + caddr_t data; +{ + struct ie_softc *sc = iecd.cd_devs[ifp->if_unit]; + struct ifaddr *ifa = (struct ifaddr *)data; + struct ifreq *ifr = (struct ifreq *)data; + int s, error = 0; + + s = splimp(); + + switch(cmd) { + + case SIOCSIFADDR: + ifp->if_flags |= IFF_UP; + + switch(ifa->ifa_addr->sa_family) { +#ifdef INET + case AF_INET: + ieinit(sc); + arp_ifinit(&sc->sc_arpcom, ifa); + break; +#endif +#ifdef NS + /* XXX - This code is probably wrong. */ + case AF_NS: + { + struct ns_addr *ina = &IA_SNS(ifa)->sns_addr; + + if (ns_nullhost(*ina)) + ina->x_host = + *(union ns_host *)(sc->sc_arpcom.ac_enaddr); + else + bcopy(ina->x_host.c_host, + sc->sc_arpcom.ac_enaddr, + sizeof(sc->sc_arpcom.ac_enaddr)); + /* Set new address. */ + ieinit(sc); + break; + } +#endif /* NS */ + default: + ieinit(sc); + break; + } + break; + + case SIOCSIFFLAGS: + sc->promisc = ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI); + if ((ifp->if_flags & IFF_UP) == 0 && + (ifp->if_flags & IFF_RUNNING) != 0) { + /* + * If interface is marked down and it is running, then + * stop it. + */ + iestop(sc); + ifp->if_flags &= ~IFF_RUNNING; + } else if ((ifp->if_flags & IFF_UP) != 0 && + (ifp->if_flags & IFF_RUNNING) == 0) { + /* + * If interface is marked up and it is stopped, then + * start it. + */ + ieinit(sc); + } else { + /* + * Reset the interface to pick up changes in any other + * flags that affect hardware registers. + */ + iestop(sc); + ieinit(sc); + } +#ifdef IEDEBUG + if (ifp->if_flags & IFF_DEBUG) + sc->sc_debug = IED_ALL; + else + sc->sc_debug = 0; +#endif + break; + + case SIOCADDMULTI: + case SIOCDELMULTI: + error = (cmd == SIOCADDMULTI) ? + ether_addmulti(ifr, &sc->sc_arpcom): + ether_delmulti(ifr, &sc->sc_arpcom); + + if (error == ENETRESET) { + /* + * Multicast list has changed; set the hardware filter + * accordingly. + */ + mc_reset(sc); + error = 0; + } + break; + + default: + error = EINVAL; + } + splx(s); + return error; +} + +static void +mc_reset(sc) + struct ie_softc *sc; +{ + struct ether_multi *enm; + struct ether_multistep step; + + /* + * Step through the list of addresses. + */ + sc->mcast_count = 0; + ETHER_FIRST_MULTI(step, &sc->sc_arpcom, enm); + while (enm) { + if (sc->mcast_count >= MAXMCAST || + bcmp(enm->enm_addrlo, enm->enm_addrhi, 6) != 0) { + sc->sc_arpcom.ac_if.if_flags |= IFF_ALLMULTI; + ieioctl(&sc->sc_arpcom.ac_if, SIOCSIFFLAGS, (void *)0); + goto setflag; + } + + bcopy(enm->enm_addrlo, &sc->mcast_addrs[sc->mcast_count], 6); + sc->mcast_count++; + ETHER_NEXT_MULTI(step, enm); + } +setflag: + sc->want_mcsetup = 1; +} + +#ifdef IEDEBUG +void +print_rbd(rbd) + volatile struct ie_recv_buf_desc *rbd; +{ + + printf("RBD at %08lx:\nactual %04x, next %04x, buffer %08x\n" + "length %04x, mbz %04x\n", (u_long)rbd, rbd->ie_rbd_actual, + rbd->ie_rbd_next, rbd->ie_rbd_buffer, rbd->ie_rbd_length, + rbd->mbz); +} +#endif |