/* $OpenBSD: if_se.c,v 1.24 2023/11/10 15:51:20 bluhm Exp $ */ /*- * Copyright (c) 2009, 2010 Christopher Zimmermann * Copyright (c) 2008, 2009, 2010 Nikolay Denev * Copyright (c) 2007, 2008 Alexander Pohoyda * Copyright (c) 1997, 1998, 1999 * Bill Paul . 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 Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 AUTHORS OR * THE VOICES IN THEIR HEADS 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. */ /* * SiS 190/191 PCI Ethernet NIC driver. * * Adapted to SiS 190 NIC by Alexander Pohoyda based on the original * SiS 900 driver by Bill Paul, using SiS 190/191 Solaris driver by * Masayuki Murayama and SiS 190/191 GNU/Linux driver by K.M. Liu * . Thanks to Pyun YongHyeon for * review and very useful comments. * * Ported to OpenBSD by Christopher Zimmermann 2009/10 * * Adapted to SiS 191 NIC by Nikolay Denev with further ideas from the * Linux and Solaris drivers. */ #include "bpfilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #if NBPFILTER > 0 #include #endif #include #include #include #include #include #define SE_RX_RING_CNT 256 /* [8, 1024] */ #define SE_TX_RING_CNT 256 /* [8, 8192] */ #define SE_RX_BUF_ALIGN sizeof(uint64_t) #define SE_RX_RING_SZ (SE_RX_RING_CNT * sizeof(struct se_desc)) #define SE_TX_RING_SZ (SE_TX_RING_CNT * sizeof(struct se_desc)) struct se_list_data { struct se_desc *se_rx_ring; struct se_desc *se_tx_ring; bus_dmamap_t se_rx_dmamap; bus_dmamap_t se_tx_dmamap; }; struct se_chain_data { struct mbuf *se_rx_mbuf[SE_RX_RING_CNT]; struct mbuf *se_tx_mbuf[SE_TX_RING_CNT]; bus_dmamap_t se_rx_map[SE_RX_RING_CNT]; bus_dmamap_t se_tx_map[SE_TX_RING_CNT]; uint se_rx_prod; uint se_tx_prod; uint se_tx_cons; uint se_tx_cnt; }; struct se_softc { struct device sc_dev; void *sc_ih; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; bus_dma_tag_t sc_dmat; struct mii_data sc_mii; struct arpcom sc_ac; struct se_list_data se_ldata; struct se_chain_data se_cdata; struct timeout sc_tick_tmo; int sc_flags; #define SE_FLAG_FASTETHER 0x0001 #define SE_FLAG_RGMII 0x0010 #define SE_FLAG_LINK 0x8000 }; /* * Various supported device vendors/types and their names. */ const struct pci_matchid se_devices[] = { { PCI_VENDOR_SIS, PCI_PRODUCT_SIS_190 }, { PCI_VENDOR_SIS, PCI_PRODUCT_SIS_191 } }; int se_match(struct device *, void *, void *); void se_attach(struct device *, struct device *, void *); int se_activate(struct device *, int); const struct cfattach se_ca = { sizeof(struct se_softc), se_match, se_attach, NULL, se_activate }; struct cfdriver se_cd = { 0, "se", DV_IFNET }; uint32_t se_miibus_cmd(struct se_softc *, uint32_t); int se_miibus_readreg(struct device *, int, int); void se_miibus_writereg(struct device *, int, int, int); void se_miibus_statchg(struct device *); int se_newbuf(struct se_softc *, uint); void se_discard_rxbuf(struct se_softc *, uint); int se_encap(struct se_softc *, struct mbuf *, uint *); void se_rxeof(struct se_softc *); void se_txeof(struct se_softc *); int se_intr(void *); void se_tick(void *); void se_start(struct ifnet *); int se_ioctl(struct ifnet *, u_long, caddr_t); int se_init(struct ifnet *); void se_stop(struct se_softc *); void se_watchdog(struct ifnet *); int se_ifmedia_upd(struct ifnet *); void se_ifmedia_sts(struct ifnet *, struct ifmediareq *); int se_pcib_match(struct pci_attach_args *); int se_get_mac_addr_apc(struct se_softc *, uint8_t *); int se_get_mac_addr_eeprom(struct se_softc *, uint8_t *); uint16_t se_read_eeprom(struct se_softc *, int); void se_iff(struct se_softc *); void se_reset(struct se_softc *); int se_list_rx_init(struct se_softc *); int se_list_rx_free(struct se_softc *); int se_list_tx_init(struct se_softc *); int se_list_tx_free(struct se_softc *); /* * Register space access macros. */ #define CSR_WRITE_4(sc, reg, val) \ bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, reg, val) #define CSR_WRITE_2(sc, reg, val) \ bus_space_write_2((sc)->sc_iot, (sc)->sc_ioh, reg, val) #define CSR_WRITE_1(sc, reg, val) \ bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, reg, val) #define CSR_READ_4(sc, reg) \ bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, reg) #define CSR_READ_2(sc, reg) \ bus_space_read_2((sc)->sc_iot, (sc)->sc_ioh, reg) #define CSR_READ_1(sc, reg) \ bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, reg) /* * Read a sequence of words from the EEPROM. */ uint16_t se_read_eeprom(struct se_softc *sc, int offset) { uint32_t val; int i; KASSERT(offset <= EI_OFFSET); CSR_WRITE_4(sc, ROMInterface, EI_REQ | EI_OP_RD | (offset << EI_OFFSET_SHIFT)); DELAY(500); for (i = 0; i < SE_TIMEOUT; i++) { val = CSR_READ_4(sc, ROMInterface); if ((val & EI_REQ) == 0) break; DELAY(100); } if (i == SE_TIMEOUT) { printf("%s: EEPROM read timeout: 0x%08x\n", sc->sc_dev.dv_xname, val); return 0xffff; } return (val & EI_DATA) >> EI_DATA_SHIFT; } int se_get_mac_addr_eeprom(struct se_softc *sc, uint8_t *dest) { uint16_t val; int i; val = se_read_eeprom(sc, EEPROMSignature); if (val == 0xffff || val == 0x0000) { printf("%s: invalid EEPROM signature : 0x%04x\n", sc->sc_dev.dv_xname, val); return (EINVAL); } for (i = 0; i < ETHER_ADDR_LEN; i += 2) { val = se_read_eeprom(sc, EEPROMMACAddr + i / 2); dest[i + 0] = (uint8_t)val; dest[i + 1] = (uint8_t)(val >> 8); } if ((se_read_eeprom(sc, EEPROMInfo) & 0x80) != 0) sc->sc_flags |= SE_FLAG_RGMII; return (0); } /* * For SiS96x, APC CMOS RAM is used to store Ethernet address. * APC CMOS RAM is accessed through ISA bridge. */ #if defined(__amd64__) || defined(__i386__) int se_pcib_match(struct pci_attach_args *pa) { const struct pci_matchid apc_devices[] = { { PCI_VENDOR_SIS, PCI_PRODUCT_SIS_965 }, { PCI_VENDOR_SIS, PCI_PRODUCT_SIS_966 }, { PCI_VENDOR_SIS, PCI_PRODUCT_SIS_968 } }; return pci_matchbyid(pa, apc_devices, nitems(apc_devices)); } #endif int se_get_mac_addr_apc(struct se_softc *sc, uint8_t *dest) { #if defined(__amd64__) || defined(__i386__) struct pci_attach_args pa; pcireg_t reg; bus_space_handle_t ioh; int rc, i; if (pci_find_device(&pa, se_pcib_match) == 0) { printf("\n%s: couldn't find PCI-ISA bridge\n", sc->sc_dev.dv_xname); return EINVAL; } /* Enable port 0x78 and 0x79 to access APC registers. */ reg = pci_conf_read(pa.pa_pc, pa.pa_tag, 0x48); pci_conf_write(pa.pa_pc, pa.pa_tag, 0x48, reg & ~0x02); DELAY(50); (void)pci_conf_read(pa.pa_pc, pa.pa_tag, 0x48); /* XXX this abuses bus_space implementation knowledge */ rc = _bus_space_map(pa.pa_iot, 0x78, 2, 0, &ioh); if (rc == 0) { /* Read stored Ethernet address. */ for (i = 0; i < ETHER_ADDR_LEN; i++) { bus_space_write_1(pa.pa_iot, ioh, 0, 0x09 + i); dest[i] = bus_space_read_1(pa.pa_iot, ioh, 1); } bus_space_write_1(pa.pa_iot, ioh, 0, 0x12); if ((bus_space_read_1(pa.pa_iot, ioh, 1) & 0x80) != 0) sc->sc_flags |= SE_FLAG_RGMII; _bus_space_unmap(pa.pa_iot, ioh, 2, NULL); } else rc = EINVAL; /* Restore access to APC registers. */ pci_conf_write(pa.pa_pc, pa.pa_tag, 0x48, reg); return rc; #endif return EINVAL; } uint32_t se_miibus_cmd(struct se_softc *sc, uint32_t ctrl) { int i; uint32_t val; CSR_WRITE_4(sc, GMIIControl, ctrl); DELAY(10); for (i = 0; i < SE_TIMEOUT; i++) { val = CSR_READ_4(sc, GMIIControl); if ((val & GMI_REQ) == 0) return val; DELAY(10); } return GMI_REQ; } int se_miibus_readreg(struct device *self, int phy, int reg) { struct se_softc *sc = (struct se_softc *)self; uint32_t ctrl, val; ctrl = (phy << GMI_PHY_SHIFT) | (reg << GMI_REG_SHIFT) | GMI_OP_RD | GMI_REQ; val = se_miibus_cmd(sc, ctrl); if ((val & GMI_REQ) != 0) { printf("%s: PHY read timeout : %d\n", sc->sc_dev.dv_xname, reg); return 0; } return (val & GMI_DATA) >> GMI_DATA_SHIFT; } void se_miibus_writereg(struct device *self, int phy, int reg, int data) { struct se_softc *sc = (struct se_softc *)self; uint32_t ctrl, val; ctrl = (phy << GMI_PHY_SHIFT) | (reg << GMI_REG_SHIFT) | GMI_OP_WR | (data << GMI_DATA_SHIFT) | GMI_REQ; val = se_miibus_cmd(sc, ctrl); if ((val & GMI_REQ) != 0) { printf("%s: PHY write timeout : %d\n", sc->sc_dev.dv_xname, reg); } } void se_miibus_statchg(struct device *self) { struct se_softc *sc = (struct se_softc *)self; #ifdef SE_DEBUG struct ifnet *ifp = &sc->sc_ac.ac_if; #endif struct mii_data *mii = &sc->sc_mii; uint32_t ctl, speed; speed = 0; sc->sc_flags &= ~SE_FLAG_LINK; if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: #ifdef SE_DEBUG if (ifp->if_flags & IFF_DEBUG) printf("%s: 10baseT link\n", ifp->if_xname); #endif sc->sc_flags |= SE_FLAG_LINK; speed = SC_SPEED_10; break; case IFM_100_TX: #ifdef SE_DEBUG if (ifp->if_flags & IFF_DEBUG) printf("%s: 100baseTX link\n", ifp->if_xname); #endif sc->sc_flags |= SE_FLAG_LINK; speed = SC_SPEED_100; break; case IFM_1000_T: #ifdef SE_DEBUG if (ifp->if_flags & IFF_DEBUG) printf("%s: 1000baseT link\n", ifp->if_xname); #endif if ((sc->sc_flags & SE_FLAG_FASTETHER) == 0) { sc->sc_flags |= SE_FLAG_LINK; speed = SC_SPEED_1000; } break; default: break; } } if ((sc->sc_flags & SE_FLAG_LINK) == 0) { #ifdef SE_DEBUG if (ifp->if_flags & IFF_DEBUG) printf("%s: no link\n", ifp->if_xname); #endif return; } /* Reprogram MAC to resolved speed/duplex/flow-control parameters. */ ctl = CSR_READ_4(sc, StationControl); ctl &= ~(0x0f000000 | SC_FDX | SC_SPEED_MASK); if (speed == SC_SPEED_1000) ctl |= 0x07000000; else ctl |= 0x04000000; #ifdef notyet if ((sc->sc_flags & SE_FLAG_GMII) != 0) ctl |= 0x03000000; #endif ctl |= speed; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) ctl |= SC_FDX; CSR_WRITE_4(sc, StationControl, ctl); if ((sc->sc_flags & SE_FLAG_RGMII) != 0) { CSR_WRITE_4(sc, RGMIIDelay, 0x0441); CSR_WRITE_4(sc, RGMIIDelay, 0x0440); } } void se_iff(struct se_softc *sc) { struct arpcom *ac = &sc->sc_ac; struct ifnet *ifp = &ac->ac_if; struct ether_multi *enm; struct ether_multistep step; uint32_t crc, hashes[2]; uint16_t rxfilt; rxfilt = CSR_READ_2(sc, RxMacControl); rxfilt &= ~(AcceptAllPhys | AcceptBroadcast | AcceptMulticast); ifp->if_flags &= ~IFF_ALLMULTI; /* * Always accept broadcast frames. * Always accept frames destined to our station address. */ rxfilt |= AcceptBroadcast | AcceptMyPhys; if (ifp->if_flags & IFF_PROMISC || ac->ac_multirangecnt > 0) { ifp->if_flags |= IFF_ALLMULTI; if (ifp->if_flags & IFF_PROMISC) rxfilt |= AcceptAllPhys; rxfilt |= AcceptMulticast; hashes[0] = hashes[1] = 0xffffffff; } else { rxfilt |= AcceptMulticast; hashes[0] = hashes[1] = 0; ETHER_FIRST_MULTI(step, ac, enm); while (enm != NULL) { crc = ether_crc32_be(enm->enm_addrlo, ETHER_ADDR_LEN); hashes[crc >> 31] |= 1 << ((crc >> 26) & 0x1f); ETHER_NEXT_MULTI(step, enm); } } CSR_WRITE_2(sc, RxMacControl, rxfilt); CSR_WRITE_4(sc, RxHashTable, hashes[0]); CSR_WRITE_4(sc, RxHashTable2, hashes[1]); } void se_reset(struct se_softc *sc) { CSR_WRITE_4(sc, IntrMask, 0); CSR_WRITE_4(sc, IntrStatus, 0xffffffff); /* Soft reset. */ CSR_WRITE_4(sc, IntrControl, 0x8000); CSR_READ_4(sc, IntrControl); DELAY(100); CSR_WRITE_4(sc, IntrControl, 0); /* Stop MAC. */ CSR_WRITE_4(sc, TX_CTL, 0x1a00); CSR_WRITE_4(sc, RX_CTL, 0x1a00); CSR_WRITE_4(sc, IntrMask, 0); CSR_WRITE_4(sc, IntrStatus, 0xffffffff); CSR_WRITE_4(sc, GMIIControl, 0); } /* * Probe for an SiS chip. Check the PCI vendor and device * IDs against our list and return a device name if we find a match. */ int se_match(struct device *parent, void *match, void *aux) { struct pci_attach_args *pa = (struct pci_attach_args *)aux; return pci_matchbyid(pa, se_devices, nitems(se_devices)); } /* * Attach the interface. Do ifmedia setup and ethernet/BPF attach. */ void se_attach(struct device *parent, struct device *self, void *aux) { struct se_softc *sc = (struct se_softc *)self; struct arpcom *ac = &sc->sc_ac; struct ifnet *ifp = &ac->ac_if; struct pci_attach_args *pa = (struct pci_attach_args *)aux; uint8_t eaddr[ETHER_ADDR_LEN]; const char *intrstr; pci_intr_handle_t ih; bus_size_t iosize; bus_dma_segment_t seg; struct se_list_data *ld; struct se_chain_data *cd; int nseg; uint i; int rc; printf(": "); /* * Map control/status registers. */ rc = pci_mapreg_map(pa, PCI_MAPREG_START, PCI_MAPREG_TYPE_MEM, 0, &sc->sc_iot, &sc->sc_ioh, NULL, &iosize, 0); if (rc != 0) { printf("can't map i/o space\n"); return; } if (pci_intr_map(pa, &ih)) { printf("can't map interrupt\n"); goto fail1; } intrstr = pci_intr_string(pa->pa_pc, ih); sc->sc_ih = pci_intr_establish(pa->pa_pc, ih, IPL_NET, se_intr, sc, self->dv_xname); if (sc->sc_ih == NULL) { printf("can't establish interrupt"); if (intrstr != NULL) printf(" at %s", intrstr); printf("\n"); goto fail1; } printf("%s", intrstr); if (pa->pa_id == PCI_ID_CODE(PCI_VENDOR_SIS, PCI_PRODUCT_SIS_190)) sc->sc_flags |= SE_FLAG_FASTETHER; /* Reset the adapter. */ se_reset(sc); /* Get MAC address from the EEPROM. */ if ((pci_conf_read(pa->pa_pc, pa->pa_tag, 0x70) & (0x01 << 24)) != 0) se_get_mac_addr_apc(sc, eaddr); else se_get_mac_addr_eeprom(sc, eaddr); printf(", address %s\n", ether_sprintf(eaddr)); bcopy(eaddr, ac->ac_enaddr, ETHER_ADDR_LEN); /* * Now do all the DMA mapping stuff */ sc->sc_dmat = pa->pa_dmat; ld = &sc->se_ldata; cd = &sc->se_cdata; /* First create TX/RX busdma maps. */ for (i = 0; i < SE_RX_RING_CNT; i++) { rc = bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1, MCLBYTES, 0, BUS_DMA_NOWAIT, &cd->se_rx_map[i]); if (rc != 0) { printf("%s: cannot init the RX map array\n", self->dv_xname); goto fail2; } } for (i = 0; i < SE_TX_RING_CNT; i++) { rc = bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1, MCLBYTES, 0, BUS_DMA_NOWAIT, &cd->se_tx_map[i]); if (rc != 0) { printf("%s: cannot init the TX map array\n", self->dv_xname); goto fail2; } } /* * Now allocate a chunk of DMA-able memory for RX and TX ring * descriptors, as a contiguous block of memory. * XXX fix deallocation upon error */ /* RX */ rc = bus_dmamem_alloc(sc->sc_dmat, SE_RX_RING_SZ, PAGE_SIZE, 0, &seg, 1, &nseg, BUS_DMA_NOWAIT); if (rc != 0) { printf("%s: no memory for RX descriptors\n", self->dv_xname); goto fail2; } rc = bus_dmamem_map(sc->sc_dmat, &seg, nseg, SE_RX_RING_SZ, (caddr_t *)&ld->se_rx_ring, BUS_DMA_NOWAIT); if (rc != 0) { printf("%s: can't map RX descriptors\n", self->dv_xname); goto fail2; } rc = bus_dmamap_create(sc->sc_dmat, SE_RX_RING_SZ, 1, SE_RX_RING_SZ, 0, BUS_DMA_NOWAIT, &ld->se_rx_dmamap); if (rc != 0) { printf("%s: can't alloc RX DMA map\n", self->dv_xname); goto fail2; } rc = bus_dmamap_load(sc->sc_dmat, ld->se_rx_dmamap, (caddr_t)ld->se_rx_ring, SE_RX_RING_SZ, NULL, BUS_DMA_NOWAIT); if (rc != 0) { printf("%s: can't load RX DMA map\n", self->dv_xname); bus_dmamem_unmap(sc->sc_dmat, (caddr_t)ld->se_rx_ring, SE_RX_RING_SZ); bus_dmamap_destroy(sc->sc_dmat, ld->se_rx_dmamap); bus_dmamem_free(sc->sc_dmat, &seg, nseg); goto fail2; } /* TX */ rc = bus_dmamem_alloc(sc->sc_dmat, SE_TX_RING_SZ, PAGE_SIZE, 0, &seg, 1, &nseg, BUS_DMA_NOWAIT); if (rc != 0) { printf("%s: no memory for TX descriptors\n", self->dv_xname); goto fail2; } rc = bus_dmamem_map(sc->sc_dmat, &seg, nseg, SE_TX_RING_SZ, (caddr_t *)&ld->se_tx_ring, BUS_DMA_NOWAIT); if (rc != 0) { printf("%s: can't map TX descriptors\n", self->dv_xname); goto fail2; } rc = bus_dmamap_create(sc->sc_dmat, SE_TX_RING_SZ, 1, SE_TX_RING_SZ, 0, BUS_DMA_NOWAIT, &ld->se_tx_dmamap); if (rc != 0) { printf("%s: can't alloc TX DMA map\n", self->dv_xname); goto fail2; } rc = bus_dmamap_load(sc->sc_dmat, ld->se_tx_dmamap, (caddr_t)ld->se_tx_ring, SE_TX_RING_SZ, NULL, BUS_DMA_NOWAIT); if (rc != 0) { printf("%s: can't load TX DMA map\n", self->dv_xname); bus_dmamem_unmap(sc->sc_dmat, (caddr_t)ld->se_tx_ring, SE_TX_RING_SZ); bus_dmamap_destroy(sc->sc_dmat, ld->se_tx_dmamap); bus_dmamem_free(sc->sc_dmat, &seg, nseg); goto fail2; } timeout_set(&sc->sc_tick_tmo, se_tick, sc); ifp = &sc->sc_ac.ac_if; ifp->if_softc = sc; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = se_ioctl; ifp->if_start = se_start; ifp->if_watchdog = se_watchdog; ifq_init_maxlen(&ifp->if_snd, SE_TX_RING_CNT - 1); bcopy(sc->sc_dev.dv_xname, ifp->if_xname, IFNAMSIZ); ifp->if_capabilities = IFCAP_VLAN_MTU; /* * Do MII setup. */ sc->sc_mii.mii_ifp = ifp; sc->sc_mii.mii_readreg = se_miibus_readreg; sc->sc_mii.mii_writereg = se_miibus_writereg; sc->sc_mii.mii_statchg = se_miibus_statchg; ifmedia_init(&sc->sc_mii.mii_media, 0, se_ifmedia_upd, se_ifmedia_sts); mii_attach(&sc->sc_dev, &sc->sc_mii, 0xffffffff, MII_PHY_ANY, MII_OFFSET_ANY, 0); if (LIST_FIRST(&sc->sc_mii.mii_phys) == NULL) { /* No PHY attached */ ifmedia_add(&sc->sc_mii.mii_media, IFM_ETHER | IFM_MANUAL, 0, NULL); ifmedia_set(&sc->sc_mii.mii_media, IFM_ETHER | IFM_MANUAL); } else ifmedia_set(&sc->sc_mii.mii_media, IFM_ETHER | IFM_AUTO); /* * Call MI attach routine. */ if_attach(ifp); ether_ifattach(ifp); return; fail2: pci_intr_disestablish(pa->pa_pc, sc->sc_ih); fail1: bus_space_unmap(sc->sc_iot, sc->sc_ioh, iosize); } int se_activate(struct device *self, int act) { struct se_softc *sc = (struct se_softc *)self; struct ifnet *ifp = &sc->sc_ac.ac_if; int rv = 0; switch (act) { case DVACT_SUSPEND: if (ifp->if_flags & IFF_RUNNING) se_stop(sc); rv = config_activate_children(self, act); break; case DVACT_RESUME: if (ifp->if_flags & IFF_UP) (void)se_init(ifp); break; default: rv = config_activate_children(self, act); break; } return (rv); } /* * Initialize the TX descriptors. */ int se_list_tx_init(struct se_softc *sc) { struct se_list_data *ld = &sc->se_ldata; struct se_chain_data *cd = &sc->se_cdata; bzero(ld->se_tx_ring, SE_TX_RING_SZ); ld->se_tx_ring[SE_TX_RING_CNT - 1].se_flags = htole32(RING_END); bus_dmamap_sync(sc->sc_dmat, ld->se_tx_dmamap, 0, SE_TX_RING_SZ, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); cd->se_tx_prod = 0; cd->se_tx_cons = 0; cd->se_tx_cnt = 0; return 0; } int se_list_tx_free(struct se_softc *sc) { struct se_chain_data *cd = &sc->se_cdata; uint i; for (i = 0; i < SE_TX_RING_CNT; i++) { if (cd->se_tx_mbuf[i] != NULL) { bus_dmamap_unload(sc->sc_dmat, cd->se_tx_map[i]); m_free(cd->se_tx_mbuf[i]); cd->se_tx_mbuf[i] = NULL; } } return 0; } /* * Initialize the RX descriptors and allocate mbufs for them. */ int se_list_rx_init(struct se_softc *sc) { struct se_list_data *ld = &sc->se_ldata; struct se_chain_data *cd = &sc->se_cdata; uint i; bzero(ld->se_rx_ring, SE_RX_RING_SZ); bus_dmamap_sync(sc->sc_dmat, ld->se_rx_dmamap, 0, SE_RX_RING_SZ, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); for (i = 0; i < SE_RX_RING_CNT; i++) { if (se_newbuf(sc, i) != 0) return ENOBUFS; } cd->se_rx_prod = 0; return 0; } int se_list_rx_free(struct se_softc *sc) { struct se_chain_data *cd = &sc->se_cdata; uint i; for (i = 0; i < SE_RX_RING_CNT; i++) { if (cd->se_rx_mbuf[i] != NULL) { bus_dmamap_unload(sc->sc_dmat, cd->se_rx_map[i]); m_free(cd->se_rx_mbuf[i]); cd->se_rx_mbuf[i] = NULL; } } return 0; } /* * Initialize an RX descriptor and attach an MBUF cluster. */ int se_newbuf(struct se_softc *sc, uint i) { #ifdef SE_DEBUG struct ifnet *ifp = &sc->sc_ac.ac_if; #endif struct se_list_data *ld = &sc->se_ldata; struct se_chain_data *cd = &sc->se_cdata; struct se_desc *desc; struct mbuf *m; int rc; m = MCLGETL(NULL, M_DONTWAIT, MCLBYTES); if (m == NULL) { #ifdef SE_DEBUG if (ifp->if_flags & IFF_DEBUG) printf("%s: MCLGETL failed\n", ifp->if_xname); #endif return ENOBUFS; } m->m_len = m->m_pkthdr.len = MCLBYTES; m_adj(m, SE_RX_BUF_ALIGN); rc = bus_dmamap_load_mbuf(sc->sc_dmat, cd->se_rx_map[i], m, BUS_DMA_NOWAIT); KASSERT(cd->se_rx_map[i]->dm_nsegs == 1); if (rc != 0) { m_freem(m); return ENOBUFS; } bus_dmamap_sync(sc->sc_dmat, cd->se_rx_map[i], 0, cd->se_rx_map[i]->dm_mapsize, BUS_DMASYNC_PREREAD); cd->se_rx_mbuf[i] = m; desc = &ld->se_rx_ring[i]; desc->se_sts_size = 0; desc->se_cmdsts = htole32(RDC_OWN | RDC_INTR); desc->se_ptr = htole32((uint32_t)cd->se_rx_map[i]->dm_segs[0].ds_addr); desc->se_flags = htole32(cd->se_rx_map[i]->dm_segs[0].ds_len); if (i == SE_RX_RING_CNT - 1) desc->se_flags |= htole32(RING_END); bus_dmamap_sync(sc->sc_dmat, ld->se_rx_dmamap, i * sizeof(*desc), sizeof(*desc), BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return 0; } void se_discard_rxbuf(struct se_softc *sc, uint i) { struct se_list_data *ld = &sc->se_ldata; struct se_desc *desc; desc = &ld->se_rx_ring[i]; desc->se_sts_size = 0; desc->se_cmdsts = htole32(RDC_OWN | RDC_INTR); desc->se_flags = htole32(MCLBYTES - SE_RX_BUF_ALIGN); if (i == SE_RX_RING_CNT - 1) desc->se_flags |= htole32(RING_END); bus_dmamap_sync(sc->sc_dmat, ld->se_rx_dmamap, i * sizeof(*desc), sizeof(*desc), BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } /* * A frame has been uploaded: pass the resulting mbuf chain up to * the higher level protocols. */ void se_rxeof(struct se_softc *sc) { struct mbuf *m; struct mbuf_list ml = MBUF_LIST_INITIALIZER(); struct ifnet *ifp = &sc->sc_ac.ac_if; struct se_list_data *ld = &sc->se_ldata; struct se_chain_data *cd = &sc->se_cdata; struct se_desc *cur_rx; uint32_t rxinfo, rxstat; uint i; bus_dmamap_sync(sc->sc_dmat, ld->se_rx_dmamap, 0, SE_RX_RING_SZ, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); for (i = cd->se_rx_prod; ; SE_INC(i, SE_RX_RING_CNT)) { cur_rx = &ld->se_rx_ring[i]; rxinfo = letoh32(cur_rx->se_cmdsts); if ((rxinfo & RDC_OWN) != 0) break; rxstat = letoh32(cur_rx->se_sts_size); /* * If an error occurs, update stats, clear the * status word and leave the mbuf cluster in place: * it should simply get re-used next time this descriptor * comes up in the ring. */ if ((rxstat & RDS_CRCOK) == 0 || SE_RX_ERROR(rxstat) != 0 || SE_RX_NSEGS(rxstat) != 1) { /* XXX We don't support multi-segment frames yet. */ if (ifp->if_flags & IFF_DEBUG) printf("%s: rx error %b\n", ifp->if_xname, rxstat, RX_ERR_BITS); se_discard_rxbuf(sc, i); ifp->if_ierrors++; continue; } /* No errors; receive the packet. */ bus_dmamap_sync(sc->sc_dmat, cd->se_rx_map[i], 0, cd->se_rx_map[i]->dm_mapsize, BUS_DMASYNC_POSTREAD); m = cd->se_rx_mbuf[i]; if (se_newbuf(sc, i) != 0) { se_discard_rxbuf(sc, i); ifp->if_iqdrops++; continue; } /* * Account for 10 bytes auto padding which is used * to align IP header on a 32bit boundary. Also note, * CRC bytes are automatically removed by the hardware. */ m->m_data += SE_RX_PAD_BYTES; m->m_pkthdr.len = m->m_len = SE_RX_BYTES(rxstat) - SE_RX_PAD_BYTES; ml_enqueue(&ml, m); } if_input(ifp, &ml); cd->se_rx_prod = i; } /* * A frame was downloaded to the chip. It's safe for us to clean up * the list buffers. */ void se_txeof(struct se_softc *sc) { struct ifnet *ifp = &sc->sc_ac.ac_if; struct se_list_data *ld = &sc->se_ldata; struct se_chain_data *cd = &sc->se_cdata; struct se_desc *cur_tx; uint32_t txstat; uint i; /* * Go through our tx list and free mbufs for those * frames that have been transmitted. */ bus_dmamap_sync(sc->sc_dmat, ld->se_tx_dmamap, 0, SE_TX_RING_SZ, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); for (i = cd->se_tx_cons; cd->se_tx_cnt > 0; cd->se_tx_cnt--, SE_INC(i, SE_TX_RING_CNT)) { cur_tx = &ld->se_tx_ring[i]; txstat = letoh32(cur_tx->se_cmdsts); if ((txstat & TDC_OWN) != 0) break; ifq_clr_oactive(&ifp->if_snd); if (SE_TX_ERROR(txstat) != 0) { if (ifp->if_flags & IFF_DEBUG) printf("%s: tx error %b\n", ifp->if_xname, txstat, TX_ERR_BITS); ifp->if_oerrors++; /* TODO: better error differentiation */ } if (cd->se_tx_mbuf[i] != NULL) { bus_dmamap_sync(sc->sc_dmat, cd->se_tx_map[i], 0, cd->se_tx_map[i]->dm_mapsize, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->sc_dmat, cd->se_tx_map[i]); m_free(cd->se_tx_mbuf[i]); cd->se_tx_mbuf[i] = NULL; } cur_tx->se_sts_size = 0; cur_tx->se_cmdsts = 0; cur_tx->se_ptr = 0; cur_tx->se_flags &= htole32(RING_END); bus_dmamap_sync(sc->sc_dmat, ld->se_tx_dmamap, i * sizeof(*cur_tx), sizeof(*cur_tx), BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } cd->se_tx_cons = i; if (cd->se_tx_cnt == 0) ifp->if_timer = 0; } void se_tick(void *xsc) { struct se_softc *sc = xsc; struct mii_data *mii; struct ifnet *ifp = &sc->sc_ac.ac_if; int s; s = splnet(); mii = &sc->sc_mii; mii_tick(mii); if ((sc->sc_flags & SE_FLAG_LINK) == 0) { se_miibus_statchg(&sc->sc_dev); if ((sc->sc_flags & SE_FLAG_LINK) != 0 && !ifq_empty(&ifp->if_snd)) se_start(ifp); } splx(s); timeout_add_sec(&sc->sc_tick_tmo, 1); } int se_intr(void *arg) { struct se_softc *sc = arg; struct ifnet *ifp = &sc->sc_ac.ac_if; uint32_t status; status = CSR_READ_4(sc, IntrStatus); if (status == 0xffffffff || (status & SE_INTRS) == 0) { /* Not ours. */ return 0; } /* Ack interrupts/ */ CSR_WRITE_4(sc, IntrStatus, status); /* Disable further interrupts. */ CSR_WRITE_4(sc, IntrMask, 0); for (;;) { if ((ifp->if_flags & IFF_RUNNING) == 0) break; if ((status & (INTR_RX_DONE | INTR_RX_IDLE)) != 0) { se_rxeof(sc); /* Wakeup Rx MAC. */ if ((status & INTR_RX_IDLE) != 0) CSR_WRITE_4(sc, RX_CTL, 0x1a00 | 0x000c | RX_CTL_POLL | RX_CTL_ENB); } if ((status & (INTR_TX_DONE | INTR_TX_IDLE)) != 0) se_txeof(sc); status = CSR_READ_4(sc, IntrStatus); if ((status & SE_INTRS) == 0) break; /* Ack interrupts. */ CSR_WRITE_4(sc, IntrStatus, status); } if ((ifp->if_flags & IFF_RUNNING) != 0) { /* Re-enable interrupts */ CSR_WRITE_4(sc, IntrMask, SE_INTRS); if (!ifq_empty(&ifp->if_snd)) se_start(ifp); } return 1; } /* * Encapsulate an mbuf chain in a descriptor by coupling the mbuf data * pointers to the fragment pointers. */ int se_encap(struct se_softc *sc, struct mbuf *m_head, uint32_t *txidx) { #ifdef SE_DEBUG struct ifnet *ifp = &sc->sc_ac.ac_if; #endif struct mbuf *m; struct se_list_data *ld = &sc->se_ldata; struct se_chain_data *cd = &sc->se_cdata; struct se_desc *desc; uint i, cnt = 0; int rc; /* * If there's no way we can send any packets, return now. */ if (SE_TX_RING_CNT - cd->se_tx_cnt < 2) { #ifdef SE_DEBUG if (ifp->if_flags & IFF_DEBUG) printf("%s: encap failed, not enough TX desc\n", ifp->if_xname); #endif return ENOBUFS; } if (m_defrag(m_head, M_DONTWAIT) != 0) { #ifdef SE_DEBUG if (ifp->if_flags & IFF_DEBUG) printf("%s: m_defrag failed\n", ifp->if_xname); #endif return ENOBUFS; /* XXX should not be fatal */ } /* * Start packing the mbufs in this chain into * the fragment pointers. Stop when we run out * of fragments or hit the end of the mbuf chain. */ i = *txidx; for (m = m_head; m != NULL; m = m->m_next) { if (m->m_len == 0) continue; if ((SE_TX_RING_CNT - (cd->se_tx_cnt + cnt)) < 2) { #ifdef SE_DEBUG if (ifp->if_flags & IFF_DEBUG) printf("%s: encap failed, not enough TX desc\n", ifp->if_xname); #endif return ENOBUFS; } cd->se_tx_mbuf[i] = m; rc = bus_dmamap_load_mbuf(sc->sc_dmat, cd->se_tx_map[i], m, BUS_DMA_NOWAIT); if (rc != 0) return ENOBUFS; KASSERT(cd->se_tx_map[i]->dm_nsegs == 1); bus_dmamap_sync(sc->sc_dmat, cd->se_tx_map[i], 0, cd->se_tx_map[i]->dm_mapsize, BUS_DMASYNC_PREWRITE); desc = &ld->se_tx_ring[i]; desc->se_sts_size = htole32(cd->se_tx_map[i]->dm_segs->ds_len); desc->se_ptr = htole32((uint32_t)cd->se_tx_map[i]->dm_segs->ds_addr); desc->se_flags = htole32(cd->se_tx_map[i]->dm_segs->ds_len); if (i == SE_TX_RING_CNT - 1) desc->se_flags |= htole32(RING_END); desc->se_cmdsts = htole32(TDC_OWN | TDC_INTR | TDC_DEF | TDC_CRC | TDC_PAD | TDC_BST); bus_dmamap_sync(sc->sc_dmat, ld->se_tx_dmamap, i * sizeof(*desc), sizeof(*desc), BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); SE_INC(i, SE_TX_RING_CNT); cnt++; } /* can't happen */ if (m != NULL) return ENOBUFS; cd->se_tx_cnt += cnt; *txidx = i; return 0; } /* * Main transmit routine. To avoid having to do mbuf copies, we put pointers * to the mbuf data regions directly in the transmit lists. We also save a * copy of the pointers since the transmit list fragment pointers are * physical addresses. */ void se_start(struct ifnet *ifp) { struct se_softc *sc = ifp->if_softc; struct mbuf *m_head = NULL; struct se_chain_data *cd = &sc->se_cdata; uint i, queued = 0; if ((sc->sc_flags & SE_FLAG_LINK) == 0 || !(ifp->if_flags & IFF_RUNNING) || ifq_is_oactive(&ifp->if_snd)) { #ifdef SE_DEBUG if (ifp->if_flags & IFF_DEBUG) printf("%s: can't tx, flags 0x%x 0x%04x\n", ifp->if_xname, sc->sc_flags, (uint)ifp->if_flags); #endif return; } i = cd->se_tx_prod; while (cd->se_tx_mbuf[i] == NULL) { m_head = ifq_deq_begin(&ifp->if_snd); if (m_head == NULL) break; if (se_encap(sc, m_head, &i) != 0) { ifq_deq_rollback(&ifp->if_snd, m_head); ifq_set_oactive(&ifp->if_snd); break; } /* now we are committed to transmit the packet */ ifq_deq_commit(&ifp->if_snd, m_head); queued++; /* * If there's a BPF listener, bounce a copy of this frame * to him. */ #if NBPFILTER > 0 if (ifp->if_bpf) bpf_mtap(ifp->if_bpf, m_head, BPF_DIRECTION_OUT); #endif } if (queued > 0) { /* Transmit */ cd->se_tx_prod = i; CSR_WRITE_4(sc, TX_CTL, 0x1a00 | TX_CTL_ENB | TX_CTL_POLL); ifp->if_timer = 5; } } int se_init(struct ifnet *ifp) { struct se_softc *sc = ifp->if_softc; uint16_t rxfilt; int i; splassert(IPL_NET); /* * Cancel pending I/O and free all RX/TX buffers. */ se_stop(sc); se_reset(sc); /* Init circular RX list. */ if (se_list_rx_init(sc) == ENOBUFS) { se_stop(sc); /* XXX necessary? */ return ENOBUFS; } /* Init TX descriptors. */ se_list_tx_init(sc); /* * Load the address of the RX and TX lists. */ CSR_WRITE_4(sc, TX_DESC, (uint32_t)sc->se_ldata.se_tx_dmamap->dm_segs[0].ds_addr); CSR_WRITE_4(sc, RX_DESC, (uint32_t)sc->se_ldata.se_rx_dmamap->dm_segs[0].ds_addr); CSR_WRITE_4(sc, TxMacControl, 0x60); CSR_WRITE_4(sc, RxWakeOnLan, 0); CSR_WRITE_4(sc, RxWakeOnLanData, 0); CSR_WRITE_2(sc, RxMPSControl, ETHER_MAX_LEN + ETHER_VLAN_ENCAP_LEN + SE_RX_PAD_BYTES); for (i = 0; i < ETHER_ADDR_LEN; i++) CSR_WRITE_1(sc, RxMacAddr + i, sc->sc_ac.ac_enaddr[i]); /* Configure RX MAC. */ rxfilt = RXMAC_STRIP_FCS | RXMAC_PAD_ENB | RXMAC_CSUM_ENB; CSR_WRITE_2(sc, RxMacControl, rxfilt); /* Program promiscuous mode and multicast filters. */ se_iff(sc); /* * Clear and enable interrupts. */ CSR_WRITE_4(sc, IntrStatus, 0xFFFFFFFF); CSR_WRITE_4(sc, IntrMask, SE_INTRS); /* Enable receiver and transmitter. */ CSR_WRITE_4(sc, TX_CTL, 0x1a00 | TX_CTL_ENB); CSR_WRITE_4(sc, RX_CTL, 0x1a00 | 0x000c | RX_CTL_POLL | RX_CTL_ENB); ifp->if_flags |= IFF_RUNNING; ifq_clr_oactive(&ifp->if_snd); sc->sc_flags &= ~SE_FLAG_LINK; mii_mediachg(&sc->sc_mii); timeout_add_sec(&sc->sc_tick_tmo, 1); return 0; } /* * Set media options. */ int se_ifmedia_upd(struct ifnet *ifp) { struct se_softc *sc = ifp->if_softc; struct mii_data *mii; mii = &sc->sc_mii; sc->sc_flags &= ~SE_FLAG_LINK; if (mii->mii_instance) { struct mii_softc *miisc; LIST_FOREACH(miisc, &mii->mii_phys, mii_list) mii_phy_reset(miisc); } return mii_mediachg(mii); } /* * Report current media status. */ void se_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct se_softc *sc = ifp->if_softc; struct mii_data *mii; mii = &sc->sc_mii; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } int se_ioctl(struct ifnet *ifp, u_long command, caddr_t data) { struct se_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *) data; int s, rc = 0; s = splnet(); switch (command) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; if ((ifp->if_flags & IFF_RUNNING) == 0) rc = se_init(ifp); break; case SIOCSIFFLAGS: if (ifp->if_flags & IFF_UP) { if (ifp->if_flags & IFF_RUNNING) rc = ENETRESET; else rc = se_init(ifp); } else { if (ifp->if_flags & IFF_RUNNING) se_stop(sc); } break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: rc = ifmedia_ioctl(ifp, ifr, &sc->sc_mii.mii_media, command); break; default: rc = ether_ioctl(ifp, &sc->sc_ac, command, data); break; } if (rc == ENETRESET) { if (ifp->if_flags & IFF_RUNNING) se_iff(sc); rc = 0; } splx(s); return rc; } void se_watchdog(struct ifnet *ifp) { struct se_softc *sc = ifp->if_softc; int s; printf("%s: watchdog timeout\n", sc->sc_dev.dv_xname); ifp->if_oerrors++; s = splnet(); se_init(ifp); if (!ifq_empty(&ifp->if_snd)) se_start(ifp); splx(s); } /* * Stop the adapter and free any mbufs allocated to the * RX and TX lists. */ void se_stop(struct se_softc *sc) { struct ifnet *ifp = &sc->sc_ac.ac_if; ifp->if_timer = 0; ifp->if_flags &= ~IFF_RUNNING; ifq_clr_oactive(&ifp->if_snd); timeout_del(&sc->sc_tick_tmo); mii_down(&sc->sc_mii); CSR_WRITE_4(sc, IntrMask, 0); CSR_READ_4(sc, IntrMask); CSR_WRITE_4(sc, IntrStatus, 0xffffffff); /* Stop TX/RX MAC. */ CSR_WRITE_4(sc, TX_CTL, 0x1a00); CSR_WRITE_4(sc, RX_CTL, 0x1a00); /* XXX Can we assume active DMA cycles gone? */ DELAY(2000); CSR_WRITE_4(sc, IntrMask, 0); CSR_WRITE_4(sc, IntrStatus, 0xffffffff); sc->sc_flags &= ~SE_FLAG_LINK; se_list_rx_free(sc); se_list_tx_free(sc); }