/*- * Copyright (c) 1995 Matt Thomas (thomas@lkg.dec.com) * 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. The name of the author may not be used to endorse or promote products * derived from this software withough specific prior written permission * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. * * $FreeBSD$ * */ /* * DEC PDQ FDDI Controller; code for BSD derived operating systems * * Written by Matt Thomas * * This driver supports the following FDDI controllers: * * Device: Config file entry: * DEC DEFPA (PCI) device fpa0 * DEC DEFEA (EISA) device fea0 at isa0 net irq ? vector feaintr * * Eventually, the following adapters will also be supported: * * DEC DEFTA (TC) device fta0 at tc? slot * vector ftaintr * DEC DEFQA (Q-Bus) device fta0 at uba? csr 0?? vector fqaintr * DEC DEFAA (FB+) device faa0 at fbus? slot * vector faaintr */ #include "fea.h" /* DEFPA EISA FDDI */ #ifndef __bsdi__ #include "fpa.h" /* DEFPA PCI FDDI */ #endif #if NFPA > 0 || NFEA > 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bpfilter.h" #if NBPFILTER > 0 #include #include #endif #ifdef INET #include #include #include #include #include #endif #include #ifdef NS #include #include #endif #include #include #if NFPA > 0 #include #include #endif #if NFEA > 0 #include #ifdef __FreeBSD__ #include #endif #ifdef __bsdi__ #include #include #include #endif #endif #include #include typedef struct { #ifdef __bsdi__ struct device sc_dev; /* base device */ struct isadev sc_id; /* ISA device */ struct intrhand sc_ih; /* intrrupt vectoring */ struct atshutdown sc_ats; /* shutdown routine */ #endif struct arpcom sc_ac; void (*if_init) __P((int unit)); pdq_t *sc_pdq; #if NFEA > 0 unsigned sc_iobase; #endif } pdq_softc_t; #define sc_if sc_ac.ac_if #if defined(__FreeBSD__) #define sc_bpf sc_if.if_bpf typedef void ifnet_ret_t; #elif defined(__bsdi__) #define sc_bpf sc_if.if_bpf typedef int ifnet_ret_t; #endif static void pdq_ifreset( pdq_softc_t *sc) { pdq_stop(sc->sc_pdq); } static void pdq_ifinit( pdq_softc_t *sc) { if (sc->sc_if.if_flags & IFF_UP) { sc->sc_if.if_flags |= IFF_RUNNING; if (sc->sc_if.if_flags & IFF_PROMISC) { sc->sc_pdq->pdq_flags |= PDQ_PROMISC; } else { sc->sc_pdq->pdq_flags &= ~PDQ_PROMISC; } if (sc->sc_if.if_flags & IFF_ALLMULTI) { sc->sc_pdq->pdq_flags |= PDQ_ALLMULTI; } else { sc->sc_pdq->pdq_flags &= ~PDQ_ALLMULTI; } if (sc->sc_if.if_flags & IFF_LINK1) { sc->sc_pdq->pdq_flags |= PDQ_PASS_SMT; } else { sc->sc_pdq->pdq_flags &= ~PDQ_PASS_SMT; } sc->sc_pdq->pdq_flags |= PDQ_RUNNING; pdq_run(sc->sc_pdq); } else { sc->sc_if.if_flags &= ~IFF_RUNNING; sc->sc_pdq->pdq_flags &= ~PDQ_RUNNING; pdq_stop(sc->sc_pdq); } } static void pdq_ifwatchdog( pdq_softc_t *sc) { struct mbuf *m; /* * No progress was made on the transmit queue for PDQ_OS_TX_TRANSMIT * seconds. Remove all queued packets. */ sc->sc_if.if_flags &= ~IFF_OACTIVE; sc->sc_if.if_timer = 0; for (;;) { IF_DEQUEUE(&sc->sc_if.if_snd, m); if (m == NULL) return; m_freem(m); } } static ifnet_ret_t pdq_ifstart( struct ifnet *ifp) { pdq_softc_t *sc = (pdq_softc_t *) ((caddr_t) ifp - offsetof(pdq_softc_t, sc_ac.ac_if)); struct ifqueue *ifq = &ifp->if_snd; struct mbuf *m; int tx = 0; if ((ifp->if_flags & IFF_RUNNING) == 0) return; if (sc->sc_if.if_timer == 0) sc->sc_if.if_timer = PDQ_OS_TX_TIMEOUT; if ((sc->sc_pdq->pdq_flags & PDQ_TXOK) == 0) { sc->sc_if.if_flags |= IFF_OACTIVE; return; } for (;; tx = 1) { IF_DEQUEUE(ifq, m); if (m == NULL) break; if (pdq_queue_transmit_data(sc->sc_pdq, m) == PDQ_FALSE) { ifp->if_flags |= IFF_OACTIVE; IF_PREPEND(ifq, m); break; } } if (tx) PDQ_DO_TYPE2_PRODUCER(sc->sc_pdq); } void pdq_os_receive_pdu( pdq_t *pdq, struct mbuf *m, size_t pktlen) { pdq_softc_t *sc = (pdq_softc_t *) pdq->pdq_os_ctx; struct fddi_header *fh = mtod(m, struct fddi_header *); sc->sc_if.if_ipackets++; #if NBPFILTER > 0 if (sc->sc_bpf != NULL) bpf_mtap(&sc->sc_if, m); if ((fh->fddi_fc & (FDDIFC_L|FDDIFC_F)) != FDDIFC_LLC_ASYNC) { m_freem(m); return; } #endif m->m_data += sizeof(struct fddi_header); m->m_len -= sizeof(struct fddi_header); m->m_pkthdr.len = pktlen - sizeof(struct fddi_header); m->m_pkthdr.rcvif = &sc->sc_if; fddi_input(&sc->sc_if, fh, m); } void pdq_os_restart_transmitter( pdq_t *pdq) { pdq_softc_t *sc = (pdq_softc_t *) pdq->pdq_os_ctx; sc->sc_if.if_flags &= ~IFF_OACTIVE; if (sc->sc_if.if_snd.ifq_head != NULL) { sc->sc_if.if_timer = PDQ_OS_TX_TIMEOUT; pdq_ifstart(&sc->sc_if); } else { sc->sc_if.if_timer = 0; } } void pdq_os_transmit_done( pdq_t *pdq, struct mbuf *m) { pdq_softc_t *sc = (pdq_softc_t *) pdq->pdq_os_ctx; #if NBPFILTER > 0 if (sc->sc_bpf != NULL) bpf_mtap(&sc->sc_if, m); #endif m_freem(m); sc->sc_if.if_opackets++; } void pdq_os_addr_fill( pdq_t *pdq, pdq_lanaddr_t *addr, size_t num_addrs) { pdq_softc_t *sc = (pdq_softc_t *) pdq->pdq_os_ctx; struct ifmultiaddr *ifma; for (ifma = sc->sc_if.if_multiaddrs.lh_first; ifma && num_addrs > 0; ifma = ifma->ifma_link.le_next) { char *mcaddr; if (ifma->ifma_addr->sa_family != AF_LINK) continue; mcaddr = LLADDR((struct sockaddr_dl *)ifma->ifma_addr); ((u_short *) addr->lanaddr_bytes)[0] = ((u_short *) mcaddr)[0]; ((u_short *) addr->lanaddr_bytes)[1] = ((u_short *) mcaddr)[1]; ((u_short *) addr->lanaddr_bytes)[2] = ((u_short *) mcaddr)[2]; addr++; num_addrs--; } } static int pdq_ifioctl( struct ifnet *ifp, int cmd, caddr_t data) { pdq_softc_t *sc = (pdq_softc_t *) ((caddr_t) ifp - offsetof(pdq_softc_t, sc_ac.ac_if)); int s, error = 0; s = splimp(); switch (cmd) { case SIOCSIFADDR: { struct ifaddr *ifa = (struct ifaddr *)data; ifp->if_flags |= IFF_UP; switch(ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: { (*sc->if_init)(ifp->if_unit); #ifdef __FreeBSD__ arp_ifinit((struct arpcom *)ifp, ifa); #else ((struct arpcom *)ifp)->ac_ipaddr = IA_SIN(ifa)->sin_addr; arpwhohas((struct arpcom *)ifp, &IA_SIN(ifa)->sin_addr); #endif break; } #endif /* INET */ #ifdef NS /* This magic copied from if_is.c; I don't use XNS, * so I have no way of telling if this actually * works or not. */ case AF_NS: { struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); if (ns_nullhost(*ina)) { ina->x_host = *(union ns_host *)(sc->sc_ac.ac_enaddr); } else { ifp->if_flags &= ~IFF_RUNNING; bcopy((caddr_t)ina->x_host.c_host, (caddr_t)sc->sc_ac.ac_enaddr, sizeof sc->sc_ac.ac_enaddr); } (*sc->if_init)(ifp->if_unit); break; } #endif /* NS */ default: { (*sc->if_init)(ifp->if_unit); break; } } break; } case SIOCSIFFLAGS: { (*sc->if_init)(ifp->if_unit); break; } case SIOCADDMULTI: case SIOCDELMULTI: /* * Update multicast listeners */ if (sc->sc_if.if_flags & IFF_RUNNING) pdq_run(sc->sc_pdq); error = 0; break; default: { error = EINVAL; break; } } splx(s); return error; } static void pdq_ifattach( pdq_softc_t *sc, ifnet_ret_t (*ifinit)(int unit), ifnet_ret_t (*ifreset)(int unit), ifnet_ret_t (*ifwatchdog)(struct ifnet *ifp)) { struct ifnet *ifp = &sc->sc_if; ifp->if_softc = sc; ifp->if_flags = IFF_BROADCAST|IFF_SIMPLEX|IFF_MULTICAST; sc->if_init = ifinit; ifp->if_watchdog = ifwatchdog; ifp->if_ioctl = pdq_ifioctl; ifp->if_output = fddi_output; ifp->if_start = pdq_ifstart; #warning "Implement fddi_resolvemulti!" /* ifp->if_resolvemulti = ether_resolvemulti; XXX */ if_attach(ifp); fddi_ifattach(ifp); #if NBPFILTER > 0 bpfattach(ifp, DLT_FDDI, sizeof(struct fddi_header)); #endif } #if NFPA > 0 /* * This is the PCI configuration support. Since the PDQ is available * on both EISA and PCI boards, one must be careful in how defines the * PDQ in the config file. */ static char *pdq_pci_probe (pcici_t config_id, pcidi_t device_id); static void pdq_pci_attach(pcici_t config_id, int unit); static u_long pdq_pci_count; static struct pci_device fpadevice = { "fpa", pdq_pci_probe, pdq_pci_attach, &pdq_pci_count, NULL }; #if defined(__FreeBSD__) static pdq_softc_t *pdqs_pci[NFPA]; #define PDQ_PCI_UNIT_TO_SOFTC(unit) (pdqs_pci[unit]) #ifdef DATA_SET DATA_SET (pcidevice_set, fpadevice); #endif #endif #define PCI_CBMA 0x10 /* Configuration Base Memory Address */ static ifnet_ret_t pdq_pci_ifreset( int unit) { pdq_ifreset(PDQ_PCI_UNIT_TO_SOFTC(unit)); } static ifnet_ret_t pdq_pci_ifinit( int unit) { pdq_ifinit(PDQ_PCI_UNIT_TO_SOFTC(unit)); } static ifnet_ret_t pdq_pci_ifwatchdog(struct ifnet *ifp) { pdq_ifwatchdog(PDQ_PCI_UNIT_TO_SOFTC(ifp->if_unit)); } static #ifdef __FreeBSD__ void #else int #endif pdq_pci_ifintr( void *vsc) { pdq_softc_t *sc = vsc; #ifndef __FreeBSD__ return #endif pdq_interrupt(sc->sc_pdq); } static char * pdq_pci_probe( pcici_t config_id, pcidi_t device_id) { if (device_id == 0x000f1011ul) return "Digital DEFPA PCI FDDI Controller"; return NULL; } static void pdq_pci_attach( pcici_t config_id, int unit) { pdq_softc_t *sc; vm_offset_t va_csrs, pa_csrs; if (unit > NFPA) { printf("fpa%d: not configured; kernel is built for only %d device%s.\n", unit, NFPA, NFPA == 1 ? "" : "s"); return; } sc = (pdq_softc_t *) malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT); if (sc == NULL) return; bzero(sc, sizeof(pdq_softc_t)); /* Zero out the softc*/ if (!pci_map_mem(config_id, PCI_CBMA, &va_csrs, &pa_csrs)) { free((void *) sc, M_DEVBUF); return; } sc->sc_if.if_name = "fpa"; sc->sc_if.if_unit = unit; sc->sc_pdq = pdq_initialize((void *) va_csrs, "fpa", unit, (void *) sc, PDQ_DEFPA); if (sc->sc_pdq == NULL) { free((void *) sc, M_DEVBUF); return; } bcopy((caddr_t) sc->sc_pdq->pdq_hwaddr.lanaddr_bytes, sc->sc_ac.ac_enaddr, 6); pdqs_pci[unit] = sc; pdq_ifattach(sc, pdq_pci_ifinit, pdq_pci_ifreset, pdq_pci_ifwatchdog); pci_map_int(config_id, pdq_pci_ifintr, (void*) sc, &net_imask); } #endif /* NFPA > 0 */ #if NFEA > 0 /* * */ static const int pdq_eisa_irqs[4] = { IRQ9, IRQ10, IRQ11, IRQ15 }; #ifdef __FreeBSD__ static pdq_softc_t *pdqs_eisa[NFEA]; #define PDQ_EISA_UNIT_TO_SOFTC(unit) (pdqs_eisa[unit]) #endif #ifdef __bsdi__ extern struct cfdriver feacd; #define PDQ_EISA_UNIT_TO_SOFTC(unit) ((pdq_softc_t *)feacd.cd_devs[unit]) #endif static ifnet_ret_t pdq_eisa_ifreset( int unit) { pdq_ifreset(PDQ_EISA_UNIT_TO_SOFTC(unit)); } static ifnet_ret_t pdq_eisa_ifinit( int unit) { pdq_ifinit(PDQ_EISA_UNIT_TO_SOFTC(unit)); } static ifnet_ret_t pdq_eisa_ifwatchdog(struct ifnet *ifp) { pdq_ifwatchdog(PDQ_EISA_UNIT_TO_SOFTC(ifp->if_unit)); } #ifdef __FreeBSD__ void #else int #endif feaintr( int unit) { pdq_interrupt(PDQ_EISA_UNIT_TO_SOFTC(unit)->sc_pdq); #ifndef __FreeBSD__ return unit; #endif } static void pdq_eisa_subprobe( pdq_uint32_t iobase, pdq_uint32_t *maddr, pdq_uint32_t *msize, pdq_uint32_t *irq) { if (irq != NULL) *irq = pdq_eisa_irqs[PDQ_OS_IORD_8(iobase + PDQ_EISA_IO_CONFIG_STAT_0) & 3]; *maddr = (PDQ_OS_IORD_8(iobase + PDQ_EISA_MEM_ADD_CMP_0) << 16) | (PDQ_OS_IORD_8(iobase + PDQ_EISA_MEM_ADD_CMP_1) << 8); *msize = (PDQ_OS_IORD_8(iobase + PDQ_EISA_MEM_ADD_MASK_0) + 4) << 8; } static void pdq_eisa_devinit( pdq_softc_t *sc) { pdq_uint8_t data; /* * Do the standard initialization for the DEFEA registers. */ PDQ_OS_IOWR_8(sc->sc_iobase + PDQ_EISA_FUNCTION_CTRL, 0x23); PDQ_OS_IOWR_8(sc->sc_iobase + PDQ_EISA_IO_CMP_1_1, (sc->sc_iobase >> 8) & 0x0F); PDQ_OS_IOWR_8(sc->sc_iobase + PDQ_EISA_IO_CMP_1_0, (sc->sc_iobase >> 8) & 0x0F); PDQ_OS_IOWR_8(sc->sc_iobase + PDQ_EISA_SLOT_CTRL, 0x01); data = PDQ_OS_IORD_8(sc->sc_iobase + PDQ_EISA_BURST_HOLDOFF); PDQ_OS_IOWR_8(sc->sc_iobase + PDQ_EISA_BURST_HOLDOFF, data | 1); data = PDQ_OS_IORD_8(sc->sc_iobase + PDQ_EISA_IO_CONFIG_STAT_0); PDQ_OS_IOWR_8(sc->sc_iobase + PDQ_EISA_IO_CONFIG_STAT_0, data | 8); } #ifdef __FreeBSD__ static int pdq_eisa_slots = ~1; static int pdq_eisa_probe( struct isa_device *id) { pdq_softc_t *sc; int slot; pdq_uint32_t data, irq, maddr, msize; slot = 0x1000 * (ffs(pdq_eisa_slots) - 1); for (; slot <= 0xF000; slot++) { pdq_eisa_slots &= ~(1 << (slot >> 12)); data = PDQ_OS_IORD_32(slot + PDQ_EISA_SLOT_ID); if ((data & 0xFFFFFF) != 0x30A310) continue; id->id_iobase = slot; pdq_eisa_subprobe(slot, &maddr, &msize, &irq); if (id->id_irq != 0 && irq != id->id_irq) { printf("fea%d: error: desired IRQ of %d does not match device's actual IRQ (%d),\n", id->id_unit, ffs(id->id_irq) - 1, ffs(irq) - 1); return 0; } id->id_irq = irq; if (maddr == 0) { printf("fea%d: error: memory not enabled! ECU reconfiguration required\n", id->id_unit); return 0; } id->id_maddr = (caddr_t) pmap_mapdev(maddr, msize); if (id->id_maddr == NULL) return 0; id->id_msize = msize; if (PDQ_EISA_UNIT_TO_SOFTC(id->id_unit) == NULL) { sc = (pdq_softc_t *) malloc(sizeof(pdq_softc_t), M_DEVBUF, M_WAITOK); if (sc == NULL) return 0; PDQ_EISA_UNIT_TO_SOFTC(id->id_unit) = sc; } return 0x1000; } return 0; } static int pdq_eisa_attach( struct isa_device *id) { pdq_softc_t *sc = PDQ_EISA_UNIT_TO_SOFTC(id->id_unit); bzero(sc, sizeof(pdq_softc_t)); /* Zero out the softc*/ sc->sc_if.if_name = "fea"; sc->sc_if.if_unit = id->id_unit; sc->sc_iobase = id->id_iobase; pdq_eisa_devinit(sc); sc->sc_pdq = pdq_initialize((void *) id->id_maddr, "fea", sc->sc_if.if_unit, (void *) sc, PDQ_DEFEA); if (sc->sc_pdq == NULL) { printf("fea%d: initialization failed\n", sc->sc_if.if_unit); return 0; } bcopy((caddr_t) sc->sc_pdq->pdq_hwaddr.lanaddr_bytes, sc->sc_ac.ac_enaddr, 6); pdq_ifattach(sc, pdq_eisa_ifinit, pdq_eisa_ifreset, pdq_eisa_ifwatchdog); return 1; } /* * */ struct isa_driver feadriver = { pdq_eisa_probe, pdq_eisa_attach, "fea" }; #endif /* __FreeBSD__ */ #ifdef __bsdi__ int pdq_eisa_probe( struct device *parent, struct cfdata *cf, void *aux) { struct isa_attach_args *ia = (struct isa_attach_args *) aux; int slot; pdq_uint32_t irq, maddr, msize; if (isa_bustype != BUS_EISA) return (0); if ((slot = eisa_match(cf, ia)) == 0) return (0); ia->ia_iobase = slot << 12; ia->ia_iosize = EISA_NPORT; eisa_slotalloc(slot); pdq_eisa_subprobe(ia->ia_iobase, &maddr, &msize, &irq); if (ia->ia_irq != IRQUNK && irq != ia->ia_irq) { printf("fea%d: error: desired IRQ of %d does not match device's actual IRQ (%d),\n", cf->cf_unit, ffs(ia->ia_irq) - 1, ffs(irq) - 1); return 0; } if (ia->ia_irq == IRQUNK) { if ((irq = isa_irqalloc(irq)) == 0) return 0; ia->ia_irq = irq; } if (maddr == 0) { printf("fea%d: error: memory not enabled! ECU reconfiguration required\n", cf->cf_unit); return 0; } /* EISA bus masters don't use host DMA channels */ ia->ia_drq = 0; /* XXX should be DRQUNK or DRQBUSMASTER? */ #if 0 ia->ia_maddr = maddr; ia->ia_msize = msize; #else ia->ia_maddr = 0; ia->ia_msize = 0; #endif return 1; } void pdq_eisa_attach( struct device *parent, struct device *self, void *aux) { pdq_softc_t *sc = (pdq_softc_t *) self; register struct isa_attach_args *ia = (struct isa_attach_args *) aux; register struct ifnet *ifp = &sc->sc_if; int i; sc->sc_if.if_unit = sc->sc_dev.dv_unit; sc->sc_if.if_name = "fea"; sc->sc_if.if_flags = 0; sc->sc_iobase = ia->ia_iobase; sc->sc_pdq = pdq_initialize((void *) ISA_HOLE_VADDR(ia->ia_maddr), "fea", sc->sc_if.if_unit, (void *) sc, PDQ_DEFEA); if (sc->sc_pdq == NULL) { printf("fea%d: initialization failed\n", sc->sc_if.if_unit); return; } bcopy((caddr_t) sc->sc_pdq->pdq_hwaddr.lanaddr_bytes, sc->sc_ac.ac_enaddr, 6); pdq_ifattach(sc, pdq_eisa_ifinit, pdq_eisa_ifreset, pdq_eisa_ifwatchdog); isa_establish(&sc->sc_id, &sc->sc_dev); sc->sc_ih.ih_fun = feaintr; sc->sc_ih.ih_arg = (void *)sc; intr_establish(ia->ia_irq, &sc->sc_ih, DV_NET); sc->sc_ats.func = (void (*)(void *)) pdq_stop; sc->sc_ats.arg = (void *) sc->sc_pdq; atshutdown(&sc->sc_ats, ATSH_ADD); } static char *pdq_eisa_ids[] = { "DEC3001", /* 0x0130A310 */ "DEC3002", /* 0x0230A310 */ "DEC3003", /* 0x0330A310 */ "DEC3004", /* 0x0430A310 */ }; struct cfdriver feacd = { 0, "fea", pdq_eisa_probe, pdq_eisa_attach, DV_IFNET, sizeof(pdq_softc_t), pdq_eisa_ids }; #endif /* __bsdi__ */ #endif /* NFEA > 0 */ #endif /* NFPA > 0 || NFEA > 0 */