Index: head/sys/dev/age/if_age.c =================================================================== --- head/sys/dev/age/if_age.c (revision 295734) +++ head/sys/dev/age/if_age.c (revision 295735) @@ -1,3344 +1,3344 @@ /*- * Copyright (c) 2008, Pyun YongHyeon * 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 unmodified, 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* Driver for Attansic Technology Corp. L1 Gigabit Ethernet. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* "device miibus" required. See GENERIC if you get errors here. */ #include "miibus_if.h" #define AGE_CSUM_FEATURES (CSUM_TCP | CSUM_UDP) MODULE_DEPEND(age, pci, 1, 1, 1); MODULE_DEPEND(age, ether, 1, 1, 1); MODULE_DEPEND(age, miibus, 1, 1, 1); /* Tunables. */ static int msi_disable = 0; static int msix_disable = 0; TUNABLE_INT("hw.age.msi_disable", &msi_disable); TUNABLE_INT("hw.age.msix_disable", &msix_disable); /* * Devices supported by this driver. */ static struct age_dev { uint16_t age_vendorid; uint16_t age_deviceid; const char *age_name; } age_devs[] = { { VENDORID_ATTANSIC, DEVICEID_ATTANSIC_L1, "Attansic Technology Corp, L1 Gigabit Ethernet" }, }; static int age_miibus_readreg(device_t, int, int); static int age_miibus_writereg(device_t, int, int, int); static void age_miibus_statchg(device_t); static void age_mediastatus(struct ifnet *, struct ifmediareq *); static int age_mediachange(struct ifnet *); static int age_probe(device_t); static void age_get_macaddr(struct age_softc *); static void age_phy_reset(struct age_softc *); static int age_attach(device_t); static int age_detach(device_t); static void age_sysctl_node(struct age_softc *); static void age_dmamap_cb(void *, bus_dma_segment_t *, int, int); static int age_check_boundary(struct age_softc *); static int age_dma_alloc(struct age_softc *); static void age_dma_free(struct age_softc *); static int age_shutdown(device_t); static void age_setwol(struct age_softc *); static int age_suspend(device_t); static int age_resume(device_t); static int age_encap(struct age_softc *, struct mbuf **); static void age_start(struct ifnet *); static void age_start_locked(struct ifnet *); static void age_watchdog(struct age_softc *); static int age_ioctl(struct ifnet *, u_long, caddr_t); static void age_mac_config(struct age_softc *); static void age_link_task(void *, int); static void age_stats_update(struct age_softc *); static int age_intr(void *); static void age_int_task(void *, int); static void age_txintr(struct age_softc *, int); static void age_rxeof(struct age_softc *sc, struct rx_rdesc *); static int age_rxintr(struct age_softc *, int, int); static void age_tick(void *); static void age_reset(struct age_softc *); static void age_init(void *); static void age_init_locked(struct age_softc *); static void age_stop(struct age_softc *); static void age_stop_txmac(struct age_softc *); static void age_stop_rxmac(struct age_softc *); static void age_init_tx_ring(struct age_softc *); static int age_init_rx_ring(struct age_softc *); static void age_init_rr_ring(struct age_softc *); static void age_init_cmb_block(struct age_softc *); static void age_init_smb_block(struct age_softc *); #ifndef __NO_STRICT_ALIGNMENT static struct mbuf *age_fixup_rx(struct ifnet *, struct mbuf *); #endif static int age_newbuf(struct age_softc *, struct age_rxdesc *); static void age_rxvlan(struct age_softc *); static void age_rxfilter(struct age_softc *); static int sysctl_age_stats(SYSCTL_HANDLER_ARGS); static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int, int); static int sysctl_hw_age_proc_limit(SYSCTL_HANDLER_ARGS); static int sysctl_hw_age_int_mod(SYSCTL_HANDLER_ARGS); static device_method_t age_methods[] = { /* Device interface. */ DEVMETHOD(device_probe, age_probe), DEVMETHOD(device_attach, age_attach), DEVMETHOD(device_detach, age_detach), DEVMETHOD(device_shutdown, age_shutdown), DEVMETHOD(device_suspend, age_suspend), DEVMETHOD(device_resume, age_resume), /* MII interface. */ DEVMETHOD(miibus_readreg, age_miibus_readreg), DEVMETHOD(miibus_writereg, age_miibus_writereg), DEVMETHOD(miibus_statchg, age_miibus_statchg), { NULL, NULL } }; static driver_t age_driver = { "age", age_methods, sizeof(struct age_softc) }; static devclass_t age_devclass; DRIVER_MODULE(age, pci, age_driver, age_devclass, 0, 0); DRIVER_MODULE(miibus, age, miibus_driver, miibus_devclass, 0, 0); static struct resource_spec age_res_spec_mem[] = { { SYS_RES_MEMORY, PCIR_BAR(0), RF_ACTIVE }, { -1, 0, 0 } }; static struct resource_spec age_irq_spec_legacy[] = { { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0, 0 } }; static struct resource_spec age_irq_spec_msi[] = { { SYS_RES_IRQ, 1, RF_ACTIVE }, { -1, 0, 0 } }; static struct resource_spec age_irq_spec_msix[] = { { SYS_RES_IRQ, 1, RF_ACTIVE }, { -1, 0, 0 } }; /* * Read a PHY register on the MII of the L1. */ static int age_miibus_readreg(device_t dev, int phy, int reg) { struct age_softc *sc; uint32_t v; int i; sc = device_get_softc(dev); CSR_WRITE_4(sc, AGE_MDIO, MDIO_OP_EXECUTE | MDIO_OP_READ | MDIO_SUP_PREAMBLE | MDIO_CLK_25_4 | MDIO_REG_ADDR(reg)); for (i = AGE_PHY_TIMEOUT; i > 0; i--) { DELAY(1); v = CSR_READ_4(sc, AGE_MDIO); if ((v & (MDIO_OP_EXECUTE | MDIO_OP_BUSY)) == 0) break; } if (i == 0) { device_printf(sc->age_dev, "phy read timeout : %d\n", reg); return (0); } return ((v & MDIO_DATA_MASK) >> MDIO_DATA_SHIFT); } /* * Write a PHY register on the MII of the L1. */ static int age_miibus_writereg(device_t dev, int phy, int reg, int val) { struct age_softc *sc; uint32_t v; int i; sc = device_get_softc(dev); CSR_WRITE_4(sc, AGE_MDIO, MDIO_OP_EXECUTE | MDIO_OP_WRITE | (val & MDIO_DATA_MASK) << MDIO_DATA_SHIFT | MDIO_SUP_PREAMBLE | MDIO_CLK_25_4 | MDIO_REG_ADDR(reg)); for (i = AGE_PHY_TIMEOUT; i > 0; i--) { DELAY(1); v = CSR_READ_4(sc, AGE_MDIO); if ((v & (MDIO_OP_EXECUTE | MDIO_OP_BUSY)) == 0) break; } if (i == 0) device_printf(sc->age_dev, "phy write timeout : %d\n", reg); return (0); } /* * Callback from MII layer when media changes. */ static void age_miibus_statchg(device_t dev) { struct age_softc *sc; sc = device_get_softc(dev); taskqueue_enqueue(taskqueue_swi, &sc->age_link_task); } /* * Get the current interface media status. */ static void age_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr) { struct age_softc *sc; struct mii_data *mii; sc = ifp->if_softc; AGE_LOCK(sc); mii = device_get_softc(sc->age_miibus); mii_pollstat(mii); ifmr->ifm_status = mii->mii_media_status; ifmr->ifm_active = mii->mii_media_active; AGE_UNLOCK(sc); } /* * Set hardware to newly-selected media. */ static int age_mediachange(struct ifnet *ifp) { struct age_softc *sc; struct mii_data *mii; struct mii_softc *miisc; int error; sc = ifp->if_softc; AGE_LOCK(sc); mii = device_get_softc(sc->age_miibus); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); AGE_UNLOCK(sc); return (error); } static int age_probe(device_t dev) { struct age_dev *sp; int i; uint16_t vendor, devid; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); sp = age_devs; for (i = 0; i < sizeof(age_devs) / sizeof(age_devs[0]); i++, sp++) { if (vendor == sp->age_vendorid && devid == sp->age_deviceid) { device_set_desc(dev, sp->age_name); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static void age_get_macaddr(struct age_softc *sc) { uint32_t ea[2], reg; int i, vpdc; reg = CSR_READ_4(sc, AGE_SPI_CTRL); if ((reg & SPI_VPD_ENB) != 0) { /* Get VPD stored in TWSI EEPROM. */ reg &= ~SPI_VPD_ENB; CSR_WRITE_4(sc, AGE_SPI_CTRL, reg); } if (pci_find_cap(sc->age_dev, PCIY_VPD, &vpdc) == 0) { /* * PCI VPD capability found, let TWSI reload EEPROM. * This will set ethernet address of controller. */ CSR_WRITE_4(sc, AGE_TWSI_CTRL, CSR_READ_4(sc, AGE_TWSI_CTRL) | TWSI_CTRL_SW_LD_START); for (i = 100; i > 0; i--) { DELAY(1000); reg = CSR_READ_4(sc, AGE_TWSI_CTRL); if ((reg & TWSI_CTRL_SW_LD_START) == 0) break; } if (i == 0) device_printf(sc->age_dev, "reloading EEPROM timeout!\n"); } else { if (bootverbose) device_printf(sc->age_dev, "PCI VPD capability not found!\n"); } ea[0] = CSR_READ_4(sc, AGE_PAR0); ea[1] = CSR_READ_4(sc, AGE_PAR1); sc->age_eaddr[0] = (ea[1] >> 8) & 0xFF; sc->age_eaddr[1] = (ea[1] >> 0) & 0xFF; sc->age_eaddr[2] = (ea[0] >> 24) & 0xFF; sc->age_eaddr[3] = (ea[0] >> 16) & 0xFF; sc->age_eaddr[4] = (ea[0] >> 8) & 0xFF; sc->age_eaddr[5] = (ea[0] >> 0) & 0xFF; } static void age_phy_reset(struct age_softc *sc) { uint16_t reg, pn; int i, linkup; /* Reset PHY. */ CSR_WRITE_4(sc, AGE_GPHY_CTRL, GPHY_CTRL_RST); DELAY(2000); CSR_WRITE_4(sc, AGE_GPHY_CTRL, GPHY_CTRL_CLR); DELAY(2000); #define ATPHY_DBG_ADDR 0x1D #define ATPHY_DBG_DATA 0x1E #define ATPHY_CDTC 0x16 #define PHY_CDTC_ENB 0x0001 #define PHY_CDTC_POFF 8 #define ATPHY_CDTS 0x1C #define PHY_CDTS_STAT_OK 0x0000 #define PHY_CDTS_STAT_SHORT 0x0100 #define PHY_CDTS_STAT_OPEN 0x0200 #define PHY_CDTS_STAT_INVAL 0x0300 #define PHY_CDTS_STAT_MASK 0x0300 /* Check power saving mode. Magic from Linux. */ age_miibus_writereg(sc->age_dev, sc->age_phyaddr, MII_BMCR, BMCR_RESET); for (linkup = 0, pn = 0; pn < 4; pn++) { age_miibus_writereg(sc->age_dev, sc->age_phyaddr, ATPHY_CDTC, (pn << PHY_CDTC_POFF) | PHY_CDTC_ENB); for (i = 200; i > 0; i--) { DELAY(1000); reg = age_miibus_readreg(sc->age_dev, sc->age_phyaddr, ATPHY_CDTC); if ((reg & PHY_CDTC_ENB) == 0) break; } DELAY(1000); reg = age_miibus_readreg(sc->age_dev, sc->age_phyaddr, ATPHY_CDTS); if ((reg & PHY_CDTS_STAT_MASK) != PHY_CDTS_STAT_OPEN) { linkup++; break; } } age_miibus_writereg(sc->age_dev, sc->age_phyaddr, MII_BMCR, BMCR_RESET | BMCR_AUTOEN | BMCR_STARTNEG); if (linkup == 0) { age_miibus_writereg(sc->age_dev, sc->age_phyaddr, ATPHY_DBG_ADDR, 0); age_miibus_writereg(sc->age_dev, sc->age_phyaddr, ATPHY_DBG_DATA, 0x124E); age_miibus_writereg(sc->age_dev, sc->age_phyaddr, ATPHY_DBG_ADDR, 1); reg = age_miibus_readreg(sc->age_dev, sc->age_phyaddr, ATPHY_DBG_DATA); age_miibus_writereg(sc->age_dev, sc->age_phyaddr, ATPHY_DBG_DATA, reg | 0x03); /* XXX */ DELAY(1500 * 1000); age_miibus_writereg(sc->age_dev, sc->age_phyaddr, ATPHY_DBG_ADDR, 0); age_miibus_writereg(sc->age_dev, sc->age_phyaddr, ATPHY_DBG_DATA, 0x024E); } #undef ATPHY_DBG_ADDR #undef ATPHY_DBG_DATA #undef ATPHY_CDTC #undef PHY_CDTC_ENB #undef PHY_CDTC_POFF #undef ATPHY_CDTS #undef PHY_CDTS_STAT_OK #undef PHY_CDTS_STAT_SHORT #undef PHY_CDTS_STAT_OPEN #undef PHY_CDTS_STAT_INVAL #undef PHY_CDTS_STAT_MASK } static int age_attach(device_t dev) { struct age_softc *sc; struct ifnet *ifp; uint16_t burst; int error, i, msic, msixc, pmc; error = 0; sc = device_get_softc(dev); sc->age_dev = dev; mtx_init(&sc->age_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init_mtx(&sc->age_tick_ch, &sc->age_mtx, 0); TASK_INIT(&sc->age_int_task, 0, age_int_task, sc); TASK_INIT(&sc->age_link_task, 0, age_link_task, sc); /* Map the device. */ pci_enable_busmaster(dev); sc->age_res_spec = age_res_spec_mem; sc->age_irq_spec = age_irq_spec_legacy; error = bus_alloc_resources(dev, sc->age_res_spec, sc->age_res); if (error != 0) { device_printf(dev, "cannot allocate memory resources.\n"); goto fail; } /* Set PHY address. */ sc->age_phyaddr = AGE_PHY_ADDR; /* Reset PHY. */ age_phy_reset(sc); /* Reset the ethernet controller. */ age_reset(sc); /* Get PCI and chip id/revision. */ sc->age_rev = pci_get_revid(dev); sc->age_chip_rev = CSR_READ_4(sc, AGE_MASTER_CFG) >> MASTER_CHIP_REV_SHIFT; if (bootverbose) { device_printf(dev, "PCI device revision : 0x%04x\n", sc->age_rev); device_printf(dev, "Chip id/revision : 0x%04x\n", sc->age_chip_rev); } /* * XXX * Unintialized hardware returns an invalid chip id/revision * as well as 0xFFFFFFFF for Tx/Rx fifo length. It seems that * unplugged cable results in putting hardware into automatic * power down mode which in turn returns invalld chip revision. */ if (sc->age_chip_rev == 0xFFFF) { device_printf(dev,"invalid chip revision : 0x%04x -- " "not initialized?\n", sc->age_chip_rev); error = ENXIO; goto fail; } device_printf(dev, "%d Tx FIFO, %d Rx FIFO\n", CSR_READ_4(sc, AGE_SRAM_TX_FIFO_LEN), CSR_READ_4(sc, AGE_SRAM_RX_FIFO_LEN)); /* Allocate IRQ resources. */ msixc = pci_msix_count(dev); msic = pci_msi_count(dev); if (bootverbose) { device_printf(dev, "MSIX count : %d\n", msixc); device_printf(dev, "MSI count : %d\n", msic); } /* Prefer MSIX over MSI. */ if (msix_disable == 0 || msi_disable == 0) { if (msix_disable == 0 && msixc == AGE_MSIX_MESSAGES && pci_alloc_msix(dev, &msixc) == 0) { if (msic == AGE_MSIX_MESSAGES) { device_printf(dev, "Using %d MSIX messages.\n", msixc); sc->age_flags |= AGE_FLAG_MSIX; sc->age_irq_spec = age_irq_spec_msix; } else pci_release_msi(dev); } if (msi_disable == 0 && (sc->age_flags & AGE_FLAG_MSIX) == 0 && msic == AGE_MSI_MESSAGES && pci_alloc_msi(dev, &msic) == 0) { if (msic == AGE_MSI_MESSAGES) { device_printf(dev, "Using %d MSI messages.\n", msic); sc->age_flags |= AGE_FLAG_MSI; sc->age_irq_spec = age_irq_spec_msi; } else pci_release_msi(dev); } } error = bus_alloc_resources(dev, sc->age_irq_spec, sc->age_irq); if (error != 0) { device_printf(dev, "cannot allocate IRQ resources.\n"); goto fail; } /* Get DMA parameters from PCIe device control register. */ if (pci_find_cap(dev, PCIY_EXPRESS, &i) == 0) { sc->age_flags |= AGE_FLAG_PCIE; burst = pci_read_config(dev, i + 0x08, 2); /* Max read request size. */ sc->age_dma_rd_burst = ((burst >> 12) & 0x07) << DMA_CFG_RD_BURST_SHIFT; /* Max payload size. */ sc->age_dma_wr_burst = ((burst >> 5) & 0x07) << DMA_CFG_WR_BURST_SHIFT; if (bootverbose) { device_printf(dev, "Read request size : %d bytes.\n", 128 << ((burst >> 12) & 0x07)); device_printf(dev, "TLP payload size : %d bytes.\n", 128 << ((burst >> 5) & 0x07)); } } else { sc->age_dma_rd_burst = DMA_CFG_RD_BURST_128; sc->age_dma_wr_burst = DMA_CFG_WR_BURST_128; } /* Create device sysctl node. */ age_sysctl_node(sc); - if ((error = age_dma_alloc(sc) != 0)) + if ((error = age_dma_alloc(sc)) != 0) goto fail; /* Load station address. */ age_get_macaddr(sc); ifp = sc->age_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(dev, "cannot allocate ifnet structure.\n"); error = ENXIO; goto fail; } ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = age_ioctl; ifp->if_start = age_start; ifp->if_init = age_init; ifp->if_snd.ifq_drv_maxlen = AGE_TX_RING_CNT - 1; IFQ_SET_MAXLEN(&ifp->if_snd, ifp->if_snd.ifq_drv_maxlen); IFQ_SET_READY(&ifp->if_snd); ifp->if_capabilities = IFCAP_HWCSUM | IFCAP_TSO4; ifp->if_hwassist = AGE_CSUM_FEATURES | CSUM_TSO; if (pci_find_cap(dev, PCIY_PMG, &pmc) == 0) { sc->age_flags |= AGE_FLAG_PMCAP; ifp->if_capabilities |= IFCAP_WOL_MAGIC | IFCAP_WOL_MCAST; } ifp->if_capenable = ifp->if_capabilities; /* Set up MII bus. */ error = mii_attach(dev, &sc->age_miibus, ifp, age_mediachange, age_mediastatus, BMSR_DEFCAPMASK, sc->age_phyaddr, MII_OFFSET_ANY, 0); if (error != 0) { device_printf(dev, "attaching PHYs failed\n"); goto fail; } ether_ifattach(ifp, sc->age_eaddr); /* VLAN capability setup. */ ifp->if_capabilities |= IFCAP_VLAN_MTU | IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_HWCSUM | IFCAP_VLAN_HWTSO; ifp->if_capenable = ifp->if_capabilities; /* Tell the upper layer(s) we support long frames. */ ifp->if_hdrlen = sizeof(struct ether_vlan_header); /* Create local taskq. */ sc->age_tq = taskqueue_create_fast("age_taskq", M_WAITOK, taskqueue_thread_enqueue, &sc->age_tq); if (sc->age_tq == NULL) { device_printf(dev, "could not create taskqueue.\n"); ether_ifdetach(ifp); error = ENXIO; goto fail; } taskqueue_start_threads(&sc->age_tq, 1, PI_NET, "%s taskq", device_get_nameunit(sc->age_dev)); if ((sc->age_flags & AGE_FLAG_MSIX) != 0) msic = AGE_MSIX_MESSAGES; else if ((sc->age_flags & AGE_FLAG_MSI) != 0) msic = AGE_MSI_MESSAGES; else msic = 1; for (i = 0; i < msic; i++) { error = bus_setup_intr(dev, sc->age_irq[i], INTR_TYPE_NET | INTR_MPSAFE, age_intr, NULL, sc, &sc->age_intrhand[i]); if (error != 0) break; } if (error != 0) { device_printf(dev, "could not set up interrupt handler.\n"); taskqueue_free(sc->age_tq); sc->age_tq = NULL; ether_ifdetach(ifp); goto fail; } fail: if (error != 0) age_detach(dev); return (error); } static int age_detach(device_t dev) { struct age_softc *sc; struct ifnet *ifp; int i, msic; sc = device_get_softc(dev); ifp = sc->age_ifp; if (device_is_attached(dev)) { AGE_LOCK(sc); sc->age_flags |= AGE_FLAG_DETACH; age_stop(sc); AGE_UNLOCK(sc); callout_drain(&sc->age_tick_ch); taskqueue_drain(sc->age_tq, &sc->age_int_task); taskqueue_drain(taskqueue_swi, &sc->age_link_task); ether_ifdetach(ifp); } if (sc->age_tq != NULL) { taskqueue_drain(sc->age_tq, &sc->age_int_task); taskqueue_free(sc->age_tq); sc->age_tq = NULL; } if (sc->age_miibus != NULL) { device_delete_child(dev, sc->age_miibus); sc->age_miibus = NULL; } bus_generic_detach(dev); age_dma_free(sc); if (ifp != NULL) { if_free(ifp); sc->age_ifp = NULL; } if ((sc->age_flags & AGE_FLAG_MSIX) != 0) msic = AGE_MSIX_MESSAGES; else if ((sc->age_flags & AGE_FLAG_MSI) != 0) msic = AGE_MSI_MESSAGES; else msic = 1; for (i = 0; i < msic; i++) { if (sc->age_intrhand[i] != NULL) { bus_teardown_intr(dev, sc->age_irq[i], sc->age_intrhand[i]); sc->age_intrhand[i] = NULL; } } bus_release_resources(dev, sc->age_irq_spec, sc->age_irq); if ((sc->age_flags & (AGE_FLAG_MSI | AGE_FLAG_MSIX)) != 0) pci_release_msi(dev); bus_release_resources(dev, sc->age_res_spec, sc->age_res); mtx_destroy(&sc->age_mtx); return (0); } static void age_sysctl_node(struct age_softc *sc) { int error; SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->age_dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->age_dev)), OID_AUTO, "stats", CTLTYPE_INT | CTLFLAG_RW, sc, 0, sysctl_age_stats, "I", "Statistics"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->age_dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->age_dev)), OID_AUTO, "int_mod", CTLTYPE_INT | CTLFLAG_RW, &sc->age_int_mod, 0, sysctl_hw_age_int_mod, "I", "age interrupt moderation"); /* Pull in device tunables. */ sc->age_int_mod = AGE_IM_TIMER_DEFAULT; error = resource_int_value(device_get_name(sc->age_dev), device_get_unit(sc->age_dev), "int_mod", &sc->age_int_mod); if (error == 0) { if (sc->age_int_mod < AGE_IM_TIMER_MIN || sc->age_int_mod > AGE_IM_TIMER_MAX) { device_printf(sc->age_dev, "int_mod value out of range; using default: %d\n", AGE_IM_TIMER_DEFAULT); sc->age_int_mod = AGE_IM_TIMER_DEFAULT; } } SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->age_dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->age_dev)), OID_AUTO, "process_limit", CTLTYPE_INT | CTLFLAG_RW, &sc->age_process_limit, 0, sysctl_hw_age_proc_limit, "I", "max number of Rx events to process"); /* Pull in device tunables. */ sc->age_process_limit = AGE_PROC_DEFAULT; error = resource_int_value(device_get_name(sc->age_dev), device_get_unit(sc->age_dev), "process_limit", &sc->age_process_limit); if (error == 0) { if (sc->age_process_limit < AGE_PROC_MIN || sc->age_process_limit > AGE_PROC_MAX) { device_printf(sc->age_dev, "process_limit value out of range; " "using default: %d\n", AGE_PROC_DEFAULT); sc->age_process_limit = AGE_PROC_DEFAULT; } } } struct age_dmamap_arg { bus_addr_t age_busaddr; }; static void age_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { struct age_dmamap_arg *ctx; if (error != 0) return; KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); ctx = (struct age_dmamap_arg *)arg; ctx->age_busaddr = segs[0].ds_addr; } /* * Attansic L1 controller have single register to specify high * address part of DMA blocks. So all descriptor structures and * DMA memory blocks should have the same high address of given * 4GB address space(i.e. crossing 4GB boundary is not allowed). */ static int age_check_boundary(struct age_softc *sc) { bus_addr_t rx_ring_end, rr_ring_end, tx_ring_end; bus_addr_t cmb_block_end, smb_block_end; /* Tx/Rx descriptor queue should reside within 4GB boundary. */ tx_ring_end = sc->age_rdata.age_tx_ring_paddr + AGE_TX_RING_SZ; rx_ring_end = sc->age_rdata.age_rx_ring_paddr + AGE_RX_RING_SZ; rr_ring_end = sc->age_rdata.age_rr_ring_paddr + AGE_RR_RING_SZ; cmb_block_end = sc->age_rdata.age_cmb_block_paddr + AGE_CMB_BLOCK_SZ; smb_block_end = sc->age_rdata.age_smb_block_paddr + AGE_SMB_BLOCK_SZ; if ((AGE_ADDR_HI(tx_ring_end) != AGE_ADDR_HI(sc->age_rdata.age_tx_ring_paddr)) || (AGE_ADDR_HI(rx_ring_end) != AGE_ADDR_HI(sc->age_rdata.age_rx_ring_paddr)) || (AGE_ADDR_HI(rr_ring_end) != AGE_ADDR_HI(sc->age_rdata.age_rr_ring_paddr)) || (AGE_ADDR_HI(cmb_block_end) != AGE_ADDR_HI(sc->age_rdata.age_cmb_block_paddr)) || (AGE_ADDR_HI(smb_block_end) != AGE_ADDR_HI(sc->age_rdata.age_smb_block_paddr))) return (EFBIG); if ((AGE_ADDR_HI(tx_ring_end) != AGE_ADDR_HI(rx_ring_end)) || (AGE_ADDR_HI(tx_ring_end) != AGE_ADDR_HI(rr_ring_end)) || (AGE_ADDR_HI(tx_ring_end) != AGE_ADDR_HI(cmb_block_end)) || (AGE_ADDR_HI(tx_ring_end) != AGE_ADDR_HI(smb_block_end))) return (EFBIG); return (0); } static int age_dma_alloc(struct age_softc *sc) { struct age_txdesc *txd; struct age_rxdesc *rxd; bus_addr_t lowaddr; struct age_dmamap_arg ctx; int error, i; lowaddr = BUS_SPACE_MAXADDR; again: /* Create parent ring/DMA block tag. */ error = bus_dma_tag_create( bus_get_dma_tag(sc->age_dev), /* parent */ 1, 0, /* alignment, boundary */ lowaddr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ 0, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->age_cdata.age_parent_tag); if (error != 0) { device_printf(sc->age_dev, "could not create parent DMA tag.\n"); goto fail; } /* Create tag for Tx ring. */ error = bus_dma_tag_create( sc->age_cdata.age_parent_tag, /* parent */ AGE_TX_RING_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ AGE_TX_RING_SZ, /* maxsize */ 1, /* nsegments */ AGE_TX_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->age_cdata.age_tx_ring_tag); if (error != 0) { device_printf(sc->age_dev, "could not create Tx ring DMA tag.\n"); goto fail; } /* Create tag for Rx ring. */ error = bus_dma_tag_create( sc->age_cdata.age_parent_tag, /* parent */ AGE_RX_RING_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ AGE_RX_RING_SZ, /* maxsize */ 1, /* nsegments */ AGE_RX_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->age_cdata.age_rx_ring_tag); if (error != 0) { device_printf(sc->age_dev, "could not create Rx ring DMA tag.\n"); goto fail; } /* Create tag for Rx return ring. */ error = bus_dma_tag_create( sc->age_cdata.age_parent_tag, /* parent */ AGE_RR_RING_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ AGE_RR_RING_SZ, /* maxsize */ 1, /* nsegments */ AGE_RR_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->age_cdata.age_rr_ring_tag); if (error != 0) { device_printf(sc->age_dev, "could not create Rx return ring DMA tag.\n"); goto fail; } /* Create tag for coalesing message block. */ error = bus_dma_tag_create( sc->age_cdata.age_parent_tag, /* parent */ AGE_CMB_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ AGE_CMB_BLOCK_SZ, /* maxsize */ 1, /* nsegments */ AGE_CMB_BLOCK_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->age_cdata.age_cmb_block_tag); if (error != 0) { device_printf(sc->age_dev, "could not create CMB DMA tag.\n"); goto fail; } /* Create tag for statistics message block. */ error = bus_dma_tag_create( sc->age_cdata.age_parent_tag, /* parent */ AGE_SMB_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ AGE_SMB_BLOCK_SZ, /* maxsize */ 1, /* nsegments */ AGE_SMB_BLOCK_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->age_cdata.age_smb_block_tag); if (error != 0) { device_printf(sc->age_dev, "could not create SMB DMA tag.\n"); goto fail; } /* Allocate DMA'able memory and load the DMA map. */ error = bus_dmamem_alloc(sc->age_cdata.age_tx_ring_tag, (void **)&sc->age_rdata.age_tx_ring, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->age_cdata.age_tx_ring_map); if (error != 0) { device_printf(sc->age_dev, "could not allocate DMA'able memory for Tx ring.\n"); goto fail; } ctx.age_busaddr = 0; error = bus_dmamap_load(sc->age_cdata.age_tx_ring_tag, sc->age_cdata.age_tx_ring_map, sc->age_rdata.age_tx_ring, AGE_TX_RING_SZ, age_dmamap_cb, &ctx, 0); if (error != 0 || ctx.age_busaddr == 0) { device_printf(sc->age_dev, "could not load DMA'able memory for Tx ring.\n"); goto fail; } sc->age_rdata.age_tx_ring_paddr = ctx.age_busaddr; /* Rx ring */ error = bus_dmamem_alloc(sc->age_cdata.age_rx_ring_tag, (void **)&sc->age_rdata.age_rx_ring, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->age_cdata.age_rx_ring_map); if (error != 0) { device_printf(sc->age_dev, "could not allocate DMA'able memory for Rx ring.\n"); goto fail; } ctx.age_busaddr = 0; error = bus_dmamap_load(sc->age_cdata.age_rx_ring_tag, sc->age_cdata.age_rx_ring_map, sc->age_rdata.age_rx_ring, AGE_RX_RING_SZ, age_dmamap_cb, &ctx, 0); if (error != 0 || ctx.age_busaddr == 0) { device_printf(sc->age_dev, "could not load DMA'able memory for Rx ring.\n"); goto fail; } sc->age_rdata.age_rx_ring_paddr = ctx.age_busaddr; /* Rx return ring */ error = bus_dmamem_alloc(sc->age_cdata.age_rr_ring_tag, (void **)&sc->age_rdata.age_rr_ring, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->age_cdata.age_rr_ring_map); if (error != 0) { device_printf(sc->age_dev, "could not allocate DMA'able memory for Rx return ring.\n"); goto fail; } ctx.age_busaddr = 0; error = bus_dmamap_load(sc->age_cdata.age_rr_ring_tag, sc->age_cdata.age_rr_ring_map, sc->age_rdata.age_rr_ring, AGE_RR_RING_SZ, age_dmamap_cb, &ctx, 0); if (error != 0 || ctx.age_busaddr == 0) { device_printf(sc->age_dev, "could not load DMA'able memory for Rx return ring.\n"); goto fail; } sc->age_rdata.age_rr_ring_paddr = ctx.age_busaddr; /* CMB block */ error = bus_dmamem_alloc(sc->age_cdata.age_cmb_block_tag, (void **)&sc->age_rdata.age_cmb_block, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->age_cdata.age_cmb_block_map); if (error != 0) { device_printf(sc->age_dev, "could not allocate DMA'able memory for CMB block.\n"); goto fail; } ctx.age_busaddr = 0; error = bus_dmamap_load(sc->age_cdata.age_cmb_block_tag, sc->age_cdata.age_cmb_block_map, sc->age_rdata.age_cmb_block, AGE_CMB_BLOCK_SZ, age_dmamap_cb, &ctx, 0); if (error != 0 || ctx.age_busaddr == 0) { device_printf(sc->age_dev, "could not load DMA'able memory for CMB block.\n"); goto fail; } sc->age_rdata.age_cmb_block_paddr = ctx.age_busaddr; /* SMB block */ error = bus_dmamem_alloc(sc->age_cdata.age_smb_block_tag, (void **)&sc->age_rdata.age_smb_block, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->age_cdata.age_smb_block_map); if (error != 0) { device_printf(sc->age_dev, "could not allocate DMA'able memory for SMB block.\n"); goto fail; } ctx.age_busaddr = 0; error = bus_dmamap_load(sc->age_cdata.age_smb_block_tag, sc->age_cdata.age_smb_block_map, sc->age_rdata.age_smb_block, AGE_SMB_BLOCK_SZ, age_dmamap_cb, &ctx, 0); if (error != 0 || ctx.age_busaddr == 0) { device_printf(sc->age_dev, "could not load DMA'able memory for SMB block.\n"); goto fail; } sc->age_rdata.age_smb_block_paddr = ctx.age_busaddr; /* * All ring buffer and DMA blocks should have the same * high address part of 64bit DMA address space. */ if (lowaddr != BUS_SPACE_MAXADDR_32BIT && (error = age_check_boundary(sc)) != 0) { device_printf(sc->age_dev, "4GB boundary crossed, " "switching to 32bit DMA addressing mode.\n"); age_dma_free(sc); /* Limit DMA address space to 32bit and try again. */ lowaddr = BUS_SPACE_MAXADDR_32BIT; goto again; } /* * Create Tx/Rx buffer parent tag. * L1 supports full 64bit DMA addressing in Tx/Rx buffers * so it needs separate parent DMA tag. * XXX * It seems enabling 64bit DMA causes data corruption. Limit * DMA address space to 32bit. */ error = bus_dma_tag_create( bus_get_dma_tag(sc->age_dev), /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ 0, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->age_cdata.age_buffer_tag); if (error != 0) { device_printf(sc->age_dev, "could not create parent buffer DMA tag.\n"); goto fail; } /* Create tag for Tx buffers. */ error = bus_dma_tag_create( sc->age_cdata.age_buffer_tag, /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ AGE_TSO_MAXSIZE, /* maxsize */ AGE_MAXTXSEGS, /* nsegments */ AGE_TSO_MAXSEGSIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->age_cdata.age_tx_tag); if (error != 0) { device_printf(sc->age_dev, "could not create Tx DMA tag.\n"); goto fail; } /* Create tag for Rx buffers. */ error = bus_dma_tag_create( sc->age_cdata.age_buffer_tag, /* parent */ AGE_RX_BUF_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, /* maxsize */ 1, /* nsegments */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->age_cdata.age_rx_tag); if (error != 0) { device_printf(sc->age_dev, "could not create Rx DMA tag.\n"); goto fail; } /* Create DMA maps for Tx buffers. */ for (i = 0; i < AGE_TX_RING_CNT; i++) { txd = &sc->age_cdata.age_txdesc[i]; txd->tx_m = NULL; txd->tx_dmamap = NULL; error = bus_dmamap_create(sc->age_cdata.age_tx_tag, 0, &txd->tx_dmamap); if (error != 0) { device_printf(sc->age_dev, "could not create Tx dmamap.\n"); goto fail; } } /* Create DMA maps for Rx buffers. */ if ((error = bus_dmamap_create(sc->age_cdata.age_rx_tag, 0, &sc->age_cdata.age_rx_sparemap)) != 0) { device_printf(sc->age_dev, "could not create spare Rx dmamap.\n"); goto fail; } for (i = 0; i < AGE_RX_RING_CNT; i++) { rxd = &sc->age_cdata.age_rxdesc[i]; rxd->rx_m = NULL; rxd->rx_dmamap = NULL; error = bus_dmamap_create(sc->age_cdata.age_rx_tag, 0, &rxd->rx_dmamap); if (error != 0) { device_printf(sc->age_dev, "could not create Rx dmamap.\n"); goto fail; } } fail: return (error); } static void age_dma_free(struct age_softc *sc) { struct age_txdesc *txd; struct age_rxdesc *rxd; int i; /* Tx buffers */ if (sc->age_cdata.age_tx_tag != NULL) { for (i = 0; i < AGE_TX_RING_CNT; i++) { txd = &sc->age_cdata.age_txdesc[i]; if (txd->tx_dmamap != NULL) { bus_dmamap_destroy(sc->age_cdata.age_tx_tag, txd->tx_dmamap); txd->tx_dmamap = NULL; } } bus_dma_tag_destroy(sc->age_cdata.age_tx_tag); sc->age_cdata.age_tx_tag = NULL; } /* Rx buffers */ if (sc->age_cdata.age_rx_tag != NULL) { for (i = 0; i < AGE_RX_RING_CNT; i++) { rxd = &sc->age_cdata.age_rxdesc[i]; if (rxd->rx_dmamap != NULL) { bus_dmamap_destroy(sc->age_cdata.age_rx_tag, rxd->rx_dmamap); rxd->rx_dmamap = NULL; } } if (sc->age_cdata.age_rx_sparemap != NULL) { bus_dmamap_destroy(sc->age_cdata.age_rx_tag, sc->age_cdata.age_rx_sparemap); sc->age_cdata.age_rx_sparemap = NULL; } bus_dma_tag_destroy(sc->age_cdata.age_rx_tag); sc->age_cdata.age_rx_tag = NULL; } /* Tx ring. */ if (sc->age_cdata.age_tx_ring_tag != NULL) { if (sc->age_rdata.age_tx_ring_paddr != 0) bus_dmamap_unload(sc->age_cdata.age_tx_ring_tag, sc->age_cdata.age_tx_ring_map); if (sc->age_rdata.age_tx_ring != NULL) bus_dmamem_free(sc->age_cdata.age_tx_ring_tag, sc->age_rdata.age_tx_ring, sc->age_cdata.age_tx_ring_map); sc->age_rdata.age_tx_ring_paddr = 0; sc->age_rdata.age_tx_ring = NULL; bus_dma_tag_destroy(sc->age_cdata.age_tx_ring_tag); sc->age_cdata.age_tx_ring_tag = NULL; } /* Rx ring. */ if (sc->age_cdata.age_rx_ring_tag != NULL) { if (sc->age_rdata.age_rx_ring_paddr != 0) bus_dmamap_unload(sc->age_cdata.age_rx_ring_tag, sc->age_cdata.age_rx_ring_map); if (sc->age_rdata.age_rx_ring != NULL) bus_dmamem_free(sc->age_cdata.age_rx_ring_tag, sc->age_rdata.age_rx_ring, sc->age_cdata.age_rx_ring_map); sc->age_rdata.age_rx_ring_paddr = 0; sc->age_rdata.age_rx_ring = NULL; bus_dma_tag_destroy(sc->age_cdata.age_rx_ring_tag); sc->age_cdata.age_rx_ring_tag = NULL; } /* Rx return ring. */ if (sc->age_cdata.age_rr_ring_tag != NULL) { if (sc->age_rdata.age_rr_ring_paddr != 0) bus_dmamap_unload(sc->age_cdata.age_rr_ring_tag, sc->age_cdata.age_rr_ring_map); if (sc->age_rdata.age_rr_ring != NULL) bus_dmamem_free(sc->age_cdata.age_rr_ring_tag, sc->age_rdata.age_rr_ring, sc->age_cdata.age_rr_ring_map); sc->age_rdata.age_rr_ring_paddr = 0; sc->age_rdata.age_rr_ring = NULL; bus_dma_tag_destroy(sc->age_cdata.age_rr_ring_tag); sc->age_cdata.age_rr_ring_tag = NULL; } /* CMB block */ if (sc->age_cdata.age_cmb_block_tag != NULL) { if (sc->age_rdata.age_cmb_block_paddr != 0) bus_dmamap_unload(sc->age_cdata.age_cmb_block_tag, sc->age_cdata.age_cmb_block_map); if (sc->age_rdata.age_cmb_block != NULL) bus_dmamem_free(sc->age_cdata.age_cmb_block_tag, sc->age_rdata.age_cmb_block, sc->age_cdata.age_cmb_block_map); sc->age_rdata.age_cmb_block_paddr = 0; sc->age_rdata.age_cmb_block = NULL; bus_dma_tag_destroy(sc->age_cdata.age_cmb_block_tag); sc->age_cdata.age_cmb_block_tag = NULL; } /* SMB block */ if (sc->age_cdata.age_smb_block_tag != NULL) { if (sc->age_rdata.age_smb_block_paddr != 0) bus_dmamap_unload(sc->age_cdata.age_smb_block_tag, sc->age_cdata.age_smb_block_map); if (sc->age_rdata.age_smb_block != NULL) bus_dmamem_free(sc->age_cdata.age_smb_block_tag, sc->age_rdata.age_smb_block, sc->age_cdata.age_smb_block_map); sc->age_rdata.age_smb_block_paddr = 0; sc->age_rdata.age_smb_block = NULL; bus_dma_tag_destroy(sc->age_cdata.age_smb_block_tag); sc->age_cdata.age_smb_block_tag = NULL; } if (sc->age_cdata.age_buffer_tag != NULL) { bus_dma_tag_destroy(sc->age_cdata.age_buffer_tag); sc->age_cdata.age_buffer_tag = NULL; } if (sc->age_cdata.age_parent_tag != NULL) { bus_dma_tag_destroy(sc->age_cdata.age_parent_tag); sc->age_cdata.age_parent_tag = NULL; } } /* * Make sure the interface is stopped at reboot time. */ static int age_shutdown(device_t dev) { return (age_suspend(dev)); } static void age_setwol(struct age_softc *sc) { struct ifnet *ifp; struct mii_data *mii; uint32_t reg, pmcs; uint16_t pmstat; int aneg, i, pmc; AGE_LOCK_ASSERT(sc); if (pci_find_cap(sc->age_dev, PCIY_PMG, &pmc) != 0) { CSR_WRITE_4(sc, AGE_WOL_CFG, 0); /* * No PME capability, PHY power down. * XXX * Due to an unknown reason powering down PHY resulted * in unexpected results such as inaccessbility of * hardware of freshly rebooted system. Disable * powering down PHY until I got more information for * Attansic/Atheros PHY hardwares. */ #ifdef notyet age_miibus_writereg(sc->age_dev, sc->age_phyaddr, MII_BMCR, BMCR_PDOWN); #endif return; } ifp = sc->age_ifp; if ((ifp->if_capenable & IFCAP_WOL) != 0) { /* * Note, this driver resets the link speed to 10/100Mbps with * auto-negotiation but we don't know whether that operation * would succeed or not as it have no control after powering * off. If the renegotiation fail WOL may not work. Running * at 1Gbps will draw more power than 375mA at 3.3V which is * specified in PCI specification and that would result in * complete shutdowning power to ethernet controller. * * TODO * Save current negotiated media speed/duplex/flow-control * to softc and restore the same link again after resuming. * PHY handling such as power down/resetting to 100Mbps * may be better handled in suspend method in phy driver. */ mii = device_get_softc(sc->age_miibus); mii_pollstat(mii); aneg = 0; if ((mii->mii_media_status & IFM_AVALID) != 0) { switch IFM_SUBTYPE(mii->mii_media_active) { case IFM_10_T: case IFM_100_TX: goto got_link; case IFM_1000_T: aneg++; default: break; } } age_miibus_writereg(sc->age_dev, sc->age_phyaddr, MII_100T2CR, 0); age_miibus_writereg(sc->age_dev, sc->age_phyaddr, MII_ANAR, ANAR_TX_FD | ANAR_TX | ANAR_10_FD | ANAR_10 | ANAR_CSMA); age_miibus_writereg(sc->age_dev, sc->age_phyaddr, MII_BMCR, BMCR_RESET | BMCR_AUTOEN | BMCR_STARTNEG); DELAY(1000); if (aneg != 0) { /* Poll link state until age(4) get a 10/100 link. */ for (i = 0; i < MII_ANEGTICKS_GIGE; i++) { mii_pollstat(mii); if ((mii->mii_media_status & IFM_AVALID) != 0) { switch (IFM_SUBTYPE( mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: age_mac_config(sc); goto got_link; default: break; } } AGE_UNLOCK(sc); pause("agelnk", hz); AGE_LOCK(sc); } if (i == MII_ANEGTICKS_GIGE) device_printf(sc->age_dev, "establishing link failed, " "WOL may not work!"); } /* * No link, force MAC to have 100Mbps, full-duplex link. * This is the last resort and may/may not work. */ mii->mii_media_status = IFM_AVALID | IFM_ACTIVE; mii->mii_media_active = IFM_ETHER | IFM_100_TX | IFM_FDX; age_mac_config(sc); } got_link: pmcs = 0; if ((ifp->if_capenable & IFCAP_WOL_MAGIC) != 0) pmcs |= WOL_CFG_MAGIC | WOL_CFG_MAGIC_ENB; CSR_WRITE_4(sc, AGE_WOL_CFG, pmcs); reg = CSR_READ_4(sc, AGE_MAC_CFG); reg &= ~(MAC_CFG_DBG | MAC_CFG_PROMISC); reg &= ~(MAC_CFG_ALLMULTI | MAC_CFG_BCAST); if ((ifp->if_capenable & IFCAP_WOL_MCAST) != 0) reg |= MAC_CFG_ALLMULTI | MAC_CFG_BCAST; if ((ifp->if_capenable & IFCAP_WOL) != 0) { reg |= MAC_CFG_RX_ENB; CSR_WRITE_4(sc, AGE_MAC_CFG, reg); } /* Request PME. */ pmstat = pci_read_config(sc->age_dev, pmc + PCIR_POWER_STATUS, 2); pmstat &= ~(PCIM_PSTAT_PME | PCIM_PSTAT_PMEENABLE); if ((ifp->if_capenable & IFCAP_WOL) != 0) pmstat |= PCIM_PSTAT_PME | PCIM_PSTAT_PMEENABLE; pci_write_config(sc->age_dev, pmc + PCIR_POWER_STATUS, pmstat, 2); #ifdef notyet /* See above for powering down PHY issues. */ if ((ifp->if_capenable & IFCAP_WOL) == 0) { /* No WOL, PHY power down. */ age_miibus_writereg(sc->age_dev, sc->age_phyaddr, MII_BMCR, BMCR_PDOWN); } #endif } static int age_suspend(device_t dev) { struct age_softc *sc; sc = device_get_softc(dev); AGE_LOCK(sc); age_stop(sc); age_setwol(sc); AGE_UNLOCK(sc); return (0); } static int age_resume(device_t dev) { struct age_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); AGE_LOCK(sc); age_phy_reset(sc); ifp = sc->age_ifp; if ((ifp->if_flags & IFF_UP) != 0) age_init_locked(sc); AGE_UNLOCK(sc); return (0); } static int age_encap(struct age_softc *sc, struct mbuf **m_head) { struct age_txdesc *txd, *txd_last; struct tx_desc *desc; struct mbuf *m; struct ip *ip; struct tcphdr *tcp; bus_dma_segment_t txsegs[AGE_MAXTXSEGS]; bus_dmamap_t map; uint32_t cflags, hdrlen, ip_off, poff, vtag; int error, i, nsegs, prod, si; AGE_LOCK_ASSERT(sc); M_ASSERTPKTHDR((*m_head)); m = *m_head; ip = NULL; tcp = NULL; cflags = vtag = 0; ip_off = poff = 0; if ((m->m_pkthdr.csum_flags & (AGE_CSUM_FEATURES | CSUM_TSO)) != 0) { /* * L1 requires offset of TCP/UDP payload in its Tx * descriptor to perform hardware Tx checksum offload. * Additionally, TSO requires IP/TCP header size and * modification of IP/TCP header in order to make TSO * engine work. This kind of operation takes many CPU * cycles on FreeBSD so fast host CPU is needed to get * smooth TSO performance. */ struct ether_header *eh; if (M_WRITABLE(m) == 0) { /* Get a writable copy. */ m = m_dup(*m_head, M_NOWAIT); /* Release original mbufs. */ m_freem(*m_head); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } *m_head = m; } ip_off = sizeof(struct ether_header); m = m_pullup(m, ip_off); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } eh = mtod(m, struct ether_header *); /* * Check if hardware VLAN insertion is off. * Additional check for LLC/SNAP frame? */ if (eh->ether_type == htons(ETHERTYPE_VLAN)) { ip_off = sizeof(struct ether_vlan_header); m = m_pullup(m, ip_off); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } } m = m_pullup(m, ip_off + sizeof(struct ip)); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } ip = (struct ip *)(mtod(m, char *) + ip_off); poff = ip_off + (ip->ip_hl << 2); if ((m->m_pkthdr.csum_flags & CSUM_TSO) != 0) { m = m_pullup(m, poff + sizeof(struct tcphdr)); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } tcp = (struct tcphdr *)(mtod(m, char *) + poff); m = m_pullup(m, poff + (tcp->th_off << 2)); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } /* * L1 requires IP/TCP header size and offset as * well as TCP pseudo checksum which complicates * TSO configuration. I guess this comes from the * adherence to Microsoft NDIS Large Send * specification which requires insertion of * pseudo checksum by upper stack. The pseudo * checksum that NDIS refers to doesn't include * TCP payload length so age(4) should recompute * the pseudo checksum here. Hopefully this wouldn't * be much burden on modern CPUs. * Reset IP checksum and recompute TCP pseudo * checksum as NDIS specification said. */ ip = (struct ip *)(mtod(m, char *) + ip_off); tcp = (struct tcphdr *)(mtod(m, char *) + poff); ip->ip_sum = 0; tcp->th_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, htons(IPPROTO_TCP)); } *m_head = m; } si = prod = sc->age_cdata.age_tx_prod; txd = &sc->age_cdata.age_txdesc[prod]; txd_last = txd; map = txd->tx_dmamap; error = bus_dmamap_load_mbuf_sg(sc->age_cdata.age_tx_tag, map, *m_head, txsegs, &nsegs, 0); if (error == EFBIG) { m = m_collapse(*m_head, M_NOWAIT, AGE_MAXTXSEGS); if (m == NULL) { m_freem(*m_head); *m_head = NULL; return (ENOMEM); } *m_head = m; error = bus_dmamap_load_mbuf_sg(sc->age_cdata.age_tx_tag, map, *m_head, txsegs, &nsegs, 0); if (error != 0) { m_freem(*m_head); *m_head = NULL; return (error); } } else if (error != 0) return (error); if (nsegs == 0) { m_freem(*m_head); *m_head = NULL; return (EIO); } /* Check descriptor overrun. */ if (sc->age_cdata.age_tx_cnt + nsegs >= AGE_TX_RING_CNT - 2) { bus_dmamap_unload(sc->age_cdata.age_tx_tag, map); return (ENOBUFS); } m = *m_head; /* Configure VLAN hardware tag insertion. */ if ((m->m_flags & M_VLANTAG) != 0) { vtag = AGE_TX_VLAN_TAG(m->m_pkthdr.ether_vtag); vtag = ((vtag << AGE_TD_VLAN_SHIFT) & AGE_TD_VLAN_MASK); cflags |= AGE_TD_INSERT_VLAN_TAG; } desc = NULL; i = 0; if ((m->m_pkthdr.csum_flags & CSUM_TSO) != 0) { /* Request TSO and set MSS. */ cflags |= AGE_TD_TSO_IPV4; cflags |= AGE_TD_IPCSUM | AGE_TD_TCPCSUM; cflags |= ((uint32_t)m->m_pkthdr.tso_segsz << AGE_TD_TSO_MSS_SHIFT); /* Set IP/TCP header size. */ cflags |= ip->ip_hl << AGE_TD_IPHDR_LEN_SHIFT; cflags |= tcp->th_off << AGE_TD_TSO_TCPHDR_LEN_SHIFT; /* * L1 requires the first buffer should only hold IP/TCP * header data. TCP payload should be handled in other * descriptors. */ hdrlen = poff + (tcp->th_off << 2); desc = &sc->age_rdata.age_tx_ring[prod]; desc->addr = htole64(txsegs[0].ds_addr); desc->len = htole32(AGE_TX_BYTES(hdrlen) | vtag); desc->flags = htole32(cflags); sc->age_cdata.age_tx_cnt++; AGE_DESC_INC(prod, AGE_TX_RING_CNT); if (m->m_len - hdrlen > 0) { /* Handle remaining payload of the 1st fragment. */ desc = &sc->age_rdata.age_tx_ring[prod]; desc->addr = htole64(txsegs[0].ds_addr + hdrlen); desc->len = htole32(AGE_TX_BYTES(m->m_len - hdrlen) | vtag); desc->flags = htole32(cflags); sc->age_cdata.age_tx_cnt++; AGE_DESC_INC(prod, AGE_TX_RING_CNT); } /* Handle remaining fragments. */ i = 1; } else if ((m->m_pkthdr.csum_flags & AGE_CSUM_FEATURES) != 0) { /* Configure Tx IP/TCP/UDP checksum offload. */ cflags |= AGE_TD_CSUM; if ((m->m_pkthdr.csum_flags & CSUM_TCP) != 0) cflags |= AGE_TD_TCPCSUM; if ((m->m_pkthdr.csum_flags & CSUM_UDP) != 0) cflags |= AGE_TD_UDPCSUM; /* Set checksum start offset. */ cflags |= (poff << AGE_TD_CSUM_PLOADOFFSET_SHIFT); /* Set checksum insertion position of TCP/UDP. */ cflags |= ((poff + m->m_pkthdr.csum_data) << AGE_TD_CSUM_XSUMOFFSET_SHIFT); } for (; i < nsegs; i++) { desc = &sc->age_rdata.age_tx_ring[prod]; desc->addr = htole64(txsegs[i].ds_addr); desc->len = htole32(AGE_TX_BYTES(txsegs[i].ds_len) | vtag); desc->flags = htole32(cflags); sc->age_cdata.age_tx_cnt++; AGE_DESC_INC(prod, AGE_TX_RING_CNT); } /* Update producer index. */ sc->age_cdata.age_tx_prod = prod; /* Set EOP on the last descriptor. */ prod = (prod + AGE_TX_RING_CNT - 1) % AGE_TX_RING_CNT; desc = &sc->age_rdata.age_tx_ring[prod]; desc->flags |= htole32(AGE_TD_EOP); /* Lastly set TSO header and modify IP/TCP header for TSO operation. */ if ((m->m_pkthdr.csum_flags & CSUM_TSO) != 0) { desc = &sc->age_rdata.age_tx_ring[si]; desc->flags |= htole32(AGE_TD_TSO_HDR); } /* Swap dmamap of the first and the last. */ txd = &sc->age_cdata.age_txdesc[prod]; map = txd_last->tx_dmamap; txd_last->tx_dmamap = txd->tx_dmamap; txd->tx_dmamap = map; txd->tx_m = m; /* Sync descriptors. */ bus_dmamap_sync(sc->age_cdata.age_tx_tag, map, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(sc->age_cdata.age_tx_ring_tag, sc->age_cdata.age_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (0); } static void age_start(struct ifnet *ifp) { struct age_softc *sc; sc = ifp->if_softc; AGE_LOCK(sc); age_start_locked(ifp); AGE_UNLOCK(sc); } static void age_start_locked(struct ifnet *ifp) { struct age_softc *sc; struct mbuf *m_head; int enq; sc = ifp->if_softc; AGE_LOCK_ASSERT(sc); if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING || (sc->age_flags & AGE_FLAG_LINK) == 0) return; for (enq = 0; !IFQ_DRV_IS_EMPTY(&ifp->if_snd); ) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; /* * Pack the data into the transmit ring. If we * don't have room, set the OACTIVE flag and wait * for the NIC to drain the ring. */ if (age_encap(sc, &m_head)) { if (m_head == NULL) break; IFQ_DRV_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } enq++; /* * If there's a BPF listener, bounce a copy of this frame * to him. */ ETHER_BPF_MTAP(ifp, m_head); } if (enq > 0) { /* Update mbox. */ AGE_COMMIT_MBOX(sc); /* Set a timeout in case the chip goes out to lunch. */ sc->age_watchdog_timer = AGE_TX_TIMEOUT; } } static void age_watchdog(struct age_softc *sc) { struct ifnet *ifp; AGE_LOCK_ASSERT(sc); if (sc->age_watchdog_timer == 0 || --sc->age_watchdog_timer) return; ifp = sc->age_ifp; if ((sc->age_flags & AGE_FLAG_LINK) == 0) { if_printf(sc->age_ifp, "watchdog timeout (missed link)\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; age_init_locked(sc); return; } if (sc->age_cdata.age_tx_cnt == 0) { if_printf(sc->age_ifp, "watchdog timeout (missed Tx interrupts) -- recovering\n"); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) age_start_locked(ifp); return; } if_printf(sc->age_ifp, "watchdog timeout\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; age_init_locked(sc); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) age_start_locked(ifp); } static int age_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct age_softc *sc; struct ifreq *ifr; struct mii_data *mii; uint32_t reg; int error, mask; sc = ifp->if_softc; ifr = (struct ifreq *)data; error = 0; switch (cmd) { case SIOCSIFMTU: if (ifr->ifr_mtu < ETHERMIN || ifr->ifr_mtu > AGE_JUMBO_MTU) error = EINVAL; else if (ifp->if_mtu != ifr->ifr_mtu) { AGE_LOCK(sc); ifp->if_mtu = ifr->ifr_mtu; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; age_init_locked(sc); } AGE_UNLOCK(sc); } break; case SIOCSIFFLAGS: AGE_LOCK(sc); if ((ifp->if_flags & IFF_UP) != 0) { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { if (((ifp->if_flags ^ sc->age_if_flags) & (IFF_PROMISC | IFF_ALLMULTI)) != 0) age_rxfilter(sc); } else { if ((sc->age_flags & AGE_FLAG_DETACH) == 0) age_init_locked(sc); } } else { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) age_stop(sc); } sc->age_if_flags = ifp->if_flags; AGE_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: AGE_LOCK(sc); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) age_rxfilter(sc); AGE_UNLOCK(sc); break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: mii = device_get_softc(sc->age_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); break; case SIOCSIFCAP: AGE_LOCK(sc); mask = ifr->ifr_reqcap ^ ifp->if_capenable; if ((mask & IFCAP_TXCSUM) != 0 && (ifp->if_capabilities & IFCAP_TXCSUM) != 0) { ifp->if_capenable ^= IFCAP_TXCSUM; if ((ifp->if_capenable & IFCAP_TXCSUM) != 0) ifp->if_hwassist |= AGE_CSUM_FEATURES; else ifp->if_hwassist &= ~AGE_CSUM_FEATURES; } if ((mask & IFCAP_RXCSUM) != 0 && (ifp->if_capabilities & IFCAP_RXCSUM) != 0) { ifp->if_capenable ^= IFCAP_RXCSUM; reg = CSR_READ_4(sc, AGE_MAC_CFG); reg &= ~MAC_CFG_RXCSUM_ENB; if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) reg |= MAC_CFG_RXCSUM_ENB; CSR_WRITE_4(sc, AGE_MAC_CFG, reg); } if ((mask & IFCAP_TSO4) != 0 && (ifp->if_capabilities & IFCAP_TSO4) != 0) { ifp->if_capenable ^= IFCAP_TSO4; if ((ifp->if_capenable & IFCAP_TSO4) != 0) ifp->if_hwassist |= CSUM_TSO; else ifp->if_hwassist &= ~CSUM_TSO; } if ((mask & IFCAP_WOL_MCAST) != 0 && (ifp->if_capabilities & IFCAP_WOL_MCAST) != 0) ifp->if_capenable ^= IFCAP_WOL_MCAST; if ((mask & IFCAP_WOL_MAGIC) != 0 && (ifp->if_capabilities & IFCAP_WOL_MAGIC) != 0) ifp->if_capenable ^= IFCAP_WOL_MAGIC; if ((mask & IFCAP_VLAN_HWCSUM) != 0 && (ifp->if_capabilities & IFCAP_VLAN_HWCSUM) != 0) ifp->if_capenable ^= IFCAP_VLAN_HWCSUM; if ((mask & IFCAP_VLAN_HWTSO) != 0 && (ifp->if_capabilities & IFCAP_VLAN_HWTSO) != 0) ifp->if_capenable ^= IFCAP_VLAN_HWTSO; if ((mask & IFCAP_VLAN_HWTAGGING) != 0 && (ifp->if_capabilities & IFCAP_VLAN_HWTAGGING) != 0) { ifp->if_capenable ^= IFCAP_VLAN_HWTAGGING; if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) == 0) ifp->if_capenable &= ~IFCAP_VLAN_HWTSO; age_rxvlan(sc); } AGE_UNLOCK(sc); VLAN_CAPABILITIES(ifp); break; default: error = ether_ioctl(ifp, cmd, data); break; } return (error); } static void age_mac_config(struct age_softc *sc) { struct mii_data *mii; uint32_t reg; AGE_LOCK_ASSERT(sc); mii = device_get_softc(sc->age_miibus); reg = CSR_READ_4(sc, AGE_MAC_CFG); reg &= ~MAC_CFG_FULL_DUPLEX; reg &= ~(MAC_CFG_TX_FC | MAC_CFG_RX_FC); reg &= ~MAC_CFG_SPEED_MASK; /* Reprogram MAC with resolved speed/duplex. */ switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: reg |= MAC_CFG_SPEED_10_100; break; case IFM_1000_T: reg |= MAC_CFG_SPEED_1000; break; } if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { reg |= MAC_CFG_FULL_DUPLEX; #ifdef notyet if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0) reg |= MAC_CFG_TX_FC; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0) reg |= MAC_CFG_RX_FC; #endif } CSR_WRITE_4(sc, AGE_MAC_CFG, reg); } static void age_link_task(void *arg, int pending) { struct age_softc *sc; struct mii_data *mii; struct ifnet *ifp; uint32_t reg; sc = (struct age_softc *)arg; AGE_LOCK(sc); mii = device_get_softc(sc->age_miibus); ifp = sc->age_ifp; if (mii == NULL || ifp == NULL || (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { AGE_UNLOCK(sc); return; } sc->age_flags &= ~AGE_FLAG_LINK; if ((mii->mii_media_status & IFM_AVALID) != 0) { switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: case IFM_1000_T: sc->age_flags |= AGE_FLAG_LINK; break; default: break; } } /* Stop Rx/Tx MACs. */ age_stop_rxmac(sc); age_stop_txmac(sc); /* Program MACs with resolved speed/duplex/flow-control. */ if ((sc->age_flags & AGE_FLAG_LINK) != 0) { age_mac_config(sc); reg = CSR_READ_4(sc, AGE_MAC_CFG); /* Restart DMA engine and Tx/Rx MAC. */ CSR_WRITE_4(sc, AGE_DMA_CFG, CSR_READ_4(sc, AGE_DMA_CFG) | DMA_CFG_RD_ENB | DMA_CFG_WR_ENB); reg |= MAC_CFG_TX_ENB | MAC_CFG_RX_ENB; CSR_WRITE_4(sc, AGE_MAC_CFG, reg); } AGE_UNLOCK(sc); } static void age_stats_update(struct age_softc *sc) { struct age_stats *stat; struct smb *smb; struct ifnet *ifp; AGE_LOCK_ASSERT(sc); stat = &sc->age_stat; bus_dmamap_sync(sc->age_cdata.age_smb_block_tag, sc->age_cdata.age_smb_block_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); smb = sc->age_rdata.age_smb_block; if (smb->updated == 0) return; ifp = sc->age_ifp; /* Rx stats. */ stat->rx_frames += smb->rx_frames; stat->rx_bcast_frames += smb->rx_bcast_frames; stat->rx_mcast_frames += smb->rx_mcast_frames; stat->rx_pause_frames += smb->rx_pause_frames; stat->rx_control_frames += smb->rx_control_frames; stat->rx_crcerrs += smb->rx_crcerrs; stat->rx_lenerrs += smb->rx_lenerrs; stat->rx_bytes += smb->rx_bytes; stat->rx_runts += smb->rx_runts; stat->rx_fragments += smb->rx_fragments; stat->rx_pkts_64 += smb->rx_pkts_64; stat->rx_pkts_65_127 += smb->rx_pkts_65_127; stat->rx_pkts_128_255 += smb->rx_pkts_128_255; stat->rx_pkts_256_511 += smb->rx_pkts_256_511; stat->rx_pkts_512_1023 += smb->rx_pkts_512_1023; stat->rx_pkts_1024_1518 += smb->rx_pkts_1024_1518; stat->rx_pkts_1519_max += smb->rx_pkts_1519_max; stat->rx_pkts_truncated += smb->rx_pkts_truncated; stat->rx_fifo_oflows += smb->rx_fifo_oflows; stat->rx_desc_oflows += smb->rx_desc_oflows; stat->rx_alignerrs += smb->rx_alignerrs; stat->rx_bcast_bytes += smb->rx_bcast_bytes; stat->rx_mcast_bytes += smb->rx_mcast_bytes; stat->rx_pkts_filtered += smb->rx_pkts_filtered; /* Tx stats. */ stat->tx_frames += smb->tx_frames; stat->tx_bcast_frames += smb->tx_bcast_frames; stat->tx_mcast_frames += smb->tx_mcast_frames; stat->tx_pause_frames += smb->tx_pause_frames; stat->tx_excess_defer += smb->tx_excess_defer; stat->tx_control_frames += smb->tx_control_frames; stat->tx_deferred += smb->tx_deferred; stat->tx_bytes += smb->tx_bytes; stat->tx_pkts_64 += smb->tx_pkts_64; stat->tx_pkts_65_127 += smb->tx_pkts_65_127; stat->tx_pkts_128_255 += smb->tx_pkts_128_255; stat->tx_pkts_256_511 += smb->tx_pkts_256_511; stat->tx_pkts_512_1023 += smb->tx_pkts_512_1023; stat->tx_pkts_1024_1518 += smb->tx_pkts_1024_1518; stat->tx_pkts_1519_max += smb->tx_pkts_1519_max; stat->tx_single_colls += smb->tx_single_colls; stat->tx_multi_colls += smb->tx_multi_colls; stat->tx_late_colls += smb->tx_late_colls; stat->tx_excess_colls += smb->tx_excess_colls; stat->tx_underrun += smb->tx_underrun; stat->tx_desc_underrun += smb->tx_desc_underrun; stat->tx_lenerrs += smb->tx_lenerrs; stat->tx_pkts_truncated += smb->tx_pkts_truncated; stat->tx_bcast_bytes += smb->tx_bcast_bytes; stat->tx_mcast_bytes += smb->tx_mcast_bytes; /* Update counters in ifnet. */ if_inc_counter(ifp, IFCOUNTER_OPACKETS, smb->tx_frames); if_inc_counter(ifp, IFCOUNTER_COLLISIONS, smb->tx_single_colls + smb->tx_multi_colls + smb->tx_late_colls + smb->tx_excess_colls * HDPX_CFG_RETRY_DEFAULT); if_inc_counter(ifp, IFCOUNTER_OERRORS, smb->tx_excess_colls + smb->tx_late_colls + smb->tx_underrun + smb->tx_pkts_truncated); if_inc_counter(ifp, IFCOUNTER_IPACKETS, smb->rx_frames); if_inc_counter(ifp, IFCOUNTER_IERRORS, smb->rx_crcerrs + smb->rx_lenerrs + smb->rx_runts + smb->rx_pkts_truncated + smb->rx_fifo_oflows + smb->rx_desc_oflows + smb->rx_alignerrs); /* Update done, clear. */ smb->updated = 0; bus_dmamap_sync(sc->age_cdata.age_smb_block_tag, sc->age_cdata.age_smb_block_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static int age_intr(void *arg) { struct age_softc *sc; uint32_t status; sc = (struct age_softc *)arg; status = CSR_READ_4(sc, AGE_INTR_STATUS); if (status == 0 || (status & AGE_INTRS) == 0) return (FILTER_STRAY); /* Disable interrupts. */ CSR_WRITE_4(sc, AGE_INTR_STATUS, status | INTR_DIS_INT); taskqueue_enqueue(sc->age_tq, &sc->age_int_task); return (FILTER_HANDLED); } static void age_int_task(void *arg, int pending) { struct age_softc *sc; struct ifnet *ifp; struct cmb *cmb; uint32_t status; sc = (struct age_softc *)arg; AGE_LOCK(sc); bus_dmamap_sync(sc->age_cdata.age_cmb_block_tag, sc->age_cdata.age_cmb_block_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); cmb = sc->age_rdata.age_cmb_block; status = le32toh(cmb->intr_status); if (sc->age_morework != 0) status |= INTR_CMB_RX; if ((status & AGE_INTRS) == 0) goto done; sc->age_tpd_cons = (le32toh(cmb->tpd_cons) & TPD_CONS_MASK) >> TPD_CONS_SHIFT; sc->age_rr_prod = (le32toh(cmb->rprod_cons) & RRD_PROD_MASK) >> RRD_PROD_SHIFT; /* Let hardware know CMB was served. */ cmb->intr_status = 0; bus_dmamap_sync(sc->age_cdata.age_cmb_block_tag, sc->age_cdata.age_cmb_block_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); #if 0 printf("INTR: 0x%08x\n", status); status &= ~INTR_DIS_DMA; CSR_WRITE_4(sc, AGE_INTR_STATUS, status | INTR_DIS_INT); #endif ifp = sc->age_ifp; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { if ((status & INTR_CMB_RX) != 0) sc->age_morework = age_rxintr(sc, sc->age_rr_prod, sc->age_process_limit); if ((status & INTR_CMB_TX) != 0) age_txintr(sc, sc->age_tpd_cons); if ((status & (INTR_DMA_RD_TO_RST | INTR_DMA_WR_TO_RST)) != 0) { if ((status & INTR_DMA_RD_TO_RST) != 0) device_printf(sc->age_dev, "DMA read error! -- resetting\n"); if ((status & INTR_DMA_WR_TO_RST) != 0) device_printf(sc->age_dev, "DMA write error! -- resetting\n"); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; age_init_locked(sc); } if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) age_start_locked(ifp); if ((status & INTR_SMB) != 0) age_stats_update(sc); } /* Check whether CMB was updated while serving Tx/Rx/SMB handler. */ bus_dmamap_sync(sc->age_cdata.age_cmb_block_tag, sc->age_cdata.age_cmb_block_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); status = le32toh(cmb->intr_status); if (sc->age_morework != 0 || (status & AGE_INTRS) != 0) { taskqueue_enqueue(sc->age_tq, &sc->age_int_task); AGE_UNLOCK(sc); return; } done: /* Re-enable interrupts. */ CSR_WRITE_4(sc, AGE_INTR_STATUS, 0); AGE_UNLOCK(sc); } static void age_txintr(struct age_softc *sc, int tpd_cons) { struct ifnet *ifp; struct age_txdesc *txd; int cons, prog; AGE_LOCK_ASSERT(sc); ifp = sc->age_ifp; bus_dmamap_sync(sc->age_cdata.age_tx_ring_tag, sc->age_cdata.age_tx_ring_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); /* * Go through our Tx list and free mbufs for those * frames which have been transmitted. */ cons = sc->age_cdata.age_tx_cons; for (prog = 0; cons != tpd_cons; AGE_DESC_INC(cons, AGE_TX_RING_CNT)) { if (sc->age_cdata.age_tx_cnt <= 0) break; prog++; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc->age_cdata.age_tx_cnt--; txd = &sc->age_cdata.age_txdesc[cons]; /* * Clear Tx descriptors, it's not required but would * help debugging in case of Tx issues. */ txd->tx_desc->addr = 0; txd->tx_desc->len = 0; txd->tx_desc->flags = 0; if (txd->tx_m == NULL) continue; /* Reclaim transmitted mbufs. */ bus_dmamap_sync(sc->age_cdata.age_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->age_cdata.age_tx_tag, txd->tx_dmamap); m_freem(txd->tx_m); txd->tx_m = NULL; } if (prog > 0) { sc->age_cdata.age_tx_cons = cons; /* * Unarm watchdog timer only when there are no pending * Tx descriptors in queue. */ if (sc->age_cdata.age_tx_cnt == 0) sc->age_watchdog_timer = 0; bus_dmamap_sync(sc->age_cdata.age_tx_ring_tag, sc->age_cdata.age_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } } #ifndef __NO_STRICT_ALIGNMENT static struct mbuf * age_fixup_rx(struct ifnet *ifp, struct mbuf *m) { struct mbuf *n; int i; uint16_t *src, *dst; src = mtod(m, uint16_t *); dst = src - 3; if (m->m_next == NULL) { for (i = 0; i < (m->m_len / sizeof(uint16_t) + 1); i++) *dst++ = *src++; m->m_data -= 6; return (m); } /* * Append a new mbuf to received mbuf chain and copy ethernet * header from the mbuf chain. This can save lots of CPU * cycles for jumbo frame. */ MGETHDR(n, M_NOWAIT, MT_DATA); if (n == NULL) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); m_freem(m); return (NULL); } bcopy(m->m_data, n->m_data, ETHER_HDR_LEN); m->m_data += ETHER_HDR_LEN; m->m_len -= ETHER_HDR_LEN; n->m_len = ETHER_HDR_LEN; M_MOVE_PKTHDR(n, m); n->m_next = m; return (n); } #endif /* Receive a frame. */ static void age_rxeof(struct age_softc *sc, struct rx_rdesc *rxrd) { struct age_rxdesc *rxd; struct ifnet *ifp; struct mbuf *mp, *m; uint32_t status, index, vtag; int count, nsegs; int rx_cons; AGE_LOCK_ASSERT(sc); ifp = sc->age_ifp; status = le32toh(rxrd->flags); index = le32toh(rxrd->index); rx_cons = AGE_RX_CONS(index); nsegs = AGE_RX_NSEGS(index); sc->age_cdata.age_rxlen = AGE_RX_BYTES(le32toh(rxrd->len)); if ((status & (AGE_RRD_ERROR | AGE_RRD_LENGTH_NOK)) != 0) { /* * We want to pass the following frames to upper * layer regardless of error status of Rx return * ring. * * o IP/TCP/UDP checksum is bad. * o frame length and protocol specific length * does not match. */ status |= AGE_RRD_IPCSUM_NOK | AGE_RRD_TCP_UDPCSUM_NOK; if ((status & (AGE_RRD_CRC | AGE_RRD_CODE | AGE_RRD_DRIBBLE | AGE_RRD_RUNT | AGE_RRD_OFLOW | AGE_RRD_TRUNC)) != 0) return; } for (count = 0; count < nsegs; count++, AGE_DESC_INC(rx_cons, AGE_RX_RING_CNT)) { rxd = &sc->age_cdata.age_rxdesc[rx_cons]; mp = rxd->rx_m; /* Add a new receive buffer to the ring. */ if (age_newbuf(sc, rxd) != 0) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); /* Reuse Rx buffers. */ if (sc->age_cdata.age_rxhead != NULL) m_freem(sc->age_cdata.age_rxhead); break; } /* * Assume we've received a full sized frame. * Actual size is fixed when we encounter the end of * multi-segmented frame. */ mp->m_len = AGE_RX_BUF_SIZE; /* Chain received mbufs. */ if (sc->age_cdata.age_rxhead == NULL) { sc->age_cdata.age_rxhead = mp; sc->age_cdata.age_rxtail = mp; } else { mp->m_flags &= ~M_PKTHDR; sc->age_cdata.age_rxprev_tail = sc->age_cdata.age_rxtail; sc->age_cdata.age_rxtail->m_next = mp; sc->age_cdata.age_rxtail = mp; } if (count == nsegs - 1) { /* Last desc. for this frame. */ m = sc->age_cdata.age_rxhead; m->m_flags |= M_PKTHDR; /* * It seems that L1 controller has no way * to tell hardware to strip CRC bytes. */ m->m_pkthdr.len = sc->age_cdata.age_rxlen - ETHER_CRC_LEN; if (nsegs > 1) { /* Set last mbuf size. */ mp->m_len = sc->age_cdata.age_rxlen - ((nsegs - 1) * AGE_RX_BUF_SIZE); /* Remove the CRC bytes in chained mbufs. */ if (mp->m_len <= ETHER_CRC_LEN) { sc->age_cdata.age_rxtail = sc->age_cdata.age_rxprev_tail; sc->age_cdata.age_rxtail->m_len -= (ETHER_CRC_LEN - mp->m_len); sc->age_cdata.age_rxtail->m_next = NULL; m_freem(mp); } else { mp->m_len -= ETHER_CRC_LEN; } } else m->m_len = m->m_pkthdr.len; m->m_pkthdr.rcvif = ifp; /* * Set checksum information. * It seems that L1 controller can compute partial * checksum. The partial checksum value can be used * to accelerate checksum computation for fragmented * TCP/UDP packets. Upper network stack already * takes advantage of the partial checksum value in * IP reassembly stage. But I'm not sure the * correctness of the partial hardware checksum * assistance due to lack of data sheet. If it is * proven to work on L1 I'll enable it. */ if ((ifp->if_capenable & IFCAP_RXCSUM) != 0 && (status & AGE_RRD_IPV4) != 0) { if ((status & AGE_RRD_IPCSUM_NOK) == 0) m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED | CSUM_IP_VALID; if ((status & (AGE_RRD_TCP | AGE_RRD_UDP)) && (status & AGE_RRD_TCP_UDPCSUM_NOK) == 0) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } /* * Don't mark bad checksum for TCP/UDP frames * as fragmented frames may always have set * bad checksummed bit of descriptor status. */ } /* Check for VLAN tagged frames. */ if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0 && (status & AGE_RRD_VLAN) != 0) { vtag = AGE_RX_VLAN(le32toh(rxrd->vtags)); m->m_pkthdr.ether_vtag = AGE_RX_VLAN_TAG(vtag); m->m_flags |= M_VLANTAG; } #ifndef __NO_STRICT_ALIGNMENT m = age_fixup_rx(ifp, m); if (m != NULL) #endif { /* Pass it on. */ AGE_UNLOCK(sc); (*ifp->if_input)(ifp, m); AGE_LOCK(sc); } } } /* Reset mbuf chains. */ AGE_RXCHAIN_RESET(sc); } static int age_rxintr(struct age_softc *sc, int rr_prod, int count) { struct rx_rdesc *rxrd; int rr_cons, nsegs, pktlen, prog; AGE_LOCK_ASSERT(sc); rr_cons = sc->age_cdata.age_rr_cons; if (rr_cons == rr_prod) return (0); bus_dmamap_sync(sc->age_cdata.age_rr_ring_tag, sc->age_cdata.age_rr_ring_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); bus_dmamap_sync(sc->age_cdata.age_rx_ring_tag, sc->age_cdata.age_rx_ring_map, BUS_DMASYNC_POSTWRITE); for (prog = 0; rr_cons != rr_prod; prog++) { if (count-- <= 0) break; rxrd = &sc->age_rdata.age_rr_ring[rr_cons]; nsegs = AGE_RX_NSEGS(le32toh(rxrd->index)); if (nsegs == 0) break; /* * Check number of segments against received bytes. * Non-matching value would indicate that hardware * is still trying to update Rx return descriptors. * I'm not sure whether this check is really needed. */ pktlen = AGE_RX_BYTES(le32toh(rxrd->len)); if (nsegs != (pktlen + (AGE_RX_BUF_SIZE - 1)) / AGE_RX_BUF_SIZE) break; /* Received a frame. */ age_rxeof(sc, rxrd); /* Clear return ring. */ rxrd->index = 0; AGE_DESC_INC(rr_cons, AGE_RR_RING_CNT); sc->age_cdata.age_rx_cons += nsegs; sc->age_cdata.age_rx_cons %= AGE_RX_RING_CNT; } if (prog > 0) { /* Update the consumer index. */ sc->age_cdata.age_rr_cons = rr_cons; bus_dmamap_sync(sc->age_cdata.age_rx_ring_tag, sc->age_cdata.age_rx_ring_map, BUS_DMASYNC_PREWRITE); /* Sync descriptors. */ bus_dmamap_sync(sc->age_cdata.age_rr_ring_tag, sc->age_cdata.age_rr_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); /* Notify hardware availability of new Rx buffers. */ AGE_COMMIT_MBOX(sc); } return (count > 0 ? 0 : EAGAIN); } static void age_tick(void *arg) { struct age_softc *sc; struct mii_data *mii; sc = (struct age_softc *)arg; AGE_LOCK_ASSERT(sc); mii = device_get_softc(sc->age_miibus); mii_tick(mii); age_watchdog(sc); callout_reset(&sc->age_tick_ch, hz, age_tick, sc); } static void age_reset(struct age_softc *sc) { uint32_t reg; int i; CSR_WRITE_4(sc, AGE_MASTER_CFG, MASTER_RESET); CSR_READ_4(sc, AGE_MASTER_CFG); DELAY(1000); for (i = AGE_RESET_TIMEOUT; i > 0; i--) { if ((reg = CSR_READ_4(sc, AGE_IDLE_STATUS)) == 0) break; DELAY(10); } if (i == 0) device_printf(sc->age_dev, "reset timeout(0x%08x)!\n", reg); /* Initialize PCIe module. From Linux. */ CSR_WRITE_4(sc, 0x12FC, 0x6500); CSR_WRITE_4(sc, 0x1008, CSR_READ_4(sc, 0x1008) | 0x8000); } static void age_init(void *xsc) { struct age_softc *sc; sc = (struct age_softc *)xsc; AGE_LOCK(sc); age_init_locked(sc); AGE_UNLOCK(sc); } static void age_init_locked(struct age_softc *sc) { struct ifnet *ifp; struct mii_data *mii; uint8_t eaddr[ETHER_ADDR_LEN]; bus_addr_t paddr; uint32_t reg, fsize; uint32_t rxf_hi, rxf_lo, rrd_hi, rrd_lo; int error; AGE_LOCK_ASSERT(sc); ifp = sc->age_ifp; mii = device_get_softc(sc->age_miibus); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) return; /* * Cancel any pending I/O. */ age_stop(sc); /* * Reset the chip to a known state. */ age_reset(sc); /* Initialize descriptors. */ error = age_init_rx_ring(sc); if (error != 0) { device_printf(sc->age_dev, "no memory for Rx buffers.\n"); age_stop(sc); return; } age_init_rr_ring(sc); age_init_tx_ring(sc); age_init_cmb_block(sc); age_init_smb_block(sc); /* Reprogram the station address. */ bcopy(IF_LLADDR(ifp), eaddr, ETHER_ADDR_LEN); CSR_WRITE_4(sc, AGE_PAR0, eaddr[2] << 24 | eaddr[3] << 16 | eaddr[4] << 8 | eaddr[5]); CSR_WRITE_4(sc, AGE_PAR1, eaddr[0] << 8 | eaddr[1]); /* Set descriptor base addresses. */ paddr = sc->age_rdata.age_tx_ring_paddr; CSR_WRITE_4(sc, AGE_DESC_ADDR_HI, AGE_ADDR_HI(paddr)); paddr = sc->age_rdata.age_rx_ring_paddr; CSR_WRITE_4(sc, AGE_DESC_RD_ADDR_LO, AGE_ADDR_LO(paddr)); paddr = sc->age_rdata.age_rr_ring_paddr; CSR_WRITE_4(sc, AGE_DESC_RRD_ADDR_LO, AGE_ADDR_LO(paddr)); paddr = sc->age_rdata.age_tx_ring_paddr; CSR_WRITE_4(sc, AGE_DESC_TPD_ADDR_LO, AGE_ADDR_LO(paddr)); paddr = sc->age_rdata.age_cmb_block_paddr; CSR_WRITE_4(sc, AGE_DESC_CMB_ADDR_LO, AGE_ADDR_LO(paddr)); paddr = sc->age_rdata.age_smb_block_paddr; CSR_WRITE_4(sc, AGE_DESC_SMB_ADDR_LO, AGE_ADDR_LO(paddr)); /* Set Rx/Rx return descriptor counter. */ CSR_WRITE_4(sc, AGE_DESC_RRD_RD_CNT, ((AGE_RR_RING_CNT << DESC_RRD_CNT_SHIFT) & DESC_RRD_CNT_MASK) | ((AGE_RX_RING_CNT << DESC_RD_CNT_SHIFT) & DESC_RD_CNT_MASK)); /* Set Tx descriptor counter. */ CSR_WRITE_4(sc, AGE_DESC_TPD_CNT, (AGE_TX_RING_CNT << DESC_TPD_CNT_SHIFT) & DESC_TPD_CNT_MASK); /* Tell hardware that we're ready to load descriptors. */ CSR_WRITE_4(sc, AGE_DMA_BLOCK, DMA_BLOCK_LOAD); /* * Initialize mailbox register. * Updated producer/consumer index information is exchanged * through this mailbox register. However Tx producer and * Rx return consumer/Rx producer are all shared such that * it's hard to separate code path between Tx and Rx without * locking. If L1 hardware have a separate mail box register * for Tx and Rx consumer/producer management we could have * indepent Tx/Rx handler which in turn Rx handler could have * been run without any locking. */ AGE_COMMIT_MBOX(sc); /* Configure IPG/IFG parameters. */ CSR_WRITE_4(sc, AGE_IPG_IFG_CFG, ((IPG_IFG_IPG2_DEFAULT << IPG_IFG_IPG2_SHIFT) & IPG_IFG_IPG2_MASK) | ((IPG_IFG_IPG1_DEFAULT << IPG_IFG_IPG1_SHIFT) & IPG_IFG_IPG1_MASK) | ((IPG_IFG_MIFG_DEFAULT << IPG_IFG_MIFG_SHIFT) & IPG_IFG_MIFG_MASK) | ((IPG_IFG_IPGT_DEFAULT << IPG_IFG_IPGT_SHIFT) & IPG_IFG_IPGT_MASK)); /* Set parameters for half-duplex media. */ CSR_WRITE_4(sc, AGE_HDPX_CFG, ((HDPX_CFG_LCOL_DEFAULT << HDPX_CFG_LCOL_SHIFT) & HDPX_CFG_LCOL_MASK) | ((HDPX_CFG_RETRY_DEFAULT << HDPX_CFG_RETRY_SHIFT) & HDPX_CFG_RETRY_MASK) | HDPX_CFG_EXC_DEF_EN | ((HDPX_CFG_ABEBT_DEFAULT << HDPX_CFG_ABEBT_SHIFT) & HDPX_CFG_ABEBT_MASK) | ((HDPX_CFG_JAMIPG_DEFAULT << HDPX_CFG_JAMIPG_SHIFT) & HDPX_CFG_JAMIPG_MASK)); /* Configure interrupt moderation timer. */ CSR_WRITE_2(sc, AGE_IM_TIMER, AGE_USECS(sc->age_int_mod)); reg = CSR_READ_4(sc, AGE_MASTER_CFG); reg &= ~MASTER_MTIMER_ENB; if (AGE_USECS(sc->age_int_mod) == 0) reg &= ~MASTER_ITIMER_ENB; else reg |= MASTER_ITIMER_ENB; CSR_WRITE_4(sc, AGE_MASTER_CFG, reg); if (bootverbose) device_printf(sc->age_dev, "interrupt moderation is %d us.\n", sc->age_int_mod); CSR_WRITE_2(sc, AGE_INTR_CLR_TIMER, AGE_USECS(1000)); /* Set Maximum frame size but don't let MTU be lass than ETHER_MTU. */ if (ifp->if_mtu < ETHERMTU) sc->age_max_frame_size = ETHERMTU; else sc->age_max_frame_size = ifp->if_mtu; sc->age_max_frame_size += ETHER_HDR_LEN + sizeof(struct ether_vlan_header) + ETHER_CRC_LEN; CSR_WRITE_4(sc, AGE_FRAME_SIZE, sc->age_max_frame_size); /* Configure jumbo frame. */ fsize = roundup(sc->age_max_frame_size, sizeof(uint64_t)); CSR_WRITE_4(sc, AGE_RXQ_JUMBO_CFG, (((fsize / sizeof(uint64_t)) << RXQ_JUMBO_CFG_SZ_THRESH_SHIFT) & RXQ_JUMBO_CFG_SZ_THRESH_MASK) | ((RXQ_JUMBO_CFG_LKAH_DEFAULT << RXQ_JUMBO_CFG_LKAH_SHIFT) & RXQ_JUMBO_CFG_LKAH_MASK) | ((AGE_USECS(8) << RXQ_JUMBO_CFG_RRD_TIMER_SHIFT) & RXQ_JUMBO_CFG_RRD_TIMER_MASK)); /* Configure flow-control parameters. From Linux. */ if ((sc->age_flags & AGE_FLAG_PCIE) != 0) { /* * Magic workaround for old-L1. * Don't know which hw revision requires this magic. */ CSR_WRITE_4(sc, 0x12FC, 0x6500); /* * Another magic workaround for flow-control mode * change. From Linux. */ CSR_WRITE_4(sc, 0x1008, CSR_READ_4(sc, 0x1008) | 0x8000); } /* * TODO * Should understand pause parameter relationships between FIFO * size and number of Rx descriptors and Rx return descriptors. * * Magic parameters came from Linux. */ switch (sc->age_chip_rev) { case 0x8001: case 0x9001: case 0x9002: case 0x9003: rxf_hi = AGE_RX_RING_CNT / 16; rxf_lo = (AGE_RX_RING_CNT * 7) / 8; rrd_hi = (AGE_RR_RING_CNT * 7) / 8; rrd_lo = AGE_RR_RING_CNT / 16; break; default: reg = CSR_READ_4(sc, AGE_SRAM_RX_FIFO_LEN); rxf_lo = reg / 16; if (rxf_lo < 192) rxf_lo = 192; rxf_hi = (reg * 7) / 8; if (rxf_hi < rxf_lo) rxf_hi = rxf_lo + 16; reg = CSR_READ_4(sc, AGE_SRAM_RRD_LEN); rrd_lo = reg / 8; rrd_hi = (reg * 7) / 8; if (rrd_lo < 2) rrd_lo = 2; if (rrd_hi < rrd_lo) rrd_hi = rrd_lo + 3; break; } CSR_WRITE_4(sc, AGE_RXQ_FIFO_PAUSE_THRESH, ((rxf_lo << RXQ_FIFO_PAUSE_THRESH_LO_SHIFT) & RXQ_FIFO_PAUSE_THRESH_LO_MASK) | ((rxf_hi << RXQ_FIFO_PAUSE_THRESH_HI_SHIFT) & RXQ_FIFO_PAUSE_THRESH_HI_MASK)); CSR_WRITE_4(sc, AGE_RXQ_RRD_PAUSE_THRESH, ((rrd_lo << RXQ_RRD_PAUSE_THRESH_LO_SHIFT) & RXQ_RRD_PAUSE_THRESH_LO_MASK) | ((rrd_hi << RXQ_RRD_PAUSE_THRESH_HI_SHIFT) & RXQ_RRD_PAUSE_THRESH_HI_MASK)); /* Configure RxQ. */ CSR_WRITE_4(sc, AGE_RXQ_CFG, ((RXQ_CFG_RD_BURST_DEFAULT << RXQ_CFG_RD_BURST_SHIFT) & RXQ_CFG_RD_BURST_MASK) | ((RXQ_CFG_RRD_BURST_THRESH_DEFAULT << RXQ_CFG_RRD_BURST_THRESH_SHIFT) & RXQ_CFG_RRD_BURST_THRESH_MASK) | ((RXQ_CFG_RD_PREF_MIN_IPG_DEFAULT << RXQ_CFG_RD_PREF_MIN_IPG_SHIFT) & RXQ_CFG_RD_PREF_MIN_IPG_MASK) | RXQ_CFG_CUT_THROUGH_ENB | RXQ_CFG_ENB); /* Configure TxQ. */ CSR_WRITE_4(sc, AGE_TXQ_CFG, ((TXQ_CFG_TPD_BURST_DEFAULT << TXQ_CFG_TPD_BURST_SHIFT) & TXQ_CFG_TPD_BURST_MASK) | ((TXQ_CFG_TX_FIFO_BURST_DEFAULT << TXQ_CFG_TX_FIFO_BURST_SHIFT) & TXQ_CFG_TX_FIFO_BURST_MASK) | ((TXQ_CFG_TPD_FETCH_DEFAULT << TXQ_CFG_TPD_FETCH_THRESH_SHIFT) & TXQ_CFG_TPD_FETCH_THRESH_MASK) | TXQ_CFG_ENB); CSR_WRITE_4(sc, AGE_TX_JUMBO_TPD_TH_IPG, (((fsize / sizeof(uint64_t) << TX_JUMBO_TPD_TH_SHIFT)) & TX_JUMBO_TPD_TH_MASK) | ((TX_JUMBO_TPD_IPG_DEFAULT << TX_JUMBO_TPD_IPG_SHIFT) & TX_JUMBO_TPD_IPG_MASK)); /* Configure DMA parameters. */ CSR_WRITE_4(sc, AGE_DMA_CFG, DMA_CFG_ENH_ORDER | DMA_CFG_RCB_64 | sc->age_dma_rd_burst | DMA_CFG_RD_ENB | sc->age_dma_wr_burst | DMA_CFG_WR_ENB); /* Configure CMB DMA write threshold. */ CSR_WRITE_4(sc, AGE_CMB_WR_THRESH, ((CMB_WR_THRESH_RRD_DEFAULT << CMB_WR_THRESH_RRD_SHIFT) & CMB_WR_THRESH_RRD_MASK) | ((CMB_WR_THRESH_TPD_DEFAULT << CMB_WR_THRESH_TPD_SHIFT) & CMB_WR_THRESH_TPD_MASK)); /* Set CMB/SMB timer and enable them. */ CSR_WRITE_4(sc, AGE_CMB_WR_TIMER, ((AGE_USECS(2) << CMB_WR_TIMER_TX_SHIFT) & CMB_WR_TIMER_TX_MASK) | ((AGE_USECS(2) << CMB_WR_TIMER_RX_SHIFT) & CMB_WR_TIMER_RX_MASK)); /* Request SMB updates for every seconds. */ CSR_WRITE_4(sc, AGE_SMB_TIMER, AGE_USECS(1000 * 1000)); CSR_WRITE_4(sc, AGE_CSMB_CTRL, CSMB_CTRL_SMB_ENB | CSMB_CTRL_CMB_ENB); /* * Disable all WOL bits as WOL can interfere normal Rx * operation. */ CSR_WRITE_4(sc, AGE_WOL_CFG, 0); /* * Configure Tx/Rx MACs. * - Auto-padding for short frames. * - Enable CRC generation. * Start with full-duplex/1000Mbps media. Actual reconfiguration * of MAC is followed after link establishment. */ CSR_WRITE_4(sc, AGE_MAC_CFG, MAC_CFG_TX_CRC_ENB | MAC_CFG_TX_AUTO_PAD | MAC_CFG_FULL_DUPLEX | MAC_CFG_SPEED_1000 | ((MAC_CFG_PREAMBLE_DEFAULT << MAC_CFG_PREAMBLE_SHIFT) & MAC_CFG_PREAMBLE_MASK)); /* Set up the receive filter. */ age_rxfilter(sc); age_rxvlan(sc); reg = CSR_READ_4(sc, AGE_MAC_CFG); if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) reg |= MAC_CFG_RXCSUM_ENB; /* Ack all pending interrupts and clear it. */ CSR_WRITE_4(sc, AGE_INTR_STATUS, 0); CSR_WRITE_4(sc, AGE_INTR_MASK, AGE_INTRS); /* Finally enable Tx/Rx MAC. */ CSR_WRITE_4(sc, AGE_MAC_CFG, reg | MAC_CFG_TX_ENB | MAC_CFG_RX_ENB); sc->age_flags &= ~AGE_FLAG_LINK; /* Switch to the current media. */ mii_mediachg(mii); callout_reset(&sc->age_tick_ch, hz, age_tick, sc); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; } static void age_stop(struct age_softc *sc) { struct ifnet *ifp; struct age_txdesc *txd; struct age_rxdesc *rxd; uint32_t reg; int i; AGE_LOCK_ASSERT(sc); /* * Mark the interface down and cancel the watchdog timer. */ ifp = sc->age_ifp; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc->age_flags &= ~AGE_FLAG_LINK; callout_stop(&sc->age_tick_ch); sc->age_watchdog_timer = 0; /* * Disable interrupts. */ CSR_WRITE_4(sc, AGE_INTR_MASK, 0); CSR_WRITE_4(sc, AGE_INTR_STATUS, 0xFFFFFFFF); /* Stop CMB/SMB updates. */ CSR_WRITE_4(sc, AGE_CSMB_CTRL, 0); /* Stop Rx/Tx MAC. */ age_stop_rxmac(sc); age_stop_txmac(sc); /* Stop DMA. */ CSR_WRITE_4(sc, AGE_DMA_CFG, CSR_READ_4(sc, AGE_DMA_CFG) & ~(DMA_CFG_RD_ENB | DMA_CFG_WR_ENB)); /* Stop TxQ/RxQ. */ CSR_WRITE_4(sc, AGE_TXQ_CFG, CSR_READ_4(sc, AGE_TXQ_CFG) & ~TXQ_CFG_ENB); CSR_WRITE_4(sc, AGE_RXQ_CFG, CSR_READ_4(sc, AGE_RXQ_CFG) & ~RXQ_CFG_ENB); for (i = AGE_RESET_TIMEOUT; i > 0; i--) { if ((reg = CSR_READ_4(sc, AGE_IDLE_STATUS)) == 0) break; DELAY(10); } if (i == 0) device_printf(sc->age_dev, "stopping Rx/Tx MACs timed out(0x%08x)!\n", reg); /* Reclaim Rx buffers that have been processed. */ if (sc->age_cdata.age_rxhead != NULL) m_freem(sc->age_cdata.age_rxhead); AGE_RXCHAIN_RESET(sc); /* * Free RX and TX mbufs still in the queues. */ for (i = 0; i < AGE_RX_RING_CNT; i++) { rxd = &sc->age_cdata.age_rxdesc[i]; if (rxd->rx_m != NULL) { bus_dmamap_sync(sc->age_cdata.age_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->age_cdata.age_rx_tag, rxd->rx_dmamap); m_freem(rxd->rx_m); rxd->rx_m = NULL; } } for (i = 0; i < AGE_TX_RING_CNT; i++) { txd = &sc->age_cdata.age_txdesc[i]; if (txd->tx_m != NULL) { bus_dmamap_sync(sc->age_cdata.age_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->age_cdata.age_tx_tag, txd->tx_dmamap); m_freem(txd->tx_m); txd->tx_m = NULL; } } } static void age_stop_txmac(struct age_softc *sc) { uint32_t reg; int i; AGE_LOCK_ASSERT(sc); reg = CSR_READ_4(sc, AGE_MAC_CFG); if ((reg & MAC_CFG_TX_ENB) != 0) { reg &= ~MAC_CFG_TX_ENB; CSR_WRITE_4(sc, AGE_MAC_CFG, reg); } /* Stop Tx DMA engine. */ reg = CSR_READ_4(sc, AGE_DMA_CFG); if ((reg & DMA_CFG_RD_ENB) != 0) { reg &= ~DMA_CFG_RD_ENB; CSR_WRITE_4(sc, AGE_DMA_CFG, reg); } for (i = AGE_RESET_TIMEOUT; i > 0; i--) { if ((CSR_READ_4(sc, AGE_IDLE_STATUS) & (IDLE_STATUS_TXMAC | IDLE_STATUS_DMARD)) == 0) break; DELAY(10); } if (i == 0) device_printf(sc->age_dev, "stopping TxMAC timeout!\n"); } static void age_stop_rxmac(struct age_softc *sc) { uint32_t reg; int i; AGE_LOCK_ASSERT(sc); reg = CSR_READ_4(sc, AGE_MAC_CFG); if ((reg & MAC_CFG_RX_ENB) != 0) { reg &= ~MAC_CFG_RX_ENB; CSR_WRITE_4(sc, AGE_MAC_CFG, reg); } /* Stop Rx DMA engine. */ reg = CSR_READ_4(sc, AGE_DMA_CFG); if ((reg & DMA_CFG_WR_ENB) != 0) { reg &= ~DMA_CFG_WR_ENB; CSR_WRITE_4(sc, AGE_DMA_CFG, reg); } for (i = AGE_RESET_TIMEOUT; i > 0; i--) { if ((CSR_READ_4(sc, AGE_IDLE_STATUS) & (IDLE_STATUS_RXMAC | IDLE_STATUS_DMAWR)) == 0) break; DELAY(10); } if (i == 0) device_printf(sc->age_dev, "stopping RxMAC timeout!\n"); } static void age_init_tx_ring(struct age_softc *sc) { struct age_ring_data *rd; struct age_txdesc *txd; int i; AGE_LOCK_ASSERT(sc); sc->age_cdata.age_tx_prod = 0; sc->age_cdata.age_tx_cons = 0; sc->age_cdata.age_tx_cnt = 0; rd = &sc->age_rdata; bzero(rd->age_tx_ring, AGE_TX_RING_SZ); for (i = 0; i < AGE_TX_RING_CNT; i++) { txd = &sc->age_cdata.age_txdesc[i]; txd->tx_desc = &rd->age_tx_ring[i]; txd->tx_m = NULL; } bus_dmamap_sync(sc->age_cdata.age_tx_ring_tag, sc->age_cdata.age_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static int age_init_rx_ring(struct age_softc *sc) { struct age_ring_data *rd; struct age_rxdesc *rxd; int i; AGE_LOCK_ASSERT(sc); sc->age_cdata.age_rx_cons = AGE_RX_RING_CNT - 1; sc->age_morework = 0; rd = &sc->age_rdata; bzero(rd->age_rx_ring, AGE_RX_RING_SZ); for (i = 0; i < AGE_RX_RING_CNT; i++) { rxd = &sc->age_cdata.age_rxdesc[i]; rxd->rx_m = NULL; rxd->rx_desc = &rd->age_rx_ring[i]; if (age_newbuf(sc, rxd) != 0) return (ENOBUFS); } bus_dmamap_sync(sc->age_cdata.age_rx_ring_tag, sc->age_cdata.age_rx_ring_map, BUS_DMASYNC_PREWRITE); return (0); } static void age_init_rr_ring(struct age_softc *sc) { struct age_ring_data *rd; AGE_LOCK_ASSERT(sc); sc->age_cdata.age_rr_cons = 0; AGE_RXCHAIN_RESET(sc); rd = &sc->age_rdata; bzero(rd->age_rr_ring, AGE_RR_RING_SZ); bus_dmamap_sync(sc->age_cdata.age_rr_ring_tag, sc->age_cdata.age_rr_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static void age_init_cmb_block(struct age_softc *sc) { struct age_ring_data *rd; AGE_LOCK_ASSERT(sc); rd = &sc->age_rdata; bzero(rd->age_cmb_block, AGE_CMB_BLOCK_SZ); bus_dmamap_sync(sc->age_cdata.age_cmb_block_tag, sc->age_cdata.age_cmb_block_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static void age_init_smb_block(struct age_softc *sc) { struct age_ring_data *rd; AGE_LOCK_ASSERT(sc); rd = &sc->age_rdata; bzero(rd->age_smb_block, AGE_SMB_BLOCK_SZ); bus_dmamap_sync(sc->age_cdata.age_smb_block_tag, sc->age_cdata.age_smb_block_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static int age_newbuf(struct age_softc *sc, struct age_rxdesc *rxd) { struct rx_desc *desc; struct mbuf *m; bus_dma_segment_t segs[1]; bus_dmamap_t map; int nsegs; AGE_LOCK_ASSERT(sc); m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) return (ENOBUFS); m->m_len = m->m_pkthdr.len = MCLBYTES; #ifndef __NO_STRICT_ALIGNMENT m_adj(m, AGE_RX_BUF_ALIGN); #endif if (bus_dmamap_load_mbuf_sg(sc->age_cdata.age_rx_tag, sc->age_cdata.age_rx_sparemap, m, segs, &nsegs, 0) != 0) { m_freem(m); return (ENOBUFS); } KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); if (rxd->rx_m != NULL) { bus_dmamap_sync(sc->age_cdata.age_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->age_cdata.age_rx_tag, rxd->rx_dmamap); } map = rxd->rx_dmamap; rxd->rx_dmamap = sc->age_cdata.age_rx_sparemap; sc->age_cdata.age_rx_sparemap = map; bus_dmamap_sync(sc->age_cdata.age_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_PREREAD); rxd->rx_m = m; desc = rxd->rx_desc; desc->addr = htole64(segs[0].ds_addr); desc->len = htole32((segs[0].ds_len & AGE_RD_LEN_MASK) << AGE_RD_LEN_SHIFT); return (0); } static void age_rxvlan(struct age_softc *sc) { struct ifnet *ifp; uint32_t reg; AGE_LOCK_ASSERT(sc); ifp = sc->age_ifp; reg = CSR_READ_4(sc, AGE_MAC_CFG); reg &= ~MAC_CFG_VLAN_TAG_STRIP; if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0) reg |= MAC_CFG_VLAN_TAG_STRIP; CSR_WRITE_4(sc, AGE_MAC_CFG, reg); } static void age_rxfilter(struct age_softc *sc) { struct ifnet *ifp; struct ifmultiaddr *ifma; uint32_t crc; uint32_t mchash[2]; uint32_t rxcfg; AGE_LOCK_ASSERT(sc); ifp = sc->age_ifp; rxcfg = CSR_READ_4(sc, AGE_MAC_CFG); rxcfg &= ~(MAC_CFG_ALLMULTI | MAC_CFG_BCAST | MAC_CFG_PROMISC); if ((ifp->if_flags & IFF_BROADCAST) != 0) rxcfg |= MAC_CFG_BCAST; if ((ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI)) != 0) { if ((ifp->if_flags & IFF_PROMISC) != 0) rxcfg |= MAC_CFG_PROMISC; if ((ifp->if_flags & IFF_ALLMULTI) != 0) rxcfg |= MAC_CFG_ALLMULTI; CSR_WRITE_4(sc, AGE_MAR0, 0xFFFFFFFF); CSR_WRITE_4(sc, AGE_MAR1, 0xFFFFFFFF); CSR_WRITE_4(sc, AGE_MAC_CFG, rxcfg); return; } /* Program new filter. */ bzero(mchash, sizeof(mchash)); if_maddr_rlock(ifp); TAILQ_FOREACH(ifma, &sc->age_ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; crc = ether_crc32_be(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN); mchash[crc >> 31] |= 1 << ((crc >> 26) & 0x1f); } if_maddr_runlock(ifp); CSR_WRITE_4(sc, AGE_MAR0, mchash[0]); CSR_WRITE_4(sc, AGE_MAR1, mchash[1]); CSR_WRITE_4(sc, AGE_MAC_CFG, rxcfg); } static int sysctl_age_stats(SYSCTL_HANDLER_ARGS) { struct age_softc *sc; struct age_stats *stats; int error, result; result = -1; error = sysctl_handle_int(oidp, &result, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (result != 1) return (error); sc = (struct age_softc *)arg1; stats = &sc->age_stat; printf("%s statistics:\n", device_get_nameunit(sc->age_dev)); printf("Transmit good frames : %ju\n", (uintmax_t)stats->tx_frames); printf("Transmit good broadcast frames : %ju\n", (uintmax_t)stats->tx_bcast_frames); printf("Transmit good multicast frames : %ju\n", (uintmax_t)stats->tx_mcast_frames); printf("Transmit pause control frames : %u\n", stats->tx_pause_frames); printf("Transmit control frames : %u\n", stats->tx_control_frames); printf("Transmit frames with excessive deferrals : %u\n", stats->tx_excess_defer); printf("Transmit deferrals : %u\n", stats->tx_deferred); printf("Transmit good octets : %ju\n", (uintmax_t)stats->tx_bytes); printf("Transmit good broadcast octets : %ju\n", (uintmax_t)stats->tx_bcast_bytes); printf("Transmit good multicast octets : %ju\n", (uintmax_t)stats->tx_mcast_bytes); printf("Transmit frames 64 bytes : %ju\n", (uintmax_t)stats->tx_pkts_64); printf("Transmit frames 65 to 127 bytes : %ju\n", (uintmax_t)stats->tx_pkts_65_127); printf("Transmit frames 128 to 255 bytes : %ju\n", (uintmax_t)stats->tx_pkts_128_255); printf("Transmit frames 256 to 511 bytes : %ju\n", (uintmax_t)stats->tx_pkts_256_511); printf("Transmit frames 512 to 1024 bytes : %ju\n", (uintmax_t)stats->tx_pkts_512_1023); printf("Transmit frames 1024 to 1518 bytes : %ju\n", (uintmax_t)stats->tx_pkts_1024_1518); printf("Transmit frames 1519 to MTU bytes : %ju\n", (uintmax_t)stats->tx_pkts_1519_max); printf("Transmit single collisions : %u\n", stats->tx_single_colls); printf("Transmit multiple collisions : %u\n", stats->tx_multi_colls); printf("Transmit late collisions : %u\n", stats->tx_late_colls); printf("Transmit abort due to excessive collisions : %u\n", stats->tx_excess_colls); printf("Transmit underruns due to FIFO underruns : %u\n", stats->tx_underrun); printf("Transmit descriptor write-back errors : %u\n", stats->tx_desc_underrun); printf("Transmit frames with length mismatched frame size : %u\n", stats->tx_lenerrs); printf("Transmit frames with truncated due to MTU size : %u\n", stats->tx_lenerrs); printf("Receive good frames : %ju\n", (uintmax_t)stats->rx_frames); printf("Receive good broadcast frames : %ju\n", (uintmax_t)stats->rx_bcast_frames); printf("Receive good multicast frames : %ju\n", (uintmax_t)stats->rx_mcast_frames); printf("Receive pause control frames : %u\n", stats->rx_pause_frames); printf("Receive control frames : %u\n", stats->rx_control_frames); printf("Receive CRC errors : %u\n", stats->rx_crcerrs); printf("Receive frames with length errors : %u\n", stats->rx_lenerrs); printf("Receive good octets : %ju\n", (uintmax_t)stats->rx_bytes); printf("Receive good broadcast octets : %ju\n", (uintmax_t)stats->rx_bcast_bytes); printf("Receive good multicast octets : %ju\n", (uintmax_t)stats->rx_mcast_bytes); printf("Receive frames too short : %u\n", stats->rx_runts); printf("Receive fragmented frames : %ju\n", (uintmax_t)stats->rx_fragments); printf("Receive frames 64 bytes : %ju\n", (uintmax_t)stats->rx_pkts_64); printf("Receive frames 65 to 127 bytes : %ju\n", (uintmax_t)stats->rx_pkts_65_127); printf("Receive frames 128 to 255 bytes : %ju\n", (uintmax_t)stats->rx_pkts_128_255); printf("Receive frames 256 to 511 bytes : %ju\n", (uintmax_t)stats->rx_pkts_256_511); printf("Receive frames 512 to 1024 bytes : %ju\n", (uintmax_t)stats->rx_pkts_512_1023); printf("Receive frames 1024 to 1518 bytes : %ju\n", (uintmax_t)stats->rx_pkts_1024_1518); printf("Receive frames 1519 to MTU bytes : %ju\n", (uintmax_t)stats->rx_pkts_1519_max); printf("Receive frames too long : %ju\n", (uint64_t)stats->rx_pkts_truncated); printf("Receive frames with FIFO overflow : %u\n", stats->rx_fifo_oflows); printf("Receive frames with return descriptor overflow : %u\n", stats->rx_desc_oflows); printf("Receive frames with alignment errors : %u\n", stats->rx_alignerrs); printf("Receive frames dropped due to address filtering : %ju\n", (uint64_t)stats->rx_pkts_filtered); return (error); } static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int low, int high) { int error, value; if (arg1 == NULL) return (EINVAL); value = *(int *)arg1; error = sysctl_handle_int(oidp, &value, 0, req); if (error || req->newptr == NULL) return (error); if (value < low || value > high) return (EINVAL); *(int *)arg1 = value; return (0); } static int sysctl_hw_age_proc_limit(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, AGE_PROC_MIN, AGE_PROC_MAX)); } static int sysctl_hw_age_int_mod(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, AGE_IM_TIMER_MIN, AGE_IM_TIMER_MAX)); } Index: head/sys/dev/alc/if_alc.c =================================================================== --- head/sys/dev/alc/if_alc.c (revision 295734) +++ head/sys/dev/alc/if_alc.c (revision 295735) @@ -1,4621 +1,4621 @@ /*- * Copyright (c) 2009, Pyun YongHyeon * 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 unmodified, 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* Driver for Atheros AR813x/AR815x PCIe Ethernet. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* "device miibus" required. See GENERIC if you get errors here. */ #include "miibus_if.h" #undef ALC_USE_CUSTOM_CSUM #ifdef ALC_USE_CUSTOM_CSUM #define ALC_CSUM_FEATURES (CSUM_TCP | CSUM_UDP) #else #define ALC_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP) #endif MODULE_DEPEND(alc, pci, 1, 1, 1); MODULE_DEPEND(alc, ether, 1, 1, 1); MODULE_DEPEND(alc, miibus, 1, 1, 1); /* Tunables. */ static int msi_disable = 0; static int msix_disable = 0; TUNABLE_INT("hw.alc.msi_disable", &msi_disable); TUNABLE_INT("hw.alc.msix_disable", &msix_disable); /* * Devices supported by this driver. */ static struct alc_ident alc_ident_table[] = { { VENDORID_ATHEROS, DEVICEID_ATHEROS_AR8131, 9 * 1024, "Atheros AR8131 PCIe Gigabit Ethernet" }, { VENDORID_ATHEROS, DEVICEID_ATHEROS_AR8132, 9 * 1024, "Atheros AR8132 PCIe Fast Ethernet" }, { VENDORID_ATHEROS, DEVICEID_ATHEROS_AR8151, 6 * 1024, "Atheros AR8151 v1.0 PCIe Gigabit Ethernet" }, { VENDORID_ATHEROS, DEVICEID_ATHEROS_AR8151_V2, 6 * 1024, "Atheros AR8151 v2.0 PCIe Gigabit Ethernet" }, { VENDORID_ATHEROS, DEVICEID_ATHEROS_AR8152_B, 6 * 1024, "Atheros AR8152 v1.1 PCIe Fast Ethernet" }, { VENDORID_ATHEROS, DEVICEID_ATHEROS_AR8152_B2, 6 * 1024, "Atheros AR8152 v2.0 PCIe Fast Ethernet" }, { VENDORID_ATHEROS, DEVICEID_ATHEROS_AR8161, 9 * 1024, "Atheros AR8161 PCIe Gigabit Ethernet" }, { VENDORID_ATHEROS, DEVICEID_ATHEROS_AR8162, 9 * 1024, "Atheros AR8162 PCIe Fast Ethernet" }, { VENDORID_ATHEROS, DEVICEID_ATHEROS_AR8171, 9 * 1024, "Atheros AR8171 PCIe Gigabit Ethernet" }, { VENDORID_ATHEROS, DEVICEID_ATHEROS_AR8172, 9 * 1024, "Atheros AR8172 PCIe Fast Ethernet" }, { VENDORID_ATHEROS, DEVICEID_ATHEROS_E2200, 9 * 1024, "Killer E2200 Gigabit Ethernet" }, { 0, 0, 0, NULL} }; static void alc_aspm(struct alc_softc *, int, int); static void alc_aspm_813x(struct alc_softc *, int); static void alc_aspm_816x(struct alc_softc *, int); static int alc_attach(device_t); static int alc_check_boundary(struct alc_softc *); static void alc_config_msi(struct alc_softc *); static int alc_detach(device_t); static void alc_disable_l0s_l1(struct alc_softc *); static int alc_dma_alloc(struct alc_softc *); static void alc_dma_free(struct alc_softc *); static void alc_dmamap_cb(void *, bus_dma_segment_t *, int, int); static void alc_dsp_fixup(struct alc_softc *, int); static int alc_encap(struct alc_softc *, struct mbuf **); static struct alc_ident * alc_find_ident(device_t); #ifndef __NO_STRICT_ALIGNMENT static struct mbuf * alc_fixup_rx(struct ifnet *, struct mbuf *); #endif static void alc_get_macaddr(struct alc_softc *); static void alc_get_macaddr_813x(struct alc_softc *); static void alc_get_macaddr_816x(struct alc_softc *); static void alc_get_macaddr_par(struct alc_softc *); static void alc_init(void *); static void alc_init_cmb(struct alc_softc *); static void alc_init_locked(struct alc_softc *); static void alc_init_rr_ring(struct alc_softc *); static int alc_init_rx_ring(struct alc_softc *); static void alc_init_smb(struct alc_softc *); static void alc_init_tx_ring(struct alc_softc *); static void alc_int_task(void *, int); static int alc_intr(void *); static int alc_ioctl(struct ifnet *, u_long, caddr_t); static void alc_mac_config(struct alc_softc *); static uint32_t alc_mii_readreg_813x(struct alc_softc *, int, int); static uint32_t alc_mii_readreg_816x(struct alc_softc *, int, int); static uint32_t alc_mii_writereg_813x(struct alc_softc *, int, int, int); static uint32_t alc_mii_writereg_816x(struct alc_softc *, int, int, int); static int alc_miibus_readreg(device_t, int, int); static void alc_miibus_statchg(device_t); static int alc_miibus_writereg(device_t, int, int, int); static uint32_t alc_miidbg_readreg(struct alc_softc *, int); static uint32_t alc_miidbg_writereg(struct alc_softc *, int, int); static uint32_t alc_miiext_readreg(struct alc_softc *, int, int); static uint32_t alc_miiext_writereg(struct alc_softc *, int, int, int); static int alc_mediachange(struct ifnet *); static int alc_mediachange_locked(struct alc_softc *); static void alc_mediastatus(struct ifnet *, struct ifmediareq *); static int alc_newbuf(struct alc_softc *, struct alc_rxdesc *); static void alc_osc_reset(struct alc_softc *); static void alc_phy_down(struct alc_softc *); static void alc_phy_reset(struct alc_softc *); static void alc_phy_reset_813x(struct alc_softc *); static void alc_phy_reset_816x(struct alc_softc *); static int alc_probe(device_t); static void alc_reset(struct alc_softc *); static int alc_resume(device_t); static void alc_rxeof(struct alc_softc *, struct rx_rdesc *); static int alc_rxintr(struct alc_softc *, int); static void alc_rxfilter(struct alc_softc *); static void alc_rxvlan(struct alc_softc *); static void alc_setlinkspeed(struct alc_softc *); static void alc_setwol(struct alc_softc *); static void alc_setwol_813x(struct alc_softc *); static void alc_setwol_816x(struct alc_softc *); static int alc_shutdown(device_t); static void alc_start(struct ifnet *); static void alc_start_locked(struct ifnet *); static void alc_start_queue(struct alc_softc *); static void alc_stats_clear(struct alc_softc *); static void alc_stats_update(struct alc_softc *); static void alc_stop(struct alc_softc *); static void alc_stop_mac(struct alc_softc *); static void alc_stop_queue(struct alc_softc *); static int alc_suspend(device_t); static void alc_sysctl_node(struct alc_softc *); static void alc_tick(void *); static void alc_txeof(struct alc_softc *); static void alc_watchdog(struct alc_softc *); static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int, int); static int sysctl_hw_alc_proc_limit(SYSCTL_HANDLER_ARGS); static int sysctl_hw_alc_int_mod(SYSCTL_HANDLER_ARGS); static device_method_t alc_methods[] = { /* Device interface. */ DEVMETHOD(device_probe, alc_probe), DEVMETHOD(device_attach, alc_attach), DEVMETHOD(device_detach, alc_detach), DEVMETHOD(device_shutdown, alc_shutdown), DEVMETHOD(device_suspend, alc_suspend), DEVMETHOD(device_resume, alc_resume), /* MII interface. */ DEVMETHOD(miibus_readreg, alc_miibus_readreg), DEVMETHOD(miibus_writereg, alc_miibus_writereg), DEVMETHOD(miibus_statchg, alc_miibus_statchg), { NULL, NULL } }; static driver_t alc_driver = { "alc", alc_methods, sizeof(struct alc_softc) }; static devclass_t alc_devclass; DRIVER_MODULE(alc, pci, alc_driver, alc_devclass, 0, 0); DRIVER_MODULE(miibus, alc, miibus_driver, miibus_devclass, 0, 0); static struct resource_spec alc_res_spec_mem[] = { { SYS_RES_MEMORY, PCIR_BAR(0), RF_ACTIVE }, { -1, 0, 0 } }; static struct resource_spec alc_irq_spec_legacy[] = { { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0, 0 } }; static struct resource_spec alc_irq_spec_msi[] = { { SYS_RES_IRQ, 1, RF_ACTIVE }, { -1, 0, 0 } }; static struct resource_spec alc_irq_spec_msix[] = { { SYS_RES_IRQ, 1, RF_ACTIVE }, { -1, 0, 0 } }; static uint32_t alc_dma_burst[] = { 128, 256, 512, 1024, 2048, 4096, 0 }; static int alc_miibus_readreg(device_t dev, int phy, int reg) { struct alc_softc *sc; int v; sc = device_get_softc(dev); if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) v = alc_mii_readreg_816x(sc, phy, reg); else v = alc_mii_readreg_813x(sc, phy, reg); return (v); } static uint32_t alc_mii_readreg_813x(struct alc_softc *sc, int phy, int reg) { uint32_t v; int i; /* * For AR8132 fast ethernet controller, do not report 1000baseT * capability to mii(4). Even though AR8132 uses the same * model/revision number of F1 gigabit PHY, the PHY has no * ability to establish 1000baseT link. */ if ((sc->alc_flags & ALC_FLAG_FASTETHER) != 0 && reg == MII_EXTSR) return (0); CSR_WRITE_4(sc, ALC_MDIO, MDIO_OP_EXECUTE | MDIO_OP_READ | MDIO_SUP_PREAMBLE | MDIO_CLK_25_4 | MDIO_REG_ADDR(reg)); for (i = ALC_PHY_TIMEOUT; i > 0; i--) { DELAY(5); v = CSR_READ_4(sc, ALC_MDIO); if ((v & (MDIO_OP_EXECUTE | MDIO_OP_BUSY)) == 0) break; } if (i == 0) { device_printf(sc->alc_dev, "phy read timeout : %d\n", reg); return (0); } return ((v & MDIO_DATA_MASK) >> MDIO_DATA_SHIFT); } static uint32_t alc_mii_readreg_816x(struct alc_softc *sc, int phy, int reg) { uint32_t clk, v; int i; if ((sc->alc_flags & ALC_FLAG_LINK) != 0) clk = MDIO_CLK_25_128; else clk = MDIO_CLK_25_4; CSR_WRITE_4(sc, ALC_MDIO, MDIO_OP_EXECUTE | MDIO_OP_READ | MDIO_SUP_PREAMBLE | clk | MDIO_REG_ADDR(reg)); for (i = ALC_PHY_TIMEOUT; i > 0; i--) { DELAY(5); v = CSR_READ_4(sc, ALC_MDIO); if ((v & MDIO_OP_BUSY) == 0) break; } if (i == 0) { device_printf(sc->alc_dev, "phy read timeout : %d\n", reg); return (0); } return ((v & MDIO_DATA_MASK) >> MDIO_DATA_SHIFT); } static int alc_miibus_writereg(device_t dev, int phy, int reg, int val) { struct alc_softc *sc; int v; sc = device_get_softc(dev); if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) v = alc_mii_writereg_816x(sc, phy, reg, val); else v = alc_mii_writereg_813x(sc, phy, reg, val); return (v); } static uint32_t alc_mii_writereg_813x(struct alc_softc *sc, int phy, int reg, int val) { uint32_t v; int i; CSR_WRITE_4(sc, ALC_MDIO, MDIO_OP_EXECUTE | MDIO_OP_WRITE | (val & MDIO_DATA_MASK) << MDIO_DATA_SHIFT | MDIO_SUP_PREAMBLE | MDIO_CLK_25_4 | MDIO_REG_ADDR(reg)); for (i = ALC_PHY_TIMEOUT; i > 0; i--) { DELAY(5); v = CSR_READ_4(sc, ALC_MDIO); if ((v & (MDIO_OP_EXECUTE | MDIO_OP_BUSY)) == 0) break; } if (i == 0) device_printf(sc->alc_dev, "phy write timeout : %d\n", reg); return (0); } static uint32_t alc_mii_writereg_816x(struct alc_softc *sc, int phy, int reg, int val) { uint32_t clk, v; int i; if ((sc->alc_flags & ALC_FLAG_LINK) != 0) clk = MDIO_CLK_25_128; else clk = MDIO_CLK_25_4; CSR_WRITE_4(sc, ALC_MDIO, MDIO_OP_EXECUTE | MDIO_OP_WRITE | ((val & MDIO_DATA_MASK) << MDIO_DATA_SHIFT) | MDIO_REG_ADDR(reg) | MDIO_SUP_PREAMBLE | clk); for (i = ALC_PHY_TIMEOUT; i > 0; i--) { DELAY(5); v = CSR_READ_4(sc, ALC_MDIO); if ((v & MDIO_OP_BUSY) == 0) break; } if (i == 0) device_printf(sc->alc_dev, "phy write timeout : %d\n", reg); return (0); } static void alc_miibus_statchg(device_t dev) { struct alc_softc *sc; struct mii_data *mii; struct ifnet *ifp; uint32_t reg; sc = device_get_softc(dev); mii = device_get_softc(sc->alc_miibus); ifp = sc->alc_ifp; if (mii == NULL || ifp == NULL || (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; sc->alc_flags &= ~ALC_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: case IFM_100_TX: sc->alc_flags |= ALC_FLAG_LINK; break; case IFM_1000_T: if ((sc->alc_flags & ALC_FLAG_FASTETHER) == 0) sc->alc_flags |= ALC_FLAG_LINK; break; default: break; } } /* Stop Rx/Tx MACs. */ alc_stop_mac(sc); /* Program MACs with resolved speed/duplex/flow-control. */ if ((sc->alc_flags & ALC_FLAG_LINK) != 0) { alc_start_queue(sc); alc_mac_config(sc); /* Re-enable Tx/Rx MACs. */ reg = CSR_READ_4(sc, ALC_MAC_CFG); reg |= MAC_CFG_TX_ENB | MAC_CFG_RX_ENB; CSR_WRITE_4(sc, ALC_MAC_CFG, reg); } alc_aspm(sc, 0, IFM_SUBTYPE(mii->mii_media_active)); alc_dsp_fixup(sc, IFM_SUBTYPE(mii->mii_media_active)); } static uint32_t alc_miidbg_readreg(struct alc_softc *sc, int reg) { alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, reg); return (alc_miibus_readreg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA)); } static uint32_t alc_miidbg_writereg(struct alc_softc *sc, int reg, int val) { alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, reg); return (alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, val)); } static uint32_t alc_miiext_readreg(struct alc_softc *sc, int devaddr, int reg) { uint32_t clk, v; int i; CSR_WRITE_4(sc, ALC_EXT_MDIO, EXT_MDIO_REG(reg) | EXT_MDIO_DEVADDR(devaddr)); if ((sc->alc_flags & ALC_FLAG_LINK) != 0) clk = MDIO_CLK_25_128; else clk = MDIO_CLK_25_4; CSR_WRITE_4(sc, ALC_MDIO, MDIO_OP_EXECUTE | MDIO_OP_READ | MDIO_SUP_PREAMBLE | clk | MDIO_MODE_EXT); for (i = ALC_PHY_TIMEOUT; i > 0; i--) { DELAY(5); v = CSR_READ_4(sc, ALC_MDIO); if ((v & MDIO_OP_BUSY) == 0) break; } if (i == 0) { device_printf(sc->alc_dev, "phy ext read timeout : %d, %d\n", devaddr, reg); return (0); } return ((v & MDIO_DATA_MASK) >> MDIO_DATA_SHIFT); } static uint32_t alc_miiext_writereg(struct alc_softc *sc, int devaddr, int reg, int val) { uint32_t clk, v; int i; CSR_WRITE_4(sc, ALC_EXT_MDIO, EXT_MDIO_REG(reg) | EXT_MDIO_DEVADDR(devaddr)); if ((sc->alc_flags & ALC_FLAG_LINK) != 0) clk = MDIO_CLK_25_128; else clk = MDIO_CLK_25_4; CSR_WRITE_4(sc, ALC_MDIO, MDIO_OP_EXECUTE | MDIO_OP_WRITE | ((val & MDIO_DATA_MASK) << MDIO_DATA_SHIFT) | MDIO_SUP_PREAMBLE | clk | MDIO_MODE_EXT); for (i = ALC_PHY_TIMEOUT; i > 0; i--) { DELAY(5); v = CSR_READ_4(sc, ALC_MDIO); if ((v & MDIO_OP_BUSY) == 0) break; } if (i == 0) device_printf(sc->alc_dev, "phy ext write timeout : %d, %d\n", devaddr, reg); return (0); } static void alc_dsp_fixup(struct alc_softc *sc, int media) { uint16_t agc, len, val; if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) return; if (AR816X_REV(sc->alc_rev) >= AR816X_REV_C0) return; /* * Vendor PHY magic. * 1000BT/AZ, wrong cable length */ if ((sc->alc_flags & ALC_FLAG_LINK) != 0) { len = alc_miiext_readreg(sc, MII_EXT_PCS, MII_EXT_CLDCTL6); len = (len >> EXT_CLDCTL6_CAB_LEN_SHIFT) & EXT_CLDCTL6_CAB_LEN_MASK; agc = alc_miidbg_readreg(sc, MII_DBG_AGC); agc = (agc >> DBG_AGC_2_VGA_SHIFT) & DBG_AGC_2_VGA_MASK; if ((media == IFM_1000_T && len > EXT_CLDCTL6_CAB_LEN_SHORT1G && agc > DBG_AGC_LONG1G_LIMT) || (media == IFM_100_TX && len > DBG_AGC_LONG100M_LIMT && agc > DBG_AGC_LONG1G_LIMT)) { alc_miidbg_writereg(sc, MII_DBG_AZ_ANADECT, DBG_AZ_ANADECT_LONG); val = alc_miiext_readreg(sc, MII_EXT_ANEG, MII_EXT_ANEG_AFE); val |= ANEG_AFEE_10BT_100M_TH; alc_miiext_writereg(sc, MII_EXT_ANEG, MII_EXT_ANEG_AFE, val); } else { alc_miidbg_writereg(sc, MII_DBG_AZ_ANADECT, DBG_AZ_ANADECT_DEFAULT); val = alc_miiext_readreg(sc, MII_EXT_ANEG, MII_EXT_ANEG_AFE); val &= ~ANEG_AFEE_10BT_100M_TH; alc_miiext_writereg(sc, MII_EXT_ANEG, MII_EXT_ANEG_AFE, val); } if ((sc->alc_flags & ALC_FLAG_LINK_WAR) != 0 && AR816X_REV(sc->alc_rev) == AR816X_REV_B0) { if (media == IFM_1000_T) { /* * Giga link threshold, raise the tolerance of * noise 50%. */ val = alc_miidbg_readreg(sc, MII_DBG_MSE20DB); val &= ~DBG_MSE20DB_TH_MASK; val |= (DBG_MSE20DB_TH_HI << DBG_MSE20DB_TH_SHIFT); alc_miidbg_writereg(sc, MII_DBG_MSE20DB, val); } else if (media == IFM_100_TX) alc_miidbg_writereg(sc, MII_DBG_MSE16DB, DBG_MSE16DB_UP); } } else { val = alc_miiext_readreg(sc, MII_EXT_ANEG, MII_EXT_ANEG_AFE); val &= ~ANEG_AFEE_10BT_100M_TH; alc_miiext_writereg(sc, MII_EXT_ANEG, MII_EXT_ANEG_AFE, val); if ((sc->alc_flags & ALC_FLAG_LINK_WAR) != 0 && AR816X_REV(sc->alc_rev) == AR816X_REV_B0) { alc_miidbg_writereg(sc, MII_DBG_MSE16DB, DBG_MSE16DB_DOWN); val = alc_miidbg_readreg(sc, MII_DBG_MSE20DB); val &= ~DBG_MSE20DB_TH_MASK; val |= (DBG_MSE20DB_TH_DEFAULT << DBG_MSE20DB_TH_SHIFT); alc_miidbg_writereg(sc, MII_DBG_MSE20DB, val); } } } static void alc_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr) { struct alc_softc *sc; struct mii_data *mii; sc = ifp->if_softc; ALC_LOCK(sc); if ((ifp->if_flags & IFF_UP) == 0) { ALC_UNLOCK(sc); return; } mii = device_get_softc(sc->alc_miibus); mii_pollstat(mii); ifmr->ifm_status = mii->mii_media_status; ifmr->ifm_active = mii->mii_media_active; ALC_UNLOCK(sc); } static int alc_mediachange(struct ifnet *ifp) { struct alc_softc *sc; int error; sc = ifp->if_softc; ALC_LOCK(sc); error = alc_mediachange_locked(sc); ALC_UNLOCK(sc); return (error); } static int alc_mediachange_locked(struct alc_softc *sc) { struct mii_data *mii; struct mii_softc *miisc; int error; ALC_LOCK_ASSERT(sc); mii = device_get_softc(sc->alc_miibus); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); return (error); } static struct alc_ident * alc_find_ident(device_t dev) { struct alc_ident *ident; uint16_t vendor, devid; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); for (ident = alc_ident_table; ident->name != NULL; ident++) { if (vendor == ident->vendorid && devid == ident->deviceid) return (ident); } return (NULL); } static int alc_probe(device_t dev) { struct alc_ident *ident; ident = alc_find_ident(dev); if (ident != NULL) { device_set_desc(dev, ident->name); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static void alc_get_macaddr(struct alc_softc *sc) { if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) alc_get_macaddr_816x(sc); else alc_get_macaddr_813x(sc); } static void alc_get_macaddr_813x(struct alc_softc *sc) { uint32_t opt; uint16_t val; int eeprom, i; eeprom = 0; opt = CSR_READ_4(sc, ALC_OPT_CFG); if ((CSR_READ_4(sc, ALC_MASTER_CFG) & MASTER_OTP_SEL) != 0 && (CSR_READ_4(sc, ALC_TWSI_DEBUG) & TWSI_DEBUG_DEV_EXIST) != 0) { /* * EEPROM found, let TWSI reload EEPROM configuration. * This will set ethernet address of controller. */ eeprom++; switch (sc->alc_ident->deviceid) { case DEVICEID_ATHEROS_AR8131: case DEVICEID_ATHEROS_AR8132: if ((opt & OPT_CFG_CLK_ENB) == 0) { opt |= OPT_CFG_CLK_ENB; CSR_WRITE_4(sc, ALC_OPT_CFG, opt); CSR_READ_4(sc, ALC_OPT_CFG); DELAY(1000); } break; case DEVICEID_ATHEROS_AR8151: case DEVICEID_ATHEROS_AR8151_V2: case DEVICEID_ATHEROS_AR8152_B: case DEVICEID_ATHEROS_AR8152_B2: alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, 0x00); val = alc_miibus_readreg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, val & 0xFF7F); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, 0x3B); val = alc_miibus_readreg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, val | 0x0008); DELAY(20); break; } CSR_WRITE_4(sc, ALC_LTSSM_ID_CFG, CSR_READ_4(sc, ALC_LTSSM_ID_CFG) & ~LTSSM_ID_WRO_ENB); CSR_WRITE_4(sc, ALC_WOL_CFG, 0); CSR_READ_4(sc, ALC_WOL_CFG); CSR_WRITE_4(sc, ALC_TWSI_CFG, CSR_READ_4(sc, ALC_TWSI_CFG) | TWSI_CFG_SW_LD_START); for (i = 100; i > 0; i--) { DELAY(1000); if ((CSR_READ_4(sc, ALC_TWSI_CFG) & TWSI_CFG_SW_LD_START) == 0) break; } if (i == 0) device_printf(sc->alc_dev, "reloading EEPROM timeout!\n"); } else { if (bootverbose) device_printf(sc->alc_dev, "EEPROM not found!\n"); } if (eeprom != 0) { switch (sc->alc_ident->deviceid) { case DEVICEID_ATHEROS_AR8131: case DEVICEID_ATHEROS_AR8132: if ((opt & OPT_CFG_CLK_ENB) != 0) { opt &= ~OPT_CFG_CLK_ENB; CSR_WRITE_4(sc, ALC_OPT_CFG, opt); CSR_READ_4(sc, ALC_OPT_CFG); DELAY(1000); } break; case DEVICEID_ATHEROS_AR8151: case DEVICEID_ATHEROS_AR8151_V2: case DEVICEID_ATHEROS_AR8152_B: case DEVICEID_ATHEROS_AR8152_B2: alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, 0x00); val = alc_miibus_readreg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, val | 0x0080); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, 0x3B); val = alc_miibus_readreg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, val & 0xFFF7); DELAY(20); break; } } alc_get_macaddr_par(sc); } static void alc_get_macaddr_816x(struct alc_softc *sc) { uint32_t reg; int i, reloaded; reloaded = 0; /* Try to reload station address via TWSI. */ for (i = 100; i > 0; i--) { reg = CSR_READ_4(sc, ALC_SLD); if ((reg & (SLD_PROGRESS | SLD_START)) == 0) break; DELAY(1000); } if (i != 0) { CSR_WRITE_4(sc, ALC_SLD, reg | SLD_START); for (i = 100; i > 0; i--) { DELAY(1000); reg = CSR_READ_4(sc, ALC_SLD); if ((reg & SLD_START) == 0) break; } if (i != 0) reloaded++; else if (bootverbose) device_printf(sc->alc_dev, "reloading station address via TWSI timed out!\n"); } /* Try to reload station address from EEPROM or FLASH. */ if (reloaded == 0) { reg = CSR_READ_4(sc, ALC_EEPROM_LD); if ((reg & (EEPROM_LD_EEPROM_EXIST | EEPROM_LD_FLASH_EXIST)) != 0) { for (i = 100; i > 0; i--) { reg = CSR_READ_4(sc, ALC_EEPROM_LD); if ((reg & (EEPROM_LD_PROGRESS | EEPROM_LD_START)) == 0) break; DELAY(1000); } if (i != 0) { CSR_WRITE_4(sc, ALC_EEPROM_LD, reg | EEPROM_LD_START); for (i = 100; i > 0; i--) { DELAY(1000); reg = CSR_READ_4(sc, ALC_EEPROM_LD); if ((reg & EEPROM_LD_START) == 0) break; } } else if (bootverbose) device_printf(sc->alc_dev, "reloading EEPROM/FLASH timed out!\n"); } } alc_get_macaddr_par(sc); } static void alc_get_macaddr_par(struct alc_softc *sc) { uint32_t ea[2]; ea[0] = CSR_READ_4(sc, ALC_PAR0); ea[1] = CSR_READ_4(sc, ALC_PAR1); sc->alc_eaddr[0] = (ea[1] >> 8) & 0xFF; sc->alc_eaddr[1] = (ea[1] >> 0) & 0xFF; sc->alc_eaddr[2] = (ea[0] >> 24) & 0xFF; sc->alc_eaddr[3] = (ea[0] >> 16) & 0xFF; sc->alc_eaddr[4] = (ea[0] >> 8) & 0xFF; sc->alc_eaddr[5] = (ea[0] >> 0) & 0xFF; } static void alc_disable_l0s_l1(struct alc_softc *sc) { uint32_t pmcfg; if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) == 0) { /* Another magic from vendor. */ pmcfg = CSR_READ_4(sc, ALC_PM_CFG); pmcfg &= ~(PM_CFG_L1_ENTRY_TIMER_MASK | PM_CFG_CLK_SWH_L1 | PM_CFG_ASPM_L0S_ENB | PM_CFG_ASPM_L1_ENB | PM_CFG_MAC_ASPM_CHK | PM_CFG_SERDES_PD_EX_L1); pmcfg |= PM_CFG_SERDES_BUDS_RX_L1_ENB | PM_CFG_SERDES_PLL_L1_ENB | PM_CFG_SERDES_L1_ENB; CSR_WRITE_4(sc, ALC_PM_CFG, pmcfg); } } static void alc_phy_reset(struct alc_softc *sc) { if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) alc_phy_reset_816x(sc); else alc_phy_reset_813x(sc); } static void alc_phy_reset_813x(struct alc_softc *sc) { uint16_t data; /* Reset magic from Linux. */ CSR_WRITE_2(sc, ALC_GPHY_CFG, GPHY_CFG_SEL_ANA_RESET); CSR_READ_2(sc, ALC_GPHY_CFG); DELAY(10 * 1000); CSR_WRITE_2(sc, ALC_GPHY_CFG, GPHY_CFG_EXT_RESET | GPHY_CFG_SEL_ANA_RESET); CSR_READ_2(sc, ALC_GPHY_CFG); DELAY(10 * 1000); /* DSP fixup, Vendor magic. */ if (sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8152_B) { alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, 0x000A); data = alc_miibus_readreg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, data & 0xDFFF); } if (sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8151 || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8151_V2 || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8152_B || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8152_B2) { alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, 0x003B); data = alc_miibus_readreg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, data & 0xFFF7); DELAY(20 * 1000); } if (sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8151) { alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, 0x0029); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, 0x929D); } if (sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8131 || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8132 || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8151_V2 || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8152_B2) { alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, 0x0029); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, 0xB6DD); } /* Load DSP codes, vendor magic. */ data = ANA_LOOP_SEL_10BT | ANA_EN_MASK_TB | ANA_EN_10BT_IDLE | ((1 << ANA_INTERVAL_SEL_TIMER_SHIFT) & ANA_INTERVAL_SEL_TIMER_MASK); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, MII_ANA_CFG18); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, data); data = ((2 << ANA_SERDES_CDR_BW_SHIFT) & ANA_SERDES_CDR_BW_MASK) | ANA_SERDES_EN_DEEM | ANA_SERDES_SEL_HSP | ANA_SERDES_EN_PLL | ANA_SERDES_EN_LCKDT; alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, MII_ANA_CFG5); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, data); data = ((44 << ANA_LONG_CABLE_TH_100_SHIFT) & ANA_LONG_CABLE_TH_100_MASK) | ((33 << ANA_SHORT_CABLE_TH_100_SHIFT) & ANA_SHORT_CABLE_TH_100_SHIFT) | ANA_BP_BAD_LINK_ACCUM | ANA_BP_SMALL_BW; alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, MII_ANA_CFG54); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, data); data = ((11 << ANA_IECHO_ADJ_3_SHIFT) & ANA_IECHO_ADJ_3_MASK) | ((11 << ANA_IECHO_ADJ_2_SHIFT) & ANA_IECHO_ADJ_2_MASK) | ((8 << ANA_IECHO_ADJ_1_SHIFT) & ANA_IECHO_ADJ_1_MASK) | ((8 << ANA_IECHO_ADJ_0_SHIFT) & ANA_IECHO_ADJ_0_MASK); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, MII_ANA_CFG4); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, data); data = ((7 & ANA_MANUL_SWICH_ON_SHIFT) & ANA_MANUL_SWICH_ON_MASK) | ANA_RESTART_CAL | ANA_MAN_ENABLE | ANA_SEL_HSP | ANA_EN_HB | ANA_OEN_125M; alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, MII_ANA_CFG0); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, data); DELAY(1000); /* Disable hibernation. */ alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, 0x0029); data = alc_miibus_readreg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA); data &= ~0x8000; alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, data); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_ADDR, 0x000B); data = alc_miibus_readreg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA); data &= ~0x8000; alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, ALC_MII_DBG_DATA, data); } static void alc_phy_reset_816x(struct alc_softc *sc) { uint32_t val; val = CSR_READ_4(sc, ALC_GPHY_CFG); val &= ~(GPHY_CFG_EXT_RESET | GPHY_CFG_LED_MODE | GPHY_CFG_GATE_25M_ENB | GPHY_CFG_PHY_IDDQ | GPHY_CFG_PHY_PLL_ON | GPHY_CFG_PWDOWN_HW | GPHY_CFG_100AB_ENB); val |= GPHY_CFG_SEL_ANA_RESET; #ifdef notyet val |= GPHY_CFG_HIB_PULSE | GPHY_CFG_HIB_EN | GPHY_CFG_SEL_ANA_RESET; #else /* Disable PHY hibernation. */ val &= ~(GPHY_CFG_HIB_PULSE | GPHY_CFG_HIB_EN); #endif CSR_WRITE_4(sc, ALC_GPHY_CFG, val); DELAY(10); CSR_WRITE_4(sc, ALC_GPHY_CFG, val | GPHY_CFG_EXT_RESET); DELAY(800); /* Vendor PHY magic. */ #ifdef notyet alc_miidbg_writereg(sc, MII_DBG_LEGCYPS, DBG_LEGCYPS_DEFAULT); alc_miidbg_writereg(sc, MII_DBG_SYSMODCTL, DBG_SYSMODCTL_DEFAULT); alc_miiext_writereg(sc, MII_EXT_PCS, MII_EXT_VDRVBIAS, EXT_VDRVBIAS_DEFAULT); #else /* Disable PHY hibernation. */ alc_miidbg_writereg(sc, MII_DBG_LEGCYPS, DBG_LEGCYPS_DEFAULT & ~DBG_LEGCYPS_ENB); alc_miidbg_writereg(sc, MII_DBG_HIBNEG, DBG_HIBNEG_DEFAULT & ~(DBG_HIBNEG_PSHIB_EN | DBG_HIBNEG_HIB_PULSE)); alc_miidbg_writereg(sc, MII_DBG_GREENCFG, DBG_GREENCFG_DEFAULT); #endif /* XXX Disable EEE. */ val = CSR_READ_4(sc, ALC_LPI_CTL); val &= ~LPI_CTL_ENB; CSR_WRITE_4(sc, ALC_LPI_CTL, val); alc_miiext_writereg(sc, MII_EXT_ANEG, MII_EXT_ANEG_LOCAL_EEEADV, 0); /* PHY power saving. */ alc_miidbg_writereg(sc, MII_DBG_TST10BTCFG, DBG_TST10BTCFG_DEFAULT); alc_miidbg_writereg(sc, MII_DBG_SRDSYSMOD, DBG_SRDSYSMOD_DEFAULT); alc_miidbg_writereg(sc, MII_DBG_TST100BTCFG, DBG_TST100BTCFG_DEFAULT); alc_miidbg_writereg(sc, MII_DBG_ANACTL, DBG_ANACTL_DEFAULT); val = alc_miidbg_readreg(sc, MII_DBG_GREENCFG2); val &= ~DBG_GREENCFG2_GATE_DFSE_EN; alc_miidbg_writereg(sc, MII_DBG_GREENCFG2, val); /* RTL8139C, 120m issue. */ alc_miiext_writereg(sc, MII_EXT_ANEG, MII_EXT_ANEG_NLP78, ANEG_NLP78_120M_DEFAULT); alc_miiext_writereg(sc, MII_EXT_ANEG, MII_EXT_ANEG_S3DIG10, ANEG_S3DIG10_DEFAULT); if ((sc->alc_flags & ALC_FLAG_LINK_WAR) != 0) { /* Turn off half amplitude. */ val = alc_miiext_readreg(sc, MII_EXT_PCS, MII_EXT_CLDCTL3); val |= EXT_CLDCTL3_BP_CABLE1TH_DET_GT; alc_miiext_writereg(sc, MII_EXT_PCS, MII_EXT_CLDCTL3, val); /* Turn off Green feature. */ val = alc_miidbg_readreg(sc, MII_DBG_GREENCFG2); val |= DBG_GREENCFG2_BP_GREEN; alc_miidbg_writereg(sc, MII_DBG_GREENCFG2, val); /* Turn off half bias. */ val = alc_miiext_readreg(sc, MII_EXT_PCS, MII_EXT_CLDCTL5); val |= EXT_CLDCTL5_BP_VD_HLFBIAS; alc_miiext_writereg(sc, MII_EXT_PCS, MII_EXT_CLDCTL5, val); } } static void alc_phy_down(struct alc_softc *sc) { uint32_t gphy; switch (sc->alc_ident->deviceid) { case DEVICEID_ATHEROS_AR8161: case DEVICEID_ATHEROS_E2200: case DEVICEID_ATHEROS_AR8162: case DEVICEID_ATHEROS_AR8171: case DEVICEID_ATHEROS_AR8172: gphy = CSR_READ_4(sc, ALC_GPHY_CFG); gphy &= ~(GPHY_CFG_EXT_RESET | GPHY_CFG_LED_MODE | GPHY_CFG_100AB_ENB | GPHY_CFG_PHY_PLL_ON); gphy |= GPHY_CFG_HIB_EN | GPHY_CFG_HIB_PULSE | GPHY_CFG_SEL_ANA_RESET; gphy |= GPHY_CFG_PHY_IDDQ | GPHY_CFG_PWDOWN_HW; CSR_WRITE_4(sc, ALC_GPHY_CFG, gphy); break; case DEVICEID_ATHEROS_AR8151: case DEVICEID_ATHEROS_AR8151_V2: case DEVICEID_ATHEROS_AR8152_B: case DEVICEID_ATHEROS_AR8152_B2: /* * GPHY power down caused more problems on AR8151 v2.0. * When driver is reloaded after GPHY power down, * accesses to PHY/MAC registers hung the system. Only * cold boot recovered from it. I'm not sure whether * AR8151 v1.0 also requires this one though. I don't * have AR8151 v1.0 controller in hand. * The only option left is to isolate the PHY and * initiates power down the PHY which in turn saves * more power when driver is unloaded. */ alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, MII_BMCR, BMCR_ISO | BMCR_PDOWN); break; default: /* Force PHY down. */ CSR_WRITE_2(sc, ALC_GPHY_CFG, GPHY_CFG_EXT_RESET | GPHY_CFG_SEL_ANA_RESET | GPHY_CFG_PHY_IDDQ | GPHY_CFG_PWDOWN_HW); DELAY(1000); break; } } static void alc_aspm(struct alc_softc *sc, int init, int media) { if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) alc_aspm_816x(sc, init); else alc_aspm_813x(sc, media); } static void alc_aspm_813x(struct alc_softc *sc, int media) { uint32_t pmcfg; uint16_t linkcfg; if ((sc->alc_flags & ALC_FLAG_LINK) == 0) return; pmcfg = CSR_READ_4(sc, ALC_PM_CFG); if ((sc->alc_flags & (ALC_FLAG_APS | ALC_FLAG_PCIE)) == (ALC_FLAG_APS | ALC_FLAG_PCIE)) linkcfg = CSR_READ_2(sc, sc->alc_expcap + PCIER_LINK_CTL); else linkcfg = 0; pmcfg &= ~PM_CFG_SERDES_PD_EX_L1; pmcfg &= ~(PM_CFG_L1_ENTRY_TIMER_MASK | PM_CFG_LCKDET_TIMER_MASK); pmcfg |= PM_CFG_MAC_ASPM_CHK; pmcfg |= (PM_CFG_LCKDET_TIMER_DEFAULT << PM_CFG_LCKDET_TIMER_SHIFT); pmcfg &= ~(PM_CFG_ASPM_L1_ENB | PM_CFG_ASPM_L0S_ENB); if ((sc->alc_flags & ALC_FLAG_APS) != 0) { /* Disable extended sync except AR8152 B v1.0 */ linkcfg &= ~PCIEM_LINK_CTL_EXTENDED_SYNC; if (sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8152_B && sc->alc_rev == ATHEROS_AR8152_B_V10) linkcfg |= PCIEM_LINK_CTL_EXTENDED_SYNC; CSR_WRITE_2(sc, sc->alc_expcap + PCIER_LINK_CTL, linkcfg); pmcfg &= ~(PM_CFG_EN_BUFS_RX_L0S | PM_CFG_SA_DLY_ENB | PM_CFG_HOTRST); pmcfg |= (PM_CFG_L1_ENTRY_TIMER_DEFAULT << PM_CFG_L1_ENTRY_TIMER_SHIFT); pmcfg &= ~PM_CFG_PM_REQ_TIMER_MASK; pmcfg |= (PM_CFG_PM_REQ_TIMER_DEFAULT << PM_CFG_PM_REQ_TIMER_SHIFT); pmcfg |= PM_CFG_SERDES_PD_EX_L1 | PM_CFG_PCIE_RECV; } if ((sc->alc_flags & ALC_FLAG_LINK) != 0) { if ((sc->alc_flags & ALC_FLAG_L0S) != 0) pmcfg |= PM_CFG_ASPM_L0S_ENB; if ((sc->alc_flags & ALC_FLAG_L1S) != 0) pmcfg |= PM_CFG_ASPM_L1_ENB; if ((sc->alc_flags & ALC_FLAG_APS) != 0) { if (sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8152_B) pmcfg &= ~PM_CFG_ASPM_L0S_ENB; pmcfg &= ~(PM_CFG_SERDES_L1_ENB | PM_CFG_SERDES_PLL_L1_ENB | PM_CFG_SERDES_BUDS_RX_L1_ENB); pmcfg |= PM_CFG_CLK_SWH_L1; if (media == IFM_100_TX || media == IFM_1000_T) { pmcfg &= ~PM_CFG_L1_ENTRY_TIMER_MASK; switch (sc->alc_ident->deviceid) { case DEVICEID_ATHEROS_AR8152_B: pmcfg |= (7 << PM_CFG_L1_ENTRY_TIMER_SHIFT); break; case DEVICEID_ATHEROS_AR8152_B2: case DEVICEID_ATHEROS_AR8151_V2: pmcfg |= (4 << PM_CFG_L1_ENTRY_TIMER_SHIFT); break; default: pmcfg |= (15 << PM_CFG_L1_ENTRY_TIMER_SHIFT); break; } } } else { pmcfg |= PM_CFG_SERDES_L1_ENB | PM_CFG_SERDES_PLL_L1_ENB | PM_CFG_SERDES_BUDS_RX_L1_ENB; pmcfg &= ~(PM_CFG_CLK_SWH_L1 | PM_CFG_ASPM_L1_ENB | PM_CFG_ASPM_L0S_ENB); } } else { pmcfg &= ~(PM_CFG_SERDES_BUDS_RX_L1_ENB | PM_CFG_SERDES_L1_ENB | PM_CFG_SERDES_PLL_L1_ENB); pmcfg |= PM_CFG_CLK_SWH_L1; if ((sc->alc_flags & ALC_FLAG_L1S) != 0) pmcfg |= PM_CFG_ASPM_L1_ENB; } CSR_WRITE_4(sc, ALC_PM_CFG, pmcfg); } static void alc_aspm_816x(struct alc_softc *sc, int init) { uint32_t pmcfg; pmcfg = CSR_READ_4(sc, ALC_PM_CFG); pmcfg &= ~PM_CFG_L1_ENTRY_TIMER_816X_MASK; pmcfg |= PM_CFG_L1_ENTRY_TIMER_816X_DEFAULT; pmcfg &= ~PM_CFG_PM_REQ_TIMER_MASK; pmcfg |= PM_CFG_PM_REQ_TIMER_816X_DEFAULT; pmcfg &= ~PM_CFG_LCKDET_TIMER_MASK; pmcfg |= PM_CFG_LCKDET_TIMER_DEFAULT; pmcfg |= PM_CFG_SERDES_PD_EX_L1 | PM_CFG_CLK_SWH_L1 | PM_CFG_PCIE_RECV; pmcfg &= ~(PM_CFG_RX_L1_AFTER_L0S | PM_CFG_TX_L1_AFTER_L0S | PM_CFG_ASPM_L1_ENB | PM_CFG_ASPM_L0S_ENB | PM_CFG_SERDES_L1_ENB | PM_CFG_SERDES_PLL_L1_ENB | PM_CFG_SERDES_BUDS_RX_L1_ENB | PM_CFG_SA_DLY_ENB | PM_CFG_MAC_ASPM_CHK | PM_CFG_HOTRST); if (AR816X_REV(sc->alc_rev) <= AR816X_REV_A1 && (sc->alc_rev & 0x01) != 0) pmcfg |= PM_CFG_SERDES_L1_ENB | PM_CFG_SERDES_PLL_L1_ENB; if ((sc->alc_flags & ALC_FLAG_LINK) != 0) { /* Link up, enable both L0s, L1s. */ pmcfg |= PM_CFG_ASPM_L0S_ENB | PM_CFG_ASPM_L1_ENB | PM_CFG_MAC_ASPM_CHK; } else { if (init != 0) pmcfg |= PM_CFG_ASPM_L0S_ENB | PM_CFG_ASPM_L1_ENB | PM_CFG_MAC_ASPM_CHK; else if ((sc->alc_ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) pmcfg |= PM_CFG_ASPM_L1_ENB | PM_CFG_MAC_ASPM_CHK; } CSR_WRITE_4(sc, ALC_PM_CFG, pmcfg); } static void alc_init_pcie(struct alc_softc *sc) { const char *aspm_state[] = { "L0s/L1", "L0s", "L1", "L0s/L1" }; uint32_t cap, ctl, val; int state; /* Clear data link and flow-control protocol error. */ val = CSR_READ_4(sc, ALC_PEX_UNC_ERR_SEV); val &= ~(PEX_UNC_ERR_SEV_DLP | PEX_UNC_ERR_SEV_FCP); CSR_WRITE_4(sc, ALC_PEX_UNC_ERR_SEV, val); if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) == 0) { CSR_WRITE_4(sc, ALC_LTSSM_ID_CFG, CSR_READ_4(sc, ALC_LTSSM_ID_CFG) & ~LTSSM_ID_WRO_ENB); CSR_WRITE_4(sc, ALC_PCIE_PHYMISC, CSR_READ_4(sc, ALC_PCIE_PHYMISC) | PCIE_PHYMISC_FORCE_RCV_DET); if (sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8152_B && sc->alc_rev == ATHEROS_AR8152_B_V10) { val = CSR_READ_4(sc, ALC_PCIE_PHYMISC2); val &= ~(PCIE_PHYMISC2_SERDES_CDR_MASK | PCIE_PHYMISC2_SERDES_TH_MASK); val |= 3 << PCIE_PHYMISC2_SERDES_CDR_SHIFT; val |= 3 << PCIE_PHYMISC2_SERDES_TH_SHIFT; CSR_WRITE_4(sc, ALC_PCIE_PHYMISC2, val); } /* Disable ASPM L0S and L1. */ cap = CSR_READ_2(sc, sc->alc_expcap + PCIER_LINK_CAP); if ((cap & PCIEM_LINK_CAP_ASPM) != 0) { ctl = CSR_READ_2(sc, sc->alc_expcap + PCIER_LINK_CTL); if ((ctl & PCIEM_LINK_CTL_RCB) != 0) sc->alc_rcb = DMA_CFG_RCB_128; if (bootverbose) device_printf(sc->alc_dev, "RCB %u bytes\n", sc->alc_rcb == DMA_CFG_RCB_64 ? 64 : 128); state = ctl & PCIEM_LINK_CTL_ASPMC; if (state & PCIEM_LINK_CTL_ASPMC_L0S) sc->alc_flags |= ALC_FLAG_L0S; if (state & PCIEM_LINK_CTL_ASPMC_L1) sc->alc_flags |= ALC_FLAG_L1S; if (bootverbose) device_printf(sc->alc_dev, "ASPM %s %s\n", aspm_state[state], state == 0 ? "disabled" : "enabled"); alc_disable_l0s_l1(sc); } else { if (bootverbose) device_printf(sc->alc_dev, "no ASPM support\n"); } } else { val = CSR_READ_4(sc, ALC_PDLL_TRNS1); val &= ~PDLL_TRNS1_D3PLLOFF_ENB; CSR_WRITE_4(sc, ALC_PDLL_TRNS1, val); val = CSR_READ_4(sc, ALC_MASTER_CFG); if (AR816X_REV(sc->alc_rev) <= AR816X_REV_A1 && (sc->alc_rev & 0x01) != 0) { if ((val & MASTER_WAKEN_25M) == 0 || (val & MASTER_CLK_SEL_DIS) == 0) { val |= MASTER_WAKEN_25M | MASTER_CLK_SEL_DIS; CSR_WRITE_4(sc, ALC_MASTER_CFG, val); } } else { if ((val & MASTER_WAKEN_25M) == 0 || (val & MASTER_CLK_SEL_DIS) != 0) { val |= MASTER_WAKEN_25M; val &= ~MASTER_CLK_SEL_DIS; CSR_WRITE_4(sc, ALC_MASTER_CFG, val); } } } alc_aspm(sc, 1, IFM_UNKNOWN); } static void alc_config_msi(struct alc_softc *sc) { uint32_t ctl, mod; if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) { /* * It seems interrupt moderation is controlled by * ALC_MSI_RETRANS_TIMER register if MSI/MSIX is active. * Driver uses RX interrupt moderation parameter to * program ALC_MSI_RETRANS_TIMER register. */ ctl = CSR_READ_4(sc, ALC_MSI_RETRANS_TIMER); ctl &= ~MSI_RETRANS_TIMER_MASK; ctl &= ~MSI_RETRANS_MASK_SEL_LINE; mod = ALC_USECS(sc->alc_int_rx_mod); if (mod == 0) mod = 1; ctl |= mod; if ((sc->alc_flags & ALC_FLAG_MSIX) != 0) CSR_WRITE_4(sc, ALC_MSI_RETRANS_TIMER, ctl | MSI_RETRANS_MASK_SEL_STD); else if ((sc->alc_flags & ALC_FLAG_MSI) != 0) CSR_WRITE_4(sc, ALC_MSI_RETRANS_TIMER, ctl | MSI_RETRANS_MASK_SEL_LINE); else CSR_WRITE_4(sc, ALC_MSI_RETRANS_TIMER, 0); } } static int alc_attach(device_t dev) { struct alc_softc *sc; struct ifnet *ifp; int base, error, i, msic, msixc; uint16_t burst; error = 0; sc = device_get_softc(dev); sc->alc_dev = dev; sc->alc_rev = pci_get_revid(dev); mtx_init(&sc->alc_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init_mtx(&sc->alc_tick_ch, &sc->alc_mtx, 0); TASK_INIT(&sc->alc_int_task, 0, alc_int_task, sc); sc->alc_ident = alc_find_ident(dev); /* Map the device. */ pci_enable_busmaster(dev); sc->alc_res_spec = alc_res_spec_mem; sc->alc_irq_spec = alc_irq_spec_legacy; error = bus_alloc_resources(dev, sc->alc_res_spec, sc->alc_res); if (error != 0) { device_printf(dev, "cannot allocate memory resources.\n"); goto fail; } /* Set PHY address. */ sc->alc_phyaddr = ALC_PHY_ADDR; /* * One odd thing is AR8132 uses the same PHY hardware(F1 * gigabit PHY) of AR8131. So atphy(4) of AR8132 reports * the PHY supports 1000Mbps but that's not true. The PHY * used in AR8132 can't establish gigabit link even if it * shows the same PHY model/revision number of AR8131. */ switch (sc->alc_ident->deviceid) { case DEVICEID_ATHEROS_AR8161: if (pci_get_subvendor(dev) == VENDORID_ATHEROS && pci_get_subdevice(dev) == 0x0091 && sc->alc_rev == 0) sc->alc_flags |= ALC_FLAG_LINK_WAR; /* FALLTHROUGH */ case DEVICEID_ATHEROS_E2200: case DEVICEID_ATHEROS_AR8171: sc->alc_flags |= ALC_FLAG_AR816X_FAMILY; break; case DEVICEID_ATHEROS_AR8162: case DEVICEID_ATHEROS_AR8172: sc->alc_flags |= ALC_FLAG_FASTETHER | ALC_FLAG_AR816X_FAMILY; break; case DEVICEID_ATHEROS_AR8152_B: case DEVICEID_ATHEROS_AR8152_B2: sc->alc_flags |= ALC_FLAG_APS; /* FALLTHROUGH */ case DEVICEID_ATHEROS_AR8132: sc->alc_flags |= ALC_FLAG_FASTETHER; break; case DEVICEID_ATHEROS_AR8151: case DEVICEID_ATHEROS_AR8151_V2: sc->alc_flags |= ALC_FLAG_APS; /* FALLTHROUGH */ default: break; } sc->alc_flags |= ALC_FLAG_JUMBO; /* * It seems that AR813x/AR815x has silicon bug for SMB. In * addition, Atheros said that enabling SMB wouldn't improve * performance. However I think it's bad to access lots of * registers to extract MAC statistics. */ sc->alc_flags |= ALC_FLAG_SMB_BUG; /* * Don't use Tx CMB. It is known to have silicon bug. */ sc->alc_flags |= ALC_FLAG_CMB_BUG; sc->alc_chip_rev = CSR_READ_4(sc, ALC_MASTER_CFG) >> MASTER_CHIP_REV_SHIFT; if (bootverbose) { device_printf(dev, "PCI device revision : 0x%04x\n", sc->alc_rev); device_printf(dev, "Chip id/revision : 0x%04x\n", sc->alc_chip_rev); if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) device_printf(dev, "AR816x revision : 0x%x\n", AR816X_REV(sc->alc_rev)); } device_printf(dev, "%u Tx FIFO, %u Rx FIFO\n", CSR_READ_4(sc, ALC_SRAM_TX_FIFO_LEN) * 8, CSR_READ_4(sc, ALC_SRAM_RX_FIFO_LEN) * 8); /* Initialize DMA parameters. */ sc->alc_dma_rd_burst = 0; sc->alc_dma_wr_burst = 0; sc->alc_rcb = DMA_CFG_RCB_64; if (pci_find_cap(dev, PCIY_EXPRESS, &base) == 0) { sc->alc_flags |= ALC_FLAG_PCIE; sc->alc_expcap = base; burst = CSR_READ_2(sc, base + PCIER_DEVICE_CTL); sc->alc_dma_rd_burst = (burst & PCIEM_CTL_MAX_READ_REQUEST) >> 12; sc->alc_dma_wr_burst = (burst & PCIEM_CTL_MAX_PAYLOAD) >> 5; if (bootverbose) { device_printf(dev, "Read request size : %u bytes.\n", alc_dma_burst[sc->alc_dma_rd_burst]); device_printf(dev, "TLP payload size : %u bytes.\n", alc_dma_burst[sc->alc_dma_wr_burst]); } if (alc_dma_burst[sc->alc_dma_rd_burst] > 1024) sc->alc_dma_rd_burst = 3; if (alc_dma_burst[sc->alc_dma_wr_burst] > 1024) sc->alc_dma_wr_burst = 3; alc_init_pcie(sc); } /* Reset PHY. */ alc_phy_reset(sc); /* Reset the ethernet controller. */ alc_stop_mac(sc); alc_reset(sc); /* Allocate IRQ resources. */ msixc = pci_msix_count(dev); msic = pci_msi_count(dev); if (bootverbose) { device_printf(dev, "MSIX count : %d\n", msixc); device_printf(dev, "MSI count : %d\n", msic); } if (msixc > 1) msixc = 1; if (msic > 1) msic = 1; /* * Prefer MSIX over MSI. * AR816x controller has a silicon bug that MSI interrupt * does not assert if PCIM_CMD_INTxDIS bit of command * register is set. pci(4) was taught to handle that case. */ if (msix_disable == 0 || msi_disable == 0) { if (msix_disable == 0 && msixc > 0 && pci_alloc_msix(dev, &msixc) == 0) { if (msic == 1) { device_printf(dev, "Using %d MSIX message(s).\n", msixc); sc->alc_flags |= ALC_FLAG_MSIX; sc->alc_irq_spec = alc_irq_spec_msix; } else pci_release_msi(dev); } if (msi_disable == 0 && (sc->alc_flags & ALC_FLAG_MSIX) == 0 && msic > 0 && pci_alloc_msi(dev, &msic) == 0) { if (msic == 1) { device_printf(dev, "Using %d MSI message(s).\n", msic); sc->alc_flags |= ALC_FLAG_MSI; sc->alc_irq_spec = alc_irq_spec_msi; } else pci_release_msi(dev); } } error = bus_alloc_resources(dev, sc->alc_irq_spec, sc->alc_irq); if (error != 0) { device_printf(dev, "cannot allocate IRQ resources.\n"); goto fail; } /* Create device sysctl node. */ alc_sysctl_node(sc); - if ((error = alc_dma_alloc(sc) != 0)) + if ((error = alc_dma_alloc(sc)) != 0) goto fail; /* Load station address. */ alc_get_macaddr(sc); ifp = sc->alc_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(dev, "cannot allocate ifnet structure.\n"); error = ENXIO; goto fail; } ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = alc_ioctl; ifp->if_start = alc_start; ifp->if_init = alc_init; ifp->if_snd.ifq_drv_maxlen = ALC_TX_RING_CNT - 1; IFQ_SET_MAXLEN(&ifp->if_snd, ifp->if_snd.ifq_drv_maxlen); IFQ_SET_READY(&ifp->if_snd); ifp->if_capabilities = IFCAP_TXCSUM | IFCAP_TSO4; ifp->if_hwassist = ALC_CSUM_FEATURES | CSUM_TSO; if (pci_find_cap(dev, PCIY_PMG, &base) == 0) { ifp->if_capabilities |= IFCAP_WOL_MAGIC | IFCAP_WOL_MCAST; sc->alc_flags |= ALC_FLAG_PM; sc->alc_pmcap = base; } ifp->if_capenable = ifp->if_capabilities; /* Set up MII bus. */ error = mii_attach(dev, &sc->alc_miibus, ifp, alc_mediachange, alc_mediastatus, BMSR_DEFCAPMASK, sc->alc_phyaddr, MII_OFFSET_ANY, MIIF_DOPAUSE); if (error != 0) { device_printf(dev, "attaching PHYs failed\n"); goto fail; } ether_ifattach(ifp, sc->alc_eaddr); /* VLAN capability setup. */ ifp->if_capabilities |= IFCAP_VLAN_MTU | IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_HWCSUM | IFCAP_VLAN_HWTSO; ifp->if_capenable = ifp->if_capabilities; /* * XXX * It seems enabling Tx checksum offloading makes more trouble. * Sometimes the controller does not receive any frames when * Tx checksum offloading is enabled. I'm not sure whether this * is a bug in Tx checksum offloading logic or I got broken * sample boards. To safety, don't enable Tx checksum offloading * by default but give chance to users to toggle it if they know * their controllers work without problems. * Fortunately, Tx checksum offloading for AR816x family * seems to work. */ if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) == 0) { ifp->if_capenable &= ~IFCAP_TXCSUM; ifp->if_hwassist &= ~ALC_CSUM_FEATURES; } /* Tell the upper layer(s) we support long frames. */ ifp->if_hdrlen = sizeof(struct ether_vlan_header); /* Create local taskq. */ sc->alc_tq = taskqueue_create_fast("alc_taskq", M_WAITOK, taskqueue_thread_enqueue, &sc->alc_tq); if (sc->alc_tq == NULL) { device_printf(dev, "could not create taskqueue.\n"); ether_ifdetach(ifp); error = ENXIO; goto fail; } taskqueue_start_threads(&sc->alc_tq, 1, PI_NET, "%s taskq", device_get_nameunit(sc->alc_dev)); alc_config_msi(sc); if ((sc->alc_flags & ALC_FLAG_MSIX) != 0) msic = ALC_MSIX_MESSAGES; else if ((sc->alc_flags & ALC_FLAG_MSI) != 0) msic = ALC_MSI_MESSAGES; else msic = 1; for (i = 0; i < msic; i++) { error = bus_setup_intr(dev, sc->alc_irq[i], INTR_TYPE_NET | INTR_MPSAFE, alc_intr, NULL, sc, &sc->alc_intrhand[i]); if (error != 0) break; } if (error != 0) { device_printf(dev, "could not set up interrupt handler.\n"); taskqueue_free(sc->alc_tq); sc->alc_tq = NULL; ether_ifdetach(ifp); goto fail; } fail: if (error != 0) alc_detach(dev); return (error); } static int alc_detach(device_t dev) { struct alc_softc *sc; struct ifnet *ifp; int i, msic; sc = device_get_softc(dev); ifp = sc->alc_ifp; if (device_is_attached(dev)) { ether_ifdetach(ifp); ALC_LOCK(sc); alc_stop(sc); ALC_UNLOCK(sc); callout_drain(&sc->alc_tick_ch); taskqueue_drain(sc->alc_tq, &sc->alc_int_task); } if (sc->alc_tq != NULL) { taskqueue_drain(sc->alc_tq, &sc->alc_int_task); taskqueue_free(sc->alc_tq); sc->alc_tq = NULL; } if (sc->alc_miibus != NULL) { device_delete_child(dev, sc->alc_miibus); sc->alc_miibus = NULL; } bus_generic_detach(dev); alc_dma_free(sc); if (ifp != NULL) { if_free(ifp); sc->alc_ifp = NULL; } if ((sc->alc_flags & ALC_FLAG_MSIX) != 0) msic = ALC_MSIX_MESSAGES; else if ((sc->alc_flags & ALC_FLAG_MSI) != 0) msic = ALC_MSI_MESSAGES; else msic = 1; for (i = 0; i < msic; i++) { if (sc->alc_intrhand[i] != NULL) { bus_teardown_intr(dev, sc->alc_irq[i], sc->alc_intrhand[i]); sc->alc_intrhand[i] = NULL; } } if (sc->alc_res[0] != NULL) alc_phy_down(sc); bus_release_resources(dev, sc->alc_irq_spec, sc->alc_irq); if ((sc->alc_flags & (ALC_FLAG_MSI | ALC_FLAG_MSIX)) != 0) pci_release_msi(dev); bus_release_resources(dev, sc->alc_res_spec, sc->alc_res); mtx_destroy(&sc->alc_mtx); return (0); } #define ALC_SYSCTL_STAT_ADD32(c, h, n, p, d) \ SYSCTL_ADD_UINT(c, h, OID_AUTO, n, CTLFLAG_RD, p, 0, d) #define ALC_SYSCTL_STAT_ADD64(c, h, n, p, d) \ SYSCTL_ADD_UQUAD(c, h, OID_AUTO, n, CTLFLAG_RD, p, d) static void alc_sysctl_node(struct alc_softc *sc) { struct sysctl_ctx_list *ctx; struct sysctl_oid_list *child, *parent; struct sysctl_oid *tree; struct alc_hw_stats *stats; int error; stats = &sc->alc_stats; ctx = device_get_sysctl_ctx(sc->alc_dev); child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->alc_dev)); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "int_rx_mod", CTLTYPE_INT | CTLFLAG_RW, &sc->alc_int_rx_mod, 0, sysctl_hw_alc_int_mod, "I", "alc Rx interrupt moderation"); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "int_tx_mod", CTLTYPE_INT | CTLFLAG_RW, &sc->alc_int_tx_mod, 0, sysctl_hw_alc_int_mod, "I", "alc Tx interrupt moderation"); /* Pull in device tunables. */ sc->alc_int_rx_mod = ALC_IM_RX_TIMER_DEFAULT; error = resource_int_value(device_get_name(sc->alc_dev), device_get_unit(sc->alc_dev), "int_rx_mod", &sc->alc_int_rx_mod); if (error == 0) { if (sc->alc_int_rx_mod < ALC_IM_TIMER_MIN || sc->alc_int_rx_mod > ALC_IM_TIMER_MAX) { device_printf(sc->alc_dev, "int_rx_mod value out of " "range; using default: %d\n", ALC_IM_RX_TIMER_DEFAULT); sc->alc_int_rx_mod = ALC_IM_RX_TIMER_DEFAULT; } } sc->alc_int_tx_mod = ALC_IM_TX_TIMER_DEFAULT; error = resource_int_value(device_get_name(sc->alc_dev), device_get_unit(sc->alc_dev), "int_tx_mod", &sc->alc_int_tx_mod); if (error == 0) { if (sc->alc_int_tx_mod < ALC_IM_TIMER_MIN || sc->alc_int_tx_mod > ALC_IM_TIMER_MAX) { device_printf(sc->alc_dev, "int_tx_mod value out of " "range; using default: %d\n", ALC_IM_TX_TIMER_DEFAULT); sc->alc_int_tx_mod = ALC_IM_TX_TIMER_DEFAULT; } } SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "process_limit", CTLTYPE_INT | CTLFLAG_RW, &sc->alc_process_limit, 0, sysctl_hw_alc_proc_limit, "I", "max number of Rx events to process"); /* Pull in device tunables. */ sc->alc_process_limit = ALC_PROC_DEFAULT; error = resource_int_value(device_get_name(sc->alc_dev), device_get_unit(sc->alc_dev), "process_limit", &sc->alc_process_limit); if (error == 0) { if (sc->alc_process_limit < ALC_PROC_MIN || sc->alc_process_limit > ALC_PROC_MAX) { device_printf(sc->alc_dev, "process_limit value out of range; " "using default: %d\n", ALC_PROC_DEFAULT); sc->alc_process_limit = ALC_PROC_DEFAULT; } } tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", CTLFLAG_RD, NULL, "ALC statistics"); parent = SYSCTL_CHILDREN(tree); /* Rx statistics. */ tree = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "rx", CTLFLAG_RD, NULL, "Rx MAC statistics"); child = SYSCTL_CHILDREN(tree); ALC_SYSCTL_STAT_ADD32(ctx, child, "good_frames", &stats->rx_frames, "Good frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "good_bcast_frames", &stats->rx_bcast_frames, "Good broadcast frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "good_mcast_frames", &stats->rx_mcast_frames, "Good multicast frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "pause_frames", &stats->rx_pause_frames, "Pause control frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "control_frames", &stats->rx_control_frames, "Control frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "crc_errs", &stats->rx_crcerrs, "CRC errors"); ALC_SYSCTL_STAT_ADD32(ctx, child, "len_errs", &stats->rx_lenerrs, "Frames with length mismatched"); ALC_SYSCTL_STAT_ADD64(ctx, child, "good_octets", &stats->rx_bytes, "Good octets"); ALC_SYSCTL_STAT_ADD64(ctx, child, "good_bcast_octets", &stats->rx_bcast_bytes, "Good broadcast octets"); ALC_SYSCTL_STAT_ADD64(ctx, child, "good_mcast_octets", &stats->rx_mcast_bytes, "Good multicast octets"); ALC_SYSCTL_STAT_ADD32(ctx, child, "runts", &stats->rx_runts, "Too short frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "fragments", &stats->rx_fragments, "Fragmented frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "frames_64", &stats->rx_pkts_64, "64 bytes frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "frames_65_127", &stats->rx_pkts_65_127, "65 to 127 bytes frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "frames_128_255", &stats->rx_pkts_128_255, "128 to 255 bytes frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "frames_256_511", &stats->rx_pkts_256_511, "256 to 511 bytes frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "frames_512_1023", &stats->rx_pkts_512_1023, "512 to 1023 bytes frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "frames_1024_1518", &stats->rx_pkts_1024_1518, "1024 to 1518 bytes frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "frames_1519_max", &stats->rx_pkts_1519_max, "1519 to max frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "trunc_errs", &stats->rx_pkts_truncated, "Truncated frames due to MTU size"); ALC_SYSCTL_STAT_ADD32(ctx, child, "fifo_oflows", &stats->rx_fifo_oflows, "FIFO overflows"); ALC_SYSCTL_STAT_ADD32(ctx, child, "rrs_errs", &stats->rx_rrs_errs, "Return status write-back errors"); ALC_SYSCTL_STAT_ADD32(ctx, child, "align_errs", &stats->rx_alignerrs, "Alignment errors"); ALC_SYSCTL_STAT_ADD32(ctx, child, "filtered", &stats->rx_pkts_filtered, "Frames dropped due to address filtering"); /* Tx statistics. */ tree = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "tx", CTLFLAG_RD, NULL, "Tx MAC statistics"); child = SYSCTL_CHILDREN(tree); ALC_SYSCTL_STAT_ADD32(ctx, child, "good_frames", &stats->tx_frames, "Good frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "good_bcast_frames", &stats->tx_bcast_frames, "Good broadcast frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "good_mcast_frames", &stats->tx_mcast_frames, "Good multicast frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "pause_frames", &stats->tx_pause_frames, "Pause control frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "control_frames", &stats->tx_control_frames, "Control frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "excess_defers", &stats->tx_excess_defer, "Frames with excessive derferrals"); ALC_SYSCTL_STAT_ADD32(ctx, child, "defers", &stats->tx_excess_defer, "Frames with derferrals"); ALC_SYSCTL_STAT_ADD64(ctx, child, "good_octets", &stats->tx_bytes, "Good octets"); ALC_SYSCTL_STAT_ADD64(ctx, child, "good_bcast_octets", &stats->tx_bcast_bytes, "Good broadcast octets"); ALC_SYSCTL_STAT_ADD64(ctx, child, "good_mcast_octets", &stats->tx_mcast_bytes, "Good multicast octets"); ALC_SYSCTL_STAT_ADD32(ctx, child, "frames_64", &stats->tx_pkts_64, "64 bytes frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "frames_65_127", &stats->tx_pkts_65_127, "65 to 127 bytes frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "frames_128_255", &stats->tx_pkts_128_255, "128 to 255 bytes frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "frames_256_511", &stats->tx_pkts_256_511, "256 to 511 bytes frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "frames_512_1023", &stats->tx_pkts_512_1023, "512 to 1023 bytes frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "frames_1024_1518", &stats->tx_pkts_1024_1518, "1024 to 1518 bytes frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "frames_1519_max", &stats->tx_pkts_1519_max, "1519 to max frames"); ALC_SYSCTL_STAT_ADD32(ctx, child, "single_colls", &stats->tx_single_colls, "Single collisions"); ALC_SYSCTL_STAT_ADD32(ctx, child, "multi_colls", &stats->tx_multi_colls, "Multiple collisions"); ALC_SYSCTL_STAT_ADD32(ctx, child, "late_colls", &stats->tx_late_colls, "Late collisions"); ALC_SYSCTL_STAT_ADD32(ctx, child, "excess_colls", &stats->tx_excess_colls, "Excessive collisions"); ALC_SYSCTL_STAT_ADD32(ctx, child, "underruns", &stats->tx_underrun, "FIFO underruns"); ALC_SYSCTL_STAT_ADD32(ctx, child, "desc_underruns", &stats->tx_desc_underrun, "Descriptor write-back errors"); ALC_SYSCTL_STAT_ADD32(ctx, child, "len_errs", &stats->tx_lenerrs, "Frames with length mismatched"); ALC_SYSCTL_STAT_ADD32(ctx, child, "trunc_errs", &stats->tx_pkts_truncated, "Truncated frames due to MTU size"); } #undef ALC_SYSCTL_STAT_ADD32 #undef ALC_SYSCTL_STAT_ADD64 struct alc_dmamap_arg { bus_addr_t alc_busaddr; }; static void alc_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { struct alc_dmamap_arg *ctx; if (error != 0) return; KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); ctx = (struct alc_dmamap_arg *)arg; ctx->alc_busaddr = segs[0].ds_addr; } /* * Normal and high Tx descriptors shares single Tx high address. * Four Rx descriptor/return rings and CMB shares the same Rx * high address. */ static int alc_check_boundary(struct alc_softc *sc) { bus_addr_t cmb_end, rx_ring_end, rr_ring_end, tx_ring_end; rx_ring_end = sc->alc_rdata.alc_rx_ring_paddr + ALC_RX_RING_SZ; rr_ring_end = sc->alc_rdata.alc_rr_ring_paddr + ALC_RR_RING_SZ; cmb_end = sc->alc_rdata.alc_cmb_paddr + ALC_CMB_SZ; tx_ring_end = sc->alc_rdata.alc_tx_ring_paddr + ALC_TX_RING_SZ; /* 4GB boundary crossing is not allowed. */ if ((ALC_ADDR_HI(rx_ring_end) != ALC_ADDR_HI(sc->alc_rdata.alc_rx_ring_paddr)) || (ALC_ADDR_HI(rr_ring_end) != ALC_ADDR_HI(sc->alc_rdata.alc_rr_ring_paddr)) || (ALC_ADDR_HI(cmb_end) != ALC_ADDR_HI(sc->alc_rdata.alc_cmb_paddr)) || (ALC_ADDR_HI(tx_ring_end) != ALC_ADDR_HI(sc->alc_rdata.alc_tx_ring_paddr))) return (EFBIG); /* * Make sure Rx return descriptor/Rx descriptor/CMB use * the same high address. */ if ((ALC_ADDR_HI(rx_ring_end) != ALC_ADDR_HI(rr_ring_end)) || (ALC_ADDR_HI(rx_ring_end) != ALC_ADDR_HI(cmb_end))) return (EFBIG); return (0); } static int alc_dma_alloc(struct alc_softc *sc) { struct alc_txdesc *txd; struct alc_rxdesc *rxd; bus_addr_t lowaddr; struct alc_dmamap_arg ctx; int error, i; lowaddr = BUS_SPACE_MAXADDR; again: /* Create parent DMA tag. */ error = bus_dma_tag_create( bus_get_dma_tag(sc->alc_dev), /* parent */ 1, 0, /* alignment, boundary */ lowaddr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ 0, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->alc_cdata.alc_parent_tag); if (error != 0) { device_printf(sc->alc_dev, "could not create parent DMA tag.\n"); goto fail; } /* Create DMA tag for Tx descriptor ring. */ error = bus_dma_tag_create( sc->alc_cdata.alc_parent_tag, /* parent */ ALC_TX_RING_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ ALC_TX_RING_SZ, /* maxsize */ 1, /* nsegments */ ALC_TX_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->alc_cdata.alc_tx_ring_tag); if (error != 0) { device_printf(sc->alc_dev, "could not create Tx ring DMA tag.\n"); goto fail; } /* Create DMA tag for Rx free descriptor ring. */ error = bus_dma_tag_create( sc->alc_cdata.alc_parent_tag, /* parent */ ALC_RX_RING_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ ALC_RX_RING_SZ, /* maxsize */ 1, /* nsegments */ ALC_RX_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->alc_cdata.alc_rx_ring_tag); if (error != 0) { device_printf(sc->alc_dev, "could not create Rx ring DMA tag.\n"); goto fail; } /* Create DMA tag for Rx return descriptor ring. */ error = bus_dma_tag_create( sc->alc_cdata.alc_parent_tag, /* parent */ ALC_RR_RING_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ ALC_RR_RING_SZ, /* maxsize */ 1, /* nsegments */ ALC_RR_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->alc_cdata.alc_rr_ring_tag); if (error != 0) { device_printf(sc->alc_dev, "could not create Rx return ring DMA tag.\n"); goto fail; } /* Create DMA tag for coalescing message block. */ error = bus_dma_tag_create( sc->alc_cdata.alc_parent_tag, /* parent */ ALC_CMB_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ ALC_CMB_SZ, /* maxsize */ 1, /* nsegments */ ALC_CMB_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->alc_cdata.alc_cmb_tag); if (error != 0) { device_printf(sc->alc_dev, "could not create CMB DMA tag.\n"); goto fail; } /* Create DMA tag for status message block. */ error = bus_dma_tag_create( sc->alc_cdata.alc_parent_tag, /* parent */ ALC_SMB_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ ALC_SMB_SZ, /* maxsize */ 1, /* nsegments */ ALC_SMB_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->alc_cdata.alc_smb_tag); if (error != 0) { device_printf(sc->alc_dev, "could not create SMB DMA tag.\n"); goto fail; } /* Allocate DMA'able memory and load the DMA map for Tx ring. */ error = bus_dmamem_alloc(sc->alc_cdata.alc_tx_ring_tag, (void **)&sc->alc_rdata.alc_tx_ring, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->alc_cdata.alc_tx_ring_map); if (error != 0) { device_printf(sc->alc_dev, "could not allocate DMA'able memory for Tx ring.\n"); goto fail; } ctx.alc_busaddr = 0; error = bus_dmamap_load(sc->alc_cdata.alc_tx_ring_tag, sc->alc_cdata.alc_tx_ring_map, sc->alc_rdata.alc_tx_ring, ALC_TX_RING_SZ, alc_dmamap_cb, &ctx, 0); if (error != 0 || ctx.alc_busaddr == 0) { device_printf(sc->alc_dev, "could not load DMA'able memory for Tx ring.\n"); goto fail; } sc->alc_rdata.alc_tx_ring_paddr = ctx.alc_busaddr; /* Allocate DMA'able memory and load the DMA map for Rx ring. */ error = bus_dmamem_alloc(sc->alc_cdata.alc_rx_ring_tag, (void **)&sc->alc_rdata.alc_rx_ring, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->alc_cdata.alc_rx_ring_map); if (error != 0) { device_printf(sc->alc_dev, "could not allocate DMA'able memory for Rx ring.\n"); goto fail; } ctx.alc_busaddr = 0; error = bus_dmamap_load(sc->alc_cdata.alc_rx_ring_tag, sc->alc_cdata.alc_rx_ring_map, sc->alc_rdata.alc_rx_ring, ALC_RX_RING_SZ, alc_dmamap_cb, &ctx, 0); if (error != 0 || ctx.alc_busaddr == 0) { device_printf(sc->alc_dev, "could not load DMA'able memory for Rx ring.\n"); goto fail; } sc->alc_rdata.alc_rx_ring_paddr = ctx.alc_busaddr; /* Allocate DMA'able memory and load the DMA map for Rx return ring. */ error = bus_dmamem_alloc(sc->alc_cdata.alc_rr_ring_tag, (void **)&sc->alc_rdata.alc_rr_ring, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->alc_cdata.alc_rr_ring_map); if (error != 0) { device_printf(sc->alc_dev, "could not allocate DMA'able memory for Rx return ring.\n"); goto fail; } ctx.alc_busaddr = 0; error = bus_dmamap_load(sc->alc_cdata.alc_rr_ring_tag, sc->alc_cdata.alc_rr_ring_map, sc->alc_rdata.alc_rr_ring, ALC_RR_RING_SZ, alc_dmamap_cb, &ctx, 0); if (error != 0 || ctx.alc_busaddr == 0) { device_printf(sc->alc_dev, "could not load DMA'able memory for Tx ring.\n"); goto fail; } sc->alc_rdata.alc_rr_ring_paddr = ctx.alc_busaddr; /* Allocate DMA'able memory and load the DMA map for CMB. */ error = bus_dmamem_alloc(sc->alc_cdata.alc_cmb_tag, (void **)&sc->alc_rdata.alc_cmb, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->alc_cdata.alc_cmb_map); if (error != 0) { device_printf(sc->alc_dev, "could not allocate DMA'able memory for CMB.\n"); goto fail; } ctx.alc_busaddr = 0; error = bus_dmamap_load(sc->alc_cdata.alc_cmb_tag, sc->alc_cdata.alc_cmb_map, sc->alc_rdata.alc_cmb, ALC_CMB_SZ, alc_dmamap_cb, &ctx, 0); if (error != 0 || ctx.alc_busaddr == 0) { device_printf(sc->alc_dev, "could not load DMA'able memory for CMB.\n"); goto fail; } sc->alc_rdata.alc_cmb_paddr = ctx.alc_busaddr; /* Allocate DMA'able memory and load the DMA map for SMB. */ error = bus_dmamem_alloc(sc->alc_cdata.alc_smb_tag, (void **)&sc->alc_rdata.alc_smb, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->alc_cdata.alc_smb_map); if (error != 0) { device_printf(sc->alc_dev, "could not allocate DMA'able memory for SMB.\n"); goto fail; } ctx.alc_busaddr = 0; error = bus_dmamap_load(sc->alc_cdata.alc_smb_tag, sc->alc_cdata.alc_smb_map, sc->alc_rdata.alc_smb, ALC_SMB_SZ, alc_dmamap_cb, &ctx, 0); if (error != 0 || ctx.alc_busaddr == 0) { device_printf(sc->alc_dev, "could not load DMA'able memory for CMB.\n"); goto fail; } sc->alc_rdata.alc_smb_paddr = ctx.alc_busaddr; /* Make sure we've not crossed 4GB boundary. */ if (lowaddr != BUS_SPACE_MAXADDR_32BIT && (error = alc_check_boundary(sc)) != 0) { device_printf(sc->alc_dev, "4GB boundary crossed, " "switching to 32bit DMA addressing mode.\n"); alc_dma_free(sc); /* * Limit max allowable DMA address space to 32bit * and try again. */ lowaddr = BUS_SPACE_MAXADDR_32BIT; goto again; } /* * Create Tx buffer parent tag. * AR81[3567]x allows 64bit DMA addressing of Tx/Rx buffers * so it needs separate parent DMA tag as parent DMA address * space could be restricted to be within 32bit address space * by 4GB boundary crossing. */ error = bus_dma_tag_create( bus_get_dma_tag(sc->alc_dev), /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ 0, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->alc_cdata.alc_buffer_tag); if (error != 0) { device_printf(sc->alc_dev, "could not create parent buffer DMA tag.\n"); goto fail; } /* Create DMA tag for Tx buffers. */ error = bus_dma_tag_create( sc->alc_cdata.alc_buffer_tag, /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ ALC_TSO_MAXSIZE, /* maxsize */ ALC_MAXTXSEGS, /* nsegments */ ALC_TSO_MAXSEGSIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->alc_cdata.alc_tx_tag); if (error != 0) { device_printf(sc->alc_dev, "could not create Tx DMA tag.\n"); goto fail; } /* Create DMA tag for Rx buffers. */ error = bus_dma_tag_create( sc->alc_cdata.alc_buffer_tag, /* parent */ ALC_RX_BUF_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, /* maxsize */ 1, /* nsegments */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->alc_cdata.alc_rx_tag); if (error != 0) { device_printf(sc->alc_dev, "could not create Rx DMA tag.\n"); goto fail; } /* Create DMA maps for Tx buffers. */ for (i = 0; i < ALC_TX_RING_CNT; i++) { txd = &sc->alc_cdata.alc_txdesc[i]; txd->tx_m = NULL; txd->tx_dmamap = NULL; error = bus_dmamap_create(sc->alc_cdata.alc_tx_tag, 0, &txd->tx_dmamap); if (error != 0) { device_printf(sc->alc_dev, "could not create Tx dmamap.\n"); goto fail; } } /* Create DMA maps for Rx buffers. */ if ((error = bus_dmamap_create(sc->alc_cdata.alc_rx_tag, 0, &sc->alc_cdata.alc_rx_sparemap)) != 0) { device_printf(sc->alc_dev, "could not create spare Rx dmamap.\n"); goto fail; } for (i = 0; i < ALC_RX_RING_CNT; i++) { rxd = &sc->alc_cdata.alc_rxdesc[i]; rxd->rx_m = NULL; rxd->rx_dmamap = NULL; error = bus_dmamap_create(sc->alc_cdata.alc_rx_tag, 0, &rxd->rx_dmamap); if (error != 0) { device_printf(sc->alc_dev, "could not create Rx dmamap.\n"); goto fail; } } fail: return (error); } static void alc_dma_free(struct alc_softc *sc) { struct alc_txdesc *txd; struct alc_rxdesc *rxd; int i; /* Tx buffers. */ if (sc->alc_cdata.alc_tx_tag != NULL) { for (i = 0; i < ALC_TX_RING_CNT; i++) { txd = &sc->alc_cdata.alc_txdesc[i]; if (txd->tx_dmamap != NULL) { bus_dmamap_destroy(sc->alc_cdata.alc_tx_tag, txd->tx_dmamap); txd->tx_dmamap = NULL; } } bus_dma_tag_destroy(sc->alc_cdata.alc_tx_tag); sc->alc_cdata.alc_tx_tag = NULL; } /* Rx buffers */ if (sc->alc_cdata.alc_rx_tag != NULL) { for (i = 0; i < ALC_RX_RING_CNT; i++) { rxd = &sc->alc_cdata.alc_rxdesc[i]; if (rxd->rx_dmamap != NULL) { bus_dmamap_destroy(sc->alc_cdata.alc_rx_tag, rxd->rx_dmamap); rxd->rx_dmamap = NULL; } } if (sc->alc_cdata.alc_rx_sparemap != NULL) { bus_dmamap_destroy(sc->alc_cdata.alc_rx_tag, sc->alc_cdata.alc_rx_sparemap); sc->alc_cdata.alc_rx_sparemap = NULL; } bus_dma_tag_destroy(sc->alc_cdata.alc_rx_tag); sc->alc_cdata.alc_rx_tag = NULL; } /* Tx descriptor ring. */ if (sc->alc_cdata.alc_tx_ring_tag != NULL) { if (sc->alc_rdata.alc_tx_ring_paddr != 0) bus_dmamap_unload(sc->alc_cdata.alc_tx_ring_tag, sc->alc_cdata.alc_tx_ring_map); if (sc->alc_rdata.alc_tx_ring != NULL) bus_dmamem_free(sc->alc_cdata.alc_tx_ring_tag, sc->alc_rdata.alc_tx_ring, sc->alc_cdata.alc_tx_ring_map); sc->alc_rdata.alc_tx_ring_paddr = 0; sc->alc_rdata.alc_tx_ring = NULL; bus_dma_tag_destroy(sc->alc_cdata.alc_tx_ring_tag); sc->alc_cdata.alc_tx_ring_tag = NULL; } /* Rx ring. */ if (sc->alc_cdata.alc_rx_ring_tag != NULL) { if (sc->alc_rdata.alc_rx_ring_paddr != 0) bus_dmamap_unload(sc->alc_cdata.alc_rx_ring_tag, sc->alc_cdata.alc_rx_ring_map); if (sc->alc_rdata.alc_rx_ring != NULL) bus_dmamem_free(sc->alc_cdata.alc_rx_ring_tag, sc->alc_rdata.alc_rx_ring, sc->alc_cdata.alc_rx_ring_map); sc->alc_rdata.alc_rx_ring_paddr = 0; sc->alc_rdata.alc_rx_ring = NULL; bus_dma_tag_destroy(sc->alc_cdata.alc_rx_ring_tag); sc->alc_cdata.alc_rx_ring_tag = NULL; } /* Rx return ring. */ if (sc->alc_cdata.alc_rr_ring_tag != NULL) { if (sc->alc_rdata.alc_rr_ring_paddr != 0) bus_dmamap_unload(sc->alc_cdata.alc_rr_ring_tag, sc->alc_cdata.alc_rr_ring_map); if (sc->alc_rdata.alc_rr_ring != NULL) bus_dmamem_free(sc->alc_cdata.alc_rr_ring_tag, sc->alc_rdata.alc_rr_ring, sc->alc_cdata.alc_rr_ring_map); sc->alc_rdata.alc_rr_ring_paddr = 0; sc->alc_rdata.alc_rr_ring = NULL; bus_dma_tag_destroy(sc->alc_cdata.alc_rr_ring_tag); sc->alc_cdata.alc_rr_ring_tag = NULL; } /* CMB block */ if (sc->alc_cdata.alc_cmb_tag != NULL) { if (sc->alc_rdata.alc_cmb_paddr != 0) bus_dmamap_unload(sc->alc_cdata.alc_cmb_tag, sc->alc_cdata.alc_cmb_map); if (sc->alc_rdata.alc_cmb != NULL) bus_dmamem_free(sc->alc_cdata.alc_cmb_tag, sc->alc_rdata.alc_cmb, sc->alc_cdata.alc_cmb_map); sc->alc_rdata.alc_cmb_paddr = 0; sc->alc_rdata.alc_cmb = NULL; bus_dma_tag_destroy(sc->alc_cdata.alc_cmb_tag); sc->alc_cdata.alc_cmb_tag = NULL; } /* SMB block */ if (sc->alc_cdata.alc_smb_tag != NULL) { if (sc->alc_rdata.alc_smb_paddr != 0) bus_dmamap_unload(sc->alc_cdata.alc_smb_tag, sc->alc_cdata.alc_smb_map); if (sc->alc_rdata.alc_smb != NULL) bus_dmamem_free(sc->alc_cdata.alc_smb_tag, sc->alc_rdata.alc_smb, sc->alc_cdata.alc_smb_map); sc->alc_rdata.alc_smb_paddr = 0; sc->alc_rdata.alc_smb = NULL; bus_dma_tag_destroy(sc->alc_cdata.alc_smb_tag); sc->alc_cdata.alc_smb_tag = NULL; } if (sc->alc_cdata.alc_buffer_tag != NULL) { bus_dma_tag_destroy(sc->alc_cdata.alc_buffer_tag); sc->alc_cdata.alc_buffer_tag = NULL; } if (sc->alc_cdata.alc_parent_tag != NULL) { bus_dma_tag_destroy(sc->alc_cdata.alc_parent_tag); sc->alc_cdata.alc_parent_tag = NULL; } } static int alc_shutdown(device_t dev) { return (alc_suspend(dev)); } /* * Note, this driver resets the link speed to 10/100Mbps by * restarting auto-negotiation in suspend/shutdown phase but we * don't know whether that auto-negotiation would succeed or not * as driver has no control after powering off/suspend operation. * If the renegotiation fail WOL may not work. Running at 1Gbps * will draw more power than 375mA at 3.3V which is specified in * PCI specification and that would result in complete * shutdowning power to ethernet controller. * * TODO * Save current negotiated media speed/duplex/flow-control to * softc and restore the same link again after resuming. PHY * handling such as power down/resetting to 100Mbps may be better * handled in suspend method in phy driver. */ static void alc_setlinkspeed(struct alc_softc *sc) { struct mii_data *mii; int aneg, i; mii = device_get_softc(sc->alc_miibus); mii_pollstat(mii); aneg = 0; if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { switch IFM_SUBTYPE(mii->mii_media_active) { case IFM_10_T: case IFM_100_TX: return; case IFM_1000_T: aneg++; break; default: break; } } alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, MII_100T2CR, 0); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, MII_ANAR, ANAR_TX_FD | ANAR_TX | ANAR_10_FD | ANAR_10 | ANAR_CSMA); alc_miibus_writereg(sc->alc_dev, sc->alc_phyaddr, MII_BMCR, BMCR_RESET | BMCR_AUTOEN | BMCR_STARTNEG); DELAY(1000); if (aneg != 0) { /* * Poll link state until alc(4) get a 10/100Mbps link. */ for (i = 0; i < MII_ANEGTICKS_GIGE; i++) { mii_pollstat(mii); if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { switch (IFM_SUBTYPE( mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: alc_mac_config(sc); return; default: break; } } ALC_UNLOCK(sc); pause("alclnk", hz); ALC_LOCK(sc); } if (i == MII_ANEGTICKS_GIGE) device_printf(sc->alc_dev, "establishing a link failed, WOL may not work!"); } /* * No link, force MAC to have 100Mbps, full-duplex link. * This is the last resort and may/may not work. */ mii->mii_media_status = IFM_AVALID | IFM_ACTIVE; mii->mii_media_active = IFM_ETHER | IFM_100_TX | IFM_FDX; alc_mac_config(sc); } static void alc_setwol(struct alc_softc *sc) { if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) alc_setwol_816x(sc); else alc_setwol_813x(sc); } static void alc_setwol_813x(struct alc_softc *sc) { struct ifnet *ifp; uint32_t reg, pmcs; uint16_t pmstat; ALC_LOCK_ASSERT(sc); alc_disable_l0s_l1(sc); ifp = sc->alc_ifp; if ((sc->alc_flags & ALC_FLAG_PM) == 0) { /* Disable WOL. */ CSR_WRITE_4(sc, ALC_WOL_CFG, 0); reg = CSR_READ_4(sc, ALC_PCIE_PHYMISC); reg |= PCIE_PHYMISC_FORCE_RCV_DET; CSR_WRITE_4(sc, ALC_PCIE_PHYMISC, reg); /* Force PHY power down. */ alc_phy_down(sc); CSR_WRITE_4(sc, ALC_MASTER_CFG, CSR_READ_4(sc, ALC_MASTER_CFG) | MASTER_CLK_SEL_DIS); return; } if ((ifp->if_capenable & IFCAP_WOL) != 0) { if ((sc->alc_flags & ALC_FLAG_FASTETHER) == 0) alc_setlinkspeed(sc); CSR_WRITE_4(sc, ALC_MASTER_CFG, CSR_READ_4(sc, ALC_MASTER_CFG) & ~MASTER_CLK_SEL_DIS); } pmcs = 0; if ((ifp->if_capenable & IFCAP_WOL_MAGIC) != 0) pmcs |= WOL_CFG_MAGIC | WOL_CFG_MAGIC_ENB; CSR_WRITE_4(sc, ALC_WOL_CFG, pmcs); reg = CSR_READ_4(sc, ALC_MAC_CFG); reg &= ~(MAC_CFG_DBG | MAC_CFG_PROMISC | MAC_CFG_ALLMULTI | MAC_CFG_BCAST); if ((ifp->if_capenable & IFCAP_WOL_MCAST) != 0) reg |= MAC_CFG_ALLMULTI | MAC_CFG_BCAST; if ((ifp->if_capenable & IFCAP_WOL) != 0) reg |= MAC_CFG_RX_ENB; CSR_WRITE_4(sc, ALC_MAC_CFG, reg); reg = CSR_READ_4(sc, ALC_PCIE_PHYMISC); reg |= PCIE_PHYMISC_FORCE_RCV_DET; CSR_WRITE_4(sc, ALC_PCIE_PHYMISC, reg); if ((ifp->if_capenable & IFCAP_WOL) == 0) { /* WOL disabled, PHY power down. */ alc_phy_down(sc); CSR_WRITE_4(sc, ALC_MASTER_CFG, CSR_READ_4(sc, ALC_MASTER_CFG) | MASTER_CLK_SEL_DIS); } /* Request PME. */ pmstat = pci_read_config(sc->alc_dev, sc->alc_pmcap + PCIR_POWER_STATUS, 2); pmstat &= ~(PCIM_PSTAT_PME | PCIM_PSTAT_PMEENABLE); if ((ifp->if_capenable & IFCAP_WOL) != 0) pmstat |= PCIM_PSTAT_PME | PCIM_PSTAT_PMEENABLE; pci_write_config(sc->alc_dev, sc->alc_pmcap + PCIR_POWER_STATUS, pmstat, 2); } static void alc_setwol_816x(struct alc_softc *sc) { struct ifnet *ifp; uint32_t gphy, mac, master, pmcs, reg; uint16_t pmstat; ALC_LOCK_ASSERT(sc); ifp = sc->alc_ifp; master = CSR_READ_4(sc, ALC_MASTER_CFG); master &= ~MASTER_CLK_SEL_DIS; gphy = CSR_READ_4(sc, ALC_GPHY_CFG); gphy &= ~(GPHY_CFG_EXT_RESET | GPHY_CFG_LED_MODE | GPHY_CFG_100AB_ENB | GPHY_CFG_PHY_PLL_ON); gphy |= GPHY_CFG_HIB_EN | GPHY_CFG_HIB_PULSE | GPHY_CFG_SEL_ANA_RESET; if ((sc->alc_flags & ALC_FLAG_PM) == 0) { CSR_WRITE_4(sc, ALC_WOL_CFG, 0); gphy |= GPHY_CFG_PHY_IDDQ | GPHY_CFG_PWDOWN_HW; mac = CSR_READ_4(sc, ALC_MAC_CFG); } else { if ((ifp->if_capenable & IFCAP_WOL) != 0) { gphy |= GPHY_CFG_EXT_RESET; if ((sc->alc_flags & ALC_FLAG_FASTETHER) == 0) alc_setlinkspeed(sc); } pmcs = 0; if ((ifp->if_capenable & IFCAP_WOL_MAGIC) != 0) pmcs |= WOL_CFG_MAGIC | WOL_CFG_MAGIC_ENB; CSR_WRITE_4(sc, ALC_WOL_CFG, pmcs); mac = CSR_READ_4(sc, ALC_MAC_CFG); mac &= ~(MAC_CFG_DBG | MAC_CFG_PROMISC | MAC_CFG_ALLMULTI | MAC_CFG_BCAST); if ((ifp->if_capenable & IFCAP_WOL_MCAST) != 0) mac |= MAC_CFG_ALLMULTI | MAC_CFG_BCAST; if ((ifp->if_capenable & IFCAP_WOL) != 0) mac |= MAC_CFG_RX_ENB; alc_miiext_writereg(sc, MII_EXT_ANEG, MII_EXT_ANEG_S3DIG10, ANEG_S3DIG10_SL); } /* Enable OSC. */ reg = CSR_READ_4(sc, ALC_MISC); reg &= ~MISC_INTNLOSC_OPEN; CSR_WRITE_4(sc, ALC_MISC, reg); reg |= MISC_INTNLOSC_OPEN; CSR_WRITE_4(sc, ALC_MISC, reg); CSR_WRITE_4(sc, ALC_MASTER_CFG, master); CSR_WRITE_4(sc, ALC_MAC_CFG, mac); CSR_WRITE_4(sc, ALC_GPHY_CFG, gphy); reg = CSR_READ_4(sc, ALC_PDLL_TRNS1); reg |= PDLL_TRNS1_D3PLLOFF_ENB; CSR_WRITE_4(sc, ALC_PDLL_TRNS1, reg); if ((sc->alc_flags & ALC_FLAG_PM) != 0) { /* Request PME. */ pmstat = pci_read_config(sc->alc_dev, sc->alc_pmcap + PCIR_POWER_STATUS, 2); pmstat &= ~(PCIM_PSTAT_PME | PCIM_PSTAT_PMEENABLE); if ((ifp->if_capenable & IFCAP_WOL) != 0) pmstat |= PCIM_PSTAT_PME | PCIM_PSTAT_PMEENABLE; pci_write_config(sc->alc_dev, sc->alc_pmcap + PCIR_POWER_STATUS, pmstat, 2); } } static int alc_suspend(device_t dev) { struct alc_softc *sc; sc = device_get_softc(dev); ALC_LOCK(sc); alc_stop(sc); alc_setwol(sc); ALC_UNLOCK(sc); return (0); } static int alc_resume(device_t dev) { struct alc_softc *sc; struct ifnet *ifp; uint16_t pmstat; sc = device_get_softc(dev); ALC_LOCK(sc); if ((sc->alc_flags & ALC_FLAG_PM) != 0) { /* Disable PME and clear PME status. */ pmstat = pci_read_config(sc->alc_dev, sc->alc_pmcap + PCIR_POWER_STATUS, 2); if ((pmstat & PCIM_PSTAT_PMEENABLE) != 0) { pmstat &= ~PCIM_PSTAT_PMEENABLE; pci_write_config(sc->alc_dev, sc->alc_pmcap + PCIR_POWER_STATUS, pmstat, 2); } } /* Reset PHY. */ alc_phy_reset(sc); ifp = sc->alc_ifp; if ((ifp->if_flags & IFF_UP) != 0) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; alc_init_locked(sc); } ALC_UNLOCK(sc); return (0); } static int alc_encap(struct alc_softc *sc, struct mbuf **m_head) { struct alc_txdesc *txd, *txd_last; struct tx_desc *desc; struct mbuf *m; struct ip *ip; struct tcphdr *tcp; bus_dma_segment_t txsegs[ALC_MAXTXSEGS]; bus_dmamap_t map; uint32_t cflags, hdrlen, ip_off, poff, vtag; int error, idx, nsegs, prod; ALC_LOCK_ASSERT(sc); M_ASSERTPKTHDR((*m_head)); m = *m_head; ip = NULL; tcp = NULL; ip_off = poff = 0; if ((m->m_pkthdr.csum_flags & (ALC_CSUM_FEATURES | CSUM_TSO)) != 0) { /* * AR81[3567]x requires offset of TCP/UDP header in its * Tx descriptor to perform Tx checksum offloading. TSO * also requires TCP header offset and modification of * IP/TCP header. This kind of operation takes many CPU * cycles on FreeBSD so fast host CPU is required to get * smooth TSO performance. */ struct ether_header *eh; if (M_WRITABLE(m) == 0) { /* Get a writable copy. */ m = m_dup(*m_head, M_NOWAIT); /* Release original mbufs. */ m_freem(*m_head); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } *m_head = m; } ip_off = sizeof(struct ether_header); m = m_pullup(m, ip_off); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } eh = mtod(m, struct ether_header *); /* * Check if hardware VLAN insertion is off. * Additional check for LLC/SNAP frame? */ if (eh->ether_type == htons(ETHERTYPE_VLAN)) { ip_off = sizeof(struct ether_vlan_header); m = m_pullup(m, ip_off); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } } m = m_pullup(m, ip_off + sizeof(struct ip)); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } ip = (struct ip *)(mtod(m, char *) + ip_off); poff = ip_off + (ip->ip_hl << 2); if ((m->m_pkthdr.csum_flags & CSUM_TSO) != 0) { m = m_pullup(m, poff + sizeof(struct tcphdr)); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } tcp = (struct tcphdr *)(mtod(m, char *) + poff); m = m_pullup(m, poff + (tcp->th_off << 2)); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } /* * Due to strict adherence of Microsoft NDIS * Large Send specification, hardware expects * a pseudo TCP checksum inserted by upper * stack. Unfortunately the pseudo TCP * checksum that NDIS refers to does not include * TCP payload length so driver should recompute * the pseudo checksum here. Hopefully this * wouldn't be much burden on modern CPUs. * * Reset IP checksum and recompute TCP pseudo * checksum as NDIS specification said. */ ip = (struct ip *)(mtod(m, char *) + ip_off); tcp = (struct tcphdr *)(mtod(m, char *) + poff); ip->ip_sum = 0; tcp->th_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, htons(IPPROTO_TCP)); } *m_head = m; } prod = sc->alc_cdata.alc_tx_prod; txd = &sc->alc_cdata.alc_txdesc[prod]; txd_last = txd; map = txd->tx_dmamap; error = bus_dmamap_load_mbuf_sg(sc->alc_cdata.alc_tx_tag, map, *m_head, txsegs, &nsegs, 0); if (error == EFBIG) { m = m_collapse(*m_head, M_NOWAIT, ALC_MAXTXSEGS); if (m == NULL) { m_freem(*m_head); *m_head = NULL; return (ENOMEM); } *m_head = m; error = bus_dmamap_load_mbuf_sg(sc->alc_cdata.alc_tx_tag, map, *m_head, txsegs, &nsegs, 0); if (error != 0) { m_freem(*m_head); *m_head = NULL; return (error); } } else if (error != 0) return (error); if (nsegs == 0) { m_freem(*m_head); *m_head = NULL; return (EIO); } /* Check descriptor overrun. */ if (sc->alc_cdata.alc_tx_cnt + nsegs >= ALC_TX_RING_CNT - 3) { bus_dmamap_unload(sc->alc_cdata.alc_tx_tag, map); return (ENOBUFS); } bus_dmamap_sync(sc->alc_cdata.alc_tx_tag, map, BUS_DMASYNC_PREWRITE); m = *m_head; cflags = TD_ETHERNET; vtag = 0; desc = NULL; idx = 0; /* Configure VLAN hardware tag insertion. */ if ((m->m_flags & M_VLANTAG) != 0) { vtag = htons(m->m_pkthdr.ether_vtag); vtag = (vtag << TD_VLAN_SHIFT) & TD_VLAN_MASK; cflags |= TD_INS_VLAN_TAG; } if ((m->m_pkthdr.csum_flags & CSUM_TSO) != 0) { /* Request TSO and set MSS. */ cflags |= TD_TSO | TD_TSO_DESCV1; cflags |= ((uint32_t)m->m_pkthdr.tso_segsz << TD_MSS_SHIFT) & TD_MSS_MASK; /* Set TCP header offset. */ cflags |= (poff << TD_TCPHDR_OFFSET_SHIFT) & TD_TCPHDR_OFFSET_MASK; /* * AR81[3567]x requires the first buffer should * only hold IP/TCP header data. Payload should * be handled in other descriptors. */ hdrlen = poff + (tcp->th_off << 2); desc = &sc->alc_rdata.alc_tx_ring[prod]; desc->len = htole32(TX_BYTES(hdrlen | vtag)); desc->flags = htole32(cflags); desc->addr = htole64(txsegs[0].ds_addr); sc->alc_cdata.alc_tx_cnt++; ALC_DESC_INC(prod, ALC_TX_RING_CNT); if (m->m_len - hdrlen > 0) { /* Handle remaining payload of the first fragment. */ desc = &sc->alc_rdata.alc_tx_ring[prod]; desc->len = htole32(TX_BYTES((m->m_len - hdrlen) | vtag)); desc->flags = htole32(cflags); desc->addr = htole64(txsegs[0].ds_addr + hdrlen); sc->alc_cdata.alc_tx_cnt++; ALC_DESC_INC(prod, ALC_TX_RING_CNT); } /* Handle remaining fragments. */ idx = 1; } else if ((m->m_pkthdr.csum_flags & ALC_CSUM_FEATURES) != 0) { /* Configure Tx checksum offload. */ #ifdef ALC_USE_CUSTOM_CSUM cflags |= TD_CUSTOM_CSUM; /* Set checksum start offset. */ cflags |= ((poff >> 1) << TD_PLOAD_OFFSET_SHIFT) & TD_PLOAD_OFFSET_MASK; /* Set checksum insertion position of TCP/UDP. */ cflags |= (((poff + m->m_pkthdr.csum_data) >> 1) << TD_CUSTOM_CSUM_OFFSET_SHIFT) & TD_CUSTOM_CSUM_OFFSET_MASK; #else if ((m->m_pkthdr.csum_flags & CSUM_IP) != 0) cflags |= TD_IPCSUM; if ((m->m_pkthdr.csum_flags & CSUM_TCP) != 0) cflags |= TD_TCPCSUM; if ((m->m_pkthdr.csum_flags & CSUM_UDP) != 0) cflags |= TD_UDPCSUM; /* Set TCP/UDP header offset. */ cflags |= (poff << TD_L4HDR_OFFSET_SHIFT) & TD_L4HDR_OFFSET_MASK; #endif } for (; idx < nsegs; idx++) { desc = &sc->alc_rdata.alc_tx_ring[prod]; desc->len = htole32(TX_BYTES(txsegs[idx].ds_len) | vtag); desc->flags = htole32(cflags); desc->addr = htole64(txsegs[idx].ds_addr); sc->alc_cdata.alc_tx_cnt++; ALC_DESC_INC(prod, ALC_TX_RING_CNT); } /* Update producer index. */ sc->alc_cdata.alc_tx_prod = prod; /* Finally set EOP on the last descriptor. */ prod = (prod + ALC_TX_RING_CNT - 1) % ALC_TX_RING_CNT; desc = &sc->alc_rdata.alc_tx_ring[prod]; desc->flags |= htole32(TD_EOP); /* Swap dmamap of the first and the last. */ txd = &sc->alc_cdata.alc_txdesc[prod]; map = txd_last->tx_dmamap; txd_last->tx_dmamap = txd->tx_dmamap; txd->tx_dmamap = map; txd->tx_m = m; return (0); } static void alc_start(struct ifnet *ifp) { struct alc_softc *sc; sc = ifp->if_softc; ALC_LOCK(sc); alc_start_locked(ifp); ALC_UNLOCK(sc); } static void alc_start_locked(struct ifnet *ifp) { struct alc_softc *sc; struct mbuf *m_head; int enq; sc = ifp->if_softc; ALC_LOCK_ASSERT(sc); /* Reclaim transmitted frames. */ if (sc->alc_cdata.alc_tx_cnt >= ALC_TX_DESC_HIWAT) alc_txeof(sc); if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING || (sc->alc_flags & ALC_FLAG_LINK) == 0) return; for (enq = 0; !IFQ_DRV_IS_EMPTY(&ifp->if_snd); ) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; /* * Pack the data into the transmit ring. If we * don't have room, set the OACTIVE flag and wait * for the NIC to drain the ring. */ if (alc_encap(sc, &m_head)) { if (m_head == NULL) break; IFQ_DRV_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } enq++; /* * If there's a BPF listener, bounce a copy of this frame * to him. */ ETHER_BPF_MTAP(ifp, m_head); } if (enq > 0) { /* Sync descriptors. */ bus_dmamap_sync(sc->alc_cdata.alc_tx_ring_tag, sc->alc_cdata.alc_tx_ring_map, BUS_DMASYNC_PREWRITE); /* Kick. Assume we're using normal Tx priority queue. */ if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) CSR_WRITE_2(sc, ALC_MBOX_TD_PRI0_PROD_IDX, (uint16_t)sc->alc_cdata.alc_tx_prod); else CSR_WRITE_4(sc, ALC_MBOX_TD_PROD_IDX, (sc->alc_cdata.alc_tx_prod << MBOX_TD_PROD_LO_IDX_SHIFT) & MBOX_TD_PROD_LO_IDX_MASK); /* Set a timeout in case the chip goes out to lunch. */ sc->alc_watchdog_timer = ALC_TX_TIMEOUT; } } static void alc_watchdog(struct alc_softc *sc) { struct ifnet *ifp; ALC_LOCK_ASSERT(sc); if (sc->alc_watchdog_timer == 0 || --sc->alc_watchdog_timer) return; ifp = sc->alc_ifp; if ((sc->alc_flags & ALC_FLAG_LINK) == 0) { if_printf(sc->alc_ifp, "watchdog timeout (lost link)\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; alc_init_locked(sc); return; } if_printf(sc->alc_ifp, "watchdog timeout -- resetting\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; alc_init_locked(sc); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) alc_start_locked(ifp); } static int alc_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct alc_softc *sc; struct ifreq *ifr; struct mii_data *mii; int error, mask; sc = ifp->if_softc; ifr = (struct ifreq *)data; error = 0; switch (cmd) { case SIOCSIFMTU: if (ifr->ifr_mtu < ETHERMIN || ifr->ifr_mtu > (sc->alc_ident->max_framelen - sizeof(struct ether_vlan_header) - ETHER_CRC_LEN) || ((sc->alc_flags & ALC_FLAG_JUMBO) == 0 && ifr->ifr_mtu > ETHERMTU)) error = EINVAL; else if (ifp->if_mtu != ifr->ifr_mtu) { ALC_LOCK(sc); ifp->if_mtu = ifr->ifr_mtu; /* AR81[3567]x has 13 bits MSS field. */ if (ifp->if_mtu > ALC_TSO_MTU && (ifp->if_capenable & IFCAP_TSO4) != 0) { ifp->if_capenable &= ~IFCAP_TSO4; ifp->if_hwassist &= ~CSUM_TSO; VLAN_CAPABILITIES(ifp); } ALC_UNLOCK(sc); } break; case SIOCSIFFLAGS: ALC_LOCK(sc); if ((ifp->if_flags & IFF_UP) != 0) { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0 && ((ifp->if_flags ^ sc->alc_if_flags) & (IFF_PROMISC | IFF_ALLMULTI)) != 0) alc_rxfilter(sc); else alc_init_locked(sc); } else if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) alc_stop(sc); sc->alc_if_flags = ifp->if_flags; ALC_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: ALC_LOCK(sc); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) alc_rxfilter(sc); ALC_UNLOCK(sc); break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: mii = device_get_softc(sc->alc_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); break; case SIOCSIFCAP: ALC_LOCK(sc); mask = ifr->ifr_reqcap ^ ifp->if_capenable; if ((mask & IFCAP_TXCSUM) != 0 && (ifp->if_capabilities & IFCAP_TXCSUM) != 0) { ifp->if_capenable ^= IFCAP_TXCSUM; if ((ifp->if_capenable & IFCAP_TXCSUM) != 0) ifp->if_hwassist |= ALC_CSUM_FEATURES; else ifp->if_hwassist &= ~ALC_CSUM_FEATURES; } if ((mask & IFCAP_TSO4) != 0 && (ifp->if_capabilities & IFCAP_TSO4) != 0) { ifp->if_capenable ^= IFCAP_TSO4; if ((ifp->if_capenable & IFCAP_TSO4) != 0) { /* AR81[3567]x has 13 bits MSS field. */ if (ifp->if_mtu > ALC_TSO_MTU) { ifp->if_capenable &= ~IFCAP_TSO4; ifp->if_hwassist &= ~CSUM_TSO; } else ifp->if_hwassist |= CSUM_TSO; } else ifp->if_hwassist &= ~CSUM_TSO; } if ((mask & IFCAP_WOL_MCAST) != 0 && (ifp->if_capabilities & IFCAP_WOL_MCAST) != 0) ifp->if_capenable ^= IFCAP_WOL_MCAST; if ((mask & IFCAP_WOL_MAGIC) != 0 && (ifp->if_capabilities & IFCAP_WOL_MAGIC) != 0) ifp->if_capenable ^= IFCAP_WOL_MAGIC; if ((mask & IFCAP_VLAN_HWTAGGING) != 0 && (ifp->if_capabilities & IFCAP_VLAN_HWTAGGING) != 0) { ifp->if_capenable ^= IFCAP_VLAN_HWTAGGING; alc_rxvlan(sc); } if ((mask & IFCAP_VLAN_HWCSUM) != 0 && (ifp->if_capabilities & IFCAP_VLAN_HWCSUM) != 0) ifp->if_capenable ^= IFCAP_VLAN_HWCSUM; if ((mask & IFCAP_VLAN_HWTSO) != 0 && (ifp->if_capabilities & IFCAP_VLAN_HWTSO) != 0) ifp->if_capenable ^= IFCAP_VLAN_HWTSO; if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) == 0) ifp->if_capenable &= ~(IFCAP_VLAN_HWTSO | IFCAP_VLAN_HWCSUM); ALC_UNLOCK(sc); VLAN_CAPABILITIES(ifp); break; default: error = ether_ioctl(ifp, cmd, data); break; } return (error); } static void alc_mac_config(struct alc_softc *sc) { struct mii_data *mii; uint32_t reg; ALC_LOCK_ASSERT(sc); mii = device_get_softc(sc->alc_miibus); reg = CSR_READ_4(sc, ALC_MAC_CFG); reg &= ~(MAC_CFG_FULL_DUPLEX | MAC_CFG_TX_FC | MAC_CFG_RX_FC | MAC_CFG_SPEED_MASK); if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0 || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8151 || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8151_V2 || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8152_B2) reg |= MAC_CFG_HASH_ALG_CRC32 | MAC_CFG_SPEED_MODE_SW; /* Reprogram MAC with resolved speed/duplex. */ switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: reg |= MAC_CFG_SPEED_10_100; break; case IFM_1000_T: reg |= MAC_CFG_SPEED_1000; break; } if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { reg |= MAC_CFG_FULL_DUPLEX; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0) reg |= MAC_CFG_TX_FC; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0) reg |= MAC_CFG_RX_FC; } CSR_WRITE_4(sc, ALC_MAC_CFG, reg); } static void alc_stats_clear(struct alc_softc *sc) { struct smb sb, *smb; uint32_t *reg; int i; if ((sc->alc_flags & ALC_FLAG_SMB_BUG) == 0) { bus_dmamap_sync(sc->alc_cdata.alc_smb_tag, sc->alc_cdata.alc_smb_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); smb = sc->alc_rdata.alc_smb; /* Update done, clear. */ smb->updated = 0; bus_dmamap_sync(sc->alc_cdata.alc_smb_tag, sc->alc_cdata.alc_smb_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } else { for (reg = &sb.rx_frames, i = 0; reg <= &sb.rx_pkts_filtered; reg++) { CSR_READ_4(sc, ALC_RX_MIB_BASE + i); i += sizeof(uint32_t); } /* Read Tx statistics. */ for (reg = &sb.tx_frames, i = 0; reg <= &sb.tx_mcast_bytes; reg++) { CSR_READ_4(sc, ALC_TX_MIB_BASE + i); i += sizeof(uint32_t); } } } static void alc_stats_update(struct alc_softc *sc) { struct alc_hw_stats *stat; struct smb sb, *smb; struct ifnet *ifp; uint32_t *reg; int i; ALC_LOCK_ASSERT(sc); ifp = sc->alc_ifp; stat = &sc->alc_stats; if ((sc->alc_flags & ALC_FLAG_SMB_BUG) == 0) { bus_dmamap_sync(sc->alc_cdata.alc_smb_tag, sc->alc_cdata.alc_smb_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); smb = sc->alc_rdata.alc_smb; if (smb->updated == 0) return; } else { smb = &sb; /* Read Rx statistics. */ for (reg = &sb.rx_frames, i = 0; reg <= &sb.rx_pkts_filtered; reg++) { *reg = CSR_READ_4(sc, ALC_RX_MIB_BASE + i); i += sizeof(uint32_t); } /* Read Tx statistics. */ for (reg = &sb.tx_frames, i = 0; reg <= &sb.tx_mcast_bytes; reg++) { *reg = CSR_READ_4(sc, ALC_TX_MIB_BASE + i); i += sizeof(uint32_t); } } /* Rx stats. */ stat->rx_frames += smb->rx_frames; stat->rx_bcast_frames += smb->rx_bcast_frames; stat->rx_mcast_frames += smb->rx_mcast_frames; stat->rx_pause_frames += smb->rx_pause_frames; stat->rx_control_frames += smb->rx_control_frames; stat->rx_crcerrs += smb->rx_crcerrs; stat->rx_lenerrs += smb->rx_lenerrs; stat->rx_bytes += smb->rx_bytes; stat->rx_runts += smb->rx_runts; stat->rx_fragments += smb->rx_fragments; stat->rx_pkts_64 += smb->rx_pkts_64; stat->rx_pkts_65_127 += smb->rx_pkts_65_127; stat->rx_pkts_128_255 += smb->rx_pkts_128_255; stat->rx_pkts_256_511 += smb->rx_pkts_256_511; stat->rx_pkts_512_1023 += smb->rx_pkts_512_1023; stat->rx_pkts_1024_1518 += smb->rx_pkts_1024_1518; stat->rx_pkts_1519_max += smb->rx_pkts_1519_max; stat->rx_pkts_truncated += smb->rx_pkts_truncated; stat->rx_fifo_oflows += smb->rx_fifo_oflows; stat->rx_rrs_errs += smb->rx_rrs_errs; stat->rx_alignerrs += smb->rx_alignerrs; stat->rx_bcast_bytes += smb->rx_bcast_bytes; stat->rx_mcast_bytes += smb->rx_mcast_bytes; stat->rx_pkts_filtered += smb->rx_pkts_filtered; /* Tx stats. */ stat->tx_frames += smb->tx_frames; stat->tx_bcast_frames += smb->tx_bcast_frames; stat->tx_mcast_frames += smb->tx_mcast_frames; stat->tx_pause_frames += smb->tx_pause_frames; stat->tx_excess_defer += smb->tx_excess_defer; stat->tx_control_frames += smb->tx_control_frames; stat->tx_deferred += smb->tx_deferred; stat->tx_bytes += smb->tx_bytes; stat->tx_pkts_64 += smb->tx_pkts_64; stat->tx_pkts_65_127 += smb->tx_pkts_65_127; stat->tx_pkts_128_255 += smb->tx_pkts_128_255; stat->tx_pkts_256_511 += smb->tx_pkts_256_511; stat->tx_pkts_512_1023 += smb->tx_pkts_512_1023; stat->tx_pkts_1024_1518 += smb->tx_pkts_1024_1518; stat->tx_pkts_1519_max += smb->tx_pkts_1519_max; stat->tx_single_colls += smb->tx_single_colls; stat->tx_multi_colls += smb->tx_multi_colls; stat->tx_late_colls += smb->tx_late_colls; stat->tx_excess_colls += smb->tx_excess_colls; stat->tx_underrun += smb->tx_underrun; stat->tx_desc_underrun += smb->tx_desc_underrun; stat->tx_lenerrs += smb->tx_lenerrs; stat->tx_pkts_truncated += smb->tx_pkts_truncated; stat->tx_bcast_bytes += smb->tx_bcast_bytes; stat->tx_mcast_bytes += smb->tx_mcast_bytes; /* Update counters in ifnet. */ if_inc_counter(ifp, IFCOUNTER_OPACKETS, smb->tx_frames); if_inc_counter(ifp, IFCOUNTER_COLLISIONS, smb->tx_single_colls + smb->tx_multi_colls * 2 + smb->tx_late_colls + smb->tx_excess_colls * HDPX_CFG_RETRY_DEFAULT); if_inc_counter(ifp, IFCOUNTER_OERRORS, smb->tx_late_colls + smb->tx_excess_colls + smb->tx_underrun + smb->tx_pkts_truncated); if_inc_counter(ifp, IFCOUNTER_IPACKETS, smb->rx_frames); if_inc_counter(ifp, IFCOUNTER_IERRORS, smb->rx_crcerrs + smb->rx_lenerrs + smb->rx_runts + smb->rx_pkts_truncated + smb->rx_fifo_oflows + smb->rx_rrs_errs + smb->rx_alignerrs); if ((sc->alc_flags & ALC_FLAG_SMB_BUG) == 0) { /* Update done, clear. */ smb->updated = 0; bus_dmamap_sync(sc->alc_cdata.alc_smb_tag, sc->alc_cdata.alc_smb_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } } static int alc_intr(void *arg) { struct alc_softc *sc; uint32_t status; sc = (struct alc_softc *)arg; status = CSR_READ_4(sc, ALC_INTR_STATUS); if ((status & ALC_INTRS) == 0) return (FILTER_STRAY); /* Disable interrupts. */ CSR_WRITE_4(sc, ALC_INTR_STATUS, INTR_DIS_INT); taskqueue_enqueue(sc->alc_tq, &sc->alc_int_task); return (FILTER_HANDLED); } static void alc_int_task(void *arg, int pending) { struct alc_softc *sc; struct ifnet *ifp; uint32_t status; int more; sc = (struct alc_softc *)arg; ifp = sc->alc_ifp; status = CSR_READ_4(sc, ALC_INTR_STATUS); ALC_LOCK(sc); if (sc->alc_morework != 0) { sc->alc_morework = 0; status |= INTR_RX_PKT; } if ((status & ALC_INTRS) == 0) goto done; /* Acknowledge interrupts but still disable interrupts. */ CSR_WRITE_4(sc, ALC_INTR_STATUS, status | INTR_DIS_INT); more = 0; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { if ((status & INTR_RX_PKT) != 0) { more = alc_rxintr(sc, sc->alc_process_limit); if (more == EAGAIN) sc->alc_morework = 1; else if (more == EIO) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; alc_init_locked(sc); ALC_UNLOCK(sc); return; } } if ((status & (INTR_DMA_RD_TO_RST | INTR_DMA_WR_TO_RST | INTR_TXQ_TO_RST)) != 0) { if ((status & INTR_DMA_RD_TO_RST) != 0) device_printf(sc->alc_dev, "DMA read error! -- resetting\n"); if ((status & INTR_DMA_WR_TO_RST) != 0) device_printf(sc->alc_dev, "DMA write error! -- resetting\n"); if ((status & INTR_TXQ_TO_RST) != 0) device_printf(sc->alc_dev, "TxQ reset! -- resetting\n"); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; alc_init_locked(sc); ALC_UNLOCK(sc); return; } if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0 && !IFQ_DRV_IS_EMPTY(&ifp->if_snd)) alc_start_locked(ifp); } if (more == EAGAIN || (CSR_READ_4(sc, ALC_INTR_STATUS) & ALC_INTRS) != 0) { ALC_UNLOCK(sc); taskqueue_enqueue(sc->alc_tq, &sc->alc_int_task); return; } done: if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { /* Re-enable interrupts if we're running. */ CSR_WRITE_4(sc, ALC_INTR_STATUS, 0x7FFFFFFF); } ALC_UNLOCK(sc); } static void alc_txeof(struct alc_softc *sc) { struct ifnet *ifp; struct alc_txdesc *txd; uint32_t cons, prod; int prog; ALC_LOCK_ASSERT(sc); ifp = sc->alc_ifp; if (sc->alc_cdata.alc_tx_cnt == 0) return; bus_dmamap_sync(sc->alc_cdata.alc_tx_ring_tag, sc->alc_cdata.alc_tx_ring_map, BUS_DMASYNC_POSTWRITE); if ((sc->alc_flags & ALC_FLAG_CMB_BUG) == 0) { bus_dmamap_sync(sc->alc_cdata.alc_cmb_tag, sc->alc_cdata.alc_cmb_map, BUS_DMASYNC_POSTREAD); prod = sc->alc_rdata.alc_cmb->cons; } else { if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) prod = CSR_READ_2(sc, ALC_MBOX_TD_PRI0_CONS_IDX); else { prod = CSR_READ_4(sc, ALC_MBOX_TD_CONS_IDX); /* Assume we're using normal Tx priority queue. */ prod = (prod & MBOX_TD_CONS_LO_IDX_MASK) >> MBOX_TD_CONS_LO_IDX_SHIFT; } } cons = sc->alc_cdata.alc_tx_cons; /* * Go through our Tx list and free mbufs for those * frames which have been transmitted. */ for (prog = 0; cons != prod; prog++, ALC_DESC_INC(cons, ALC_TX_RING_CNT)) { if (sc->alc_cdata.alc_tx_cnt <= 0) break; prog++; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc->alc_cdata.alc_tx_cnt--; txd = &sc->alc_cdata.alc_txdesc[cons]; if (txd->tx_m != NULL) { /* Reclaim transmitted mbufs. */ bus_dmamap_sync(sc->alc_cdata.alc_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->alc_cdata.alc_tx_tag, txd->tx_dmamap); m_freem(txd->tx_m); txd->tx_m = NULL; } } if ((sc->alc_flags & ALC_FLAG_CMB_BUG) == 0) bus_dmamap_sync(sc->alc_cdata.alc_cmb_tag, sc->alc_cdata.alc_cmb_map, BUS_DMASYNC_PREREAD); sc->alc_cdata.alc_tx_cons = cons; /* * Unarm watchdog timer only when there is no pending * frames in Tx queue. */ if (sc->alc_cdata.alc_tx_cnt == 0) sc->alc_watchdog_timer = 0; } static int alc_newbuf(struct alc_softc *sc, struct alc_rxdesc *rxd) { struct mbuf *m; bus_dma_segment_t segs[1]; bus_dmamap_t map; int nsegs; m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) return (ENOBUFS); m->m_len = m->m_pkthdr.len = RX_BUF_SIZE_MAX; #ifndef __NO_STRICT_ALIGNMENT m_adj(m, sizeof(uint64_t)); #endif if (bus_dmamap_load_mbuf_sg(sc->alc_cdata.alc_rx_tag, sc->alc_cdata.alc_rx_sparemap, m, segs, &nsegs, 0) != 0) { m_freem(m); return (ENOBUFS); } KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); if (rxd->rx_m != NULL) { bus_dmamap_sync(sc->alc_cdata.alc_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->alc_cdata.alc_rx_tag, rxd->rx_dmamap); } map = rxd->rx_dmamap; rxd->rx_dmamap = sc->alc_cdata.alc_rx_sparemap; sc->alc_cdata.alc_rx_sparemap = map; bus_dmamap_sync(sc->alc_cdata.alc_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_PREREAD); rxd->rx_m = m; rxd->rx_desc->addr = htole64(segs[0].ds_addr); return (0); } static int alc_rxintr(struct alc_softc *sc, int count) { struct ifnet *ifp; struct rx_rdesc *rrd; uint32_t nsegs, status; int rr_cons, prog; bus_dmamap_sync(sc->alc_cdata.alc_rr_ring_tag, sc->alc_cdata.alc_rr_ring_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); bus_dmamap_sync(sc->alc_cdata.alc_rx_ring_tag, sc->alc_cdata.alc_rx_ring_map, BUS_DMASYNC_POSTWRITE); rr_cons = sc->alc_cdata.alc_rr_cons; ifp = sc->alc_ifp; for (prog = 0; (ifp->if_drv_flags & IFF_DRV_RUNNING) != 0;) { if (count-- <= 0) break; rrd = &sc->alc_rdata.alc_rr_ring[rr_cons]; status = le32toh(rrd->status); if ((status & RRD_VALID) == 0) break; nsegs = RRD_RD_CNT(le32toh(rrd->rdinfo)); if (nsegs == 0) { /* This should not happen! */ device_printf(sc->alc_dev, "unexpected segment count -- resetting\n"); return (EIO); } alc_rxeof(sc, rrd); /* Clear Rx return status. */ rrd->status = 0; ALC_DESC_INC(rr_cons, ALC_RR_RING_CNT); sc->alc_cdata.alc_rx_cons += nsegs; sc->alc_cdata.alc_rx_cons %= ALC_RR_RING_CNT; prog += nsegs; } if (prog > 0) { /* Update the consumer index. */ sc->alc_cdata.alc_rr_cons = rr_cons; /* Sync Rx return descriptors. */ bus_dmamap_sync(sc->alc_cdata.alc_rr_ring_tag, sc->alc_cdata.alc_rr_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); /* * Sync updated Rx descriptors such that controller see * modified buffer addresses. */ bus_dmamap_sync(sc->alc_cdata.alc_rx_ring_tag, sc->alc_cdata.alc_rx_ring_map, BUS_DMASYNC_PREWRITE); /* * Let controller know availability of new Rx buffers. * Since alc(4) use RXQ_CFG_RD_BURST_DEFAULT descriptors * it may be possible to update ALC_MBOX_RD0_PROD_IDX * only when Rx buffer pre-fetching is required. In * addition we already set ALC_RX_RD_FREE_THRESH to * RX_RD_FREE_THRESH_LO_DEFAULT descriptors. However * it still seems that pre-fetching needs more * experimentation. */ if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) CSR_WRITE_2(sc, ALC_MBOX_RD0_PROD_IDX, (uint16_t)sc->alc_cdata.alc_rx_cons); else CSR_WRITE_4(sc, ALC_MBOX_RD0_PROD_IDX, sc->alc_cdata.alc_rx_cons); } return (count > 0 ? 0 : EAGAIN); } #ifndef __NO_STRICT_ALIGNMENT static struct mbuf * alc_fixup_rx(struct ifnet *ifp, struct mbuf *m) { struct mbuf *n; int i; uint16_t *src, *dst; src = mtod(m, uint16_t *); dst = src - 3; if (m->m_next == NULL) { for (i = 0; i < (m->m_len / sizeof(uint16_t) + 1); i++) *dst++ = *src++; m->m_data -= 6; return (m); } /* * Append a new mbuf to received mbuf chain and copy ethernet * header from the mbuf chain. This can save lots of CPU * cycles for jumbo frame. */ MGETHDR(n, M_NOWAIT, MT_DATA); if (n == NULL) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); m_freem(m); return (NULL); } bcopy(m->m_data, n->m_data, ETHER_HDR_LEN); m->m_data += ETHER_HDR_LEN; m->m_len -= ETHER_HDR_LEN; n->m_len = ETHER_HDR_LEN; M_MOVE_PKTHDR(n, m); n->m_next = m; return (n); } #endif /* Receive a frame. */ static void alc_rxeof(struct alc_softc *sc, struct rx_rdesc *rrd) { struct alc_rxdesc *rxd; struct ifnet *ifp; struct mbuf *mp, *m; uint32_t rdinfo, status, vtag; int count, nsegs, rx_cons; ifp = sc->alc_ifp; status = le32toh(rrd->status); rdinfo = le32toh(rrd->rdinfo); rx_cons = RRD_RD_IDX(rdinfo); nsegs = RRD_RD_CNT(rdinfo); sc->alc_cdata.alc_rxlen = RRD_BYTES(status); if ((status & (RRD_ERR_SUM | RRD_ERR_LENGTH)) != 0) { /* * We want to pass the following frames to upper * layer regardless of error status of Rx return * ring. * * o IP/TCP/UDP checksum is bad. * o frame length and protocol specific length * does not match. * * Force network stack compute checksum for * errored frames. */ status |= RRD_TCP_UDPCSUM_NOK | RRD_IPCSUM_NOK; if ((status & (RRD_ERR_CRC | RRD_ERR_ALIGN | RRD_ERR_TRUNC | RRD_ERR_RUNT)) != 0) return; } for (count = 0; count < nsegs; count++, ALC_DESC_INC(rx_cons, ALC_RX_RING_CNT)) { rxd = &sc->alc_cdata.alc_rxdesc[rx_cons]; mp = rxd->rx_m; /* Add a new receive buffer to the ring. */ if (alc_newbuf(sc, rxd) != 0) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); /* Reuse Rx buffers. */ if (sc->alc_cdata.alc_rxhead != NULL) m_freem(sc->alc_cdata.alc_rxhead); break; } /* * Assume we've received a full sized frame. * Actual size is fixed when we encounter the end of * multi-segmented frame. */ mp->m_len = sc->alc_buf_size; /* Chain received mbufs. */ if (sc->alc_cdata.alc_rxhead == NULL) { sc->alc_cdata.alc_rxhead = mp; sc->alc_cdata.alc_rxtail = mp; } else { mp->m_flags &= ~M_PKTHDR; sc->alc_cdata.alc_rxprev_tail = sc->alc_cdata.alc_rxtail; sc->alc_cdata.alc_rxtail->m_next = mp; sc->alc_cdata.alc_rxtail = mp; } if (count == nsegs - 1) { /* Last desc. for this frame. */ m = sc->alc_cdata.alc_rxhead; m->m_flags |= M_PKTHDR; /* * It seems that L1C/L2C controller has no way * to tell hardware to strip CRC bytes. */ m->m_pkthdr.len = sc->alc_cdata.alc_rxlen - ETHER_CRC_LEN; if (nsegs > 1) { /* Set last mbuf size. */ mp->m_len = sc->alc_cdata.alc_rxlen - (nsegs - 1) * sc->alc_buf_size; /* Remove the CRC bytes in chained mbufs. */ if (mp->m_len <= ETHER_CRC_LEN) { sc->alc_cdata.alc_rxtail = sc->alc_cdata.alc_rxprev_tail; sc->alc_cdata.alc_rxtail->m_len -= (ETHER_CRC_LEN - mp->m_len); sc->alc_cdata.alc_rxtail->m_next = NULL; m_freem(mp); } else { mp->m_len -= ETHER_CRC_LEN; } } else m->m_len = m->m_pkthdr.len; m->m_pkthdr.rcvif = ifp; /* * Due to hardware bugs, Rx checksum offloading * was intentionally disabled. */ if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0 && (status & RRD_VLAN_TAG) != 0) { vtag = RRD_VLAN(le32toh(rrd->vtag)); m->m_pkthdr.ether_vtag = ntohs(vtag); m->m_flags |= M_VLANTAG; } #ifndef __NO_STRICT_ALIGNMENT m = alc_fixup_rx(ifp, m); if (m != NULL) #endif { /* Pass it on. */ ALC_UNLOCK(sc); (*ifp->if_input)(ifp, m); ALC_LOCK(sc); } } } /* Reset mbuf chains. */ ALC_RXCHAIN_RESET(sc); } static void alc_tick(void *arg) { struct alc_softc *sc; struct mii_data *mii; sc = (struct alc_softc *)arg; ALC_LOCK_ASSERT(sc); mii = device_get_softc(sc->alc_miibus); mii_tick(mii); alc_stats_update(sc); /* * alc(4) does not rely on Tx completion interrupts to reclaim * transferred buffers. Instead Tx completion interrupts are * used to hint for scheduling Tx task. So it's necessary to * release transmitted buffers by kicking Tx completion * handler. This limits the maximum reclamation delay to a hz. */ alc_txeof(sc); alc_watchdog(sc); callout_reset(&sc->alc_tick_ch, hz, alc_tick, sc); } static void alc_osc_reset(struct alc_softc *sc) { uint32_t reg; reg = CSR_READ_4(sc, ALC_MISC3); reg &= ~MISC3_25M_BY_SW; reg |= MISC3_25M_NOTO_INTNL; CSR_WRITE_4(sc, ALC_MISC3, reg); reg = CSR_READ_4(sc, ALC_MISC); if (AR816X_REV(sc->alc_rev) >= AR816X_REV_B0) { /* * Restore over-current protection default value. * This value could be reset by MAC reset. */ reg &= ~MISC_PSW_OCP_MASK; reg |= (MISC_PSW_OCP_DEFAULT << MISC_PSW_OCP_SHIFT); reg &= ~MISC_INTNLOSC_OPEN; CSR_WRITE_4(sc, ALC_MISC, reg); CSR_WRITE_4(sc, ALC_MISC, reg | MISC_INTNLOSC_OPEN); reg = CSR_READ_4(sc, ALC_MISC2); reg &= ~MISC2_CALB_START; CSR_WRITE_4(sc, ALC_MISC2, reg); CSR_WRITE_4(sc, ALC_MISC2, reg | MISC2_CALB_START); } else { reg &= ~MISC_INTNLOSC_OPEN; /* Disable isolate for revision A devices. */ if (AR816X_REV(sc->alc_rev) <= AR816X_REV_A1) reg &= ~MISC_ISO_ENB; CSR_WRITE_4(sc, ALC_MISC, reg | MISC_INTNLOSC_OPEN); CSR_WRITE_4(sc, ALC_MISC, reg); } DELAY(20); } static void alc_reset(struct alc_softc *sc) { uint32_t pmcfg, reg; int i; pmcfg = 0; if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) { /* Reset workaround. */ CSR_WRITE_4(sc, ALC_MBOX_RD0_PROD_IDX, 1); if (AR816X_REV(sc->alc_rev) <= AR816X_REV_A1 && (sc->alc_rev & 0x01) != 0) { /* Disable L0s/L1s before reset. */ pmcfg = CSR_READ_4(sc, ALC_PM_CFG); if ((pmcfg & (PM_CFG_ASPM_L0S_ENB | PM_CFG_ASPM_L1_ENB)) != 0) { pmcfg &= ~(PM_CFG_ASPM_L0S_ENB | PM_CFG_ASPM_L1_ENB); CSR_WRITE_4(sc, ALC_PM_CFG, pmcfg); } } } reg = CSR_READ_4(sc, ALC_MASTER_CFG); reg |= MASTER_OOB_DIS_OFF | MASTER_RESET; CSR_WRITE_4(sc, ALC_MASTER_CFG, reg); if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) { for (i = ALC_RESET_TIMEOUT; i > 0; i--) { DELAY(10); if (CSR_READ_4(sc, ALC_MBOX_RD0_PROD_IDX) == 0) break; } if (i == 0) device_printf(sc->alc_dev, "MAC reset timeout!\n"); } for (i = ALC_RESET_TIMEOUT; i > 0; i--) { DELAY(10); if ((CSR_READ_4(sc, ALC_MASTER_CFG) & MASTER_RESET) == 0) break; } if (i == 0) device_printf(sc->alc_dev, "master reset timeout!\n"); for (i = ALC_RESET_TIMEOUT; i > 0; i--) { reg = CSR_READ_4(sc, ALC_IDLE_STATUS); if ((reg & (IDLE_STATUS_RXMAC | IDLE_STATUS_TXMAC | IDLE_STATUS_RXQ | IDLE_STATUS_TXQ)) == 0) break; DELAY(10); } if (i == 0) device_printf(sc->alc_dev, "reset timeout(0x%08x)!\n", reg); if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) { if (AR816X_REV(sc->alc_rev) <= AR816X_REV_A1 && (sc->alc_rev & 0x01) != 0) { reg = CSR_READ_4(sc, ALC_MASTER_CFG); reg |= MASTER_CLK_SEL_DIS; CSR_WRITE_4(sc, ALC_MASTER_CFG, reg); /* Restore L0s/L1s config. */ if ((pmcfg & (PM_CFG_ASPM_L0S_ENB | PM_CFG_ASPM_L1_ENB)) != 0) CSR_WRITE_4(sc, ALC_PM_CFG, pmcfg); } alc_osc_reset(sc); reg = CSR_READ_4(sc, ALC_MISC3); reg &= ~MISC3_25M_BY_SW; reg |= MISC3_25M_NOTO_INTNL; CSR_WRITE_4(sc, ALC_MISC3, reg); reg = CSR_READ_4(sc, ALC_MISC); reg &= ~MISC_INTNLOSC_OPEN; if (AR816X_REV(sc->alc_rev) <= AR816X_REV_A1) reg &= ~MISC_ISO_ENB; CSR_WRITE_4(sc, ALC_MISC, reg); DELAY(20); } if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0 || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8152_B || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8151_V2) CSR_WRITE_4(sc, ALC_SERDES_LOCK, CSR_READ_4(sc, ALC_SERDES_LOCK) | SERDES_MAC_CLK_SLOWDOWN | SERDES_PHY_CLK_SLOWDOWN); } static void alc_init(void *xsc) { struct alc_softc *sc; sc = (struct alc_softc *)xsc; ALC_LOCK(sc); alc_init_locked(sc); ALC_UNLOCK(sc); } static void alc_init_locked(struct alc_softc *sc) { struct ifnet *ifp; struct mii_data *mii; uint8_t eaddr[ETHER_ADDR_LEN]; bus_addr_t paddr; uint32_t reg, rxf_hi, rxf_lo; ALC_LOCK_ASSERT(sc); ifp = sc->alc_ifp; mii = device_get_softc(sc->alc_miibus); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) return; /* * Cancel any pending I/O. */ alc_stop(sc); /* * Reset the chip to a known state. */ alc_reset(sc); /* Initialize Rx descriptors. */ if (alc_init_rx_ring(sc) != 0) { device_printf(sc->alc_dev, "no memory for Rx buffers.\n"); alc_stop(sc); return; } alc_init_rr_ring(sc); alc_init_tx_ring(sc); alc_init_cmb(sc); alc_init_smb(sc); /* Enable all clocks. */ if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) { CSR_WRITE_4(sc, ALC_CLK_GATING_CFG, CLK_GATING_DMAW_ENB | CLK_GATING_DMAR_ENB | CLK_GATING_TXQ_ENB | CLK_GATING_RXQ_ENB | CLK_GATING_TXMAC_ENB | CLK_GATING_RXMAC_ENB); if (AR816X_REV(sc->alc_rev) >= AR816X_REV_B0) CSR_WRITE_4(sc, ALC_IDLE_DECISN_TIMER, IDLE_DECISN_TIMER_DEFAULT_1MS); } else CSR_WRITE_4(sc, ALC_CLK_GATING_CFG, 0); /* Reprogram the station address. */ bcopy(IF_LLADDR(ifp), eaddr, ETHER_ADDR_LEN); CSR_WRITE_4(sc, ALC_PAR0, eaddr[2] << 24 | eaddr[3] << 16 | eaddr[4] << 8 | eaddr[5]); CSR_WRITE_4(sc, ALC_PAR1, eaddr[0] << 8 | eaddr[1]); /* * Clear WOL status and disable all WOL feature as WOL * would interfere Rx operation under normal environments. */ CSR_READ_4(sc, ALC_WOL_CFG); CSR_WRITE_4(sc, ALC_WOL_CFG, 0); /* Set Tx descriptor base addresses. */ paddr = sc->alc_rdata.alc_tx_ring_paddr; CSR_WRITE_4(sc, ALC_TX_BASE_ADDR_HI, ALC_ADDR_HI(paddr)); CSR_WRITE_4(sc, ALC_TDL_HEAD_ADDR_LO, ALC_ADDR_LO(paddr)); /* We don't use high priority ring. */ CSR_WRITE_4(sc, ALC_TDH_HEAD_ADDR_LO, 0); /* Set Tx descriptor counter. */ CSR_WRITE_4(sc, ALC_TD_RING_CNT, (ALC_TX_RING_CNT << TD_RING_CNT_SHIFT) & TD_RING_CNT_MASK); /* Set Rx descriptor base addresses. */ paddr = sc->alc_rdata.alc_rx_ring_paddr; CSR_WRITE_4(sc, ALC_RX_BASE_ADDR_HI, ALC_ADDR_HI(paddr)); CSR_WRITE_4(sc, ALC_RD0_HEAD_ADDR_LO, ALC_ADDR_LO(paddr)); if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) == 0) { /* We use one Rx ring. */ CSR_WRITE_4(sc, ALC_RD1_HEAD_ADDR_LO, 0); CSR_WRITE_4(sc, ALC_RD2_HEAD_ADDR_LO, 0); CSR_WRITE_4(sc, ALC_RD3_HEAD_ADDR_LO, 0); } /* Set Rx descriptor counter. */ CSR_WRITE_4(sc, ALC_RD_RING_CNT, (ALC_RX_RING_CNT << RD_RING_CNT_SHIFT) & RD_RING_CNT_MASK); /* * Let hardware split jumbo frames into alc_max_buf_sized chunks. * if it do not fit the buffer size. Rx return descriptor holds * a counter that indicates how many fragments were made by the * hardware. The buffer size should be multiple of 8 bytes. * Since hardware has limit on the size of buffer size, always * use the maximum value. * For strict-alignment architectures make sure to reduce buffer * size by 8 bytes to make room for alignment fixup. */ #ifndef __NO_STRICT_ALIGNMENT sc->alc_buf_size = RX_BUF_SIZE_MAX - sizeof(uint64_t); #else sc->alc_buf_size = RX_BUF_SIZE_MAX; #endif CSR_WRITE_4(sc, ALC_RX_BUF_SIZE, sc->alc_buf_size); paddr = sc->alc_rdata.alc_rr_ring_paddr; /* Set Rx return descriptor base addresses. */ CSR_WRITE_4(sc, ALC_RRD0_HEAD_ADDR_LO, ALC_ADDR_LO(paddr)); if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) == 0) { /* We use one Rx return ring. */ CSR_WRITE_4(sc, ALC_RRD1_HEAD_ADDR_LO, 0); CSR_WRITE_4(sc, ALC_RRD2_HEAD_ADDR_LO, 0); CSR_WRITE_4(sc, ALC_RRD3_HEAD_ADDR_LO, 0); } /* Set Rx return descriptor counter. */ CSR_WRITE_4(sc, ALC_RRD_RING_CNT, (ALC_RR_RING_CNT << RRD_RING_CNT_SHIFT) & RRD_RING_CNT_MASK); paddr = sc->alc_rdata.alc_cmb_paddr; CSR_WRITE_4(sc, ALC_CMB_BASE_ADDR_LO, ALC_ADDR_LO(paddr)); paddr = sc->alc_rdata.alc_smb_paddr; CSR_WRITE_4(sc, ALC_SMB_BASE_ADDR_HI, ALC_ADDR_HI(paddr)); CSR_WRITE_4(sc, ALC_SMB_BASE_ADDR_LO, ALC_ADDR_LO(paddr)); if (sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8152_B) { /* Reconfigure SRAM - Vendor magic. */ CSR_WRITE_4(sc, ALC_SRAM_RX_FIFO_LEN, 0x000002A0); CSR_WRITE_4(sc, ALC_SRAM_TX_FIFO_LEN, 0x00000100); CSR_WRITE_4(sc, ALC_SRAM_RX_FIFO_ADDR, 0x029F0000); CSR_WRITE_4(sc, ALC_SRAM_RD0_ADDR, 0x02BF02A0); CSR_WRITE_4(sc, ALC_SRAM_TX_FIFO_ADDR, 0x03BF02C0); CSR_WRITE_4(sc, ALC_SRAM_TD_ADDR, 0x03DF03C0); CSR_WRITE_4(sc, ALC_TXF_WATER_MARK, 0x00000000); CSR_WRITE_4(sc, ALC_RD_DMA_CFG, 0x00000000); } /* Tell hardware that we're ready to load DMA blocks. */ CSR_WRITE_4(sc, ALC_DMA_BLOCK, DMA_BLOCK_LOAD); /* Configure interrupt moderation timer. */ reg = ALC_USECS(sc->alc_int_rx_mod) << IM_TIMER_RX_SHIFT; if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) == 0) reg |= ALC_USECS(sc->alc_int_tx_mod) << IM_TIMER_TX_SHIFT; CSR_WRITE_4(sc, ALC_IM_TIMER, reg); /* * We don't want to automatic interrupt clear as task queue * for the interrupt should know interrupt status. */ reg = CSR_READ_4(sc, ALC_MASTER_CFG); reg &= ~(MASTER_IM_RX_TIMER_ENB | MASTER_IM_TX_TIMER_ENB); reg |= MASTER_SA_TIMER_ENB; if (ALC_USECS(sc->alc_int_rx_mod) != 0) reg |= MASTER_IM_RX_TIMER_ENB; if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) == 0 && ALC_USECS(sc->alc_int_tx_mod) != 0) reg |= MASTER_IM_TX_TIMER_ENB; CSR_WRITE_4(sc, ALC_MASTER_CFG, reg); /* * Disable interrupt re-trigger timer. We don't want automatic * re-triggering of un-ACKed interrupts. */ CSR_WRITE_4(sc, ALC_INTR_RETRIG_TIMER, ALC_USECS(0)); /* Configure CMB. */ if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) { CSR_WRITE_4(sc, ALC_CMB_TD_THRESH, ALC_TX_RING_CNT / 3); CSR_WRITE_4(sc, ALC_CMB_TX_TIMER, ALC_USECS(sc->alc_int_tx_mod)); } else { if ((sc->alc_flags & ALC_FLAG_CMB_BUG) == 0) { CSR_WRITE_4(sc, ALC_CMB_TD_THRESH, 4); CSR_WRITE_4(sc, ALC_CMB_TX_TIMER, ALC_USECS(5000)); } else CSR_WRITE_4(sc, ALC_CMB_TX_TIMER, ALC_USECS(0)); } /* * Hardware can be configured to issue SMB interrupt based * on programmed interval. Since there is a callout that is * invoked for every hz in driver we use that instead of * relying on periodic SMB interrupt. */ CSR_WRITE_4(sc, ALC_SMB_STAT_TIMER, ALC_USECS(0)); /* Clear MAC statistics. */ alc_stats_clear(sc); /* * Always use maximum frame size that controller can support. * Otherwise received frames that has larger frame length * than alc(4) MTU would be silently dropped in hardware. This * would make path-MTU discovery hard as sender wouldn't get * any responses from receiver. alc(4) supports * multi-fragmented frames on Rx path so it has no issue on * assembling fragmented frames. Using maximum frame size also * removes the need to reinitialize hardware when interface * MTU configuration was changed. * * Be conservative in what you do, be liberal in what you * accept from others - RFC 793. */ CSR_WRITE_4(sc, ALC_FRAME_SIZE, sc->alc_ident->max_framelen); if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) == 0) { /* Disable header split(?) */ CSR_WRITE_4(sc, ALC_HDS_CFG, 0); /* Configure IPG/IFG parameters. */ CSR_WRITE_4(sc, ALC_IPG_IFG_CFG, ((IPG_IFG_IPGT_DEFAULT << IPG_IFG_IPGT_SHIFT) & IPG_IFG_IPGT_MASK) | ((IPG_IFG_MIFG_DEFAULT << IPG_IFG_MIFG_SHIFT) & IPG_IFG_MIFG_MASK) | ((IPG_IFG_IPG1_DEFAULT << IPG_IFG_IPG1_SHIFT) & IPG_IFG_IPG1_MASK) | ((IPG_IFG_IPG2_DEFAULT << IPG_IFG_IPG2_SHIFT) & IPG_IFG_IPG2_MASK)); /* Set parameters for half-duplex media. */ CSR_WRITE_4(sc, ALC_HDPX_CFG, ((HDPX_CFG_LCOL_DEFAULT << HDPX_CFG_LCOL_SHIFT) & HDPX_CFG_LCOL_MASK) | ((HDPX_CFG_RETRY_DEFAULT << HDPX_CFG_RETRY_SHIFT) & HDPX_CFG_RETRY_MASK) | HDPX_CFG_EXC_DEF_EN | ((HDPX_CFG_ABEBT_DEFAULT << HDPX_CFG_ABEBT_SHIFT) & HDPX_CFG_ABEBT_MASK) | ((HDPX_CFG_JAMIPG_DEFAULT << HDPX_CFG_JAMIPG_SHIFT) & HDPX_CFG_JAMIPG_MASK)); } /* * Set TSO/checksum offload threshold. For frames that is * larger than this threshold, hardware wouldn't do * TSO/checksum offloading. */ reg = (sc->alc_ident->max_framelen >> TSO_OFFLOAD_THRESH_UNIT_SHIFT) & TSO_OFFLOAD_THRESH_MASK; if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) reg |= TSO_OFFLOAD_ERRLGPKT_DROP_ENB; CSR_WRITE_4(sc, ALC_TSO_OFFLOAD_THRESH, reg); /* Configure TxQ. */ reg = (alc_dma_burst[sc->alc_dma_rd_burst] << TXQ_CFG_TX_FIFO_BURST_SHIFT) & TXQ_CFG_TX_FIFO_BURST_MASK; if (sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8152_B || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8152_B2) reg >>= 1; reg |= (TXQ_CFG_TD_BURST_DEFAULT << TXQ_CFG_TD_BURST_SHIFT) & TXQ_CFG_TD_BURST_MASK; reg |= TXQ_CFG_IP_OPTION_ENB | TXQ_CFG_8023_ENB; CSR_WRITE_4(sc, ALC_TXQ_CFG, reg | TXQ_CFG_ENHANCED_MODE); if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) { reg = (TXQ_CFG_TD_BURST_DEFAULT << HQTD_CFG_Q1_BURST_SHIFT | TXQ_CFG_TD_BURST_DEFAULT << HQTD_CFG_Q2_BURST_SHIFT | TXQ_CFG_TD_BURST_DEFAULT << HQTD_CFG_Q3_BURST_SHIFT | HQTD_CFG_BURST_ENB); CSR_WRITE_4(sc, ALC_HQTD_CFG, reg); reg = WRR_PRI_RESTRICT_NONE; reg |= (WRR_PRI_DEFAULT << WRR_PRI0_SHIFT | WRR_PRI_DEFAULT << WRR_PRI1_SHIFT | WRR_PRI_DEFAULT << WRR_PRI2_SHIFT | WRR_PRI_DEFAULT << WRR_PRI3_SHIFT); CSR_WRITE_4(sc, ALC_WRR, reg); } else { /* Configure Rx free descriptor pre-fetching. */ CSR_WRITE_4(sc, ALC_RX_RD_FREE_THRESH, ((RX_RD_FREE_THRESH_HI_DEFAULT << RX_RD_FREE_THRESH_HI_SHIFT) & RX_RD_FREE_THRESH_HI_MASK) | ((RX_RD_FREE_THRESH_LO_DEFAULT << RX_RD_FREE_THRESH_LO_SHIFT) & RX_RD_FREE_THRESH_LO_MASK)); } /* * Configure flow control parameters. * XON : 80% of Rx FIFO * XOFF : 30% of Rx FIFO */ if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) { reg = CSR_READ_4(sc, ALC_SRAM_RX_FIFO_LEN); reg &= SRAM_RX_FIFO_LEN_MASK; reg *= 8; if (reg > 8 * 1024) reg -= RX_FIFO_PAUSE_816X_RSVD; else reg -= RX_BUF_SIZE_MAX; reg /= 8; CSR_WRITE_4(sc, ALC_RX_FIFO_PAUSE_THRESH, ((reg << RX_FIFO_PAUSE_THRESH_LO_SHIFT) & RX_FIFO_PAUSE_THRESH_LO_MASK) | (((RX_FIFO_PAUSE_816X_RSVD / 8) << RX_FIFO_PAUSE_THRESH_HI_SHIFT) & RX_FIFO_PAUSE_THRESH_HI_MASK)); } else if (sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8131 || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8132) { reg = CSR_READ_4(sc, ALC_SRAM_RX_FIFO_LEN); rxf_hi = (reg * 8) / 10; rxf_lo = (reg * 3) / 10; CSR_WRITE_4(sc, ALC_RX_FIFO_PAUSE_THRESH, ((rxf_lo << RX_FIFO_PAUSE_THRESH_LO_SHIFT) & RX_FIFO_PAUSE_THRESH_LO_MASK) | ((rxf_hi << RX_FIFO_PAUSE_THRESH_HI_SHIFT) & RX_FIFO_PAUSE_THRESH_HI_MASK)); } if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) == 0) { /* Disable RSS until I understand L1C/L2C's RSS logic. */ CSR_WRITE_4(sc, ALC_RSS_IDT_TABLE0, 0); CSR_WRITE_4(sc, ALC_RSS_CPU, 0); } /* Configure RxQ. */ reg = (RXQ_CFG_RD_BURST_DEFAULT << RXQ_CFG_RD_BURST_SHIFT) & RXQ_CFG_RD_BURST_MASK; reg |= RXQ_CFG_RSS_MODE_DIS; if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) reg |= (RXQ_CFG_816X_IDT_TBL_SIZE_DEFAULT << RXQ_CFG_816X_IDT_TBL_SIZE_SHIFT) & RXQ_CFG_816X_IDT_TBL_SIZE_MASK; if ((sc->alc_flags & ALC_FLAG_FASTETHER) == 0 && sc->alc_ident->deviceid != DEVICEID_ATHEROS_AR8151_V2) reg |= RXQ_CFG_ASPM_THROUGHPUT_LIMIT_1M; CSR_WRITE_4(sc, ALC_RXQ_CFG, reg); /* Configure DMA parameters. */ reg = DMA_CFG_OUT_ORDER | DMA_CFG_RD_REQ_PRI; reg |= sc->alc_rcb; if ((sc->alc_flags & ALC_FLAG_CMB_BUG) == 0) reg |= DMA_CFG_CMB_ENB; if ((sc->alc_flags & ALC_FLAG_SMB_BUG) == 0) reg |= DMA_CFG_SMB_ENB; else reg |= DMA_CFG_SMB_DIS; reg |= (sc->alc_dma_rd_burst & DMA_CFG_RD_BURST_MASK) << DMA_CFG_RD_BURST_SHIFT; reg |= (sc->alc_dma_wr_burst & DMA_CFG_WR_BURST_MASK) << DMA_CFG_WR_BURST_SHIFT; reg |= (DMA_CFG_RD_DELAY_CNT_DEFAULT << DMA_CFG_RD_DELAY_CNT_SHIFT) & DMA_CFG_RD_DELAY_CNT_MASK; reg |= (DMA_CFG_WR_DELAY_CNT_DEFAULT << DMA_CFG_WR_DELAY_CNT_SHIFT) & DMA_CFG_WR_DELAY_CNT_MASK; if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0) { switch (AR816X_REV(sc->alc_rev)) { case AR816X_REV_A0: case AR816X_REV_A1: reg |= DMA_CFG_RD_CHNL_SEL_1; break; case AR816X_REV_B0: /* FALLTHROUGH */ default: reg |= DMA_CFG_RD_CHNL_SEL_3; break; } } CSR_WRITE_4(sc, ALC_DMA_CFG, reg); /* * Configure Tx/Rx MACs. * - Auto-padding for short frames. * - Enable CRC generation. * Actual reconfiguration of MAC for resolved speed/duplex * is followed after detection of link establishment. * AR813x/AR815x always does checksum computation regardless * of MAC_CFG_RXCSUM_ENB bit. Also the controller is known to * have bug in protocol field in Rx return structure so * these controllers can't handle fragmented frames. Disable * Rx checksum offloading until there is a newer controller * that has sane implementation. */ reg = MAC_CFG_TX_CRC_ENB | MAC_CFG_TX_AUTO_PAD | MAC_CFG_FULL_DUPLEX | ((MAC_CFG_PREAMBLE_DEFAULT << MAC_CFG_PREAMBLE_SHIFT) & MAC_CFG_PREAMBLE_MASK); if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) != 0 || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8151 || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8151_V2 || sc->alc_ident->deviceid == DEVICEID_ATHEROS_AR8152_B2) reg |= MAC_CFG_HASH_ALG_CRC32 | MAC_CFG_SPEED_MODE_SW; if ((sc->alc_flags & ALC_FLAG_FASTETHER) != 0) reg |= MAC_CFG_SPEED_10_100; else reg |= MAC_CFG_SPEED_1000; CSR_WRITE_4(sc, ALC_MAC_CFG, reg); /* Set up the receive filter. */ alc_rxfilter(sc); alc_rxvlan(sc); /* Acknowledge all pending interrupts and clear it. */ CSR_WRITE_4(sc, ALC_INTR_MASK, ALC_INTRS); CSR_WRITE_4(sc, ALC_INTR_STATUS, 0xFFFFFFFF); CSR_WRITE_4(sc, ALC_INTR_STATUS, 0); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc->alc_flags &= ~ALC_FLAG_LINK; /* Switch to the current media. */ alc_mediachange_locked(sc); callout_reset(&sc->alc_tick_ch, hz, alc_tick, sc); } static void alc_stop(struct alc_softc *sc) { struct ifnet *ifp; struct alc_txdesc *txd; struct alc_rxdesc *rxd; uint32_t reg; int i; ALC_LOCK_ASSERT(sc); /* * Mark the interface down and cancel the watchdog timer. */ ifp = sc->alc_ifp; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc->alc_flags &= ~ALC_FLAG_LINK; callout_stop(&sc->alc_tick_ch); sc->alc_watchdog_timer = 0; alc_stats_update(sc); /* Disable interrupts. */ CSR_WRITE_4(sc, ALC_INTR_MASK, 0); CSR_WRITE_4(sc, ALC_INTR_STATUS, 0xFFFFFFFF); /* Disable DMA. */ reg = CSR_READ_4(sc, ALC_DMA_CFG); reg &= ~(DMA_CFG_CMB_ENB | DMA_CFG_SMB_ENB); reg |= DMA_CFG_SMB_DIS; CSR_WRITE_4(sc, ALC_DMA_CFG, reg); DELAY(1000); /* Stop Rx/Tx MACs. */ alc_stop_mac(sc); /* Disable interrupts which might be touched in taskq handler. */ CSR_WRITE_4(sc, ALC_INTR_STATUS, 0xFFFFFFFF); /* Disable L0s/L1s */ alc_aspm(sc, 0, IFM_UNKNOWN); /* Reclaim Rx buffers that have been processed. */ if (sc->alc_cdata.alc_rxhead != NULL) m_freem(sc->alc_cdata.alc_rxhead); ALC_RXCHAIN_RESET(sc); /* * Free Tx/Rx mbufs still in the queues. */ for (i = 0; i < ALC_RX_RING_CNT; i++) { rxd = &sc->alc_cdata.alc_rxdesc[i]; if (rxd->rx_m != NULL) { bus_dmamap_sync(sc->alc_cdata.alc_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->alc_cdata.alc_rx_tag, rxd->rx_dmamap); m_freem(rxd->rx_m); rxd->rx_m = NULL; } } for (i = 0; i < ALC_TX_RING_CNT; i++) { txd = &sc->alc_cdata.alc_txdesc[i]; if (txd->tx_m != NULL) { bus_dmamap_sync(sc->alc_cdata.alc_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->alc_cdata.alc_tx_tag, txd->tx_dmamap); m_freem(txd->tx_m); txd->tx_m = NULL; } } } static void alc_stop_mac(struct alc_softc *sc) { uint32_t reg; int i; alc_stop_queue(sc); /* Disable Rx/Tx MAC. */ reg = CSR_READ_4(sc, ALC_MAC_CFG); if ((reg & (MAC_CFG_TX_ENB | MAC_CFG_RX_ENB)) != 0) { reg &= ~(MAC_CFG_TX_ENB | MAC_CFG_RX_ENB); CSR_WRITE_4(sc, ALC_MAC_CFG, reg); } for (i = ALC_TIMEOUT; i > 0; i--) { reg = CSR_READ_4(sc, ALC_IDLE_STATUS); if ((reg & (IDLE_STATUS_RXMAC | IDLE_STATUS_TXMAC)) == 0) break; DELAY(10); } if (i == 0) device_printf(sc->alc_dev, "could not disable Rx/Tx MAC(0x%08x)!\n", reg); } static void alc_start_queue(struct alc_softc *sc) { uint32_t qcfg[] = { 0, RXQ_CFG_QUEUE0_ENB, RXQ_CFG_QUEUE0_ENB | RXQ_CFG_QUEUE1_ENB, RXQ_CFG_QUEUE0_ENB | RXQ_CFG_QUEUE1_ENB | RXQ_CFG_QUEUE2_ENB, RXQ_CFG_ENB }; uint32_t cfg; ALC_LOCK_ASSERT(sc); /* Enable RxQ. */ cfg = CSR_READ_4(sc, ALC_RXQ_CFG); if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) == 0) { cfg &= ~RXQ_CFG_ENB; cfg |= qcfg[1]; } else cfg |= RXQ_CFG_QUEUE0_ENB; CSR_WRITE_4(sc, ALC_RXQ_CFG, cfg); /* Enable TxQ. */ cfg = CSR_READ_4(sc, ALC_TXQ_CFG); cfg |= TXQ_CFG_ENB; CSR_WRITE_4(sc, ALC_TXQ_CFG, cfg); } static void alc_stop_queue(struct alc_softc *sc) { uint32_t reg; int i; /* Disable RxQ. */ reg = CSR_READ_4(sc, ALC_RXQ_CFG); if ((sc->alc_flags & ALC_FLAG_AR816X_FAMILY) == 0) { if ((reg & RXQ_CFG_ENB) != 0) { reg &= ~RXQ_CFG_ENB; CSR_WRITE_4(sc, ALC_RXQ_CFG, reg); } } else { if ((reg & RXQ_CFG_QUEUE0_ENB) != 0) { reg &= ~RXQ_CFG_QUEUE0_ENB; CSR_WRITE_4(sc, ALC_RXQ_CFG, reg); } } /* Disable TxQ. */ reg = CSR_READ_4(sc, ALC_TXQ_CFG); if ((reg & TXQ_CFG_ENB) != 0) { reg &= ~TXQ_CFG_ENB; CSR_WRITE_4(sc, ALC_TXQ_CFG, reg); } DELAY(40); for (i = ALC_TIMEOUT; i > 0; i--) { reg = CSR_READ_4(sc, ALC_IDLE_STATUS); if ((reg & (IDLE_STATUS_RXQ | IDLE_STATUS_TXQ)) == 0) break; DELAY(10); } if (i == 0) device_printf(sc->alc_dev, "could not disable RxQ/TxQ (0x%08x)!\n", reg); } static void alc_init_tx_ring(struct alc_softc *sc) { struct alc_ring_data *rd; struct alc_txdesc *txd; int i; ALC_LOCK_ASSERT(sc); sc->alc_cdata.alc_tx_prod = 0; sc->alc_cdata.alc_tx_cons = 0; sc->alc_cdata.alc_tx_cnt = 0; rd = &sc->alc_rdata; bzero(rd->alc_tx_ring, ALC_TX_RING_SZ); for (i = 0; i < ALC_TX_RING_CNT; i++) { txd = &sc->alc_cdata.alc_txdesc[i]; txd->tx_m = NULL; } bus_dmamap_sync(sc->alc_cdata.alc_tx_ring_tag, sc->alc_cdata.alc_tx_ring_map, BUS_DMASYNC_PREWRITE); } static int alc_init_rx_ring(struct alc_softc *sc) { struct alc_ring_data *rd; struct alc_rxdesc *rxd; int i; ALC_LOCK_ASSERT(sc); sc->alc_cdata.alc_rx_cons = ALC_RX_RING_CNT - 1; sc->alc_morework = 0; rd = &sc->alc_rdata; bzero(rd->alc_rx_ring, ALC_RX_RING_SZ); for (i = 0; i < ALC_RX_RING_CNT; i++) { rxd = &sc->alc_cdata.alc_rxdesc[i]; rxd->rx_m = NULL; rxd->rx_desc = &rd->alc_rx_ring[i]; if (alc_newbuf(sc, rxd) != 0) return (ENOBUFS); } /* * Since controller does not update Rx descriptors, driver * does have to read Rx descriptors back so BUS_DMASYNC_PREWRITE * is enough to ensure coherence. */ bus_dmamap_sync(sc->alc_cdata.alc_rx_ring_tag, sc->alc_cdata.alc_rx_ring_map, BUS_DMASYNC_PREWRITE); /* Let controller know availability of new Rx buffers. */ CSR_WRITE_4(sc, ALC_MBOX_RD0_PROD_IDX, sc->alc_cdata.alc_rx_cons); return (0); } static void alc_init_rr_ring(struct alc_softc *sc) { struct alc_ring_data *rd; ALC_LOCK_ASSERT(sc); sc->alc_cdata.alc_rr_cons = 0; ALC_RXCHAIN_RESET(sc); rd = &sc->alc_rdata; bzero(rd->alc_rr_ring, ALC_RR_RING_SZ); bus_dmamap_sync(sc->alc_cdata.alc_rr_ring_tag, sc->alc_cdata.alc_rr_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static void alc_init_cmb(struct alc_softc *sc) { struct alc_ring_data *rd; ALC_LOCK_ASSERT(sc); rd = &sc->alc_rdata; bzero(rd->alc_cmb, ALC_CMB_SZ); bus_dmamap_sync(sc->alc_cdata.alc_cmb_tag, sc->alc_cdata.alc_cmb_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static void alc_init_smb(struct alc_softc *sc) { struct alc_ring_data *rd; ALC_LOCK_ASSERT(sc); rd = &sc->alc_rdata; bzero(rd->alc_smb, ALC_SMB_SZ); bus_dmamap_sync(sc->alc_cdata.alc_smb_tag, sc->alc_cdata.alc_smb_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static void alc_rxvlan(struct alc_softc *sc) { struct ifnet *ifp; uint32_t reg; ALC_LOCK_ASSERT(sc); ifp = sc->alc_ifp; reg = CSR_READ_4(sc, ALC_MAC_CFG); if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0) reg |= MAC_CFG_VLAN_TAG_STRIP; else reg &= ~MAC_CFG_VLAN_TAG_STRIP; CSR_WRITE_4(sc, ALC_MAC_CFG, reg); } static void alc_rxfilter(struct alc_softc *sc) { struct ifnet *ifp; struct ifmultiaddr *ifma; uint32_t crc; uint32_t mchash[2]; uint32_t rxcfg; ALC_LOCK_ASSERT(sc); ifp = sc->alc_ifp; bzero(mchash, sizeof(mchash)); rxcfg = CSR_READ_4(sc, ALC_MAC_CFG); rxcfg &= ~(MAC_CFG_ALLMULTI | MAC_CFG_BCAST | MAC_CFG_PROMISC); if ((ifp->if_flags & IFF_BROADCAST) != 0) rxcfg |= MAC_CFG_BCAST; if ((ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI)) != 0) { if ((ifp->if_flags & IFF_PROMISC) != 0) rxcfg |= MAC_CFG_PROMISC; if ((ifp->if_flags & IFF_ALLMULTI) != 0) rxcfg |= MAC_CFG_ALLMULTI; mchash[0] = 0xFFFFFFFF; mchash[1] = 0xFFFFFFFF; goto chipit; } if_maddr_rlock(ifp); TAILQ_FOREACH(ifma, &sc->alc_ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; crc = ether_crc32_be(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN); mchash[crc >> 31] |= 1 << ((crc >> 26) & 0x1f); } if_maddr_runlock(ifp); chipit: CSR_WRITE_4(sc, ALC_MAR0, mchash[0]); CSR_WRITE_4(sc, ALC_MAR1, mchash[1]); CSR_WRITE_4(sc, ALC_MAC_CFG, rxcfg); } static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int low, int high) { int error, value; if (arg1 == NULL) return (EINVAL); value = *(int *)arg1; error = sysctl_handle_int(oidp, &value, 0, req); if (error || req->newptr == NULL) return (error); if (value < low || value > high) return (EINVAL); *(int *)arg1 = value; return (0); } static int sysctl_hw_alc_proc_limit(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, ALC_PROC_MIN, ALC_PROC_MAX)); } static int sysctl_hw_alc_int_mod(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, ALC_IM_TIMER_MIN, ALC_IM_TIMER_MAX)); } Index: head/sys/dev/ale/if_ale.c =================================================================== --- head/sys/dev/ale/if_ale.c (revision 295734) +++ head/sys/dev/ale/if_ale.c (revision 295735) @@ -1,3084 +1,3084 @@ /*- * Copyright (c) 2008, Pyun YongHyeon * 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 unmodified, 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* Driver for Atheros AR8121/AR8113/AR8114 PCIe Ethernet. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* "device miibus" required. See GENERIC if you get errors here. */ #include "miibus_if.h" /* For more information about Tx checksum offload issues see ale_encap(). */ #define ALE_CSUM_FEATURES (CSUM_TCP | CSUM_UDP) MODULE_DEPEND(ale, pci, 1, 1, 1); MODULE_DEPEND(ale, ether, 1, 1, 1); MODULE_DEPEND(ale, miibus, 1, 1, 1); /* Tunables. */ static int msi_disable = 0; static int msix_disable = 0; TUNABLE_INT("hw.ale.msi_disable", &msi_disable); TUNABLE_INT("hw.ale.msix_disable", &msix_disable); /* * Devices supported by this driver. */ static const struct ale_dev { uint16_t ale_vendorid; uint16_t ale_deviceid; const char *ale_name; } ale_devs[] = { { VENDORID_ATHEROS, DEVICEID_ATHEROS_AR81XX, "Atheros AR8121/AR8113/AR8114 PCIe Ethernet" }, }; static int ale_attach(device_t); static int ale_check_boundary(struct ale_softc *); static int ale_detach(device_t); static int ale_dma_alloc(struct ale_softc *); static void ale_dma_free(struct ale_softc *); static void ale_dmamap_cb(void *, bus_dma_segment_t *, int, int); static int ale_encap(struct ale_softc *, struct mbuf **); static void ale_get_macaddr(struct ale_softc *); static void ale_init(void *); static void ale_init_locked(struct ale_softc *); static void ale_init_rx_pages(struct ale_softc *); static void ale_init_tx_ring(struct ale_softc *); static void ale_int_task(void *, int); static int ale_intr(void *); static int ale_ioctl(struct ifnet *, u_long, caddr_t); static void ale_mac_config(struct ale_softc *); static int ale_miibus_readreg(device_t, int, int); static void ale_miibus_statchg(device_t); static int ale_miibus_writereg(device_t, int, int, int); static int ale_mediachange(struct ifnet *); static void ale_mediastatus(struct ifnet *, struct ifmediareq *); static void ale_phy_reset(struct ale_softc *); static int ale_probe(device_t); static void ale_reset(struct ale_softc *); static int ale_resume(device_t); static void ale_rx_update_page(struct ale_softc *, struct ale_rx_page **, uint32_t, uint32_t *); static void ale_rxcsum(struct ale_softc *, struct mbuf *, uint32_t); static int ale_rxeof(struct ale_softc *sc, int); static void ale_rxfilter(struct ale_softc *); static void ale_rxvlan(struct ale_softc *); static void ale_setlinkspeed(struct ale_softc *); static void ale_setwol(struct ale_softc *); static int ale_shutdown(device_t); static void ale_start(struct ifnet *); static void ale_start_locked(struct ifnet *); static void ale_stats_clear(struct ale_softc *); static void ale_stats_update(struct ale_softc *); static void ale_stop(struct ale_softc *); static void ale_stop_mac(struct ale_softc *); static int ale_suspend(device_t); static void ale_sysctl_node(struct ale_softc *); static void ale_tick(void *); static void ale_txeof(struct ale_softc *); static void ale_watchdog(struct ale_softc *); static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int, int); static int sysctl_hw_ale_proc_limit(SYSCTL_HANDLER_ARGS); static int sysctl_hw_ale_int_mod(SYSCTL_HANDLER_ARGS); static device_method_t ale_methods[] = { /* Device interface. */ DEVMETHOD(device_probe, ale_probe), DEVMETHOD(device_attach, ale_attach), DEVMETHOD(device_detach, ale_detach), DEVMETHOD(device_shutdown, ale_shutdown), DEVMETHOD(device_suspend, ale_suspend), DEVMETHOD(device_resume, ale_resume), /* MII interface. */ DEVMETHOD(miibus_readreg, ale_miibus_readreg), DEVMETHOD(miibus_writereg, ale_miibus_writereg), DEVMETHOD(miibus_statchg, ale_miibus_statchg), DEVMETHOD_END }; static driver_t ale_driver = { "ale", ale_methods, sizeof(struct ale_softc) }; static devclass_t ale_devclass; DRIVER_MODULE(ale, pci, ale_driver, ale_devclass, NULL, NULL); DRIVER_MODULE(miibus, ale, miibus_driver, miibus_devclass, NULL, NULL); static struct resource_spec ale_res_spec_mem[] = { { SYS_RES_MEMORY, PCIR_BAR(0), RF_ACTIVE }, { -1, 0, 0 } }; static struct resource_spec ale_irq_spec_legacy[] = { { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0, 0 } }; static struct resource_spec ale_irq_spec_msi[] = { { SYS_RES_IRQ, 1, RF_ACTIVE }, { -1, 0, 0 } }; static struct resource_spec ale_irq_spec_msix[] = { { SYS_RES_IRQ, 1, RF_ACTIVE }, { -1, 0, 0 } }; static int ale_miibus_readreg(device_t dev, int phy, int reg) { struct ale_softc *sc; uint32_t v; int i; sc = device_get_softc(dev); CSR_WRITE_4(sc, ALE_MDIO, MDIO_OP_EXECUTE | MDIO_OP_READ | MDIO_SUP_PREAMBLE | MDIO_CLK_25_4 | MDIO_REG_ADDR(reg)); for (i = ALE_PHY_TIMEOUT; i > 0; i--) { DELAY(5); v = CSR_READ_4(sc, ALE_MDIO); if ((v & (MDIO_OP_EXECUTE | MDIO_OP_BUSY)) == 0) break; } if (i == 0) { device_printf(sc->ale_dev, "phy read timeout : %d\n", reg); return (0); } return ((v & MDIO_DATA_MASK) >> MDIO_DATA_SHIFT); } static int ale_miibus_writereg(device_t dev, int phy, int reg, int val) { struct ale_softc *sc; uint32_t v; int i; sc = device_get_softc(dev); CSR_WRITE_4(sc, ALE_MDIO, MDIO_OP_EXECUTE | MDIO_OP_WRITE | (val & MDIO_DATA_MASK) << MDIO_DATA_SHIFT | MDIO_SUP_PREAMBLE | MDIO_CLK_25_4 | MDIO_REG_ADDR(reg)); for (i = ALE_PHY_TIMEOUT; i > 0; i--) { DELAY(5); v = CSR_READ_4(sc, ALE_MDIO); if ((v & (MDIO_OP_EXECUTE | MDIO_OP_BUSY)) == 0) break; } if (i == 0) device_printf(sc->ale_dev, "phy write timeout : %d\n", reg); return (0); } static void ale_miibus_statchg(device_t dev) { struct ale_softc *sc; struct mii_data *mii; struct ifnet *ifp; uint32_t reg; sc = device_get_softc(dev); mii = device_get_softc(sc->ale_miibus); ifp = sc->ale_ifp; if (mii == NULL || ifp == NULL || (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; sc->ale_flags &= ~ALE_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: case IFM_100_TX: sc->ale_flags |= ALE_FLAG_LINK; break; case IFM_1000_T: if ((sc->ale_flags & ALE_FLAG_FASTETHER) == 0) sc->ale_flags |= ALE_FLAG_LINK; break; default: break; } } /* Stop Rx/Tx MACs. */ ale_stop_mac(sc); /* Program MACs with resolved speed/duplex/flow-control. */ if ((sc->ale_flags & ALE_FLAG_LINK) != 0) { ale_mac_config(sc); /* Reenable Tx/Rx MACs. */ reg = CSR_READ_4(sc, ALE_MAC_CFG); reg |= MAC_CFG_TX_ENB | MAC_CFG_RX_ENB; CSR_WRITE_4(sc, ALE_MAC_CFG, reg); } } static void ale_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr) { struct ale_softc *sc; struct mii_data *mii; sc = ifp->if_softc; ALE_LOCK(sc); if ((ifp->if_flags & IFF_UP) == 0) { ALE_UNLOCK(sc); return; } mii = device_get_softc(sc->ale_miibus); mii_pollstat(mii); ifmr->ifm_status = mii->mii_media_status; ifmr->ifm_active = mii->mii_media_active; ALE_UNLOCK(sc); } static int ale_mediachange(struct ifnet *ifp) { struct ale_softc *sc; struct mii_data *mii; struct mii_softc *miisc; int error; sc = ifp->if_softc; ALE_LOCK(sc); mii = device_get_softc(sc->ale_miibus); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); ALE_UNLOCK(sc); return (error); } static int ale_probe(device_t dev) { const struct ale_dev *sp; int i; uint16_t vendor, devid; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); sp = ale_devs; for (i = 0; i < sizeof(ale_devs) / sizeof(ale_devs[0]); i++) { if (vendor == sp->ale_vendorid && devid == sp->ale_deviceid) { device_set_desc(dev, sp->ale_name); return (BUS_PROBE_DEFAULT); } sp++; } return (ENXIO); } static void ale_get_macaddr(struct ale_softc *sc) { uint32_t ea[2], reg; int i, vpdc; reg = CSR_READ_4(sc, ALE_SPI_CTRL); if ((reg & SPI_VPD_ENB) != 0) { reg &= ~SPI_VPD_ENB; CSR_WRITE_4(sc, ALE_SPI_CTRL, reg); } if (pci_find_cap(sc->ale_dev, PCIY_VPD, &vpdc) == 0) { /* * PCI VPD capability found, let TWSI reload EEPROM. * This will set ethernet address of controller. */ CSR_WRITE_4(sc, ALE_TWSI_CTRL, CSR_READ_4(sc, ALE_TWSI_CTRL) | TWSI_CTRL_SW_LD_START); for (i = 100; i > 0; i--) { DELAY(1000); reg = CSR_READ_4(sc, ALE_TWSI_CTRL); if ((reg & TWSI_CTRL_SW_LD_START) == 0) break; } if (i == 0) device_printf(sc->ale_dev, "reloading EEPROM timeout!\n"); } else { if (bootverbose) device_printf(sc->ale_dev, "PCI VPD capability not found!\n"); } ea[0] = CSR_READ_4(sc, ALE_PAR0); ea[1] = CSR_READ_4(sc, ALE_PAR1); sc->ale_eaddr[0] = (ea[1] >> 8) & 0xFF; sc->ale_eaddr[1] = (ea[1] >> 0) & 0xFF; sc->ale_eaddr[2] = (ea[0] >> 24) & 0xFF; sc->ale_eaddr[3] = (ea[0] >> 16) & 0xFF; sc->ale_eaddr[4] = (ea[0] >> 8) & 0xFF; sc->ale_eaddr[5] = (ea[0] >> 0) & 0xFF; } static void ale_phy_reset(struct ale_softc *sc) { /* Reset magic from Linux. */ CSR_WRITE_2(sc, ALE_GPHY_CTRL, GPHY_CTRL_HIB_EN | GPHY_CTRL_HIB_PULSE | GPHY_CTRL_SEL_ANA_RESET | GPHY_CTRL_PHY_PLL_ON); DELAY(1000); CSR_WRITE_2(sc, ALE_GPHY_CTRL, GPHY_CTRL_EXT_RESET | GPHY_CTRL_HIB_EN | GPHY_CTRL_HIB_PULSE | GPHY_CTRL_SEL_ANA_RESET | GPHY_CTRL_PHY_PLL_ON); DELAY(1000); #define ATPHY_DBG_ADDR 0x1D #define ATPHY_DBG_DATA 0x1E /* Enable hibernation mode. */ ale_miibus_writereg(sc->ale_dev, sc->ale_phyaddr, ATPHY_DBG_ADDR, 0x0B); ale_miibus_writereg(sc->ale_dev, sc->ale_phyaddr, ATPHY_DBG_DATA, 0xBC00); /* Set Class A/B for all modes. */ ale_miibus_writereg(sc->ale_dev, sc->ale_phyaddr, ATPHY_DBG_ADDR, 0x00); ale_miibus_writereg(sc->ale_dev, sc->ale_phyaddr, ATPHY_DBG_DATA, 0x02EF); /* Enable 10BT power saving. */ ale_miibus_writereg(sc->ale_dev, sc->ale_phyaddr, ATPHY_DBG_ADDR, 0x12); ale_miibus_writereg(sc->ale_dev, sc->ale_phyaddr, ATPHY_DBG_DATA, 0x4C04); /* Adjust 1000T power. */ ale_miibus_writereg(sc->ale_dev, sc->ale_phyaddr, ATPHY_DBG_ADDR, 0x04); ale_miibus_writereg(sc->ale_dev, sc->ale_phyaddr, ATPHY_DBG_ADDR, 0x8BBB); /* 10BT center tap voltage. */ ale_miibus_writereg(sc->ale_dev, sc->ale_phyaddr, ATPHY_DBG_ADDR, 0x05); ale_miibus_writereg(sc->ale_dev, sc->ale_phyaddr, ATPHY_DBG_ADDR, 0x2C46); #undef ATPHY_DBG_ADDR #undef ATPHY_DBG_DATA DELAY(1000); } static int ale_attach(device_t dev) { struct ale_softc *sc; struct ifnet *ifp; uint16_t burst; int error, i, msic, msixc, pmc; uint32_t rxf_len, txf_len; error = 0; sc = device_get_softc(dev); sc->ale_dev = dev; mtx_init(&sc->ale_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init_mtx(&sc->ale_tick_ch, &sc->ale_mtx, 0); TASK_INIT(&sc->ale_int_task, 0, ale_int_task, sc); /* Map the device. */ pci_enable_busmaster(dev); sc->ale_res_spec = ale_res_spec_mem; sc->ale_irq_spec = ale_irq_spec_legacy; error = bus_alloc_resources(dev, sc->ale_res_spec, sc->ale_res); if (error != 0) { device_printf(dev, "cannot allocate memory resources.\n"); goto fail; } /* Set PHY address. */ sc->ale_phyaddr = ALE_PHY_ADDR; /* Reset PHY. */ ale_phy_reset(sc); /* Reset the ethernet controller. */ ale_reset(sc); /* Get PCI and chip id/revision. */ sc->ale_rev = pci_get_revid(dev); if (sc->ale_rev >= 0xF0) { /* L2E Rev. B. AR8114 */ sc->ale_flags |= ALE_FLAG_FASTETHER; } else { if ((CSR_READ_4(sc, ALE_PHY_STATUS) & PHY_STATUS_100M) != 0) { /* L1E AR8121 */ sc->ale_flags |= ALE_FLAG_JUMBO; } else { /* L2E Rev. A. AR8113 */ sc->ale_flags |= ALE_FLAG_FASTETHER; } } /* * All known controllers seems to require 4 bytes alignment * of Tx buffers to make Tx checksum offload with custom * checksum generation method work. */ sc->ale_flags |= ALE_FLAG_TXCSUM_BUG; /* * All known controllers seems to have issues on Rx checksum * offload for fragmented IP datagrams. */ sc->ale_flags |= ALE_FLAG_RXCSUM_BUG; /* * Don't use Tx CMB. It is known to cause RRS update failure * under certain circumstances. Typical phenomenon of the * issue would be unexpected sequence number encountered in * Rx handler. */ sc->ale_flags |= ALE_FLAG_TXCMB_BUG; sc->ale_chip_rev = CSR_READ_4(sc, ALE_MASTER_CFG) >> MASTER_CHIP_REV_SHIFT; if (bootverbose) { device_printf(dev, "PCI device revision : 0x%04x\n", sc->ale_rev); device_printf(dev, "Chip id/revision : 0x%04x\n", sc->ale_chip_rev); } txf_len = CSR_READ_4(sc, ALE_SRAM_TX_FIFO_LEN); rxf_len = CSR_READ_4(sc, ALE_SRAM_RX_FIFO_LEN); /* * Uninitialized hardware returns an invalid chip id/revision * as well as 0xFFFFFFFF for Tx/Rx fifo length. */ if (sc->ale_chip_rev == 0xFFFF || txf_len == 0xFFFFFFFF || rxf_len == 0xFFFFFFF) { device_printf(dev,"chip revision : 0x%04x, %u Tx FIFO " "%u Rx FIFO -- not initialized?\n", sc->ale_chip_rev, txf_len, rxf_len); error = ENXIO; goto fail; } device_printf(dev, "%u Tx FIFO, %u Rx FIFO\n", txf_len, rxf_len); /* Allocate IRQ resources. */ msixc = pci_msix_count(dev); msic = pci_msi_count(dev); if (bootverbose) { device_printf(dev, "MSIX count : %d\n", msixc); device_printf(dev, "MSI count : %d\n", msic); } /* Prefer MSIX over MSI. */ if (msix_disable == 0 || msi_disable == 0) { if (msix_disable == 0 && msixc == ALE_MSIX_MESSAGES && pci_alloc_msix(dev, &msixc) == 0) { if (msixc == ALE_MSIX_MESSAGES) { device_printf(dev, "Using %d MSIX messages.\n", msixc); sc->ale_flags |= ALE_FLAG_MSIX; sc->ale_irq_spec = ale_irq_spec_msix; } else pci_release_msi(dev); } if (msi_disable == 0 && (sc->ale_flags & ALE_FLAG_MSIX) == 0 && msic == ALE_MSI_MESSAGES && pci_alloc_msi(dev, &msic) == 0) { if (msic == ALE_MSI_MESSAGES) { device_printf(dev, "Using %d MSI messages.\n", msic); sc->ale_flags |= ALE_FLAG_MSI; sc->ale_irq_spec = ale_irq_spec_msi; } else pci_release_msi(dev); } } error = bus_alloc_resources(dev, sc->ale_irq_spec, sc->ale_irq); if (error != 0) { device_printf(dev, "cannot allocate IRQ resources.\n"); goto fail; } /* Get DMA parameters from PCIe device control register. */ if (pci_find_cap(dev, PCIY_EXPRESS, &i) == 0) { sc->ale_flags |= ALE_FLAG_PCIE; burst = pci_read_config(dev, i + 0x08, 2); /* Max read request size. */ sc->ale_dma_rd_burst = ((burst >> 12) & 0x07) << DMA_CFG_RD_BURST_SHIFT; /* Max payload size. */ sc->ale_dma_wr_burst = ((burst >> 5) & 0x07) << DMA_CFG_WR_BURST_SHIFT; if (bootverbose) { device_printf(dev, "Read request size : %d bytes.\n", 128 << ((burst >> 12) & 0x07)); device_printf(dev, "TLP payload size : %d bytes.\n", 128 << ((burst >> 5) & 0x07)); } } else { sc->ale_dma_rd_burst = DMA_CFG_RD_BURST_128; sc->ale_dma_wr_burst = DMA_CFG_WR_BURST_128; } /* Create device sysctl node. */ ale_sysctl_node(sc); - if ((error = ale_dma_alloc(sc) != 0)) + if ((error = ale_dma_alloc(sc)) != 0) goto fail; /* Load station address. */ ale_get_macaddr(sc); ifp = sc->ale_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(dev, "cannot allocate ifnet structure.\n"); error = ENXIO; goto fail; } ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = ale_ioctl; ifp->if_start = ale_start; ifp->if_init = ale_init; ifp->if_snd.ifq_drv_maxlen = ALE_TX_RING_CNT - 1; IFQ_SET_MAXLEN(&ifp->if_snd, ifp->if_snd.ifq_drv_maxlen); IFQ_SET_READY(&ifp->if_snd); ifp->if_capabilities = IFCAP_RXCSUM | IFCAP_TXCSUM | IFCAP_TSO4; ifp->if_hwassist = ALE_CSUM_FEATURES | CSUM_TSO; if (pci_find_cap(dev, PCIY_PMG, &pmc) == 0) { sc->ale_flags |= ALE_FLAG_PMCAP; ifp->if_capabilities |= IFCAP_WOL_MAGIC | IFCAP_WOL_MCAST; } ifp->if_capenable = ifp->if_capabilities; /* Set up MII bus. */ error = mii_attach(dev, &sc->ale_miibus, ifp, ale_mediachange, ale_mediastatus, BMSR_DEFCAPMASK, sc->ale_phyaddr, MII_OFFSET_ANY, MIIF_DOPAUSE); if (error != 0) { device_printf(dev, "attaching PHYs failed\n"); goto fail; } ether_ifattach(ifp, sc->ale_eaddr); /* VLAN capability setup. */ ifp->if_capabilities |= IFCAP_VLAN_MTU | IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_HWCSUM | IFCAP_VLAN_HWTSO; ifp->if_capenable = ifp->if_capabilities; /* * Even though controllers supported by ale(3) have Rx checksum * offload bug the workaround for fragmented frames seemed to * work so far. However it seems Rx checksum offload does not * work under certain conditions. So disable Rx checksum offload * until I find more clue about it but allow users to override it. */ ifp->if_capenable &= ~IFCAP_RXCSUM; /* Tell the upper layer(s) we support long frames. */ ifp->if_hdrlen = sizeof(struct ether_vlan_header); /* Create local taskq. */ sc->ale_tq = taskqueue_create_fast("ale_taskq", M_WAITOK, taskqueue_thread_enqueue, &sc->ale_tq); if (sc->ale_tq == NULL) { device_printf(dev, "could not create taskqueue.\n"); ether_ifdetach(ifp); error = ENXIO; goto fail; } taskqueue_start_threads(&sc->ale_tq, 1, PI_NET, "%s taskq", device_get_nameunit(sc->ale_dev)); if ((sc->ale_flags & ALE_FLAG_MSIX) != 0) msic = ALE_MSIX_MESSAGES; else if ((sc->ale_flags & ALE_FLAG_MSI) != 0) msic = ALE_MSI_MESSAGES; else msic = 1; for (i = 0; i < msic; i++) { error = bus_setup_intr(dev, sc->ale_irq[i], INTR_TYPE_NET | INTR_MPSAFE, ale_intr, NULL, sc, &sc->ale_intrhand[i]); if (error != 0) break; } if (error != 0) { device_printf(dev, "could not set up interrupt handler.\n"); taskqueue_free(sc->ale_tq); sc->ale_tq = NULL; ether_ifdetach(ifp); goto fail; } fail: if (error != 0) ale_detach(dev); return (error); } static int ale_detach(device_t dev) { struct ale_softc *sc; struct ifnet *ifp; int i, msic; sc = device_get_softc(dev); ifp = sc->ale_ifp; if (device_is_attached(dev)) { ether_ifdetach(ifp); ALE_LOCK(sc); ale_stop(sc); ALE_UNLOCK(sc); callout_drain(&sc->ale_tick_ch); taskqueue_drain(sc->ale_tq, &sc->ale_int_task); } if (sc->ale_tq != NULL) { taskqueue_drain(sc->ale_tq, &sc->ale_int_task); taskqueue_free(sc->ale_tq); sc->ale_tq = NULL; } if (sc->ale_miibus != NULL) { device_delete_child(dev, sc->ale_miibus); sc->ale_miibus = NULL; } bus_generic_detach(dev); ale_dma_free(sc); if (ifp != NULL) { if_free(ifp); sc->ale_ifp = NULL; } if ((sc->ale_flags & ALE_FLAG_MSIX) != 0) msic = ALE_MSIX_MESSAGES; else if ((sc->ale_flags & ALE_FLAG_MSI) != 0) msic = ALE_MSI_MESSAGES; else msic = 1; for (i = 0; i < msic; i++) { if (sc->ale_intrhand[i] != NULL) { bus_teardown_intr(dev, sc->ale_irq[i], sc->ale_intrhand[i]); sc->ale_intrhand[i] = NULL; } } bus_release_resources(dev, sc->ale_irq_spec, sc->ale_irq); if ((sc->ale_flags & (ALE_FLAG_MSI | ALE_FLAG_MSIX)) != 0) pci_release_msi(dev); bus_release_resources(dev, sc->ale_res_spec, sc->ale_res); mtx_destroy(&sc->ale_mtx); return (0); } #define ALE_SYSCTL_STAT_ADD32(c, h, n, p, d) \ SYSCTL_ADD_UINT(c, h, OID_AUTO, n, CTLFLAG_RD, p, 0, d) #if __FreeBSD_version >= 900030 #define ALE_SYSCTL_STAT_ADD64(c, h, n, p, d) \ SYSCTL_ADD_UQUAD(c, h, OID_AUTO, n, CTLFLAG_RD, p, d) #elif __FreeBSD_version > 800000 #define ALE_SYSCTL_STAT_ADD64(c, h, n, p, d) \ SYSCTL_ADD_QUAD(c, h, OID_AUTO, n, CTLFLAG_RD, p, d) #else #define ALE_SYSCTL_STAT_ADD64(c, h, n, p, d) \ SYSCTL_ADD_ULONG(c, h, OID_AUTO, n, CTLFLAG_RD, p, d) #endif static void ale_sysctl_node(struct ale_softc *sc) { struct sysctl_ctx_list *ctx; struct sysctl_oid_list *child, *parent; struct sysctl_oid *tree; struct ale_hw_stats *stats; int error; stats = &sc->ale_stats; ctx = device_get_sysctl_ctx(sc->ale_dev); child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->ale_dev)); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "int_rx_mod", CTLTYPE_INT | CTLFLAG_RW, &sc->ale_int_rx_mod, 0, sysctl_hw_ale_int_mod, "I", "ale Rx interrupt moderation"); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "int_tx_mod", CTLTYPE_INT | CTLFLAG_RW, &sc->ale_int_tx_mod, 0, sysctl_hw_ale_int_mod, "I", "ale Tx interrupt moderation"); /* Pull in device tunables. */ sc->ale_int_rx_mod = ALE_IM_RX_TIMER_DEFAULT; error = resource_int_value(device_get_name(sc->ale_dev), device_get_unit(sc->ale_dev), "int_rx_mod", &sc->ale_int_rx_mod); if (error == 0) { if (sc->ale_int_rx_mod < ALE_IM_TIMER_MIN || sc->ale_int_rx_mod > ALE_IM_TIMER_MAX) { device_printf(sc->ale_dev, "int_rx_mod value out of " "range; using default: %d\n", ALE_IM_RX_TIMER_DEFAULT); sc->ale_int_rx_mod = ALE_IM_RX_TIMER_DEFAULT; } } sc->ale_int_tx_mod = ALE_IM_TX_TIMER_DEFAULT; error = resource_int_value(device_get_name(sc->ale_dev), device_get_unit(sc->ale_dev), "int_tx_mod", &sc->ale_int_tx_mod); if (error == 0) { if (sc->ale_int_tx_mod < ALE_IM_TIMER_MIN || sc->ale_int_tx_mod > ALE_IM_TIMER_MAX) { device_printf(sc->ale_dev, "int_tx_mod value out of " "range; using default: %d\n", ALE_IM_TX_TIMER_DEFAULT); sc->ale_int_tx_mod = ALE_IM_TX_TIMER_DEFAULT; } } SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "process_limit", CTLTYPE_INT | CTLFLAG_RW, &sc->ale_process_limit, 0, sysctl_hw_ale_proc_limit, "I", "max number of Rx events to process"); /* Pull in device tunables. */ sc->ale_process_limit = ALE_PROC_DEFAULT; error = resource_int_value(device_get_name(sc->ale_dev), device_get_unit(sc->ale_dev), "process_limit", &sc->ale_process_limit); if (error == 0) { if (sc->ale_process_limit < ALE_PROC_MIN || sc->ale_process_limit > ALE_PROC_MAX) { device_printf(sc->ale_dev, "process_limit value out of range; " "using default: %d\n", ALE_PROC_DEFAULT); sc->ale_process_limit = ALE_PROC_DEFAULT; } } /* Misc statistics. */ ALE_SYSCTL_STAT_ADD32(ctx, child, "reset_brk_seq", &stats->reset_brk_seq, "Controller resets due to broken Rx sequnce number"); tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", CTLFLAG_RD, NULL, "ATE statistics"); parent = SYSCTL_CHILDREN(tree); /* Rx statistics. */ tree = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "rx", CTLFLAG_RD, NULL, "Rx MAC statistics"); child = SYSCTL_CHILDREN(tree); ALE_SYSCTL_STAT_ADD32(ctx, child, "good_frames", &stats->rx_frames, "Good frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "good_bcast_frames", &stats->rx_bcast_frames, "Good broadcast frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "good_mcast_frames", &stats->rx_mcast_frames, "Good multicast frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "pause_frames", &stats->rx_pause_frames, "Pause control frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "control_frames", &stats->rx_control_frames, "Control frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "crc_errs", &stats->rx_crcerrs, "CRC errors"); ALE_SYSCTL_STAT_ADD32(ctx, child, "len_errs", &stats->rx_lenerrs, "Frames with length mismatched"); ALE_SYSCTL_STAT_ADD64(ctx, child, "good_octets", &stats->rx_bytes, "Good octets"); ALE_SYSCTL_STAT_ADD64(ctx, child, "good_bcast_octets", &stats->rx_bcast_bytes, "Good broadcast octets"); ALE_SYSCTL_STAT_ADD64(ctx, child, "good_mcast_octets", &stats->rx_mcast_bytes, "Good multicast octets"); ALE_SYSCTL_STAT_ADD32(ctx, child, "runts", &stats->rx_runts, "Too short frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "fragments", &stats->rx_fragments, "Fragmented frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "frames_64", &stats->rx_pkts_64, "64 bytes frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "frames_65_127", &stats->rx_pkts_65_127, "65 to 127 bytes frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "frames_128_255", &stats->rx_pkts_128_255, "128 to 255 bytes frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "frames_256_511", &stats->rx_pkts_256_511, "256 to 511 bytes frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "frames_512_1023", &stats->rx_pkts_512_1023, "512 to 1023 bytes frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "frames_1024_1518", &stats->rx_pkts_1024_1518, "1024 to 1518 bytes frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "frames_1519_max", &stats->rx_pkts_1519_max, "1519 to max frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "trunc_errs", &stats->rx_pkts_truncated, "Truncated frames due to MTU size"); ALE_SYSCTL_STAT_ADD32(ctx, child, "fifo_oflows", &stats->rx_fifo_oflows, "FIFO overflows"); ALE_SYSCTL_STAT_ADD32(ctx, child, "rrs_errs", &stats->rx_rrs_errs, "Return status write-back errors"); ALE_SYSCTL_STAT_ADD32(ctx, child, "align_errs", &stats->rx_alignerrs, "Alignment errors"); ALE_SYSCTL_STAT_ADD32(ctx, child, "filtered", &stats->rx_pkts_filtered, "Frames dropped due to address filtering"); /* Tx statistics. */ tree = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "tx", CTLFLAG_RD, NULL, "Tx MAC statistics"); child = SYSCTL_CHILDREN(tree); ALE_SYSCTL_STAT_ADD32(ctx, child, "good_frames", &stats->tx_frames, "Good frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "good_bcast_frames", &stats->tx_bcast_frames, "Good broadcast frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "good_mcast_frames", &stats->tx_mcast_frames, "Good multicast frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "pause_frames", &stats->tx_pause_frames, "Pause control frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "control_frames", &stats->tx_control_frames, "Control frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "excess_defers", &stats->tx_excess_defer, "Frames with excessive derferrals"); ALE_SYSCTL_STAT_ADD32(ctx, child, "defers", &stats->tx_excess_defer, "Frames with derferrals"); ALE_SYSCTL_STAT_ADD64(ctx, child, "good_octets", &stats->tx_bytes, "Good octets"); ALE_SYSCTL_STAT_ADD64(ctx, child, "good_bcast_octets", &stats->tx_bcast_bytes, "Good broadcast octets"); ALE_SYSCTL_STAT_ADD64(ctx, child, "good_mcast_octets", &stats->tx_mcast_bytes, "Good multicast octets"); ALE_SYSCTL_STAT_ADD32(ctx, child, "frames_64", &stats->tx_pkts_64, "64 bytes frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "frames_65_127", &stats->tx_pkts_65_127, "65 to 127 bytes frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "frames_128_255", &stats->tx_pkts_128_255, "128 to 255 bytes frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "frames_256_511", &stats->tx_pkts_256_511, "256 to 511 bytes frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "frames_512_1023", &stats->tx_pkts_512_1023, "512 to 1023 bytes frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "frames_1024_1518", &stats->tx_pkts_1024_1518, "1024 to 1518 bytes frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "frames_1519_max", &stats->tx_pkts_1519_max, "1519 to max frames"); ALE_SYSCTL_STAT_ADD32(ctx, child, "single_colls", &stats->tx_single_colls, "Single collisions"); ALE_SYSCTL_STAT_ADD32(ctx, child, "multi_colls", &stats->tx_multi_colls, "Multiple collisions"); ALE_SYSCTL_STAT_ADD32(ctx, child, "late_colls", &stats->tx_late_colls, "Late collisions"); ALE_SYSCTL_STAT_ADD32(ctx, child, "excess_colls", &stats->tx_excess_colls, "Excessive collisions"); ALE_SYSCTL_STAT_ADD32(ctx, child, "underruns", &stats->tx_underrun, "FIFO underruns"); ALE_SYSCTL_STAT_ADD32(ctx, child, "desc_underruns", &stats->tx_desc_underrun, "Descriptor write-back errors"); ALE_SYSCTL_STAT_ADD32(ctx, child, "len_errs", &stats->tx_lenerrs, "Frames with length mismatched"); ALE_SYSCTL_STAT_ADD32(ctx, child, "trunc_errs", &stats->tx_pkts_truncated, "Truncated frames due to MTU size"); } #undef ALE_SYSCTL_STAT_ADD32 #undef ALE_SYSCTL_STAT_ADD64 struct ale_dmamap_arg { bus_addr_t ale_busaddr; }; static void ale_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { struct ale_dmamap_arg *ctx; if (error != 0) return; KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); ctx = (struct ale_dmamap_arg *)arg; ctx->ale_busaddr = segs[0].ds_addr; } /* * Tx descriptors/RXF0/CMB DMA blocks share ALE_DESC_ADDR_HI register * which specifies high address region of DMA blocks. Therefore these * blocks should have the same high address of given 4GB address * space(i.e. crossing 4GB boundary is not allowed). */ static int ale_check_boundary(struct ale_softc *sc) { bus_addr_t rx_cmb_end[ALE_RX_PAGES], tx_cmb_end; bus_addr_t rx_page_end[ALE_RX_PAGES], tx_ring_end; rx_page_end[0] = sc->ale_cdata.ale_rx_page[0].page_paddr + sc->ale_pagesize; rx_page_end[1] = sc->ale_cdata.ale_rx_page[1].page_paddr + sc->ale_pagesize; tx_ring_end = sc->ale_cdata.ale_tx_ring_paddr + ALE_TX_RING_SZ; tx_cmb_end = sc->ale_cdata.ale_tx_cmb_paddr + ALE_TX_CMB_SZ; rx_cmb_end[0] = sc->ale_cdata.ale_rx_page[0].cmb_paddr + ALE_RX_CMB_SZ; rx_cmb_end[1] = sc->ale_cdata.ale_rx_page[1].cmb_paddr + ALE_RX_CMB_SZ; if ((ALE_ADDR_HI(tx_ring_end) != ALE_ADDR_HI(sc->ale_cdata.ale_tx_ring_paddr)) || (ALE_ADDR_HI(rx_page_end[0]) != ALE_ADDR_HI(sc->ale_cdata.ale_rx_page[0].page_paddr)) || (ALE_ADDR_HI(rx_page_end[1]) != ALE_ADDR_HI(sc->ale_cdata.ale_rx_page[1].page_paddr)) || (ALE_ADDR_HI(tx_cmb_end) != ALE_ADDR_HI(sc->ale_cdata.ale_tx_cmb_paddr)) || (ALE_ADDR_HI(rx_cmb_end[0]) != ALE_ADDR_HI(sc->ale_cdata.ale_rx_page[0].cmb_paddr)) || (ALE_ADDR_HI(rx_cmb_end[1]) != ALE_ADDR_HI(sc->ale_cdata.ale_rx_page[1].cmb_paddr))) return (EFBIG); if ((ALE_ADDR_HI(tx_ring_end) != ALE_ADDR_HI(rx_page_end[0])) || (ALE_ADDR_HI(tx_ring_end) != ALE_ADDR_HI(rx_page_end[1])) || (ALE_ADDR_HI(tx_ring_end) != ALE_ADDR_HI(rx_cmb_end[0])) || (ALE_ADDR_HI(tx_ring_end) != ALE_ADDR_HI(rx_cmb_end[1])) || (ALE_ADDR_HI(tx_ring_end) != ALE_ADDR_HI(tx_cmb_end))) return (EFBIG); return (0); } static int ale_dma_alloc(struct ale_softc *sc) { struct ale_txdesc *txd; bus_addr_t lowaddr; struct ale_dmamap_arg ctx; int error, guard_size, i; if ((sc->ale_flags & ALE_FLAG_JUMBO) != 0) guard_size = ALE_JUMBO_FRAMELEN; else guard_size = ALE_MAX_FRAMELEN; sc->ale_pagesize = roundup(guard_size + ALE_RX_PAGE_SZ, ALE_RX_PAGE_ALIGN); lowaddr = BUS_SPACE_MAXADDR; again: /* Create parent DMA tag. */ error = bus_dma_tag_create( bus_get_dma_tag(sc->ale_dev), /* parent */ 1, 0, /* alignment, boundary */ lowaddr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ 0, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ale_cdata.ale_parent_tag); if (error != 0) { device_printf(sc->ale_dev, "could not create parent DMA tag.\n"); goto fail; } /* Create DMA tag for Tx descriptor ring. */ error = bus_dma_tag_create( sc->ale_cdata.ale_parent_tag, /* parent */ ALE_TX_RING_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ ALE_TX_RING_SZ, /* maxsize */ 1, /* nsegments */ ALE_TX_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ale_cdata.ale_tx_ring_tag); if (error != 0) { device_printf(sc->ale_dev, "could not create Tx ring DMA tag.\n"); goto fail; } /* Create DMA tag for Rx pages. */ for (i = 0; i < ALE_RX_PAGES; i++) { error = bus_dma_tag_create( sc->ale_cdata.ale_parent_tag, /* parent */ ALE_RX_PAGE_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sc->ale_pagesize, /* maxsize */ 1, /* nsegments */ sc->ale_pagesize, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ale_cdata.ale_rx_page[i].page_tag); if (error != 0) { device_printf(sc->ale_dev, "could not create Rx page %d DMA tag.\n", i); goto fail; } } /* Create DMA tag for Tx coalescing message block. */ error = bus_dma_tag_create( sc->ale_cdata.ale_parent_tag, /* parent */ ALE_CMB_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ ALE_TX_CMB_SZ, /* maxsize */ 1, /* nsegments */ ALE_TX_CMB_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ale_cdata.ale_tx_cmb_tag); if (error != 0) { device_printf(sc->ale_dev, "could not create Tx CMB DMA tag.\n"); goto fail; } /* Create DMA tag for Rx coalescing message block. */ for (i = 0; i < ALE_RX_PAGES; i++) { error = bus_dma_tag_create( sc->ale_cdata.ale_parent_tag, /* parent */ ALE_CMB_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ ALE_RX_CMB_SZ, /* maxsize */ 1, /* nsegments */ ALE_RX_CMB_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ale_cdata.ale_rx_page[i].cmb_tag); if (error != 0) { device_printf(sc->ale_dev, "could not create Rx page %d CMB DMA tag.\n", i); goto fail; } } /* Allocate DMA'able memory and load the DMA map for Tx ring. */ error = bus_dmamem_alloc(sc->ale_cdata.ale_tx_ring_tag, (void **)&sc->ale_cdata.ale_tx_ring, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->ale_cdata.ale_tx_ring_map); if (error != 0) { device_printf(sc->ale_dev, "could not allocate DMA'able memory for Tx ring.\n"); goto fail; } ctx.ale_busaddr = 0; error = bus_dmamap_load(sc->ale_cdata.ale_tx_ring_tag, sc->ale_cdata.ale_tx_ring_map, sc->ale_cdata.ale_tx_ring, ALE_TX_RING_SZ, ale_dmamap_cb, &ctx, 0); if (error != 0 || ctx.ale_busaddr == 0) { device_printf(sc->ale_dev, "could not load DMA'able memory for Tx ring.\n"); goto fail; } sc->ale_cdata.ale_tx_ring_paddr = ctx.ale_busaddr; /* Rx pages. */ for (i = 0; i < ALE_RX_PAGES; i++) { error = bus_dmamem_alloc(sc->ale_cdata.ale_rx_page[i].page_tag, (void **)&sc->ale_cdata.ale_rx_page[i].page_addr, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->ale_cdata.ale_rx_page[i].page_map); if (error != 0) { device_printf(sc->ale_dev, "could not allocate DMA'able memory for " "Rx page %d.\n", i); goto fail; } ctx.ale_busaddr = 0; error = bus_dmamap_load(sc->ale_cdata.ale_rx_page[i].page_tag, sc->ale_cdata.ale_rx_page[i].page_map, sc->ale_cdata.ale_rx_page[i].page_addr, sc->ale_pagesize, ale_dmamap_cb, &ctx, 0); if (error != 0 || ctx.ale_busaddr == 0) { device_printf(sc->ale_dev, "could not load DMA'able memory for " "Rx page %d.\n", i); goto fail; } sc->ale_cdata.ale_rx_page[i].page_paddr = ctx.ale_busaddr; } /* Tx CMB. */ error = bus_dmamem_alloc(sc->ale_cdata.ale_tx_cmb_tag, (void **)&sc->ale_cdata.ale_tx_cmb, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->ale_cdata.ale_tx_cmb_map); if (error != 0) { device_printf(sc->ale_dev, "could not allocate DMA'able memory for Tx CMB.\n"); goto fail; } ctx.ale_busaddr = 0; error = bus_dmamap_load(sc->ale_cdata.ale_tx_cmb_tag, sc->ale_cdata.ale_tx_cmb_map, sc->ale_cdata.ale_tx_cmb, ALE_TX_CMB_SZ, ale_dmamap_cb, &ctx, 0); if (error != 0 || ctx.ale_busaddr == 0) { device_printf(sc->ale_dev, "could not load DMA'able memory for Tx CMB.\n"); goto fail; } sc->ale_cdata.ale_tx_cmb_paddr = ctx.ale_busaddr; /* Rx CMB. */ for (i = 0; i < ALE_RX_PAGES; i++) { error = bus_dmamem_alloc(sc->ale_cdata.ale_rx_page[i].cmb_tag, (void **)&sc->ale_cdata.ale_rx_page[i].cmb_addr, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->ale_cdata.ale_rx_page[i].cmb_map); if (error != 0) { device_printf(sc->ale_dev, "could not allocate " "DMA'able memory for Rx page %d CMB.\n", i); goto fail; } ctx.ale_busaddr = 0; error = bus_dmamap_load(sc->ale_cdata.ale_rx_page[i].cmb_tag, sc->ale_cdata.ale_rx_page[i].cmb_map, sc->ale_cdata.ale_rx_page[i].cmb_addr, ALE_RX_CMB_SZ, ale_dmamap_cb, &ctx, 0); if (error != 0 || ctx.ale_busaddr == 0) { device_printf(sc->ale_dev, "could not load DMA'able " "memory for Rx page %d CMB.\n", i); goto fail; } sc->ale_cdata.ale_rx_page[i].cmb_paddr = ctx.ale_busaddr; } /* * Tx descriptors/RXF0/CMB DMA blocks share the same * high address region of 64bit DMA address space. */ if (lowaddr != BUS_SPACE_MAXADDR_32BIT && (error = ale_check_boundary(sc)) != 0) { device_printf(sc->ale_dev, "4GB boundary crossed, " "switching to 32bit DMA addressing mode.\n"); ale_dma_free(sc); /* * Limit max allowable DMA address space to 32bit * and try again. */ lowaddr = BUS_SPACE_MAXADDR_32BIT; goto again; } /* * Create Tx buffer parent tag. * AR81xx allows 64bit DMA addressing of Tx buffers so it * needs separate parent DMA tag as parent DMA address space * could be restricted to be within 32bit address space by * 4GB boundary crossing. */ error = bus_dma_tag_create( bus_get_dma_tag(sc->ale_dev), /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ 0, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ale_cdata.ale_buffer_tag); if (error != 0) { device_printf(sc->ale_dev, "could not create parent buffer DMA tag.\n"); goto fail; } /* Create DMA tag for Tx buffers. */ error = bus_dma_tag_create( sc->ale_cdata.ale_buffer_tag, /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ ALE_TSO_MAXSIZE, /* maxsize */ ALE_MAXTXSEGS, /* nsegments */ ALE_TSO_MAXSEGSIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ale_cdata.ale_tx_tag); if (error != 0) { device_printf(sc->ale_dev, "could not create Tx DMA tag.\n"); goto fail; } /* Create DMA maps for Tx buffers. */ for (i = 0; i < ALE_TX_RING_CNT; i++) { txd = &sc->ale_cdata.ale_txdesc[i]; txd->tx_m = NULL; txd->tx_dmamap = NULL; error = bus_dmamap_create(sc->ale_cdata.ale_tx_tag, 0, &txd->tx_dmamap); if (error != 0) { device_printf(sc->ale_dev, "could not create Tx dmamap.\n"); goto fail; } } fail: return (error); } static void ale_dma_free(struct ale_softc *sc) { struct ale_txdesc *txd; int i; /* Tx buffers. */ if (sc->ale_cdata.ale_tx_tag != NULL) { for (i = 0; i < ALE_TX_RING_CNT; i++) { txd = &sc->ale_cdata.ale_txdesc[i]; if (txd->tx_dmamap != NULL) { bus_dmamap_destroy(sc->ale_cdata.ale_tx_tag, txd->tx_dmamap); txd->tx_dmamap = NULL; } } bus_dma_tag_destroy(sc->ale_cdata.ale_tx_tag); sc->ale_cdata.ale_tx_tag = NULL; } /* Tx descriptor ring. */ if (sc->ale_cdata.ale_tx_ring_tag != NULL) { if (sc->ale_cdata.ale_tx_ring_paddr != 0) bus_dmamap_unload(sc->ale_cdata.ale_tx_ring_tag, sc->ale_cdata.ale_tx_ring_map); if (sc->ale_cdata.ale_tx_ring != NULL) bus_dmamem_free(sc->ale_cdata.ale_tx_ring_tag, sc->ale_cdata.ale_tx_ring, sc->ale_cdata.ale_tx_ring_map); sc->ale_cdata.ale_tx_ring_paddr = 0; sc->ale_cdata.ale_tx_ring = NULL; bus_dma_tag_destroy(sc->ale_cdata.ale_tx_ring_tag); sc->ale_cdata.ale_tx_ring_tag = NULL; } /* Rx page block. */ for (i = 0; i < ALE_RX_PAGES; i++) { if (sc->ale_cdata.ale_rx_page[i].page_tag != NULL) { if (sc->ale_cdata.ale_rx_page[i].page_paddr != 0) bus_dmamap_unload( sc->ale_cdata.ale_rx_page[i].page_tag, sc->ale_cdata.ale_rx_page[i].page_map); if (sc->ale_cdata.ale_rx_page[i].page_addr != NULL) bus_dmamem_free( sc->ale_cdata.ale_rx_page[i].page_tag, sc->ale_cdata.ale_rx_page[i].page_addr, sc->ale_cdata.ale_rx_page[i].page_map); sc->ale_cdata.ale_rx_page[i].page_paddr = 0; sc->ale_cdata.ale_rx_page[i].page_addr = NULL; bus_dma_tag_destroy( sc->ale_cdata.ale_rx_page[i].page_tag); sc->ale_cdata.ale_rx_page[i].page_tag = NULL; } } /* Rx CMB. */ for (i = 0; i < ALE_RX_PAGES; i++) { if (sc->ale_cdata.ale_rx_page[i].cmb_tag != NULL) { if (sc->ale_cdata.ale_rx_page[i].cmb_paddr != 0) bus_dmamap_unload( sc->ale_cdata.ale_rx_page[i].cmb_tag, sc->ale_cdata.ale_rx_page[i].cmb_map); if (sc->ale_cdata.ale_rx_page[i].cmb_addr != NULL) bus_dmamem_free( sc->ale_cdata.ale_rx_page[i].cmb_tag, sc->ale_cdata.ale_rx_page[i].cmb_addr, sc->ale_cdata.ale_rx_page[i].cmb_map); sc->ale_cdata.ale_rx_page[i].cmb_paddr = 0; sc->ale_cdata.ale_rx_page[i].cmb_addr = NULL; bus_dma_tag_destroy( sc->ale_cdata.ale_rx_page[i].cmb_tag); sc->ale_cdata.ale_rx_page[i].cmb_tag = NULL; } } /* Tx CMB. */ if (sc->ale_cdata.ale_tx_cmb_tag != NULL) { if (sc->ale_cdata.ale_tx_cmb_paddr != 0) bus_dmamap_unload(sc->ale_cdata.ale_tx_cmb_tag, sc->ale_cdata.ale_tx_cmb_map); if (sc->ale_cdata.ale_tx_cmb != NULL) bus_dmamem_free(sc->ale_cdata.ale_tx_cmb_tag, sc->ale_cdata.ale_tx_cmb, sc->ale_cdata.ale_tx_cmb_map); sc->ale_cdata.ale_tx_cmb_paddr = 0; sc->ale_cdata.ale_tx_cmb = NULL; bus_dma_tag_destroy(sc->ale_cdata.ale_tx_cmb_tag); sc->ale_cdata.ale_tx_cmb_tag = NULL; } if (sc->ale_cdata.ale_buffer_tag != NULL) { bus_dma_tag_destroy(sc->ale_cdata.ale_buffer_tag); sc->ale_cdata.ale_buffer_tag = NULL; } if (sc->ale_cdata.ale_parent_tag != NULL) { bus_dma_tag_destroy(sc->ale_cdata.ale_parent_tag); sc->ale_cdata.ale_parent_tag = NULL; } } static int ale_shutdown(device_t dev) { return (ale_suspend(dev)); } /* * Note, this driver resets the link speed to 10/100Mbps by * restarting auto-negotiation in suspend/shutdown phase but we * don't know whether that auto-negotiation would succeed or not * as driver has no control after powering off/suspend operation. * If the renegotiation fail WOL may not work. Running at 1Gbps * will draw more power than 375mA at 3.3V which is specified in * PCI specification and that would result in complete * shutdowning power to ethernet controller. * * TODO * Save current negotiated media speed/duplex/flow-control to * softc and restore the same link again after resuming. PHY * handling such as power down/resetting to 100Mbps may be better * handled in suspend method in phy driver. */ static void ale_setlinkspeed(struct ale_softc *sc) { struct mii_data *mii; int aneg, i; mii = device_get_softc(sc->ale_miibus); mii_pollstat(mii); aneg = 0; if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { switch IFM_SUBTYPE(mii->mii_media_active) { case IFM_10_T: case IFM_100_TX: return; case IFM_1000_T: aneg++; break; default: break; } } ale_miibus_writereg(sc->ale_dev, sc->ale_phyaddr, MII_100T2CR, 0); ale_miibus_writereg(sc->ale_dev, sc->ale_phyaddr, MII_ANAR, ANAR_TX_FD | ANAR_TX | ANAR_10_FD | ANAR_10 | ANAR_CSMA); ale_miibus_writereg(sc->ale_dev, sc->ale_phyaddr, MII_BMCR, BMCR_RESET | BMCR_AUTOEN | BMCR_STARTNEG); DELAY(1000); if (aneg != 0) { /* * Poll link state until ale(4) get a 10/100Mbps link. */ for (i = 0; i < MII_ANEGTICKS_GIGE; i++) { mii_pollstat(mii); if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { switch (IFM_SUBTYPE( mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: ale_mac_config(sc); return; default: break; } } ALE_UNLOCK(sc); pause("alelnk", hz); ALE_LOCK(sc); } if (i == MII_ANEGTICKS_GIGE) device_printf(sc->ale_dev, "establishing a link failed, WOL may not work!"); } /* * No link, force MAC to have 100Mbps, full-duplex link. * This is the last resort and may/may not work. */ mii->mii_media_status = IFM_AVALID | IFM_ACTIVE; mii->mii_media_active = IFM_ETHER | IFM_100_TX | IFM_FDX; ale_mac_config(sc); } static void ale_setwol(struct ale_softc *sc) { struct ifnet *ifp; uint32_t reg, pmcs; uint16_t pmstat; int pmc; ALE_LOCK_ASSERT(sc); if (pci_find_cap(sc->ale_dev, PCIY_PMG, &pmc) != 0) { /* Disable WOL. */ CSR_WRITE_4(sc, ALE_WOL_CFG, 0); reg = CSR_READ_4(sc, ALE_PCIE_PHYMISC); reg |= PCIE_PHYMISC_FORCE_RCV_DET; CSR_WRITE_4(sc, ALE_PCIE_PHYMISC, reg); /* Force PHY power down. */ CSR_WRITE_2(sc, ALE_GPHY_CTRL, GPHY_CTRL_EXT_RESET | GPHY_CTRL_HIB_EN | GPHY_CTRL_HIB_PULSE | GPHY_CTRL_PHY_PLL_ON | GPHY_CTRL_SEL_ANA_RESET | GPHY_CTRL_PHY_IDDQ | GPHY_CTRL_PCLK_SEL_DIS | GPHY_CTRL_PWDOWN_HW); return; } ifp = sc->ale_ifp; if ((ifp->if_capenable & IFCAP_WOL) != 0) { if ((sc->ale_flags & ALE_FLAG_FASTETHER) == 0) ale_setlinkspeed(sc); } pmcs = 0; if ((ifp->if_capenable & IFCAP_WOL_MAGIC) != 0) pmcs |= WOL_CFG_MAGIC | WOL_CFG_MAGIC_ENB; CSR_WRITE_4(sc, ALE_WOL_CFG, pmcs); reg = CSR_READ_4(sc, ALE_MAC_CFG); reg &= ~(MAC_CFG_DBG | MAC_CFG_PROMISC | MAC_CFG_ALLMULTI | MAC_CFG_BCAST); if ((ifp->if_capenable & IFCAP_WOL_MCAST) != 0) reg |= MAC_CFG_ALLMULTI | MAC_CFG_BCAST; if ((ifp->if_capenable & IFCAP_WOL) != 0) reg |= MAC_CFG_RX_ENB; CSR_WRITE_4(sc, ALE_MAC_CFG, reg); if ((ifp->if_capenable & IFCAP_WOL) == 0) { /* WOL disabled, PHY power down. */ reg = CSR_READ_4(sc, ALE_PCIE_PHYMISC); reg |= PCIE_PHYMISC_FORCE_RCV_DET; CSR_WRITE_4(sc, ALE_PCIE_PHYMISC, reg); CSR_WRITE_2(sc, ALE_GPHY_CTRL, GPHY_CTRL_EXT_RESET | GPHY_CTRL_HIB_EN | GPHY_CTRL_HIB_PULSE | GPHY_CTRL_SEL_ANA_RESET | GPHY_CTRL_PHY_IDDQ | GPHY_CTRL_PCLK_SEL_DIS | GPHY_CTRL_PWDOWN_HW); } /* Request PME. */ pmstat = pci_read_config(sc->ale_dev, pmc + PCIR_POWER_STATUS, 2); pmstat &= ~(PCIM_PSTAT_PME | PCIM_PSTAT_PMEENABLE); if ((ifp->if_capenable & IFCAP_WOL) != 0) pmstat |= PCIM_PSTAT_PME | PCIM_PSTAT_PMEENABLE; pci_write_config(sc->ale_dev, pmc + PCIR_POWER_STATUS, pmstat, 2); } static int ale_suspend(device_t dev) { struct ale_softc *sc; sc = device_get_softc(dev); ALE_LOCK(sc); ale_stop(sc); ale_setwol(sc); ALE_UNLOCK(sc); return (0); } static int ale_resume(device_t dev) { struct ale_softc *sc; struct ifnet *ifp; int pmc; uint16_t pmstat; sc = device_get_softc(dev); ALE_LOCK(sc); if (pci_find_cap(sc->ale_dev, PCIY_PMG, &pmc) == 0) { /* Disable PME and clear PME status. */ pmstat = pci_read_config(sc->ale_dev, pmc + PCIR_POWER_STATUS, 2); if ((pmstat & PCIM_PSTAT_PMEENABLE) != 0) { pmstat &= ~PCIM_PSTAT_PMEENABLE; pci_write_config(sc->ale_dev, pmc + PCIR_POWER_STATUS, pmstat, 2); } } /* Reset PHY. */ ale_phy_reset(sc); ifp = sc->ale_ifp; if ((ifp->if_flags & IFF_UP) != 0) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; ale_init_locked(sc); } ALE_UNLOCK(sc); return (0); } static int ale_encap(struct ale_softc *sc, struct mbuf **m_head) { struct ale_txdesc *txd, *txd_last; struct tx_desc *desc; struct mbuf *m; struct ip *ip; struct tcphdr *tcp; bus_dma_segment_t txsegs[ALE_MAXTXSEGS]; bus_dmamap_t map; uint32_t cflags, hdrlen, ip_off, poff, vtag; int error, i, nsegs, prod, si; ALE_LOCK_ASSERT(sc); M_ASSERTPKTHDR((*m_head)); m = *m_head; ip = NULL; tcp = NULL; cflags = vtag = 0; ip_off = poff = 0; if ((m->m_pkthdr.csum_flags & (ALE_CSUM_FEATURES | CSUM_TSO)) != 0) { /* * AR81xx requires offset of TCP/UDP payload in its Tx * descriptor to perform hardware Tx checksum offload. * Additionally, TSO requires IP/TCP header size and * modification of IP/TCP header in order to make TSO * engine work. This kind of operation takes many CPU * cycles on FreeBSD so fast host CPU is required to * get smooth TSO performance. */ struct ether_header *eh; if (M_WRITABLE(m) == 0) { /* Get a writable copy. */ m = m_dup(*m_head, M_NOWAIT); /* Release original mbufs. */ m_freem(*m_head); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } *m_head = m; } /* * Buggy-controller requires 4 byte aligned Tx buffer * to make custom checksum offload work. */ if ((sc->ale_flags & ALE_FLAG_TXCSUM_BUG) != 0 && (m->m_pkthdr.csum_flags & ALE_CSUM_FEATURES) != 0 && (mtod(m, intptr_t) & 3) != 0) { m = m_defrag(*m_head, M_NOWAIT); if (m == NULL) { m_freem(*m_head); *m_head = NULL; return (ENOBUFS); } *m_head = m; } ip_off = sizeof(struct ether_header); m = m_pullup(m, ip_off); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } eh = mtod(m, struct ether_header *); /* * Check if hardware VLAN insertion is off. * Additional check for LLC/SNAP frame? */ if (eh->ether_type == htons(ETHERTYPE_VLAN)) { ip_off = sizeof(struct ether_vlan_header); m = m_pullup(m, ip_off); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } } m = m_pullup(m, ip_off + sizeof(struct ip)); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } ip = (struct ip *)(mtod(m, char *) + ip_off); poff = ip_off + (ip->ip_hl << 2); if ((m->m_pkthdr.csum_flags & CSUM_TSO) != 0) { /* * XXX * AR81xx requires the first descriptor should * not include any TCP playload for TSO case. * (i.e. ethernet header + IP + TCP header only) * m_pullup(9) above will ensure this too. * However it's not correct if the first mbuf * of the chain does not use cluster. */ m = m_pullup(m, poff + sizeof(struct tcphdr)); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } ip = (struct ip *)(mtod(m, char *) + ip_off); tcp = (struct tcphdr *)(mtod(m, char *) + poff); m = m_pullup(m, poff + (tcp->th_off << 2)); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } /* * AR81xx requires IP/TCP header size and offset as * well as TCP pseudo checksum which complicates * TSO configuration. I guess this comes from the * adherence to Microsoft NDIS Large Send * specification which requires insertion of * pseudo checksum by upper stack. The pseudo * checksum that NDIS refers to doesn't include * TCP payload length so ale(4) should recompute * the pseudo checksum here. Hopefully this wouldn't * be much burden on modern CPUs. * Reset IP checksum and recompute TCP pseudo * checksum as NDIS specification said. */ ip->ip_sum = 0; tcp->th_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, htons(IPPROTO_TCP)); } *m_head = m; } si = prod = sc->ale_cdata.ale_tx_prod; txd = &sc->ale_cdata.ale_txdesc[prod]; txd_last = txd; map = txd->tx_dmamap; error = bus_dmamap_load_mbuf_sg(sc->ale_cdata.ale_tx_tag, map, *m_head, txsegs, &nsegs, 0); if (error == EFBIG) { m = m_collapse(*m_head, M_NOWAIT, ALE_MAXTXSEGS); if (m == NULL) { m_freem(*m_head); *m_head = NULL; return (ENOMEM); } *m_head = m; error = bus_dmamap_load_mbuf_sg(sc->ale_cdata.ale_tx_tag, map, *m_head, txsegs, &nsegs, 0); if (error != 0) { m_freem(*m_head); *m_head = NULL; return (error); } } else if (error != 0) return (error); if (nsegs == 0) { m_freem(*m_head); *m_head = NULL; return (EIO); } /* Check descriptor overrun. */ if (sc->ale_cdata.ale_tx_cnt + nsegs >= ALE_TX_RING_CNT - 3) { bus_dmamap_unload(sc->ale_cdata.ale_tx_tag, map); return (ENOBUFS); } bus_dmamap_sync(sc->ale_cdata.ale_tx_tag, map, BUS_DMASYNC_PREWRITE); m = *m_head; if ((m->m_pkthdr.csum_flags & CSUM_TSO) != 0) { /* Request TSO and set MSS. */ cflags |= ALE_TD_TSO; cflags |= ((uint32_t)m->m_pkthdr.tso_segsz << ALE_TD_MSS_SHIFT); /* Set IP/TCP header size. */ cflags |= ip->ip_hl << ALE_TD_IPHDR_LEN_SHIFT; cflags |= tcp->th_off << ALE_TD_TCPHDR_LEN_SHIFT; } else if ((m->m_pkthdr.csum_flags & ALE_CSUM_FEATURES) != 0) { /* * AR81xx supports Tx custom checksum offload feature * that offloads single 16bit checksum computation. * So you can choose one among IP, TCP and UDP. * Normally driver sets checksum start/insertion * position from the information of TCP/UDP frame as * TCP/UDP checksum takes more time than that of IP. * However it seems that custom checksum offload * requires 4 bytes aligned Tx buffers due to hardware * bug. * AR81xx also supports explicit Tx checksum computation * if it is told that the size of IP header and TCP * header(for UDP, the header size does not matter * because it's fixed length). However with this scheme * TSO does not work so you have to choose one either * TSO or explicit Tx checksum offload. I chosen TSO * plus custom checksum offload with work-around which * will cover most common usage for this consumer * ethernet controller. The work-around takes a lot of * CPU cycles if Tx buffer is not aligned on 4 bytes * boundary, though. */ cflags |= ALE_TD_CXSUM; /* Set checksum start offset. */ cflags |= (poff << ALE_TD_CSUM_PLOADOFFSET_SHIFT); /* Set checksum insertion position of TCP/UDP. */ cflags |= ((poff + m->m_pkthdr.csum_data) << ALE_TD_CSUM_XSUMOFFSET_SHIFT); } /* Configure VLAN hardware tag insertion. */ if ((m->m_flags & M_VLANTAG) != 0) { vtag = ALE_TX_VLAN_TAG(m->m_pkthdr.ether_vtag); vtag = ((vtag << ALE_TD_VLAN_SHIFT) & ALE_TD_VLAN_MASK); cflags |= ALE_TD_INSERT_VLAN_TAG; } i = 0; if ((m->m_pkthdr.csum_flags & CSUM_TSO) != 0) { /* * Make sure the first fragment contains * only ethernet and IP/TCP header with options. */ hdrlen = poff + (tcp->th_off << 2); desc = &sc->ale_cdata.ale_tx_ring[prod]; desc->addr = htole64(txsegs[i].ds_addr); desc->len = htole32(ALE_TX_BYTES(hdrlen) | vtag); desc->flags = htole32(cflags); sc->ale_cdata.ale_tx_cnt++; ALE_DESC_INC(prod, ALE_TX_RING_CNT); if (m->m_len - hdrlen > 0) { /* Handle remaining payload of the first fragment. */ desc = &sc->ale_cdata.ale_tx_ring[prod]; desc->addr = htole64(txsegs[i].ds_addr + hdrlen); desc->len = htole32(ALE_TX_BYTES(m->m_len - hdrlen) | vtag); desc->flags = htole32(cflags); sc->ale_cdata.ale_tx_cnt++; ALE_DESC_INC(prod, ALE_TX_RING_CNT); } i = 1; } for (; i < nsegs; i++) { desc = &sc->ale_cdata.ale_tx_ring[prod]; desc->addr = htole64(txsegs[i].ds_addr); desc->len = htole32(ALE_TX_BYTES(txsegs[i].ds_len) | vtag); desc->flags = htole32(cflags); sc->ale_cdata.ale_tx_cnt++; ALE_DESC_INC(prod, ALE_TX_RING_CNT); } /* Update producer index. */ sc->ale_cdata.ale_tx_prod = prod; /* Set TSO header on the first descriptor. */ if ((m->m_pkthdr.csum_flags & CSUM_TSO) != 0) { desc = &sc->ale_cdata.ale_tx_ring[si]; desc->flags |= htole32(ALE_TD_TSO_HDR); } /* Finally set EOP on the last descriptor. */ prod = (prod + ALE_TX_RING_CNT - 1) % ALE_TX_RING_CNT; desc = &sc->ale_cdata.ale_tx_ring[prod]; desc->flags |= htole32(ALE_TD_EOP); /* Swap dmamap of the first and the last. */ txd = &sc->ale_cdata.ale_txdesc[prod]; map = txd_last->tx_dmamap; txd_last->tx_dmamap = txd->tx_dmamap; txd->tx_dmamap = map; txd->tx_m = m; /* Sync descriptors. */ bus_dmamap_sync(sc->ale_cdata.ale_tx_ring_tag, sc->ale_cdata.ale_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (0); } static void ale_start(struct ifnet *ifp) { struct ale_softc *sc; sc = ifp->if_softc; ALE_LOCK(sc); ale_start_locked(ifp); ALE_UNLOCK(sc); } static void ale_start_locked(struct ifnet *ifp) { struct ale_softc *sc; struct mbuf *m_head; int enq; sc = ifp->if_softc; ALE_LOCK_ASSERT(sc); /* Reclaim transmitted frames. */ if (sc->ale_cdata.ale_tx_cnt >= ALE_TX_DESC_HIWAT) ale_txeof(sc); if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING || (sc->ale_flags & ALE_FLAG_LINK) == 0) return; for (enq = 0; !IFQ_DRV_IS_EMPTY(&ifp->if_snd); ) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; /* * Pack the data into the transmit ring. If we * don't have room, set the OACTIVE flag and wait * for the NIC to drain the ring. */ if (ale_encap(sc, &m_head)) { if (m_head == NULL) break; IFQ_DRV_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } enq++; /* * If there's a BPF listener, bounce a copy of this frame * to him. */ ETHER_BPF_MTAP(ifp, m_head); } if (enq > 0) { /* Kick. */ CSR_WRITE_4(sc, ALE_MBOX_TPD_PROD_IDX, sc->ale_cdata.ale_tx_prod); /* Set a timeout in case the chip goes out to lunch. */ sc->ale_watchdog_timer = ALE_TX_TIMEOUT; } } static void ale_watchdog(struct ale_softc *sc) { struct ifnet *ifp; ALE_LOCK_ASSERT(sc); if (sc->ale_watchdog_timer == 0 || --sc->ale_watchdog_timer) return; ifp = sc->ale_ifp; if ((sc->ale_flags & ALE_FLAG_LINK) == 0) { if_printf(sc->ale_ifp, "watchdog timeout (lost link)\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; ale_init_locked(sc); return; } if_printf(sc->ale_ifp, "watchdog timeout -- resetting\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; ale_init_locked(sc); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) ale_start_locked(ifp); } static int ale_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct ale_softc *sc; struct ifreq *ifr; struct mii_data *mii; int error, mask; sc = ifp->if_softc; ifr = (struct ifreq *)data; error = 0; switch (cmd) { case SIOCSIFMTU: if (ifr->ifr_mtu < ETHERMIN || ifr->ifr_mtu > ALE_JUMBO_MTU || ((sc->ale_flags & ALE_FLAG_JUMBO) == 0 && ifr->ifr_mtu > ETHERMTU)) error = EINVAL; else if (ifp->if_mtu != ifr->ifr_mtu) { ALE_LOCK(sc); ifp->if_mtu = ifr->ifr_mtu; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; ale_init_locked(sc); } ALE_UNLOCK(sc); } break; case SIOCSIFFLAGS: ALE_LOCK(sc); if ((ifp->if_flags & IFF_UP) != 0) { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { if (((ifp->if_flags ^ sc->ale_if_flags) & (IFF_PROMISC | IFF_ALLMULTI)) != 0) ale_rxfilter(sc); } else { ale_init_locked(sc); } } else { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) ale_stop(sc); } sc->ale_if_flags = ifp->if_flags; ALE_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: ALE_LOCK(sc); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) ale_rxfilter(sc); ALE_UNLOCK(sc); break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: mii = device_get_softc(sc->ale_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); break; case SIOCSIFCAP: ALE_LOCK(sc); mask = ifr->ifr_reqcap ^ ifp->if_capenable; if ((mask & IFCAP_TXCSUM) != 0 && (ifp->if_capabilities & IFCAP_TXCSUM) != 0) { ifp->if_capenable ^= IFCAP_TXCSUM; if ((ifp->if_capenable & IFCAP_TXCSUM) != 0) ifp->if_hwassist |= ALE_CSUM_FEATURES; else ifp->if_hwassist &= ~ALE_CSUM_FEATURES; } if ((mask & IFCAP_RXCSUM) != 0 && (ifp->if_capabilities & IFCAP_RXCSUM) != 0) ifp->if_capenable ^= IFCAP_RXCSUM; if ((mask & IFCAP_TSO4) != 0 && (ifp->if_capabilities & IFCAP_TSO4) != 0) { ifp->if_capenable ^= IFCAP_TSO4; if ((ifp->if_capenable & IFCAP_TSO4) != 0) ifp->if_hwassist |= CSUM_TSO; else ifp->if_hwassist &= ~CSUM_TSO; } if ((mask & IFCAP_WOL_MCAST) != 0 && (ifp->if_capabilities & IFCAP_WOL_MCAST) != 0) ifp->if_capenable ^= IFCAP_WOL_MCAST; if ((mask & IFCAP_WOL_MAGIC) != 0 && (ifp->if_capabilities & IFCAP_WOL_MAGIC) != 0) ifp->if_capenable ^= IFCAP_WOL_MAGIC; if ((mask & IFCAP_VLAN_HWCSUM) != 0 && (ifp->if_capabilities & IFCAP_VLAN_HWCSUM) != 0) ifp->if_capenable ^= IFCAP_VLAN_HWCSUM; if ((mask & IFCAP_VLAN_HWTSO) != 0 && (ifp->if_capabilities & IFCAP_VLAN_HWTSO) != 0) ifp->if_capenable ^= IFCAP_VLAN_HWTSO; if ((mask & IFCAP_VLAN_HWTAGGING) != 0 && (ifp->if_capabilities & IFCAP_VLAN_HWTAGGING) != 0) { ifp->if_capenable ^= IFCAP_VLAN_HWTAGGING; if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) == 0) ifp->if_capenable &= ~IFCAP_VLAN_HWTSO; ale_rxvlan(sc); } ALE_UNLOCK(sc); VLAN_CAPABILITIES(ifp); break; default: error = ether_ioctl(ifp, cmd, data); break; } return (error); } static void ale_mac_config(struct ale_softc *sc) { struct mii_data *mii; uint32_t reg; ALE_LOCK_ASSERT(sc); mii = device_get_softc(sc->ale_miibus); reg = CSR_READ_4(sc, ALE_MAC_CFG); reg &= ~(MAC_CFG_FULL_DUPLEX | MAC_CFG_TX_FC | MAC_CFG_RX_FC | MAC_CFG_SPEED_MASK); /* Reprogram MAC with resolved speed/duplex. */ switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: reg |= MAC_CFG_SPEED_10_100; break; case IFM_1000_T: reg |= MAC_CFG_SPEED_1000; break; } if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { reg |= MAC_CFG_FULL_DUPLEX; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0) reg |= MAC_CFG_TX_FC; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0) reg |= MAC_CFG_RX_FC; } CSR_WRITE_4(sc, ALE_MAC_CFG, reg); } static void ale_stats_clear(struct ale_softc *sc) { struct smb sb; uint32_t *reg; int i; for (reg = &sb.rx_frames, i = 0; reg <= &sb.rx_pkts_filtered; reg++) { CSR_READ_4(sc, ALE_RX_MIB_BASE + i); i += sizeof(uint32_t); } /* Read Tx statistics. */ for (reg = &sb.tx_frames, i = 0; reg <= &sb.tx_mcast_bytes; reg++) { CSR_READ_4(sc, ALE_TX_MIB_BASE + i); i += sizeof(uint32_t); } } static void ale_stats_update(struct ale_softc *sc) { struct ale_hw_stats *stat; struct smb sb, *smb; struct ifnet *ifp; uint32_t *reg; int i; ALE_LOCK_ASSERT(sc); ifp = sc->ale_ifp; stat = &sc->ale_stats; smb = &sb; /* Read Rx statistics. */ for (reg = &sb.rx_frames, i = 0; reg <= &sb.rx_pkts_filtered; reg++) { *reg = CSR_READ_4(sc, ALE_RX_MIB_BASE + i); i += sizeof(uint32_t); } /* Read Tx statistics. */ for (reg = &sb.tx_frames, i = 0; reg <= &sb.tx_mcast_bytes; reg++) { *reg = CSR_READ_4(sc, ALE_TX_MIB_BASE + i); i += sizeof(uint32_t); } /* Rx stats. */ stat->rx_frames += smb->rx_frames; stat->rx_bcast_frames += smb->rx_bcast_frames; stat->rx_mcast_frames += smb->rx_mcast_frames; stat->rx_pause_frames += smb->rx_pause_frames; stat->rx_control_frames += smb->rx_control_frames; stat->rx_crcerrs += smb->rx_crcerrs; stat->rx_lenerrs += smb->rx_lenerrs; stat->rx_bytes += smb->rx_bytes; stat->rx_runts += smb->rx_runts; stat->rx_fragments += smb->rx_fragments; stat->rx_pkts_64 += smb->rx_pkts_64; stat->rx_pkts_65_127 += smb->rx_pkts_65_127; stat->rx_pkts_128_255 += smb->rx_pkts_128_255; stat->rx_pkts_256_511 += smb->rx_pkts_256_511; stat->rx_pkts_512_1023 += smb->rx_pkts_512_1023; stat->rx_pkts_1024_1518 += smb->rx_pkts_1024_1518; stat->rx_pkts_1519_max += smb->rx_pkts_1519_max; stat->rx_pkts_truncated += smb->rx_pkts_truncated; stat->rx_fifo_oflows += smb->rx_fifo_oflows; stat->rx_rrs_errs += smb->rx_rrs_errs; stat->rx_alignerrs += smb->rx_alignerrs; stat->rx_bcast_bytes += smb->rx_bcast_bytes; stat->rx_mcast_bytes += smb->rx_mcast_bytes; stat->rx_pkts_filtered += smb->rx_pkts_filtered; /* Tx stats. */ stat->tx_frames += smb->tx_frames; stat->tx_bcast_frames += smb->tx_bcast_frames; stat->tx_mcast_frames += smb->tx_mcast_frames; stat->tx_pause_frames += smb->tx_pause_frames; stat->tx_excess_defer += smb->tx_excess_defer; stat->tx_control_frames += smb->tx_control_frames; stat->tx_deferred += smb->tx_deferred; stat->tx_bytes += smb->tx_bytes; stat->tx_pkts_64 += smb->tx_pkts_64; stat->tx_pkts_65_127 += smb->tx_pkts_65_127; stat->tx_pkts_128_255 += smb->tx_pkts_128_255; stat->tx_pkts_256_511 += smb->tx_pkts_256_511; stat->tx_pkts_512_1023 += smb->tx_pkts_512_1023; stat->tx_pkts_1024_1518 += smb->tx_pkts_1024_1518; stat->tx_pkts_1519_max += smb->tx_pkts_1519_max; stat->tx_single_colls += smb->tx_single_colls; stat->tx_multi_colls += smb->tx_multi_colls; stat->tx_late_colls += smb->tx_late_colls; stat->tx_excess_colls += smb->tx_excess_colls; stat->tx_underrun += smb->tx_underrun; stat->tx_desc_underrun += smb->tx_desc_underrun; stat->tx_lenerrs += smb->tx_lenerrs; stat->tx_pkts_truncated += smb->tx_pkts_truncated; stat->tx_bcast_bytes += smb->tx_bcast_bytes; stat->tx_mcast_bytes += smb->tx_mcast_bytes; /* Update counters in ifnet. */ if_inc_counter(ifp, IFCOUNTER_OPACKETS, smb->tx_frames); if_inc_counter(ifp, IFCOUNTER_COLLISIONS, smb->tx_single_colls + smb->tx_multi_colls * 2 + smb->tx_late_colls + smb->tx_excess_colls * HDPX_CFG_RETRY_DEFAULT); if_inc_counter(ifp, IFCOUNTER_OERRORS, smb->tx_late_colls + smb->tx_excess_colls + smb->tx_underrun + smb->tx_pkts_truncated); if_inc_counter(ifp, IFCOUNTER_IPACKETS, smb->rx_frames); if_inc_counter(ifp, IFCOUNTER_IERRORS, smb->rx_crcerrs + smb->rx_lenerrs + smb->rx_runts + smb->rx_pkts_truncated + smb->rx_fifo_oflows + smb->rx_rrs_errs + smb->rx_alignerrs); } static int ale_intr(void *arg) { struct ale_softc *sc; uint32_t status; sc = (struct ale_softc *)arg; status = CSR_READ_4(sc, ALE_INTR_STATUS); if ((status & ALE_INTRS) == 0) return (FILTER_STRAY); /* Disable interrupts. */ CSR_WRITE_4(sc, ALE_INTR_STATUS, INTR_DIS_INT); taskqueue_enqueue(sc->ale_tq, &sc->ale_int_task); return (FILTER_HANDLED); } static void ale_int_task(void *arg, int pending) { struct ale_softc *sc; struct ifnet *ifp; uint32_t status; int more; sc = (struct ale_softc *)arg; status = CSR_READ_4(sc, ALE_INTR_STATUS); ALE_LOCK(sc); if (sc->ale_morework != 0) status |= INTR_RX_PKT; if ((status & ALE_INTRS) == 0) goto done; /* Acknowledge interrupts but still disable interrupts. */ CSR_WRITE_4(sc, ALE_INTR_STATUS, status | INTR_DIS_INT); ifp = sc->ale_ifp; more = 0; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { more = ale_rxeof(sc, sc->ale_process_limit); if (more == EAGAIN) sc->ale_morework = 1; else if (more == EIO) { sc->ale_stats.reset_brk_seq++; ifp->if_drv_flags &= ~IFF_DRV_RUNNING; ale_init_locked(sc); ALE_UNLOCK(sc); return; } if ((status & (INTR_DMA_RD_TO_RST | INTR_DMA_WR_TO_RST)) != 0) { if ((status & INTR_DMA_RD_TO_RST) != 0) device_printf(sc->ale_dev, "DMA read error! -- resetting\n"); if ((status & INTR_DMA_WR_TO_RST) != 0) device_printf(sc->ale_dev, "DMA write error! -- resetting\n"); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; ale_init_locked(sc); ALE_UNLOCK(sc); return; } if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) ale_start_locked(ifp); } if (more == EAGAIN || (CSR_READ_4(sc, ALE_INTR_STATUS) & ALE_INTRS) != 0) { ALE_UNLOCK(sc); taskqueue_enqueue(sc->ale_tq, &sc->ale_int_task); return; } done: ALE_UNLOCK(sc); /* Re-enable interrupts. */ CSR_WRITE_4(sc, ALE_INTR_STATUS, 0x7FFFFFFF); } static void ale_txeof(struct ale_softc *sc) { struct ifnet *ifp; struct ale_txdesc *txd; uint32_t cons, prod; int prog; ALE_LOCK_ASSERT(sc); ifp = sc->ale_ifp; if (sc->ale_cdata.ale_tx_cnt == 0) return; bus_dmamap_sync(sc->ale_cdata.ale_tx_ring_tag, sc->ale_cdata.ale_tx_ring_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); if ((sc->ale_flags & ALE_FLAG_TXCMB_BUG) == 0) { bus_dmamap_sync(sc->ale_cdata.ale_tx_cmb_tag, sc->ale_cdata.ale_tx_cmb_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); prod = *sc->ale_cdata.ale_tx_cmb & TPD_CNT_MASK; } else prod = CSR_READ_2(sc, ALE_TPD_CONS_IDX); cons = sc->ale_cdata.ale_tx_cons; /* * Go through our Tx list and free mbufs for those * frames which have been transmitted. */ for (prog = 0; cons != prod; prog++, ALE_DESC_INC(cons, ALE_TX_RING_CNT)) { if (sc->ale_cdata.ale_tx_cnt <= 0) break; prog++; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc->ale_cdata.ale_tx_cnt--; txd = &sc->ale_cdata.ale_txdesc[cons]; if (txd->tx_m != NULL) { /* Reclaim transmitted mbufs. */ bus_dmamap_sync(sc->ale_cdata.ale_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->ale_cdata.ale_tx_tag, txd->tx_dmamap); m_freem(txd->tx_m); txd->tx_m = NULL; } } if (prog > 0) { sc->ale_cdata.ale_tx_cons = cons; /* * Unarm watchdog timer only when there is no pending * Tx descriptors in queue. */ if (sc->ale_cdata.ale_tx_cnt == 0) sc->ale_watchdog_timer = 0; } } static void ale_rx_update_page(struct ale_softc *sc, struct ale_rx_page **page, uint32_t length, uint32_t *prod) { struct ale_rx_page *rx_page; rx_page = *page; /* Update consumer position. */ rx_page->cons += roundup(length + sizeof(struct rx_rs), ALE_RX_PAGE_ALIGN); if (rx_page->cons >= ALE_RX_PAGE_SZ) { /* * End of Rx page reached, let hardware reuse * this page. */ rx_page->cons = 0; *rx_page->cmb_addr = 0; bus_dmamap_sync(rx_page->cmb_tag, rx_page->cmb_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); CSR_WRITE_1(sc, ALE_RXF0_PAGE0 + sc->ale_cdata.ale_rx_curp, RXF_VALID); /* Switch to alternate Rx page. */ sc->ale_cdata.ale_rx_curp ^= 1; rx_page = *page = &sc->ale_cdata.ale_rx_page[sc->ale_cdata.ale_rx_curp]; /* Page flipped, sync CMB and Rx page. */ bus_dmamap_sync(rx_page->page_tag, rx_page->page_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); bus_dmamap_sync(rx_page->cmb_tag, rx_page->cmb_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); /* Sync completed, cache updated producer index. */ *prod = *rx_page->cmb_addr; } } /* * It seems that AR81xx controller can compute partial checksum. * The partial checksum value can be used to accelerate checksum * computation for fragmented TCP/UDP packets. Upper network stack * already takes advantage of the partial checksum value in IP * reassembly stage. But I'm not sure the correctness of the * partial hardware checksum assistance due to lack of data sheet. * In addition, the Rx feature of controller that requires copying * for every frames effectively nullifies one of most nice offload * capability of controller. */ static void ale_rxcsum(struct ale_softc *sc, struct mbuf *m, uint32_t status) { struct ifnet *ifp; struct ip *ip; char *p; ifp = sc->ale_ifp; m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED; if ((status & ALE_RD_IPCSUM_NOK) == 0) m->m_pkthdr.csum_flags |= CSUM_IP_VALID; if ((sc->ale_flags & ALE_FLAG_RXCSUM_BUG) == 0) { if (((status & ALE_RD_IPV4_FRAG) == 0) && ((status & (ALE_RD_TCP | ALE_RD_UDP)) != 0) && ((status & ALE_RD_TCP_UDPCSUM_NOK) == 0)) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } } else { if ((status & (ALE_RD_TCP | ALE_RD_UDP)) != 0 && (status & ALE_RD_TCP_UDPCSUM_NOK) == 0) { p = mtod(m, char *); p += ETHER_HDR_LEN; if ((status & ALE_RD_802_3) != 0) p += LLC_SNAPFRAMELEN; if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) == 0 && (status & ALE_RD_VLAN) != 0) p += ETHER_VLAN_ENCAP_LEN; ip = (struct ip *)p; if (ip->ip_off != 0 && (status & ALE_RD_IPV4_DF) == 0) return; m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } } /* * Don't mark bad checksum for TCP/UDP frames * as fragmented frames may always have set * bad checksummed bit of frame status. */ } /* Process received frames. */ static int ale_rxeof(struct ale_softc *sc, int count) { struct ale_rx_page *rx_page; struct rx_rs *rs; struct ifnet *ifp; struct mbuf *m; uint32_t length, prod, seqno, status, vtags; int prog; ifp = sc->ale_ifp; rx_page = &sc->ale_cdata.ale_rx_page[sc->ale_cdata.ale_rx_curp]; bus_dmamap_sync(rx_page->cmb_tag, rx_page->cmb_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); bus_dmamap_sync(rx_page->page_tag, rx_page->page_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); /* * Don't directly access producer index as hardware may * update it while Rx handler is in progress. It would * be even better if there is a way to let hardware * know how far driver processed its received frames. * Alternatively, hardware could provide a way to disable * CMB updates until driver acknowledges the end of CMB * access. */ prod = *rx_page->cmb_addr; for (prog = 0; prog < count; prog++) { if (rx_page->cons >= prod) break; rs = (struct rx_rs *)(rx_page->page_addr + rx_page->cons); seqno = ALE_RX_SEQNO(le32toh(rs->seqno)); if (sc->ale_cdata.ale_rx_seqno != seqno) { /* * Normally I believe this should not happen unless * severe driver bug or corrupted memory. However * it seems to happen under certain conditions which * is triggered by abrupt Rx events such as initiation * of bulk transfer of remote host. It's not easy to * reproduce this and I doubt it could be related * with FIFO overflow of hardware or activity of Tx * CMB updates. I also remember similar behaviour * seen on RealTek 8139 which uses resembling Rx * scheme. */ if (bootverbose) device_printf(sc->ale_dev, "garbled seq: %u, expected: %u -- " "resetting!\n", seqno, sc->ale_cdata.ale_rx_seqno); return (EIO); } /* Frame received. */ sc->ale_cdata.ale_rx_seqno++; length = ALE_RX_BYTES(le32toh(rs->length)); status = le32toh(rs->flags); if ((status & ALE_RD_ERROR) != 0) { /* * We want to pass the following frames to upper * layer regardless of error status of Rx return * status. * * o IP/TCP/UDP checksum is bad. * o frame length and protocol specific length * does not match. */ if ((status & (ALE_RD_CRC | ALE_RD_CODE | ALE_RD_DRIBBLE | ALE_RD_RUNT | ALE_RD_OFLOW | ALE_RD_TRUNC)) != 0) { ale_rx_update_page(sc, &rx_page, length, &prod); continue; } } /* * m_devget(9) is major bottle-neck of ale(4)(It comes * from hardware limitation). For jumbo frames we could * get a slightly better performance if driver use * m_getjcl(9) with proper buffer size argument. However * that would make code more complicated and I don't * think users would expect good Rx performance numbers * on these low-end consumer ethernet controller. */ m = m_devget((char *)(rs + 1), length - ETHER_CRC_LEN, ETHER_ALIGN, ifp, NULL); if (m == NULL) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); ale_rx_update_page(sc, &rx_page, length, &prod); continue; } if ((ifp->if_capenable & IFCAP_RXCSUM) != 0 && (status & ALE_RD_IPV4) != 0) ale_rxcsum(sc, m, status); if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0 && (status & ALE_RD_VLAN) != 0) { vtags = ALE_RX_VLAN(le32toh(rs->vtags)); m->m_pkthdr.ether_vtag = ALE_RX_VLAN_TAG(vtags); m->m_flags |= M_VLANTAG; } /* Pass it to upper layer. */ ALE_UNLOCK(sc); (*ifp->if_input)(ifp, m); ALE_LOCK(sc); ale_rx_update_page(sc, &rx_page, length, &prod); } return (count > 0 ? 0 : EAGAIN); } static void ale_tick(void *arg) { struct ale_softc *sc; struct mii_data *mii; sc = (struct ale_softc *)arg; ALE_LOCK_ASSERT(sc); mii = device_get_softc(sc->ale_miibus); mii_tick(mii); ale_stats_update(sc); /* * Reclaim Tx buffers that have been transferred. It's not * needed here but it would release allocated mbuf chains * faster and limit the maximum delay to a hz. */ ale_txeof(sc); ale_watchdog(sc); callout_reset(&sc->ale_tick_ch, hz, ale_tick, sc); } static void ale_reset(struct ale_softc *sc) { uint32_t reg; int i; /* Initialize PCIe module. From Linux. */ CSR_WRITE_4(sc, 0x1008, CSR_READ_4(sc, 0x1008) | 0x8000); CSR_WRITE_4(sc, ALE_MASTER_CFG, MASTER_RESET); for (i = ALE_RESET_TIMEOUT; i > 0; i--) { DELAY(10); if ((CSR_READ_4(sc, ALE_MASTER_CFG) & MASTER_RESET) == 0) break; } if (i == 0) device_printf(sc->ale_dev, "master reset timeout!\n"); for (i = ALE_RESET_TIMEOUT; i > 0; i--) { if ((reg = CSR_READ_4(sc, ALE_IDLE_STATUS)) == 0) break; DELAY(10); } if (i == 0) device_printf(sc->ale_dev, "reset timeout(0x%08x)!\n", reg); } static void ale_init(void *xsc) { struct ale_softc *sc; sc = (struct ale_softc *)xsc; ALE_LOCK(sc); ale_init_locked(sc); ALE_UNLOCK(sc); } static void ale_init_locked(struct ale_softc *sc) { struct ifnet *ifp; struct mii_data *mii; uint8_t eaddr[ETHER_ADDR_LEN]; bus_addr_t paddr; uint32_t reg, rxf_hi, rxf_lo; ALE_LOCK_ASSERT(sc); ifp = sc->ale_ifp; mii = device_get_softc(sc->ale_miibus); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) return; /* * Cancel any pending I/O. */ ale_stop(sc); /* * Reset the chip to a known state. */ ale_reset(sc); /* Initialize Tx descriptors, DMA memory blocks. */ ale_init_rx_pages(sc); ale_init_tx_ring(sc); /* Reprogram the station address. */ bcopy(IF_LLADDR(ifp), eaddr, ETHER_ADDR_LEN); CSR_WRITE_4(sc, ALE_PAR0, eaddr[2] << 24 | eaddr[3] << 16 | eaddr[4] << 8 | eaddr[5]); CSR_WRITE_4(sc, ALE_PAR1, eaddr[0] << 8 | eaddr[1]); /* * Clear WOL status and disable all WOL feature as WOL * would interfere Rx operation under normal environments. */ CSR_READ_4(sc, ALE_WOL_CFG); CSR_WRITE_4(sc, ALE_WOL_CFG, 0); /* * Set Tx descriptor/RXF0/CMB base addresses. They share * the same high address part of DMAable region. */ paddr = sc->ale_cdata.ale_tx_ring_paddr; CSR_WRITE_4(sc, ALE_TPD_ADDR_HI, ALE_ADDR_HI(paddr)); CSR_WRITE_4(sc, ALE_TPD_ADDR_LO, ALE_ADDR_LO(paddr)); CSR_WRITE_4(sc, ALE_TPD_CNT, (ALE_TX_RING_CNT << TPD_CNT_SHIFT) & TPD_CNT_MASK); /* Set Rx page base address, note we use single queue. */ paddr = sc->ale_cdata.ale_rx_page[0].page_paddr; CSR_WRITE_4(sc, ALE_RXF0_PAGE0_ADDR_LO, ALE_ADDR_LO(paddr)); paddr = sc->ale_cdata.ale_rx_page[1].page_paddr; CSR_WRITE_4(sc, ALE_RXF0_PAGE1_ADDR_LO, ALE_ADDR_LO(paddr)); /* Set Tx/Rx CMB addresses. */ paddr = sc->ale_cdata.ale_tx_cmb_paddr; CSR_WRITE_4(sc, ALE_TX_CMB_ADDR_LO, ALE_ADDR_LO(paddr)); paddr = sc->ale_cdata.ale_rx_page[0].cmb_paddr; CSR_WRITE_4(sc, ALE_RXF0_CMB0_ADDR_LO, ALE_ADDR_LO(paddr)); paddr = sc->ale_cdata.ale_rx_page[1].cmb_paddr; CSR_WRITE_4(sc, ALE_RXF0_CMB1_ADDR_LO, ALE_ADDR_LO(paddr)); /* Mark RXF0 is valid. */ CSR_WRITE_1(sc, ALE_RXF0_PAGE0, RXF_VALID); CSR_WRITE_1(sc, ALE_RXF0_PAGE1, RXF_VALID); /* * No need to initialize RFX1/RXF2/RXF3. We don't use * multi-queue yet. */ /* Set Rx page size, excluding guard frame size. */ CSR_WRITE_4(sc, ALE_RXF_PAGE_SIZE, ALE_RX_PAGE_SZ); /* Tell hardware that we're ready to load DMA blocks. */ CSR_WRITE_4(sc, ALE_DMA_BLOCK, DMA_BLOCK_LOAD); /* Set Rx/Tx interrupt trigger threshold. */ CSR_WRITE_4(sc, ALE_INT_TRIG_THRESH, (1 << INT_TRIG_RX_THRESH_SHIFT) | (4 << INT_TRIG_TX_THRESH_SHIFT)); /* * XXX * Set interrupt trigger timer, its purpose and relation * with interrupt moderation mechanism is not clear yet. */ CSR_WRITE_4(sc, ALE_INT_TRIG_TIMER, ((ALE_USECS(10) << INT_TRIG_RX_TIMER_SHIFT) | (ALE_USECS(1000) << INT_TRIG_TX_TIMER_SHIFT))); /* Configure interrupt moderation timer. */ reg = ALE_USECS(sc->ale_int_rx_mod) << IM_TIMER_RX_SHIFT; reg |= ALE_USECS(sc->ale_int_tx_mod) << IM_TIMER_TX_SHIFT; CSR_WRITE_4(sc, ALE_IM_TIMER, reg); reg = CSR_READ_4(sc, ALE_MASTER_CFG); reg &= ~(MASTER_CHIP_REV_MASK | MASTER_CHIP_ID_MASK); reg &= ~(MASTER_IM_RX_TIMER_ENB | MASTER_IM_TX_TIMER_ENB); if (ALE_USECS(sc->ale_int_rx_mod) != 0) reg |= MASTER_IM_RX_TIMER_ENB; if (ALE_USECS(sc->ale_int_tx_mod) != 0) reg |= MASTER_IM_TX_TIMER_ENB; CSR_WRITE_4(sc, ALE_MASTER_CFG, reg); CSR_WRITE_2(sc, ALE_INTR_CLR_TIMER, ALE_USECS(1000)); /* Set Maximum frame size of controller. */ if (ifp->if_mtu < ETHERMTU) sc->ale_max_frame_size = ETHERMTU; else sc->ale_max_frame_size = ifp->if_mtu; sc->ale_max_frame_size += ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN + ETHER_CRC_LEN; CSR_WRITE_4(sc, ALE_FRAME_SIZE, sc->ale_max_frame_size); /* Configure IPG/IFG parameters. */ CSR_WRITE_4(sc, ALE_IPG_IFG_CFG, ((IPG_IFG_IPGT_DEFAULT << IPG_IFG_IPGT_SHIFT) & IPG_IFG_IPGT_MASK) | ((IPG_IFG_MIFG_DEFAULT << IPG_IFG_MIFG_SHIFT) & IPG_IFG_MIFG_MASK) | ((IPG_IFG_IPG1_DEFAULT << IPG_IFG_IPG1_SHIFT) & IPG_IFG_IPG1_MASK) | ((IPG_IFG_IPG2_DEFAULT << IPG_IFG_IPG2_SHIFT) & IPG_IFG_IPG2_MASK)); /* Set parameters for half-duplex media. */ CSR_WRITE_4(sc, ALE_HDPX_CFG, ((HDPX_CFG_LCOL_DEFAULT << HDPX_CFG_LCOL_SHIFT) & HDPX_CFG_LCOL_MASK) | ((HDPX_CFG_RETRY_DEFAULT << HDPX_CFG_RETRY_SHIFT) & HDPX_CFG_RETRY_MASK) | HDPX_CFG_EXC_DEF_EN | ((HDPX_CFG_ABEBT_DEFAULT << HDPX_CFG_ABEBT_SHIFT) & HDPX_CFG_ABEBT_MASK) | ((HDPX_CFG_JAMIPG_DEFAULT << HDPX_CFG_JAMIPG_SHIFT) & HDPX_CFG_JAMIPG_MASK)); /* Configure Tx jumbo frame parameters. */ if ((sc->ale_flags & ALE_FLAG_JUMBO) != 0) { if (ifp->if_mtu < ETHERMTU) reg = sc->ale_max_frame_size; else if (ifp->if_mtu < 6 * 1024) reg = (sc->ale_max_frame_size * 2) / 3; else reg = sc->ale_max_frame_size / 2; CSR_WRITE_4(sc, ALE_TX_JUMBO_THRESH, roundup(reg, TX_JUMBO_THRESH_UNIT) >> TX_JUMBO_THRESH_UNIT_SHIFT); } /* Configure TxQ. */ reg = (128 << (sc->ale_dma_rd_burst >> DMA_CFG_RD_BURST_SHIFT)) << TXQ_CFG_TX_FIFO_BURST_SHIFT; reg |= (TXQ_CFG_TPD_BURST_DEFAULT << TXQ_CFG_TPD_BURST_SHIFT) & TXQ_CFG_TPD_BURST_MASK; CSR_WRITE_4(sc, ALE_TXQ_CFG, reg | TXQ_CFG_ENHANCED_MODE | TXQ_CFG_ENB); /* Configure Rx jumbo frame & flow control parameters. */ if ((sc->ale_flags & ALE_FLAG_JUMBO) != 0) { reg = roundup(sc->ale_max_frame_size, RX_JUMBO_THRESH_UNIT); CSR_WRITE_4(sc, ALE_RX_JUMBO_THRESH, (((reg >> RX_JUMBO_THRESH_UNIT_SHIFT) << RX_JUMBO_THRESH_MASK_SHIFT) & RX_JUMBO_THRESH_MASK) | ((RX_JUMBO_LKAH_DEFAULT << RX_JUMBO_LKAH_SHIFT) & RX_JUMBO_LKAH_MASK)); reg = CSR_READ_4(sc, ALE_SRAM_RX_FIFO_LEN); rxf_hi = (reg * 7) / 10; rxf_lo = (reg * 3)/ 10; CSR_WRITE_4(sc, ALE_RX_FIFO_PAUSE_THRESH, ((rxf_lo << RX_FIFO_PAUSE_THRESH_LO_SHIFT) & RX_FIFO_PAUSE_THRESH_LO_MASK) | ((rxf_hi << RX_FIFO_PAUSE_THRESH_HI_SHIFT) & RX_FIFO_PAUSE_THRESH_HI_MASK)); } /* Disable RSS. */ CSR_WRITE_4(sc, ALE_RSS_IDT_TABLE0, 0); CSR_WRITE_4(sc, ALE_RSS_CPU, 0); /* Configure RxQ. */ CSR_WRITE_4(sc, ALE_RXQ_CFG, RXQ_CFG_ALIGN_32 | RXQ_CFG_CUT_THROUGH_ENB | RXQ_CFG_ENB); /* Configure DMA parameters. */ reg = 0; if ((sc->ale_flags & ALE_FLAG_TXCMB_BUG) == 0) reg |= DMA_CFG_TXCMB_ENB; CSR_WRITE_4(sc, ALE_DMA_CFG, DMA_CFG_OUT_ORDER | DMA_CFG_RD_REQ_PRI | DMA_CFG_RCB_64 | sc->ale_dma_rd_burst | reg | sc->ale_dma_wr_burst | DMA_CFG_RXCMB_ENB | ((DMA_CFG_RD_DELAY_CNT_DEFAULT << DMA_CFG_RD_DELAY_CNT_SHIFT) & DMA_CFG_RD_DELAY_CNT_MASK) | ((DMA_CFG_WR_DELAY_CNT_DEFAULT << DMA_CFG_WR_DELAY_CNT_SHIFT) & DMA_CFG_WR_DELAY_CNT_MASK)); /* * Hardware can be configured to issue SMB interrupt based * on programmed interval. Since there is a callout that is * invoked for every hz in driver we use that instead of * relying on periodic SMB interrupt. */ CSR_WRITE_4(sc, ALE_SMB_STAT_TIMER, ALE_USECS(0)); /* Clear MAC statistics. */ ale_stats_clear(sc); /* * Configure Tx/Rx MACs. * - Auto-padding for short frames. * - Enable CRC generation. * Actual reconfiguration of MAC for resolved speed/duplex * is followed after detection of link establishment. * AR81xx always does checksum computation regardless of * MAC_CFG_RXCSUM_ENB bit. In fact, setting the bit will * cause Rx handling issue for fragmented IP datagrams due * to silicon bug. */ reg = MAC_CFG_TX_CRC_ENB | MAC_CFG_TX_AUTO_PAD | MAC_CFG_FULL_DUPLEX | ((MAC_CFG_PREAMBLE_DEFAULT << MAC_CFG_PREAMBLE_SHIFT) & MAC_CFG_PREAMBLE_MASK); if ((sc->ale_flags & ALE_FLAG_FASTETHER) != 0) reg |= MAC_CFG_SPEED_10_100; else reg |= MAC_CFG_SPEED_1000; CSR_WRITE_4(sc, ALE_MAC_CFG, reg); /* Set up the receive filter. */ ale_rxfilter(sc); ale_rxvlan(sc); /* Acknowledge all pending interrupts and clear it. */ CSR_WRITE_4(sc, ALE_INTR_MASK, ALE_INTRS); CSR_WRITE_4(sc, ALE_INTR_STATUS, 0xFFFFFFFF); CSR_WRITE_4(sc, ALE_INTR_STATUS, 0); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc->ale_flags &= ~ALE_FLAG_LINK; /* Switch to the current media. */ mii_mediachg(mii); callout_reset(&sc->ale_tick_ch, hz, ale_tick, sc); } static void ale_stop(struct ale_softc *sc) { struct ifnet *ifp; struct ale_txdesc *txd; uint32_t reg; int i; ALE_LOCK_ASSERT(sc); /* * Mark the interface down and cancel the watchdog timer. */ ifp = sc->ale_ifp; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc->ale_flags &= ~ALE_FLAG_LINK; callout_stop(&sc->ale_tick_ch); sc->ale_watchdog_timer = 0; ale_stats_update(sc); /* Disable interrupts. */ CSR_WRITE_4(sc, ALE_INTR_MASK, 0); CSR_WRITE_4(sc, ALE_INTR_STATUS, 0xFFFFFFFF); /* Disable queue processing and DMA. */ reg = CSR_READ_4(sc, ALE_TXQ_CFG); reg &= ~TXQ_CFG_ENB; CSR_WRITE_4(sc, ALE_TXQ_CFG, reg); reg = CSR_READ_4(sc, ALE_RXQ_CFG); reg &= ~RXQ_CFG_ENB; CSR_WRITE_4(sc, ALE_RXQ_CFG, reg); reg = CSR_READ_4(sc, ALE_DMA_CFG); reg &= ~(DMA_CFG_TXCMB_ENB | DMA_CFG_RXCMB_ENB); CSR_WRITE_4(sc, ALE_DMA_CFG, reg); DELAY(1000); /* Stop Rx/Tx MACs. */ ale_stop_mac(sc); /* Disable interrupts which might be touched in taskq handler. */ CSR_WRITE_4(sc, ALE_INTR_STATUS, 0xFFFFFFFF); /* * Free TX mbufs still in the queues. */ for (i = 0; i < ALE_TX_RING_CNT; i++) { txd = &sc->ale_cdata.ale_txdesc[i]; if (txd->tx_m != NULL) { bus_dmamap_sync(sc->ale_cdata.ale_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->ale_cdata.ale_tx_tag, txd->tx_dmamap); m_freem(txd->tx_m); txd->tx_m = NULL; } } } static void ale_stop_mac(struct ale_softc *sc) { uint32_t reg; int i; ALE_LOCK_ASSERT(sc); reg = CSR_READ_4(sc, ALE_MAC_CFG); if ((reg & (MAC_CFG_TX_ENB | MAC_CFG_RX_ENB)) != 0) { reg &= ~(MAC_CFG_TX_ENB | MAC_CFG_RX_ENB); CSR_WRITE_4(sc, ALE_MAC_CFG, reg); } for (i = ALE_TIMEOUT; i > 0; i--) { reg = CSR_READ_4(sc, ALE_IDLE_STATUS); if (reg == 0) break; DELAY(10); } if (i == 0) device_printf(sc->ale_dev, "could not disable Tx/Rx MAC(0x%08x)!\n", reg); } static void ale_init_tx_ring(struct ale_softc *sc) { struct ale_txdesc *txd; int i; ALE_LOCK_ASSERT(sc); sc->ale_cdata.ale_tx_prod = 0; sc->ale_cdata.ale_tx_cons = 0; sc->ale_cdata.ale_tx_cnt = 0; bzero(sc->ale_cdata.ale_tx_ring, ALE_TX_RING_SZ); bzero(sc->ale_cdata.ale_tx_cmb, ALE_TX_CMB_SZ); for (i = 0; i < ALE_TX_RING_CNT; i++) { txd = &sc->ale_cdata.ale_txdesc[i]; txd->tx_m = NULL; } *sc->ale_cdata.ale_tx_cmb = 0; bus_dmamap_sync(sc->ale_cdata.ale_tx_cmb_tag, sc->ale_cdata.ale_tx_cmb_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); bus_dmamap_sync(sc->ale_cdata.ale_tx_ring_tag, sc->ale_cdata.ale_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static void ale_init_rx_pages(struct ale_softc *sc) { struct ale_rx_page *rx_page; int i; ALE_LOCK_ASSERT(sc); sc->ale_morework = 0; sc->ale_cdata.ale_rx_seqno = 0; sc->ale_cdata.ale_rx_curp = 0; for (i = 0; i < ALE_RX_PAGES; i++) { rx_page = &sc->ale_cdata.ale_rx_page[i]; bzero(rx_page->page_addr, sc->ale_pagesize); bzero(rx_page->cmb_addr, ALE_RX_CMB_SZ); rx_page->cons = 0; *rx_page->cmb_addr = 0; bus_dmamap_sync(rx_page->page_tag, rx_page->page_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); bus_dmamap_sync(rx_page->cmb_tag, rx_page->cmb_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } } static void ale_rxvlan(struct ale_softc *sc) { struct ifnet *ifp; uint32_t reg; ALE_LOCK_ASSERT(sc); ifp = sc->ale_ifp; reg = CSR_READ_4(sc, ALE_MAC_CFG); reg &= ~MAC_CFG_VLAN_TAG_STRIP; if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0) reg |= MAC_CFG_VLAN_TAG_STRIP; CSR_WRITE_4(sc, ALE_MAC_CFG, reg); } static void ale_rxfilter(struct ale_softc *sc) { struct ifnet *ifp; struct ifmultiaddr *ifma; uint32_t crc; uint32_t mchash[2]; uint32_t rxcfg; ALE_LOCK_ASSERT(sc); ifp = sc->ale_ifp; rxcfg = CSR_READ_4(sc, ALE_MAC_CFG); rxcfg &= ~(MAC_CFG_ALLMULTI | MAC_CFG_BCAST | MAC_CFG_PROMISC); if ((ifp->if_flags & IFF_BROADCAST) != 0) rxcfg |= MAC_CFG_BCAST; if ((ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI)) != 0) { if ((ifp->if_flags & IFF_PROMISC) != 0) rxcfg |= MAC_CFG_PROMISC; if ((ifp->if_flags & IFF_ALLMULTI) != 0) rxcfg |= MAC_CFG_ALLMULTI; CSR_WRITE_4(sc, ALE_MAR0, 0xFFFFFFFF); CSR_WRITE_4(sc, ALE_MAR1, 0xFFFFFFFF); CSR_WRITE_4(sc, ALE_MAC_CFG, rxcfg); return; } /* Program new filter. */ bzero(mchash, sizeof(mchash)); if_maddr_rlock(ifp); TAILQ_FOREACH(ifma, &sc->ale_ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; crc = ether_crc32_be(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN); mchash[crc >> 31] |= 1 << ((crc >> 26) & 0x1f); } if_maddr_runlock(ifp); CSR_WRITE_4(sc, ALE_MAR0, mchash[0]); CSR_WRITE_4(sc, ALE_MAR1, mchash[1]); CSR_WRITE_4(sc, ALE_MAC_CFG, rxcfg); } static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int low, int high) { int error, value; if (arg1 == NULL) return (EINVAL); value = *(int *)arg1; error = sysctl_handle_int(oidp, &value, 0, req); if (error || req->newptr == NULL) return (error); if (value < low || value > high) return (EINVAL); *(int *)arg1 = value; return (0); } static int sysctl_hw_ale_proc_limit(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, ALE_PROC_MIN, ALE_PROC_MAX)); } static int sysctl_hw_ale_int_mod(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, ALE_IM_TIMER_MIN, ALE_IM_TIMER_MAX)); } Index: head/sys/dev/jme/if_jme.c =================================================================== --- head/sys/dev/jme/if_jme.c (revision 295734) +++ head/sys/dev/jme/if_jme.c (revision 295735) @@ -1,3457 +1,3457 @@ /*- * Copyright (c) 2008, Pyun YongHyeon * 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 unmodified, 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* "device miibus" required. See GENERIC if you get errors here. */ #include "miibus_if.h" /* Define the following to disable printing Rx errors. */ #undef JME_SHOW_ERRORS #define JME_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP) MODULE_DEPEND(jme, pci, 1, 1, 1); MODULE_DEPEND(jme, ether, 1, 1, 1); MODULE_DEPEND(jme, miibus, 1, 1, 1); /* Tunables. */ static int msi_disable = 0; static int msix_disable = 0; TUNABLE_INT("hw.jme.msi_disable", &msi_disable); TUNABLE_INT("hw.jme.msix_disable", &msix_disable); /* * Devices supported by this driver. */ static struct jme_dev { uint16_t jme_vendorid; uint16_t jme_deviceid; const char *jme_name; } jme_devs[] = { { VENDORID_JMICRON, DEVICEID_JMC250, "JMicron Inc, JMC25x Gigabit Ethernet" }, { VENDORID_JMICRON, DEVICEID_JMC260, "JMicron Inc, JMC26x Fast Ethernet" }, }; static int jme_miibus_readreg(device_t, int, int); static int jme_miibus_writereg(device_t, int, int, int); static void jme_miibus_statchg(device_t); static void jme_mediastatus(struct ifnet *, struct ifmediareq *); static int jme_mediachange(struct ifnet *); static int jme_probe(device_t); static int jme_eeprom_read_byte(struct jme_softc *, uint8_t, uint8_t *); static int jme_eeprom_macaddr(struct jme_softc *); static int jme_efuse_macaddr(struct jme_softc *); static void jme_reg_macaddr(struct jme_softc *); static void jme_set_macaddr(struct jme_softc *, uint8_t *); static void jme_map_intr_vector(struct jme_softc *); static int jme_attach(device_t); static int jme_detach(device_t); static void jme_sysctl_node(struct jme_softc *); static void jme_dmamap_cb(void *, bus_dma_segment_t *, int, int); static int jme_dma_alloc(struct jme_softc *); static void jme_dma_free(struct jme_softc *); static int jme_shutdown(device_t); static void jme_setlinkspeed(struct jme_softc *); static void jme_setwol(struct jme_softc *); static int jme_suspend(device_t); static int jme_resume(device_t); static int jme_encap(struct jme_softc *, struct mbuf **); static void jme_start(struct ifnet *); static void jme_start_locked(struct ifnet *); static void jme_watchdog(struct jme_softc *); static int jme_ioctl(struct ifnet *, u_long, caddr_t); static void jme_mac_config(struct jme_softc *); static void jme_link_task(void *, int); static int jme_intr(void *); static void jme_int_task(void *, int); static void jme_txeof(struct jme_softc *); static __inline void jme_discard_rxbuf(struct jme_softc *, int); static void jme_rxeof(struct jme_softc *); static int jme_rxintr(struct jme_softc *, int); static void jme_tick(void *); static void jme_reset(struct jme_softc *); static void jme_init(void *); static void jme_init_locked(struct jme_softc *); static void jme_stop(struct jme_softc *); static void jme_stop_tx(struct jme_softc *); static void jme_stop_rx(struct jme_softc *); static int jme_init_rx_ring(struct jme_softc *); static void jme_init_tx_ring(struct jme_softc *); static void jme_init_ssb(struct jme_softc *); static int jme_newbuf(struct jme_softc *, struct jme_rxdesc *); static void jme_set_vlan(struct jme_softc *); static void jme_set_filter(struct jme_softc *); static void jme_stats_clear(struct jme_softc *); static void jme_stats_save(struct jme_softc *); static void jme_stats_update(struct jme_softc *); static void jme_phy_down(struct jme_softc *); static void jme_phy_up(struct jme_softc *); static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int, int); static int sysctl_hw_jme_tx_coal_to(SYSCTL_HANDLER_ARGS); static int sysctl_hw_jme_tx_coal_pkt(SYSCTL_HANDLER_ARGS); static int sysctl_hw_jme_rx_coal_to(SYSCTL_HANDLER_ARGS); static int sysctl_hw_jme_rx_coal_pkt(SYSCTL_HANDLER_ARGS); static int sysctl_hw_jme_proc_limit(SYSCTL_HANDLER_ARGS); static device_method_t jme_methods[] = { /* Device interface. */ DEVMETHOD(device_probe, jme_probe), DEVMETHOD(device_attach, jme_attach), DEVMETHOD(device_detach, jme_detach), DEVMETHOD(device_shutdown, jme_shutdown), DEVMETHOD(device_suspend, jme_suspend), DEVMETHOD(device_resume, jme_resume), /* MII interface. */ DEVMETHOD(miibus_readreg, jme_miibus_readreg), DEVMETHOD(miibus_writereg, jme_miibus_writereg), DEVMETHOD(miibus_statchg, jme_miibus_statchg), { NULL, NULL } }; static driver_t jme_driver = { "jme", jme_methods, sizeof(struct jme_softc) }; static devclass_t jme_devclass; DRIVER_MODULE(jme, pci, jme_driver, jme_devclass, 0, 0); DRIVER_MODULE(miibus, jme, miibus_driver, miibus_devclass, 0, 0); static struct resource_spec jme_res_spec_mem[] = { { SYS_RES_MEMORY, PCIR_BAR(0), RF_ACTIVE }, { -1, 0, 0 } }; static struct resource_spec jme_irq_spec_legacy[] = { { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0, 0 } }; static struct resource_spec jme_irq_spec_msi[] = { { SYS_RES_IRQ, 1, RF_ACTIVE }, { -1, 0, 0 } }; /* * Read a PHY register on the MII of the JMC250. */ static int jme_miibus_readreg(device_t dev, int phy, int reg) { struct jme_softc *sc; uint32_t val; int i; sc = device_get_softc(dev); /* For FPGA version, PHY address 0 should be ignored. */ if ((sc->jme_flags & JME_FLAG_FPGA) != 0 && phy == 0) return (0); CSR_WRITE_4(sc, JME_SMI, SMI_OP_READ | SMI_OP_EXECUTE | SMI_PHY_ADDR(phy) | SMI_REG_ADDR(reg)); for (i = JME_PHY_TIMEOUT; i > 0; i--) { DELAY(1); if (((val = CSR_READ_4(sc, JME_SMI)) & SMI_OP_EXECUTE) == 0) break; } if (i == 0) { device_printf(sc->jme_dev, "phy read timeout : %d\n", reg); return (0); } return ((val & SMI_DATA_MASK) >> SMI_DATA_SHIFT); } /* * Write a PHY register on the MII of the JMC250. */ static int jme_miibus_writereg(device_t dev, int phy, int reg, int val) { struct jme_softc *sc; int i; sc = device_get_softc(dev); /* For FPGA version, PHY address 0 should be ignored. */ if ((sc->jme_flags & JME_FLAG_FPGA) != 0 && phy == 0) return (0); CSR_WRITE_4(sc, JME_SMI, SMI_OP_WRITE | SMI_OP_EXECUTE | ((val << SMI_DATA_SHIFT) & SMI_DATA_MASK) | SMI_PHY_ADDR(phy) | SMI_REG_ADDR(reg)); for (i = JME_PHY_TIMEOUT; i > 0; i--) { DELAY(1); if (((val = CSR_READ_4(sc, JME_SMI)) & SMI_OP_EXECUTE) == 0) break; } if (i == 0) device_printf(sc->jme_dev, "phy write timeout : %d\n", reg); return (0); } /* * Callback from MII layer when media changes. */ static void jme_miibus_statchg(device_t dev) { struct jme_softc *sc; sc = device_get_softc(dev); taskqueue_enqueue(taskqueue_swi, &sc->jme_link_task); } /* * Get the current interface media status. */ static void jme_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr) { struct jme_softc *sc; struct mii_data *mii; sc = ifp->if_softc; JME_LOCK(sc); if ((ifp->if_flags & IFF_UP) == 0) { JME_UNLOCK(sc); return; } mii = device_get_softc(sc->jme_miibus); mii_pollstat(mii); ifmr->ifm_status = mii->mii_media_status; ifmr->ifm_active = mii->mii_media_active; JME_UNLOCK(sc); } /* * Set hardware to newly-selected media. */ static int jme_mediachange(struct ifnet *ifp) { struct jme_softc *sc; struct mii_data *mii; struct mii_softc *miisc; int error; sc = ifp->if_softc; JME_LOCK(sc); mii = device_get_softc(sc->jme_miibus); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); JME_UNLOCK(sc); return (error); } static int jme_probe(device_t dev) { struct jme_dev *sp; int i; uint16_t vendor, devid; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); sp = jme_devs; for (i = 0; i < sizeof(jme_devs) / sizeof(jme_devs[0]); i++, sp++) { if (vendor == sp->jme_vendorid && devid == sp->jme_deviceid) { device_set_desc(dev, sp->jme_name); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int jme_eeprom_read_byte(struct jme_softc *sc, uint8_t addr, uint8_t *val) { uint32_t reg; int i; *val = 0; for (i = JME_TIMEOUT; i > 0; i--) { reg = CSR_READ_4(sc, JME_SMBCSR); if ((reg & SMBCSR_HW_BUSY_MASK) == SMBCSR_HW_IDLE) break; DELAY(1); } if (i == 0) { device_printf(sc->jme_dev, "EEPROM idle timeout!\n"); return (ETIMEDOUT); } reg = ((uint32_t)addr << SMBINTF_ADDR_SHIFT) & SMBINTF_ADDR_MASK; CSR_WRITE_4(sc, JME_SMBINTF, reg | SMBINTF_RD | SMBINTF_CMD_TRIGGER); for (i = JME_TIMEOUT; i > 0; i--) { DELAY(1); reg = CSR_READ_4(sc, JME_SMBINTF); if ((reg & SMBINTF_CMD_TRIGGER) == 0) break; } if (i == 0) { device_printf(sc->jme_dev, "EEPROM read timeout!\n"); return (ETIMEDOUT); } reg = CSR_READ_4(sc, JME_SMBINTF); *val = (reg & SMBINTF_RD_DATA_MASK) >> SMBINTF_RD_DATA_SHIFT; return (0); } static int jme_eeprom_macaddr(struct jme_softc *sc) { uint8_t eaddr[ETHER_ADDR_LEN]; uint8_t fup, reg, val; uint32_t offset; int match; offset = 0; if (jme_eeprom_read_byte(sc, offset++, &fup) != 0 || fup != JME_EEPROM_SIG0) return (ENOENT); if (jme_eeprom_read_byte(sc, offset++, &fup) != 0 || fup != JME_EEPROM_SIG1) return (ENOENT); match = 0; do { if (jme_eeprom_read_byte(sc, offset, &fup) != 0) break; if (JME_EEPROM_MKDESC(JME_EEPROM_FUNC0, JME_EEPROM_PAGE_BAR1) == (fup & (JME_EEPROM_FUNC_MASK | JME_EEPROM_PAGE_MASK))) { if (jme_eeprom_read_byte(sc, offset + 1, ®) != 0) break; if (reg >= JME_PAR0 && reg < JME_PAR0 + ETHER_ADDR_LEN) { if (jme_eeprom_read_byte(sc, offset + 2, &val) != 0) break; eaddr[reg - JME_PAR0] = val; match++; } } /* Check for the end of EEPROM descriptor. */ if ((fup & JME_EEPROM_DESC_END) == JME_EEPROM_DESC_END) break; /* Try next eeprom descriptor. */ offset += JME_EEPROM_DESC_BYTES; } while (match != ETHER_ADDR_LEN && offset < JME_EEPROM_END); if (match == ETHER_ADDR_LEN) { bcopy(eaddr, sc->jme_eaddr, ETHER_ADDR_LEN); return (0); } return (ENOENT); } static int jme_efuse_macaddr(struct jme_softc *sc) { uint32_t reg; int i; reg = pci_read_config(sc->jme_dev, JME_EFUSE_CTL1, 4); if ((reg & (EFUSE_CTL1_AUTOLOAD_ERR | EFUSE_CTL1_AUTOLAOD_DONE)) != EFUSE_CTL1_AUTOLAOD_DONE) return (ENOENT); /* Reset eFuse controller. */ reg = pci_read_config(sc->jme_dev, JME_EFUSE_CTL2, 4); reg |= EFUSE_CTL2_RESET; pci_write_config(sc->jme_dev, JME_EFUSE_CTL2, reg, 4); reg = pci_read_config(sc->jme_dev, JME_EFUSE_CTL2, 4); reg &= ~EFUSE_CTL2_RESET; pci_write_config(sc->jme_dev, JME_EFUSE_CTL2, reg, 4); /* Have eFuse reload station address to MAC controller. */ reg = pci_read_config(sc->jme_dev, JME_EFUSE_CTL1, 4); reg &= ~EFUSE_CTL1_CMD_MASK; reg |= EFUSE_CTL1_CMD_AUTOLOAD | EFUSE_CTL1_EXECUTE; pci_write_config(sc->jme_dev, JME_EFUSE_CTL1, reg, 4); /* * Verify completion of eFuse autload command. It should be * completed within 108us. */ DELAY(110); for (i = 10; i > 0; i--) { reg = pci_read_config(sc->jme_dev, JME_EFUSE_CTL1, 4); if ((reg & (EFUSE_CTL1_AUTOLOAD_ERR | EFUSE_CTL1_AUTOLAOD_DONE)) != EFUSE_CTL1_AUTOLAOD_DONE) { DELAY(20); continue; } if ((reg & EFUSE_CTL1_EXECUTE) == 0) break; /* Station address loading is still in progress. */ DELAY(20); } if (i == 0) { device_printf(sc->jme_dev, "eFuse autoload timed out.\n"); return (ETIMEDOUT); } return (0); } static void jme_reg_macaddr(struct jme_softc *sc) { uint32_t par0, par1; /* Read station address. */ par0 = CSR_READ_4(sc, JME_PAR0); par1 = CSR_READ_4(sc, JME_PAR1); par1 &= 0xFFFF; if ((par0 == 0 && par1 == 0) || (par0 == 0xFFFFFFFF && par1 == 0xFFFF)) { device_printf(sc->jme_dev, "Failed to retrieve Ethernet address.\n"); } else { /* * For controllers that use eFuse, the station address * could also be extracted from JME_PCI_PAR0 and * JME_PCI_PAR1 registers in PCI configuration space. * Each register holds exactly half of station address(24bits) * so use JME_PAR0, JME_PAR1 registers instead. */ sc->jme_eaddr[0] = (par0 >> 0) & 0xFF; sc->jme_eaddr[1] = (par0 >> 8) & 0xFF; sc->jme_eaddr[2] = (par0 >> 16) & 0xFF; sc->jme_eaddr[3] = (par0 >> 24) & 0xFF; sc->jme_eaddr[4] = (par1 >> 0) & 0xFF; sc->jme_eaddr[5] = (par1 >> 8) & 0xFF; } } static void jme_set_macaddr(struct jme_softc *sc, uint8_t *eaddr) { uint32_t val; int i; if ((sc->jme_flags & JME_FLAG_EFUSE) != 0) { /* * Avoid reprogramming station address if the address * is the same as previous one. Note, reprogrammed * station address is permanent as if it was written * to EEPROM. So if station address was changed by * admistrator it's possible to lose factory configured * address when driver fails to restore its address. * (e.g. reboot or system crash) */ if (bcmp(eaddr, sc->jme_eaddr, ETHER_ADDR_LEN) != 0) { for (i = 0; i < ETHER_ADDR_LEN; i++) { val = JME_EFUSE_EEPROM_FUNC0 << JME_EFUSE_EEPROM_FUNC_SHIFT; val |= JME_EFUSE_EEPROM_PAGE_BAR1 << JME_EFUSE_EEPROM_PAGE_SHIFT; val |= (JME_PAR0 + i) << JME_EFUSE_EEPROM_ADDR_SHIFT; val |= eaddr[i] << JME_EFUSE_EEPROM_DATA_SHIFT; pci_write_config(sc->jme_dev, JME_EFUSE_EEPROM, val | JME_EFUSE_EEPROM_WRITE, 4); } } } else { CSR_WRITE_4(sc, JME_PAR0, eaddr[3] << 24 | eaddr[2] << 16 | eaddr[1] << 8 | eaddr[0]); CSR_WRITE_4(sc, JME_PAR1, eaddr[5] << 8 | eaddr[4]); } } static void jme_map_intr_vector(struct jme_softc *sc) { uint32_t map[MSINUM_NUM_INTR_SOURCE / JME_MSI_MESSAGES]; bzero(map, sizeof(map)); /* Map Tx interrupts source to MSI/MSIX vector 2. */ map[MSINUM_REG_INDEX(N_INTR_TXQ0_COMP)] = MSINUM_INTR_SOURCE(2, N_INTR_TXQ0_COMP); map[MSINUM_REG_INDEX(N_INTR_TXQ1_COMP)] |= MSINUM_INTR_SOURCE(2, N_INTR_TXQ1_COMP); map[MSINUM_REG_INDEX(N_INTR_TXQ2_COMP)] |= MSINUM_INTR_SOURCE(2, N_INTR_TXQ2_COMP); map[MSINUM_REG_INDEX(N_INTR_TXQ3_COMP)] |= MSINUM_INTR_SOURCE(2, N_INTR_TXQ3_COMP); map[MSINUM_REG_INDEX(N_INTR_TXQ4_COMP)] |= MSINUM_INTR_SOURCE(2, N_INTR_TXQ4_COMP); map[MSINUM_REG_INDEX(N_INTR_TXQ4_COMP)] |= MSINUM_INTR_SOURCE(2, N_INTR_TXQ5_COMP); map[MSINUM_REG_INDEX(N_INTR_TXQ6_COMP)] |= MSINUM_INTR_SOURCE(2, N_INTR_TXQ6_COMP); map[MSINUM_REG_INDEX(N_INTR_TXQ7_COMP)] |= MSINUM_INTR_SOURCE(2, N_INTR_TXQ7_COMP); map[MSINUM_REG_INDEX(N_INTR_TXQ_COAL)] |= MSINUM_INTR_SOURCE(2, N_INTR_TXQ_COAL); map[MSINUM_REG_INDEX(N_INTR_TXQ_COAL_TO)] |= MSINUM_INTR_SOURCE(2, N_INTR_TXQ_COAL_TO); /* Map Rx interrupts source to MSI/MSIX vector 1. */ map[MSINUM_REG_INDEX(N_INTR_RXQ0_COMP)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ0_COMP); map[MSINUM_REG_INDEX(N_INTR_RXQ1_COMP)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ1_COMP); map[MSINUM_REG_INDEX(N_INTR_RXQ2_COMP)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ2_COMP); map[MSINUM_REG_INDEX(N_INTR_RXQ3_COMP)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ3_COMP); map[MSINUM_REG_INDEX(N_INTR_RXQ0_DESC_EMPTY)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ0_DESC_EMPTY); map[MSINUM_REG_INDEX(N_INTR_RXQ1_DESC_EMPTY)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ1_DESC_EMPTY); map[MSINUM_REG_INDEX(N_INTR_RXQ2_DESC_EMPTY)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ2_DESC_EMPTY); map[MSINUM_REG_INDEX(N_INTR_RXQ3_DESC_EMPTY)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ3_DESC_EMPTY); map[MSINUM_REG_INDEX(N_INTR_RXQ0_COAL)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ0_COAL); map[MSINUM_REG_INDEX(N_INTR_RXQ1_COAL)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ1_COAL); map[MSINUM_REG_INDEX(N_INTR_RXQ2_COAL)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ2_COAL); map[MSINUM_REG_INDEX(N_INTR_RXQ3_COAL)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ3_COAL); map[MSINUM_REG_INDEX(N_INTR_RXQ0_COAL_TO)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ0_COAL_TO); map[MSINUM_REG_INDEX(N_INTR_RXQ1_COAL_TO)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ1_COAL_TO); map[MSINUM_REG_INDEX(N_INTR_RXQ2_COAL_TO)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ2_COAL_TO); map[MSINUM_REG_INDEX(N_INTR_RXQ3_COAL_TO)] = MSINUM_INTR_SOURCE(1, N_INTR_RXQ3_COAL_TO); /* Map all other interrupts source to MSI/MSIX vector 0. */ CSR_WRITE_4(sc, JME_MSINUM_BASE + sizeof(uint32_t) * 0, map[0]); CSR_WRITE_4(sc, JME_MSINUM_BASE + sizeof(uint32_t) * 1, map[1]); CSR_WRITE_4(sc, JME_MSINUM_BASE + sizeof(uint32_t) * 2, map[2]); CSR_WRITE_4(sc, JME_MSINUM_BASE + sizeof(uint32_t) * 3, map[3]); } static int jme_attach(device_t dev) { struct jme_softc *sc; struct ifnet *ifp; struct mii_softc *miisc; struct mii_data *mii; uint32_t reg; uint16_t burst; int error, i, mii_flags, msic, msixc, pmc; error = 0; sc = device_get_softc(dev); sc->jme_dev = dev; mtx_init(&sc->jme_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init_mtx(&sc->jme_tick_ch, &sc->jme_mtx, 0); TASK_INIT(&sc->jme_int_task, 0, jme_int_task, sc); TASK_INIT(&sc->jme_link_task, 0, jme_link_task, sc); /* * Map the device. JMC250 supports both memory mapped and I/O * register space access. Because I/O register access should * use different BARs to access registers it's waste of time * to use I/O register spce access. JMC250 uses 16K to map * entire memory space. */ pci_enable_busmaster(dev); sc->jme_res_spec = jme_res_spec_mem; sc->jme_irq_spec = jme_irq_spec_legacy; error = bus_alloc_resources(dev, sc->jme_res_spec, sc->jme_res); if (error != 0) { device_printf(dev, "cannot allocate memory resources.\n"); goto fail; } /* Allocate IRQ resources. */ msixc = pci_msix_count(dev); msic = pci_msi_count(dev); if (bootverbose) { device_printf(dev, "MSIX count : %d\n", msixc); device_printf(dev, "MSI count : %d\n", msic); } /* Use 1 MSI/MSI-X. */ if (msixc > 1) msixc = 1; if (msic > 1) msic = 1; /* Prefer MSIX over MSI. */ if (msix_disable == 0 || msi_disable == 0) { if (msix_disable == 0 && msixc > 0 && pci_alloc_msix(dev, &msixc) == 0) { if (msixc == 1) { device_printf(dev, "Using %d MSIX messages.\n", msixc); sc->jme_flags |= JME_FLAG_MSIX; sc->jme_irq_spec = jme_irq_spec_msi; } else pci_release_msi(dev); } if (msi_disable == 0 && (sc->jme_flags & JME_FLAG_MSIX) == 0 && msic > 0 && pci_alloc_msi(dev, &msic) == 0) { if (msic == 1) { device_printf(dev, "Using %d MSI messages.\n", msic); sc->jme_flags |= JME_FLAG_MSI; sc->jme_irq_spec = jme_irq_spec_msi; } else pci_release_msi(dev); } /* Map interrupt vector 0, 1 and 2. */ if ((sc->jme_flags & JME_FLAG_MSI) != 0 || (sc->jme_flags & JME_FLAG_MSIX) != 0) jme_map_intr_vector(sc); } error = bus_alloc_resources(dev, sc->jme_irq_spec, sc->jme_irq); if (error != 0) { device_printf(dev, "cannot allocate IRQ resources.\n"); goto fail; } sc->jme_rev = pci_get_device(dev); if ((sc->jme_rev & DEVICEID_JMC2XX_MASK) == DEVICEID_JMC260) { sc->jme_flags |= JME_FLAG_FASTETH; sc->jme_flags |= JME_FLAG_NOJUMBO; } reg = CSR_READ_4(sc, JME_CHIPMODE); sc->jme_chip_rev = (reg & CHIPMODE_REV_MASK) >> CHIPMODE_REV_SHIFT; if (((reg & CHIPMODE_FPGA_REV_MASK) >> CHIPMODE_FPGA_REV_SHIFT) != CHIPMODE_NOT_FPGA) sc->jme_flags |= JME_FLAG_FPGA; if (bootverbose) { device_printf(dev, "PCI device revision : 0x%04x\n", sc->jme_rev); device_printf(dev, "Chip revision : 0x%02x\n", sc->jme_chip_rev); if ((sc->jme_flags & JME_FLAG_FPGA) != 0) device_printf(dev, "FPGA revision : 0x%04x\n", (reg & CHIPMODE_FPGA_REV_MASK) >> CHIPMODE_FPGA_REV_SHIFT); } if (sc->jme_chip_rev == 0xFF) { device_printf(dev, "Unknown chip revision : 0x%02x\n", sc->jme_rev); error = ENXIO; goto fail; } /* Identify controller features and bugs. */ if (CHIPMODE_REVFM(sc->jme_chip_rev) >= 2) { if ((sc->jme_rev & DEVICEID_JMC2XX_MASK) == DEVICEID_JMC260 && CHIPMODE_REVFM(sc->jme_chip_rev) == 2) sc->jme_flags |= JME_FLAG_DMA32BIT; if (CHIPMODE_REVFM(sc->jme_chip_rev) >= 5) sc->jme_flags |= JME_FLAG_EFUSE | JME_FLAG_PCCPCD; sc->jme_flags |= JME_FLAG_TXCLK | JME_FLAG_RXCLK; sc->jme_flags |= JME_FLAG_HWMIB; } /* Reset the ethernet controller. */ jme_reset(sc); /* Get station address. */ if ((sc->jme_flags & JME_FLAG_EFUSE) != 0) { error = jme_efuse_macaddr(sc); if (error == 0) jme_reg_macaddr(sc); } else { error = ENOENT; reg = CSR_READ_4(sc, JME_SMBCSR); if ((reg & SMBCSR_EEPROM_PRESENT) != 0) error = jme_eeprom_macaddr(sc); if (error != 0 && bootverbose) device_printf(sc->jme_dev, "ethernet hardware address not found in EEPROM.\n"); if (error != 0) jme_reg_macaddr(sc); } /* * Save PHY address. * Integrated JR0211 has fixed PHY address whereas FPGA version * requires PHY probing to get correct PHY address. */ if ((sc->jme_flags & JME_FLAG_FPGA) == 0) { sc->jme_phyaddr = CSR_READ_4(sc, JME_GPREG0) & GPREG0_PHY_ADDR_MASK; if (bootverbose) device_printf(dev, "PHY is at address %d.\n", sc->jme_phyaddr); } else sc->jme_phyaddr = 0; /* Set max allowable DMA size. */ if (pci_find_cap(dev, PCIY_EXPRESS, &i) == 0) { sc->jme_flags |= JME_FLAG_PCIE; burst = pci_read_config(dev, i + PCIER_DEVICE_CTL, 2); if (bootverbose) { device_printf(dev, "Read request size : %d bytes.\n", 128 << ((burst >> 12) & 0x07)); device_printf(dev, "TLP payload size : %d bytes.\n", 128 << ((burst >> 5) & 0x07)); } switch ((burst >> 12) & 0x07) { case 0: sc->jme_tx_dma_size = TXCSR_DMA_SIZE_128; break; case 1: sc->jme_tx_dma_size = TXCSR_DMA_SIZE_256; break; default: sc->jme_tx_dma_size = TXCSR_DMA_SIZE_512; break; } sc->jme_rx_dma_size = RXCSR_DMA_SIZE_128; } else { sc->jme_tx_dma_size = TXCSR_DMA_SIZE_512; sc->jme_rx_dma_size = RXCSR_DMA_SIZE_128; } /* Create coalescing sysctl node. */ jme_sysctl_node(sc); - if ((error = jme_dma_alloc(sc) != 0)) + if ((error = jme_dma_alloc(sc)) != 0) goto fail; ifp = sc->jme_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(dev, "cannot allocate ifnet structure.\n"); error = ENXIO; goto fail; } ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = jme_ioctl; ifp->if_start = jme_start; ifp->if_init = jme_init; ifp->if_snd.ifq_drv_maxlen = JME_TX_RING_CNT - 1; IFQ_SET_MAXLEN(&ifp->if_snd, ifp->if_snd.ifq_drv_maxlen); IFQ_SET_READY(&ifp->if_snd); /* JMC250 supports Tx/Rx checksum offload as well as TSO. */ ifp->if_capabilities = IFCAP_HWCSUM | IFCAP_TSO4; ifp->if_hwassist = JME_CSUM_FEATURES | CSUM_TSO; if (pci_find_cap(dev, PCIY_PMG, &pmc) == 0) { sc->jme_flags |= JME_FLAG_PMCAP; ifp->if_capabilities |= IFCAP_WOL_MAGIC; } ifp->if_capenable = ifp->if_capabilities; /* Wakeup PHY. */ jme_phy_up(sc); mii_flags = MIIF_DOPAUSE; /* Ask PHY calibration to PHY driver. */ if (CHIPMODE_REVFM(sc->jme_chip_rev) >= 5) mii_flags |= MIIF_MACPRIV0; /* Set up MII bus. */ error = mii_attach(dev, &sc->jme_miibus, ifp, jme_mediachange, jme_mediastatus, BMSR_DEFCAPMASK, sc->jme_flags & JME_FLAG_FPGA ? MII_PHY_ANY : sc->jme_phyaddr, MII_OFFSET_ANY, mii_flags); if (error != 0) { device_printf(dev, "attaching PHYs failed\n"); goto fail; } /* * Force PHY to FPGA mode. */ if ((sc->jme_flags & JME_FLAG_FPGA) != 0) { mii = device_get_softc(sc->jme_miibus); if (mii->mii_instance != 0) { LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { if (miisc->mii_phy != 0) { sc->jme_phyaddr = miisc->mii_phy; break; } } if (sc->jme_phyaddr != 0) { device_printf(sc->jme_dev, "FPGA PHY is at %d\n", sc->jme_phyaddr); /* vendor magic. */ jme_miibus_writereg(dev, sc->jme_phyaddr, 27, 0x0004); } } } ether_ifattach(ifp, sc->jme_eaddr); /* VLAN capability setup */ ifp->if_capabilities |= IFCAP_VLAN_MTU | IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_HWCSUM | IFCAP_VLAN_HWTSO; ifp->if_capenable = ifp->if_capabilities; /* Tell the upper layer(s) we support long frames. */ ifp->if_hdrlen = sizeof(struct ether_vlan_header); /* Create local taskq. */ sc->jme_tq = taskqueue_create_fast("jme_taskq", M_WAITOK, taskqueue_thread_enqueue, &sc->jme_tq); if (sc->jme_tq == NULL) { device_printf(dev, "could not create taskqueue.\n"); ether_ifdetach(ifp); error = ENXIO; goto fail; } taskqueue_start_threads(&sc->jme_tq, 1, PI_NET, "%s taskq", device_get_nameunit(sc->jme_dev)); for (i = 0; i < 1; i++) { error = bus_setup_intr(dev, sc->jme_irq[i], INTR_TYPE_NET | INTR_MPSAFE, jme_intr, NULL, sc, &sc->jme_intrhand[i]); if (error != 0) break; } if (error != 0) { device_printf(dev, "could not set up interrupt handler.\n"); taskqueue_free(sc->jme_tq); sc->jme_tq = NULL; ether_ifdetach(ifp); goto fail; } fail: if (error != 0) jme_detach(dev); return (error); } static int jme_detach(device_t dev) { struct jme_softc *sc; struct ifnet *ifp; int i; sc = device_get_softc(dev); ifp = sc->jme_ifp; if (device_is_attached(dev)) { JME_LOCK(sc); sc->jme_flags |= JME_FLAG_DETACH; jme_stop(sc); JME_UNLOCK(sc); callout_drain(&sc->jme_tick_ch); taskqueue_drain(sc->jme_tq, &sc->jme_int_task); taskqueue_drain(taskqueue_swi, &sc->jme_link_task); /* Restore possibly modified station address. */ if ((sc->jme_flags & JME_FLAG_EFUSE) != 0) jme_set_macaddr(sc, sc->jme_eaddr); ether_ifdetach(ifp); } if (sc->jme_tq != NULL) { taskqueue_drain(sc->jme_tq, &sc->jme_int_task); taskqueue_free(sc->jme_tq); sc->jme_tq = NULL; } if (sc->jme_miibus != NULL) { device_delete_child(dev, sc->jme_miibus); sc->jme_miibus = NULL; } bus_generic_detach(dev); jme_dma_free(sc); if (ifp != NULL) { if_free(ifp); sc->jme_ifp = NULL; } for (i = 0; i < 1; i++) { if (sc->jme_intrhand[i] != NULL) { bus_teardown_intr(dev, sc->jme_irq[i], sc->jme_intrhand[i]); sc->jme_intrhand[i] = NULL; } } if (sc->jme_irq[0] != NULL) bus_release_resources(dev, sc->jme_irq_spec, sc->jme_irq); if ((sc->jme_flags & (JME_FLAG_MSIX | JME_FLAG_MSI)) != 0) pci_release_msi(dev); if (sc->jme_res[0] != NULL) bus_release_resources(dev, sc->jme_res_spec, sc->jme_res); mtx_destroy(&sc->jme_mtx); return (0); } #define JME_SYSCTL_STAT_ADD32(c, h, n, p, d) \ SYSCTL_ADD_UINT(c, h, OID_AUTO, n, CTLFLAG_RD, p, 0, d) static void jme_sysctl_node(struct jme_softc *sc) { struct sysctl_ctx_list *ctx; struct sysctl_oid_list *child, *parent; struct sysctl_oid *tree; struct jme_hw_stats *stats; int error; stats = &sc->jme_stats; ctx = device_get_sysctl_ctx(sc->jme_dev); child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->jme_dev)); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "tx_coal_to", CTLTYPE_INT | CTLFLAG_RW, &sc->jme_tx_coal_to, 0, sysctl_hw_jme_tx_coal_to, "I", "jme tx coalescing timeout"); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "tx_coal_pkt", CTLTYPE_INT | CTLFLAG_RW, &sc->jme_tx_coal_pkt, 0, sysctl_hw_jme_tx_coal_pkt, "I", "jme tx coalescing packet"); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "rx_coal_to", CTLTYPE_INT | CTLFLAG_RW, &sc->jme_rx_coal_to, 0, sysctl_hw_jme_rx_coal_to, "I", "jme rx coalescing timeout"); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "rx_coal_pkt", CTLTYPE_INT | CTLFLAG_RW, &sc->jme_rx_coal_pkt, 0, sysctl_hw_jme_rx_coal_pkt, "I", "jme rx coalescing packet"); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "process_limit", CTLTYPE_INT | CTLFLAG_RW, &sc->jme_process_limit, 0, sysctl_hw_jme_proc_limit, "I", "max number of Rx events to process"); /* Pull in device tunables. */ sc->jme_process_limit = JME_PROC_DEFAULT; error = resource_int_value(device_get_name(sc->jme_dev), device_get_unit(sc->jme_dev), "process_limit", &sc->jme_process_limit); if (error == 0) { if (sc->jme_process_limit < JME_PROC_MIN || sc->jme_process_limit > JME_PROC_MAX) { device_printf(sc->jme_dev, "process_limit value out of range; " "using default: %d\n", JME_PROC_DEFAULT); sc->jme_process_limit = JME_PROC_DEFAULT; } } sc->jme_tx_coal_to = PCCTX_COAL_TO_DEFAULT; error = resource_int_value(device_get_name(sc->jme_dev), device_get_unit(sc->jme_dev), "tx_coal_to", &sc->jme_tx_coal_to); if (error == 0) { if (sc->jme_tx_coal_to < PCCTX_COAL_TO_MIN || sc->jme_tx_coal_to > PCCTX_COAL_TO_MAX) { device_printf(sc->jme_dev, "tx_coal_to value out of range; " "using default: %d\n", PCCTX_COAL_TO_DEFAULT); sc->jme_tx_coal_to = PCCTX_COAL_TO_DEFAULT; } } sc->jme_tx_coal_pkt = PCCTX_COAL_PKT_DEFAULT; error = resource_int_value(device_get_name(sc->jme_dev), device_get_unit(sc->jme_dev), "tx_coal_pkt", &sc->jme_tx_coal_to); if (error == 0) { if (sc->jme_tx_coal_pkt < PCCTX_COAL_PKT_MIN || sc->jme_tx_coal_pkt > PCCTX_COAL_PKT_MAX) { device_printf(sc->jme_dev, "tx_coal_pkt value out of range; " "using default: %d\n", PCCTX_COAL_PKT_DEFAULT); sc->jme_tx_coal_pkt = PCCTX_COAL_PKT_DEFAULT; } } sc->jme_rx_coal_to = PCCRX_COAL_TO_DEFAULT; error = resource_int_value(device_get_name(sc->jme_dev), device_get_unit(sc->jme_dev), "rx_coal_to", &sc->jme_rx_coal_to); if (error == 0) { if (sc->jme_rx_coal_to < PCCRX_COAL_TO_MIN || sc->jme_rx_coal_to > PCCRX_COAL_TO_MAX) { device_printf(sc->jme_dev, "rx_coal_to value out of range; " "using default: %d\n", PCCRX_COAL_TO_DEFAULT); sc->jme_rx_coal_to = PCCRX_COAL_TO_DEFAULT; } } sc->jme_rx_coal_pkt = PCCRX_COAL_PKT_DEFAULT; error = resource_int_value(device_get_name(sc->jme_dev), device_get_unit(sc->jme_dev), "rx_coal_pkt", &sc->jme_rx_coal_to); if (error == 0) { if (sc->jme_rx_coal_pkt < PCCRX_COAL_PKT_MIN || sc->jme_rx_coal_pkt > PCCRX_COAL_PKT_MAX) { device_printf(sc->jme_dev, "tx_coal_pkt value out of range; " "using default: %d\n", PCCRX_COAL_PKT_DEFAULT); sc->jme_rx_coal_pkt = PCCRX_COAL_PKT_DEFAULT; } } if ((sc->jme_flags & JME_FLAG_HWMIB) == 0) return; tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", CTLFLAG_RD, NULL, "JME statistics"); parent = SYSCTL_CHILDREN(tree); /* Rx statistics. */ tree = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "rx", CTLFLAG_RD, NULL, "Rx MAC statistics"); child = SYSCTL_CHILDREN(tree); JME_SYSCTL_STAT_ADD32(ctx, child, "good_frames", &stats->rx_good_frames, "Good frames"); JME_SYSCTL_STAT_ADD32(ctx, child, "crc_errs", &stats->rx_crc_errs, "CRC errors"); JME_SYSCTL_STAT_ADD32(ctx, child, "mii_errs", &stats->rx_mii_errs, "MII errors"); JME_SYSCTL_STAT_ADD32(ctx, child, "fifo_oflows", &stats->rx_fifo_oflows, "FIFO overflows"); JME_SYSCTL_STAT_ADD32(ctx, child, "desc_empty", &stats->rx_desc_empty, "Descriptor empty"); JME_SYSCTL_STAT_ADD32(ctx, child, "bad_frames", &stats->rx_bad_frames, "Bad frames"); /* Tx statistics. */ tree = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "tx", CTLFLAG_RD, NULL, "Tx MAC statistics"); child = SYSCTL_CHILDREN(tree); JME_SYSCTL_STAT_ADD32(ctx, child, "good_frames", &stats->tx_good_frames, "Good frames"); JME_SYSCTL_STAT_ADD32(ctx, child, "bad_frames", &stats->tx_bad_frames, "Bad frames"); } #undef JME_SYSCTL_STAT_ADD32 struct jme_dmamap_arg { bus_addr_t jme_busaddr; }; static void jme_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { struct jme_dmamap_arg *ctx; if (error != 0) return; KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); ctx = (struct jme_dmamap_arg *)arg; ctx->jme_busaddr = segs[0].ds_addr; } static int jme_dma_alloc(struct jme_softc *sc) { struct jme_dmamap_arg ctx; struct jme_txdesc *txd; struct jme_rxdesc *rxd; bus_addr_t lowaddr, rx_ring_end, tx_ring_end; int error, i; lowaddr = BUS_SPACE_MAXADDR; if ((sc->jme_flags & JME_FLAG_DMA32BIT) != 0) lowaddr = BUS_SPACE_MAXADDR_32BIT; again: /* Create parent ring tag. */ error = bus_dma_tag_create(bus_get_dma_tag(sc->jme_dev),/* parent */ 1, 0, /* algnmnt, boundary */ lowaddr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ 0, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->jme_cdata.jme_ring_tag); if (error != 0) { device_printf(sc->jme_dev, "could not create parent ring DMA tag.\n"); goto fail; } /* Create tag for Tx ring. */ error = bus_dma_tag_create(sc->jme_cdata.jme_ring_tag,/* parent */ JME_TX_RING_ALIGN, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ JME_TX_RING_SIZE, /* maxsize */ 1, /* nsegments */ JME_TX_RING_SIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->jme_cdata.jme_tx_ring_tag); if (error != 0) { device_printf(sc->jme_dev, "could not allocate Tx ring DMA tag.\n"); goto fail; } /* Create tag for Rx ring. */ error = bus_dma_tag_create(sc->jme_cdata.jme_ring_tag,/* parent */ JME_RX_RING_ALIGN, 0, /* algnmnt, boundary */ lowaddr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ JME_RX_RING_SIZE, /* maxsize */ 1, /* nsegments */ JME_RX_RING_SIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->jme_cdata.jme_rx_ring_tag); if (error != 0) { device_printf(sc->jme_dev, "could not allocate Rx ring DMA tag.\n"); goto fail; } /* Allocate DMA'able memory and load the DMA map for Tx ring. */ error = bus_dmamem_alloc(sc->jme_cdata.jme_tx_ring_tag, (void **)&sc->jme_rdata.jme_tx_ring, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->jme_cdata.jme_tx_ring_map); if (error != 0) { device_printf(sc->jme_dev, "could not allocate DMA'able memory for Tx ring.\n"); goto fail; } ctx.jme_busaddr = 0; error = bus_dmamap_load(sc->jme_cdata.jme_tx_ring_tag, sc->jme_cdata.jme_tx_ring_map, sc->jme_rdata.jme_tx_ring, JME_TX_RING_SIZE, jme_dmamap_cb, &ctx, BUS_DMA_NOWAIT); if (error != 0 || ctx.jme_busaddr == 0) { device_printf(sc->jme_dev, "could not load DMA'able memory for Tx ring.\n"); goto fail; } sc->jme_rdata.jme_tx_ring_paddr = ctx.jme_busaddr; /* Allocate DMA'able memory and load the DMA map for Rx ring. */ error = bus_dmamem_alloc(sc->jme_cdata.jme_rx_ring_tag, (void **)&sc->jme_rdata.jme_rx_ring, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->jme_cdata.jme_rx_ring_map); if (error != 0) { device_printf(sc->jme_dev, "could not allocate DMA'able memory for Rx ring.\n"); goto fail; } ctx.jme_busaddr = 0; error = bus_dmamap_load(sc->jme_cdata.jme_rx_ring_tag, sc->jme_cdata.jme_rx_ring_map, sc->jme_rdata.jme_rx_ring, JME_RX_RING_SIZE, jme_dmamap_cb, &ctx, BUS_DMA_NOWAIT); if (error != 0 || ctx.jme_busaddr == 0) { device_printf(sc->jme_dev, "could not load DMA'able memory for Rx ring.\n"); goto fail; } sc->jme_rdata.jme_rx_ring_paddr = ctx.jme_busaddr; if (lowaddr != BUS_SPACE_MAXADDR_32BIT) { /* Tx/Rx descriptor queue should reside within 4GB boundary. */ tx_ring_end = sc->jme_rdata.jme_tx_ring_paddr + JME_TX_RING_SIZE; rx_ring_end = sc->jme_rdata.jme_rx_ring_paddr + JME_RX_RING_SIZE; if ((JME_ADDR_HI(tx_ring_end) != JME_ADDR_HI(sc->jme_rdata.jme_tx_ring_paddr)) || (JME_ADDR_HI(rx_ring_end) != JME_ADDR_HI(sc->jme_rdata.jme_rx_ring_paddr))) { device_printf(sc->jme_dev, "4GB boundary crossed, " "switching to 32bit DMA address mode.\n"); jme_dma_free(sc); /* Limit DMA address space to 32bit and try again. */ lowaddr = BUS_SPACE_MAXADDR_32BIT; goto again; } } lowaddr = BUS_SPACE_MAXADDR; if ((sc->jme_flags & JME_FLAG_DMA32BIT) != 0) lowaddr = BUS_SPACE_MAXADDR_32BIT; /* Create parent buffer tag. */ error = bus_dma_tag_create(bus_get_dma_tag(sc->jme_dev),/* parent */ 1, 0, /* algnmnt, boundary */ lowaddr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ 0, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->jme_cdata.jme_buffer_tag); if (error != 0) { device_printf(sc->jme_dev, "could not create parent buffer DMA tag.\n"); goto fail; } /* Create shadow status block tag. */ error = bus_dma_tag_create(sc->jme_cdata.jme_buffer_tag,/* parent */ JME_SSB_ALIGN, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ JME_SSB_SIZE, /* maxsize */ 1, /* nsegments */ JME_SSB_SIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->jme_cdata.jme_ssb_tag); if (error != 0) { device_printf(sc->jme_dev, "could not create shared status block DMA tag.\n"); goto fail; } /* Create tag for Tx buffers. */ error = bus_dma_tag_create(sc->jme_cdata.jme_buffer_tag,/* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ JME_TSO_MAXSIZE, /* maxsize */ JME_MAXTXSEGS, /* nsegments */ JME_TSO_MAXSEGSIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->jme_cdata.jme_tx_tag); if (error != 0) { device_printf(sc->jme_dev, "could not create Tx DMA tag.\n"); goto fail; } /* Create tag for Rx buffers. */ error = bus_dma_tag_create(sc->jme_cdata.jme_buffer_tag,/* parent */ JME_RX_BUF_ALIGN, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, /* maxsize */ 1, /* nsegments */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->jme_cdata.jme_rx_tag); if (error != 0) { device_printf(sc->jme_dev, "could not create Rx DMA tag.\n"); goto fail; } /* * Allocate DMA'able memory and load the DMA map for shared * status block. */ error = bus_dmamem_alloc(sc->jme_cdata.jme_ssb_tag, (void **)&sc->jme_rdata.jme_ssb_block, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->jme_cdata.jme_ssb_map); if (error != 0) { device_printf(sc->jme_dev, "could not allocate DMA'able " "memory for shared status block.\n"); goto fail; } ctx.jme_busaddr = 0; error = bus_dmamap_load(sc->jme_cdata.jme_ssb_tag, sc->jme_cdata.jme_ssb_map, sc->jme_rdata.jme_ssb_block, JME_SSB_SIZE, jme_dmamap_cb, &ctx, BUS_DMA_NOWAIT); if (error != 0 || ctx.jme_busaddr == 0) { device_printf(sc->jme_dev, "could not load DMA'able memory " "for shared status block.\n"); goto fail; } sc->jme_rdata.jme_ssb_block_paddr = ctx.jme_busaddr; /* Create DMA maps for Tx buffers. */ for (i = 0; i < JME_TX_RING_CNT; i++) { txd = &sc->jme_cdata.jme_txdesc[i]; txd->tx_m = NULL; txd->tx_dmamap = NULL; error = bus_dmamap_create(sc->jme_cdata.jme_tx_tag, 0, &txd->tx_dmamap); if (error != 0) { device_printf(sc->jme_dev, "could not create Tx dmamap.\n"); goto fail; } } /* Create DMA maps for Rx buffers. */ if ((error = bus_dmamap_create(sc->jme_cdata.jme_rx_tag, 0, &sc->jme_cdata.jme_rx_sparemap)) != 0) { device_printf(sc->jme_dev, "could not create spare Rx dmamap.\n"); goto fail; } for (i = 0; i < JME_RX_RING_CNT; i++) { rxd = &sc->jme_cdata.jme_rxdesc[i]; rxd->rx_m = NULL; rxd->rx_dmamap = NULL; error = bus_dmamap_create(sc->jme_cdata.jme_rx_tag, 0, &rxd->rx_dmamap); if (error != 0) { device_printf(sc->jme_dev, "could not create Rx dmamap.\n"); goto fail; } } fail: return (error); } static void jme_dma_free(struct jme_softc *sc) { struct jme_txdesc *txd; struct jme_rxdesc *rxd; int i; /* Tx ring */ if (sc->jme_cdata.jme_tx_ring_tag != NULL) { if (sc->jme_rdata.jme_tx_ring_paddr) bus_dmamap_unload(sc->jme_cdata.jme_tx_ring_tag, sc->jme_cdata.jme_tx_ring_map); if (sc->jme_rdata.jme_tx_ring) bus_dmamem_free(sc->jme_cdata.jme_tx_ring_tag, sc->jme_rdata.jme_tx_ring, sc->jme_cdata.jme_tx_ring_map); sc->jme_rdata.jme_tx_ring = NULL; sc->jme_rdata.jme_tx_ring_paddr = 0; bus_dma_tag_destroy(sc->jme_cdata.jme_tx_ring_tag); sc->jme_cdata.jme_tx_ring_tag = NULL; } /* Rx ring */ if (sc->jme_cdata.jme_rx_ring_tag != NULL) { if (sc->jme_rdata.jme_rx_ring_paddr) bus_dmamap_unload(sc->jme_cdata.jme_rx_ring_tag, sc->jme_cdata.jme_rx_ring_map); if (sc->jme_rdata.jme_rx_ring) bus_dmamem_free(sc->jme_cdata.jme_rx_ring_tag, sc->jme_rdata.jme_rx_ring, sc->jme_cdata.jme_rx_ring_map); sc->jme_rdata.jme_rx_ring = NULL; sc->jme_rdata.jme_rx_ring_paddr = 0; bus_dma_tag_destroy(sc->jme_cdata.jme_rx_ring_tag); sc->jme_cdata.jme_rx_ring_tag = NULL; } /* Tx buffers */ if (sc->jme_cdata.jme_tx_tag != NULL) { for (i = 0; i < JME_TX_RING_CNT; i++) { txd = &sc->jme_cdata.jme_txdesc[i]; if (txd->tx_dmamap != NULL) { bus_dmamap_destroy(sc->jme_cdata.jme_tx_tag, txd->tx_dmamap); txd->tx_dmamap = NULL; } } bus_dma_tag_destroy(sc->jme_cdata.jme_tx_tag); sc->jme_cdata.jme_tx_tag = NULL; } /* Rx buffers */ if (sc->jme_cdata.jme_rx_tag != NULL) { for (i = 0; i < JME_RX_RING_CNT; i++) { rxd = &sc->jme_cdata.jme_rxdesc[i]; if (rxd->rx_dmamap != NULL) { bus_dmamap_destroy(sc->jme_cdata.jme_rx_tag, rxd->rx_dmamap); rxd->rx_dmamap = NULL; } } if (sc->jme_cdata.jme_rx_sparemap != NULL) { bus_dmamap_destroy(sc->jme_cdata.jme_rx_tag, sc->jme_cdata.jme_rx_sparemap); sc->jme_cdata.jme_rx_sparemap = NULL; } bus_dma_tag_destroy(sc->jme_cdata.jme_rx_tag); sc->jme_cdata.jme_rx_tag = NULL; } /* Shared status block. */ if (sc->jme_cdata.jme_ssb_tag != NULL) { if (sc->jme_rdata.jme_ssb_block_paddr) bus_dmamap_unload(sc->jme_cdata.jme_ssb_tag, sc->jme_cdata.jme_ssb_map); if (sc->jme_rdata.jme_ssb_block) bus_dmamem_free(sc->jme_cdata.jme_ssb_tag, sc->jme_rdata.jme_ssb_block, sc->jme_cdata.jme_ssb_map); sc->jme_rdata.jme_ssb_block = NULL; sc->jme_rdata.jme_ssb_block_paddr = 0; bus_dma_tag_destroy(sc->jme_cdata.jme_ssb_tag); sc->jme_cdata.jme_ssb_tag = NULL; } if (sc->jme_cdata.jme_buffer_tag != NULL) { bus_dma_tag_destroy(sc->jme_cdata.jme_buffer_tag); sc->jme_cdata.jme_buffer_tag = NULL; } if (sc->jme_cdata.jme_ring_tag != NULL) { bus_dma_tag_destroy(sc->jme_cdata.jme_ring_tag); sc->jme_cdata.jme_ring_tag = NULL; } } /* * Make sure the interface is stopped at reboot time. */ static int jme_shutdown(device_t dev) { return (jme_suspend(dev)); } /* * Unlike other ethernet controllers, JMC250 requires * explicit resetting link speed to 10/100Mbps as gigabit * link will cunsume more power than 375mA. * Note, we reset the link speed to 10/100Mbps with * auto-negotiation but we don't know whether that operation * would succeed or not as we have no control after powering * off. If the renegotiation fail WOL may not work. Running * at 1Gbps draws more power than 375mA at 3.3V which is * specified in PCI specification and that would result in * complete shutdowning power to ethernet controller. * * TODO * Save current negotiated media speed/duplex/flow-control * to softc and restore the same link again after resuming. * PHY handling such as power down/resetting to 100Mbps * may be better handled in suspend method in phy driver. */ static void jme_setlinkspeed(struct jme_softc *sc) { struct mii_data *mii; int aneg, i; JME_LOCK_ASSERT(sc); mii = device_get_softc(sc->jme_miibus); mii_pollstat(mii); aneg = 0; if ((mii->mii_media_status & IFM_AVALID) != 0) { switch IFM_SUBTYPE(mii->mii_media_active) { case IFM_10_T: case IFM_100_TX: return; case IFM_1000_T: aneg++; default: break; } } jme_miibus_writereg(sc->jme_dev, sc->jme_phyaddr, MII_100T2CR, 0); jme_miibus_writereg(sc->jme_dev, sc->jme_phyaddr, MII_ANAR, ANAR_TX_FD | ANAR_TX | ANAR_10_FD | ANAR_10 | ANAR_CSMA); jme_miibus_writereg(sc->jme_dev, sc->jme_phyaddr, MII_BMCR, BMCR_AUTOEN | BMCR_STARTNEG); DELAY(1000); if (aneg != 0) { /* Poll link state until jme(4) get a 10/100 link. */ for (i = 0; i < MII_ANEGTICKS_GIGE; i++) { mii_pollstat(mii); if ((mii->mii_media_status & IFM_AVALID) != 0) { switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: jme_mac_config(sc); return; default: break; } } JME_UNLOCK(sc); pause("jmelnk", hz); JME_LOCK(sc); } if (i == MII_ANEGTICKS_GIGE) device_printf(sc->jme_dev, "establishing link failed, " "WOL may not work!"); } /* * No link, force MAC to have 100Mbps, full-duplex link. * This is the last resort and may/may not work. */ mii->mii_media_status = IFM_AVALID | IFM_ACTIVE; mii->mii_media_active = IFM_ETHER | IFM_100_TX | IFM_FDX; jme_mac_config(sc); } static void jme_setwol(struct jme_softc *sc) { struct ifnet *ifp; uint32_t gpr, pmcs; uint16_t pmstat; int pmc; JME_LOCK_ASSERT(sc); if (pci_find_cap(sc->jme_dev, PCIY_PMG, &pmc) != 0) { /* Remove Tx MAC/offload clock to save more power. */ if ((sc->jme_flags & JME_FLAG_TXCLK) != 0) CSR_WRITE_4(sc, JME_GHC, CSR_READ_4(sc, JME_GHC) & ~(GHC_TX_OFFLD_CLK_100 | GHC_TX_MAC_CLK_100 | GHC_TX_OFFLD_CLK_1000 | GHC_TX_MAC_CLK_1000)); if ((sc->jme_flags & JME_FLAG_RXCLK) != 0) CSR_WRITE_4(sc, JME_GPREG1, CSR_READ_4(sc, JME_GPREG1) | GPREG1_RX_MAC_CLK_DIS); /* No PME capability, PHY power down. */ jme_phy_down(sc); return; } ifp = sc->jme_ifp; gpr = CSR_READ_4(sc, JME_GPREG0) & ~GPREG0_PME_ENB; pmcs = CSR_READ_4(sc, JME_PMCS); pmcs &= ~PMCS_WOL_ENB_MASK; if ((ifp->if_capenable & IFCAP_WOL_MAGIC) != 0) { pmcs |= PMCS_MAGIC_FRAME | PMCS_MAGIC_FRAME_ENB; /* Enable PME message. */ gpr |= GPREG0_PME_ENB; /* For gigabit controllers, reset link speed to 10/100. */ if ((sc->jme_flags & JME_FLAG_FASTETH) == 0) jme_setlinkspeed(sc); } CSR_WRITE_4(sc, JME_PMCS, pmcs); CSR_WRITE_4(sc, JME_GPREG0, gpr); /* Remove Tx MAC/offload clock to save more power. */ if ((sc->jme_flags & JME_FLAG_TXCLK) != 0) CSR_WRITE_4(sc, JME_GHC, CSR_READ_4(sc, JME_GHC) & ~(GHC_TX_OFFLD_CLK_100 | GHC_TX_MAC_CLK_100 | GHC_TX_OFFLD_CLK_1000 | GHC_TX_MAC_CLK_1000)); /* Request PME. */ pmstat = pci_read_config(sc->jme_dev, pmc + PCIR_POWER_STATUS, 2); pmstat &= ~(PCIM_PSTAT_PME | PCIM_PSTAT_PMEENABLE); if ((ifp->if_capenable & IFCAP_WOL) != 0) pmstat |= PCIM_PSTAT_PME | PCIM_PSTAT_PMEENABLE; pci_write_config(sc->jme_dev, pmc + PCIR_POWER_STATUS, pmstat, 2); if ((ifp->if_capenable & IFCAP_WOL) == 0) { /* No WOL, PHY power down. */ jme_phy_down(sc); } } static int jme_suspend(device_t dev) { struct jme_softc *sc; sc = device_get_softc(dev); JME_LOCK(sc); jme_stop(sc); jme_setwol(sc); JME_UNLOCK(sc); return (0); } static int jme_resume(device_t dev) { struct jme_softc *sc; struct ifnet *ifp; uint16_t pmstat; int pmc; sc = device_get_softc(dev); JME_LOCK(sc); if (pci_find_cap(sc->jme_dev, PCIY_PMG, &pmc) == 0) { pmstat = pci_read_config(sc->jme_dev, pmc + PCIR_POWER_STATUS, 2); /* Disable PME clear PME status. */ pmstat &= ~PCIM_PSTAT_PMEENABLE; pci_write_config(sc->jme_dev, pmc + PCIR_POWER_STATUS, pmstat, 2); } /* Wakeup PHY. */ jme_phy_up(sc); ifp = sc->jme_ifp; if ((ifp->if_flags & IFF_UP) != 0) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; jme_init_locked(sc); } JME_UNLOCK(sc); return (0); } static int jme_encap(struct jme_softc *sc, struct mbuf **m_head) { struct jme_txdesc *txd; struct jme_desc *desc; struct mbuf *m; bus_dma_segment_t txsegs[JME_MAXTXSEGS]; int error, i, nsegs, prod; uint32_t cflags, tsosegsz; JME_LOCK_ASSERT(sc); M_ASSERTPKTHDR((*m_head)); if (((*m_head)->m_pkthdr.csum_flags & CSUM_TSO) != 0) { /* * Due to the adherence to NDIS specification JMC250 * assumes upper stack computed TCP pseudo checksum * without including payload length. This breaks * checksum offload for TSO case so recompute TCP * pseudo checksum for JMC250. Hopefully this wouldn't * be much burden on modern CPUs. */ struct ether_header *eh; struct ip *ip; struct tcphdr *tcp; uint32_t ip_off, poff; if (M_WRITABLE(*m_head) == 0) { /* Get a writable copy. */ m = m_dup(*m_head, M_NOWAIT); m_freem(*m_head); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } *m_head = m; } ip_off = sizeof(struct ether_header); m = m_pullup(*m_head, ip_off); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } eh = mtod(m, struct ether_header *); /* Check the existence of VLAN tag. */ if (eh->ether_type == htons(ETHERTYPE_VLAN)) { ip_off = sizeof(struct ether_vlan_header); m = m_pullup(m, ip_off); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } } m = m_pullup(m, ip_off + sizeof(struct ip)); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } ip = (struct ip *)(mtod(m, char *) + ip_off); poff = ip_off + (ip->ip_hl << 2); m = m_pullup(m, poff + sizeof(struct tcphdr)); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } /* * Reset IP checksum and recompute TCP pseudo * checksum that NDIS specification requires. */ ip = (struct ip *)(mtod(m, char *) + ip_off); tcp = (struct tcphdr *)(mtod(m, char *) + poff); ip->ip_sum = 0; if (poff + (tcp->th_off << 2) == m->m_pkthdr.len) { tcp->th_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, htons((tcp->th_off << 2) + IPPROTO_TCP)); /* No need to TSO, force IP checksum offload. */ (*m_head)->m_pkthdr.csum_flags &= ~CSUM_TSO; (*m_head)->m_pkthdr.csum_flags |= CSUM_IP; } else tcp->th_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, htons(IPPROTO_TCP)); *m_head = m; } prod = sc->jme_cdata.jme_tx_prod; txd = &sc->jme_cdata.jme_txdesc[prod]; error = bus_dmamap_load_mbuf_sg(sc->jme_cdata.jme_tx_tag, txd->tx_dmamap, *m_head, txsegs, &nsegs, 0); if (error == EFBIG) { m = m_collapse(*m_head, M_NOWAIT, JME_MAXTXSEGS); if (m == NULL) { m_freem(*m_head); *m_head = NULL; return (ENOMEM); } *m_head = m; error = bus_dmamap_load_mbuf_sg(sc->jme_cdata.jme_tx_tag, txd->tx_dmamap, *m_head, txsegs, &nsegs, 0); if (error != 0) { m_freem(*m_head); *m_head = NULL; return (error); } } else if (error != 0) return (error); if (nsegs == 0) { m_freem(*m_head); *m_head = NULL; return (EIO); } /* * Check descriptor overrun. Leave one free descriptor. * Since we always use 64bit address mode for transmitting, * each Tx request requires one more dummy descriptor. */ if (sc->jme_cdata.jme_tx_cnt + nsegs + 1 > JME_TX_RING_CNT - 1) { bus_dmamap_unload(sc->jme_cdata.jme_tx_tag, txd->tx_dmamap); return (ENOBUFS); } m = *m_head; cflags = 0; tsosegsz = 0; /* Configure checksum offload and TSO. */ if ((m->m_pkthdr.csum_flags & CSUM_TSO) != 0) { tsosegsz = (uint32_t)m->m_pkthdr.tso_segsz << JME_TD_MSS_SHIFT; cflags |= JME_TD_TSO; } else { if ((m->m_pkthdr.csum_flags & CSUM_IP) != 0) cflags |= JME_TD_IPCSUM; if ((m->m_pkthdr.csum_flags & CSUM_TCP) != 0) cflags |= JME_TD_TCPCSUM; if ((m->m_pkthdr.csum_flags & CSUM_UDP) != 0) cflags |= JME_TD_UDPCSUM; } /* Configure VLAN. */ if ((m->m_flags & M_VLANTAG) != 0) { cflags |= (m->m_pkthdr.ether_vtag & JME_TD_VLAN_MASK); cflags |= JME_TD_VLAN_TAG; } desc = &sc->jme_rdata.jme_tx_ring[prod]; desc->flags = htole32(cflags); desc->buflen = htole32(tsosegsz); desc->addr_hi = htole32(m->m_pkthdr.len); desc->addr_lo = 0; sc->jme_cdata.jme_tx_cnt++; JME_DESC_INC(prod, JME_TX_RING_CNT); for (i = 0; i < nsegs; i++) { desc = &sc->jme_rdata.jme_tx_ring[prod]; desc->flags = htole32(JME_TD_OWN | JME_TD_64BIT); desc->buflen = htole32(txsegs[i].ds_len); desc->addr_hi = htole32(JME_ADDR_HI(txsegs[i].ds_addr)); desc->addr_lo = htole32(JME_ADDR_LO(txsegs[i].ds_addr)); sc->jme_cdata.jme_tx_cnt++; JME_DESC_INC(prod, JME_TX_RING_CNT); } /* Update producer index. */ sc->jme_cdata.jme_tx_prod = prod; /* * Finally request interrupt and give the first descriptor * owenership to hardware. */ desc = txd->tx_desc; desc->flags |= htole32(JME_TD_OWN | JME_TD_INTR); txd->tx_m = m; txd->tx_ndesc = nsegs + 1; /* Sync descriptors. */ bus_dmamap_sync(sc->jme_cdata.jme_tx_tag, txd->tx_dmamap, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(sc->jme_cdata.jme_tx_ring_tag, sc->jme_cdata.jme_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (0); } static void jme_start(struct ifnet *ifp) { struct jme_softc *sc; sc = ifp->if_softc; JME_LOCK(sc); jme_start_locked(ifp); JME_UNLOCK(sc); } static void jme_start_locked(struct ifnet *ifp) { struct jme_softc *sc; struct mbuf *m_head; int enq; sc = ifp->if_softc; JME_LOCK_ASSERT(sc); if (sc->jme_cdata.jme_tx_cnt >= JME_TX_DESC_HIWAT) jme_txeof(sc); if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING || (sc->jme_flags & JME_FLAG_LINK) == 0) return; for (enq = 0; !IFQ_DRV_IS_EMPTY(&ifp->if_snd); ) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; /* * Pack the data into the transmit ring. If we * don't have room, set the OACTIVE flag and wait * for the NIC to drain the ring. */ if (jme_encap(sc, &m_head)) { if (m_head == NULL) break; IFQ_DRV_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } enq++; /* * If there's a BPF listener, bounce a copy of this frame * to him. */ ETHER_BPF_MTAP(ifp, m_head); } if (enq > 0) { /* * Reading TXCSR takes very long time under heavy load * so cache TXCSR value and writes the ORed value with * the kick command to the TXCSR. This saves one register * access cycle. */ CSR_WRITE_4(sc, JME_TXCSR, sc->jme_txcsr | TXCSR_TX_ENB | TXCSR_TXQ_N_START(TXCSR_TXQ0)); /* Set a timeout in case the chip goes out to lunch. */ sc->jme_watchdog_timer = JME_TX_TIMEOUT; } } static void jme_watchdog(struct jme_softc *sc) { struct ifnet *ifp; JME_LOCK_ASSERT(sc); if (sc->jme_watchdog_timer == 0 || --sc->jme_watchdog_timer) return; ifp = sc->jme_ifp; if ((sc->jme_flags & JME_FLAG_LINK) == 0) { if_printf(sc->jme_ifp, "watchdog timeout (missed link)\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; jme_init_locked(sc); return; } jme_txeof(sc); if (sc->jme_cdata.jme_tx_cnt == 0) { if_printf(sc->jme_ifp, "watchdog timeout (missed Tx interrupts) -- recovering\n"); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) jme_start_locked(ifp); return; } if_printf(sc->jme_ifp, "watchdog timeout\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; jme_init_locked(sc); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) jme_start_locked(ifp); } static int jme_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct jme_softc *sc; struct ifreq *ifr; struct mii_data *mii; uint32_t reg; int error, mask; sc = ifp->if_softc; ifr = (struct ifreq *)data; error = 0; switch (cmd) { case SIOCSIFMTU: if (ifr->ifr_mtu < ETHERMIN || ifr->ifr_mtu > JME_JUMBO_MTU || ((sc->jme_flags & JME_FLAG_NOJUMBO) != 0 && ifr->ifr_mtu > JME_MAX_MTU)) { error = EINVAL; break; } if (ifp->if_mtu != ifr->ifr_mtu) { /* * No special configuration is required when interface * MTU is changed but availability of TSO/Tx checksum * offload should be chcked against new MTU size as * FIFO size is just 2K. */ JME_LOCK(sc); if (ifr->ifr_mtu >= JME_TX_FIFO_SIZE) { ifp->if_capenable &= ~(IFCAP_TXCSUM | IFCAP_TSO4); ifp->if_hwassist &= ~(JME_CSUM_FEATURES | CSUM_TSO); VLAN_CAPABILITIES(ifp); } ifp->if_mtu = ifr->ifr_mtu; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; jme_init_locked(sc); } JME_UNLOCK(sc); } break; case SIOCSIFFLAGS: JME_LOCK(sc); if ((ifp->if_flags & IFF_UP) != 0) { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { if (((ifp->if_flags ^ sc->jme_if_flags) & (IFF_PROMISC | IFF_ALLMULTI)) != 0) jme_set_filter(sc); } else { if ((sc->jme_flags & JME_FLAG_DETACH) == 0) jme_init_locked(sc); } } else { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) jme_stop(sc); } sc->jme_if_flags = ifp->if_flags; JME_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: JME_LOCK(sc); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) jme_set_filter(sc); JME_UNLOCK(sc); break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: mii = device_get_softc(sc->jme_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); break; case SIOCSIFCAP: JME_LOCK(sc); mask = ifr->ifr_reqcap ^ ifp->if_capenable; if ((mask & IFCAP_TXCSUM) != 0 && ifp->if_mtu < JME_TX_FIFO_SIZE) { if ((IFCAP_TXCSUM & ifp->if_capabilities) != 0) { ifp->if_capenable ^= IFCAP_TXCSUM; if ((IFCAP_TXCSUM & ifp->if_capenable) != 0) ifp->if_hwassist |= JME_CSUM_FEATURES; else ifp->if_hwassist &= ~JME_CSUM_FEATURES; } } if ((mask & IFCAP_RXCSUM) != 0 && (IFCAP_RXCSUM & ifp->if_capabilities) != 0) { ifp->if_capenable ^= IFCAP_RXCSUM; reg = CSR_READ_4(sc, JME_RXMAC); reg &= ~RXMAC_CSUM_ENB; if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) reg |= RXMAC_CSUM_ENB; CSR_WRITE_4(sc, JME_RXMAC, reg); } if ((mask & IFCAP_TSO4) != 0 && ifp->if_mtu < JME_TX_FIFO_SIZE) { if ((IFCAP_TSO4 & ifp->if_capabilities) != 0) { ifp->if_capenable ^= IFCAP_TSO4; if ((IFCAP_TSO4 & ifp->if_capenable) != 0) ifp->if_hwassist |= CSUM_TSO; else ifp->if_hwassist &= ~CSUM_TSO; } } if ((mask & IFCAP_WOL_MAGIC) != 0 && (IFCAP_WOL_MAGIC & ifp->if_capabilities) != 0) ifp->if_capenable ^= IFCAP_WOL_MAGIC; if ((mask & IFCAP_VLAN_HWCSUM) != 0 && (ifp->if_capabilities & IFCAP_VLAN_HWCSUM) != 0) ifp->if_capenable ^= IFCAP_VLAN_HWCSUM; if ((mask & IFCAP_VLAN_HWTSO) != 0 && (ifp->if_capabilities & IFCAP_VLAN_HWTSO) != 0) ifp->if_capenable ^= IFCAP_VLAN_HWTSO; if ((mask & IFCAP_VLAN_HWTAGGING) != 0 && (IFCAP_VLAN_HWTAGGING & ifp->if_capabilities) != 0) { ifp->if_capenable ^= IFCAP_VLAN_HWTAGGING; jme_set_vlan(sc); } JME_UNLOCK(sc); VLAN_CAPABILITIES(ifp); break; default: error = ether_ioctl(ifp, cmd, data); break; } return (error); } static void jme_mac_config(struct jme_softc *sc) { struct mii_data *mii; uint32_t ghc, gpreg, rxmac, txmac, txpause; uint32_t txclk; JME_LOCK_ASSERT(sc); mii = device_get_softc(sc->jme_miibus); CSR_WRITE_4(sc, JME_GHC, GHC_RESET); DELAY(10); CSR_WRITE_4(sc, JME_GHC, 0); ghc = 0; txclk = 0; rxmac = CSR_READ_4(sc, JME_RXMAC); rxmac &= ~RXMAC_FC_ENB; txmac = CSR_READ_4(sc, JME_TXMAC); txmac &= ~(TXMAC_CARRIER_EXT | TXMAC_FRAME_BURST); txpause = CSR_READ_4(sc, JME_TXPFC); txpause &= ~TXPFC_PAUSE_ENB; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { ghc |= GHC_FULL_DUPLEX; rxmac &= ~RXMAC_COLL_DET_ENB; txmac &= ~(TXMAC_COLL_ENB | TXMAC_CARRIER_SENSE | TXMAC_BACKOFF | TXMAC_CARRIER_EXT | TXMAC_FRAME_BURST); if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0) txpause |= TXPFC_PAUSE_ENB; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0) rxmac |= RXMAC_FC_ENB; /* Disable retry transmit timer/retry limit. */ CSR_WRITE_4(sc, JME_TXTRHD, CSR_READ_4(sc, JME_TXTRHD) & ~(TXTRHD_RT_PERIOD_ENB | TXTRHD_RT_LIMIT_ENB)); } else { rxmac |= RXMAC_COLL_DET_ENB; txmac |= TXMAC_COLL_ENB | TXMAC_CARRIER_SENSE | TXMAC_BACKOFF; /* Enable retry transmit timer/retry limit. */ CSR_WRITE_4(sc, JME_TXTRHD, CSR_READ_4(sc, JME_TXTRHD) | TXTRHD_RT_PERIOD_ENB | TXTRHD_RT_LIMIT_ENB); } /* Reprogram Tx/Rx MACs with resolved speed/duplex. */ switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: ghc |= GHC_SPEED_10; txclk |= GHC_TX_OFFLD_CLK_100 | GHC_TX_MAC_CLK_100; break; case IFM_100_TX: ghc |= GHC_SPEED_100; txclk |= GHC_TX_OFFLD_CLK_100 | GHC_TX_MAC_CLK_100; break; case IFM_1000_T: if ((sc->jme_flags & JME_FLAG_FASTETH) != 0) break; ghc |= GHC_SPEED_1000; txclk |= GHC_TX_OFFLD_CLK_1000 | GHC_TX_MAC_CLK_1000; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) == 0) txmac |= TXMAC_CARRIER_EXT | TXMAC_FRAME_BURST; break; default: break; } if (sc->jme_rev == DEVICEID_JMC250 && sc->jme_chip_rev == DEVICEREVID_JMC250_A2) { /* * Workaround occasional packet loss issue of JMC250 A2 * when it runs on half-duplex media. */ gpreg = CSR_READ_4(sc, JME_GPREG1); if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) gpreg &= ~GPREG1_HDPX_FIX; else gpreg |= GPREG1_HDPX_FIX; CSR_WRITE_4(sc, JME_GPREG1, gpreg); /* Workaround CRC errors at 100Mbps on JMC250 A2. */ if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX) { /* Extend interface FIFO depth. */ jme_miibus_writereg(sc->jme_dev, sc->jme_phyaddr, 0x1B, 0x0000); } else { /* Select default interface FIFO depth. */ jme_miibus_writereg(sc->jme_dev, sc->jme_phyaddr, 0x1B, 0x0004); } } if ((sc->jme_flags & JME_FLAG_TXCLK) != 0) ghc |= txclk; CSR_WRITE_4(sc, JME_GHC, ghc); CSR_WRITE_4(sc, JME_RXMAC, rxmac); CSR_WRITE_4(sc, JME_TXMAC, txmac); CSR_WRITE_4(sc, JME_TXPFC, txpause); } static void jme_link_task(void *arg, int pending) { struct jme_softc *sc; struct mii_data *mii; struct ifnet *ifp; struct jme_txdesc *txd; bus_addr_t paddr; int i; sc = (struct jme_softc *)arg; JME_LOCK(sc); mii = device_get_softc(sc->jme_miibus); ifp = sc->jme_ifp; if (mii == NULL || ifp == NULL || (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { JME_UNLOCK(sc); return; } sc->jme_flags &= ~JME_FLAG_LINK; if ((mii->mii_media_status & IFM_AVALID) != 0) { switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: sc->jme_flags |= JME_FLAG_LINK; break; case IFM_1000_T: if ((sc->jme_flags & JME_FLAG_FASTETH) != 0) break; sc->jme_flags |= JME_FLAG_LINK; break; default: break; } } /* * Disabling Rx/Tx MACs have a side-effect of resetting * JME_TXNDA/JME_RXNDA register to the first address of * Tx/Rx descriptor address. So driver should reset its * internal procucer/consumer pointer and reclaim any * allocated resources. Note, just saving the value of * JME_TXNDA and JME_RXNDA registers before stopping MAC * and restoring JME_TXNDA/JME_RXNDA register is not * sufficient to make sure correct MAC state because * stopping MAC operation can take a while and hardware * might have updated JME_TXNDA/JME_RXNDA registers * during the stop operation. */ /* Block execution of task. */ taskqueue_block(sc->jme_tq); /* Disable interrupts and stop driver. */ CSR_WRITE_4(sc, JME_INTR_MASK_CLR, JME_INTRS); ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); callout_stop(&sc->jme_tick_ch); sc->jme_watchdog_timer = 0; /* Stop receiver/transmitter. */ jme_stop_rx(sc); jme_stop_tx(sc); /* XXX Drain all queued tasks. */ JME_UNLOCK(sc); taskqueue_drain(sc->jme_tq, &sc->jme_int_task); JME_LOCK(sc); if (sc->jme_cdata.jme_rxhead != NULL) m_freem(sc->jme_cdata.jme_rxhead); JME_RXCHAIN_RESET(sc); jme_txeof(sc); if (sc->jme_cdata.jme_tx_cnt != 0) { /* Remove queued packets for transmit. */ for (i = 0; i < JME_TX_RING_CNT; i++) { txd = &sc->jme_cdata.jme_txdesc[i]; if (txd->tx_m != NULL) { bus_dmamap_sync( sc->jme_cdata.jme_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload( sc->jme_cdata.jme_tx_tag, txd->tx_dmamap); m_freem(txd->tx_m); txd->tx_m = NULL; txd->tx_ndesc = 0; if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); } } } /* * Reuse configured Rx descriptors and reset * producer/consumer index. */ sc->jme_cdata.jme_rx_cons = 0; sc->jme_morework = 0; jme_init_tx_ring(sc); /* Initialize shadow status block. */ jme_init_ssb(sc); /* Program MAC with resolved speed/duplex/flow-control. */ if ((sc->jme_flags & JME_FLAG_LINK) != 0) { jme_mac_config(sc); jme_stats_clear(sc); CSR_WRITE_4(sc, JME_RXCSR, sc->jme_rxcsr); CSR_WRITE_4(sc, JME_TXCSR, sc->jme_txcsr); /* Set Tx ring address to the hardware. */ paddr = JME_TX_RING_ADDR(sc, 0); CSR_WRITE_4(sc, JME_TXDBA_HI, JME_ADDR_HI(paddr)); CSR_WRITE_4(sc, JME_TXDBA_LO, JME_ADDR_LO(paddr)); /* Set Rx ring address to the hardware. */ paddr = JME_RX_RING_ADDR(sc, 0); CSR_WRITE_4(sc, JME_RXDBA_HI, JME_ADDR_HI(paddr)); CSR_WRITE_4(sc, JME_RXDBA_LO, JME_ADDR_LO(paddr)); /* Restart receiver/transmitter. */ CSR_WRITE_4(sc, JME_RXCSR, sc->jme_rxcsr | RXCSR_RX_ENB | RXCSR_RXQ_START); CSR_WRITE_4(sc, JME_TXCSR, sc->jme_txcsr | TXCSR_TX_ENB); /* Lastly enable TX/RX clock. */ if ((sc->jme_flags & JME_FLAG_TXCLK) != 0) CSR_WRITE_4(sc, JME_GHC, CSR_READ_4(sc, JME_GHC) & ~GHC_TX_MAC_CLK_DIS); if ((sc->jme_flags & JME_FLAG_RXCLK) != 0) CSR_WRITE_4(sc, JME_GPREG1, CSR_READ_4(sc, JME_GPREG1) & ~GPREG1_RX_MAC_CLK_DIS); } ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; callout_reset(&sc->jme_tick_ch, hz, jme_tick, sc); /* Unblock execution of task. */ taskqueue_unblock(sc->jme_tq); /* Reenable interrupts. */ CSR_WRITE_4(sc, JME_INTR_MASK_SET, JME_INTRS); JME_UNLOCK(sc); } static int jme_intr(void *arg) { struct jme_softc *sc; uint32_t status; sc = (struct jme_softc *)arg; status = CSR_READ_4(sc, JME_INTR_REQ_STATUS); if (status == 0 || status == 0xFFFFFFFF) return (FILTER_STRAY); /* Disable interrupts. */ CSR_WRITE_4(sc, JME_INTR_MASK_CLR, JME_INTRS); taskqueue_enqueue(sc->jme_tq, &sc->jme_int_task); return (FILTER_HANDLED); } static void jme_int_task(void *arg, int pending) { struct jme_softc *sc; struct ifnet *ifp; uint32_t status; int more; sc = (struct jme_softc *)arg; ifp = sc->jme_ifp; JME_LOCK(sc); status = CSR_READ_4(sc, JME_INTR_STATUS); if (sc->jme_morework != 0) { sc->jme_morework = 0; status |= INTR_RXQ_COAL | INTR_RXQ_COAL_TO; } if ((status & JME_INTRS) == 0 || status == 0xFFFFFFFF) goto done; /* Reset PCC counter/timer and Ack interrupts. */ status &= ~(INTR_TXQ_COMP | INTR_RXQ_COMP); if ((status & (INTR_TXQ_COAL | INTR_TXQ_COAL_TO)) != 0) status |= INTR_TXQ_COAL | INTR_TXQ_COAL_TO | INTR_TXQ_COMP; if ((status & (INTR_RXQ_COAL | INTR_RXQ_COAL_TO)) != 0) status |= INTR_RXQ_COAL | INTR_RXQ_COAL_TO | INTR_RXQ_COMP; CSR_WRITE_4(sc, JME_INTR_STATUS, status); more = 0; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { if ((status & (INTR_RXQ_COAL | INTR_RXQ_COAL_TO)) != 0) { more = jme_rxintr(sc, sc->jme_process_limit); if (more != 0) sc->jme_morework = 1; } if ((status & INTR_RXQ_DESC_EMPTY) != 0) { /* * Notify hardware availability of new Rx * buffers. * Reading RXCSR takes very long time under * heavy load so cache RXCSR value and writes * the ORed value with the kick command to * the RXCSR. This saves one register access * cycle. */ CSR_WRITE_4(sc, JME_RXCSR, sc->jme_rxcsr | RXCSR_RX_ENB | RXCSR_RXQ_START); } if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) jme_start_locked(ifp); } if (more != 0 || (CSR_READ_4(sc, JME_INTR_STATUS) & JME_INTRS) != 0) { taskqueue_enqueue(sc->jme_tq, &sc->jme_int_task); JME_UNLOCK(sc); return; } done: JME_UNLOCK(sc); /* Reenable interrupts. */ CSR_WRITE_4(sc, JME_INTR_MASK_SET, JME_INTRS); } static void jme_txeof(struct jme_softc *sc) { struct ifnet *ifp; struct jme_txdesc *txd; uint32_t status; int cons, nsegs; JME_LOCK_ASSERT(sc); ifp = sc->jme_ifp; cons = sc->jme_cdata.jme_tx_cons; if (cons == sc->jme_cdata.jme_tx_prod) return; bus_dmamap_sync(sc->jme_cdata.jme_tx_ring_tag, sc->jme_cdata.jme_tx_ring_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); /* * Go through our Tx list and free mbufs for those * frames which have been transmitted. */ for (; cons != sc->jme_cdata.jme_tx_prod;) { txd = &sc->jme_cdata.jme_txdesc[cons]; status = le32toh(txd->tx_desc->flags); if ((status & JME_TD_OWN) == JME_TD_OWN) break; if ((status & (JME_TD_TMOUT | JME_TD_RETRY_EXP)) != 0) if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); else { if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); if ((status & JME_TD_COLLISION) != 0) if_inc_counter(ifp, IFCOUNTER_COLLISIONS, le32toh(txd->tx_desc->buflen) & JME_TD_BUF_LEN_MASK); } /* * Only the first descriptor of multi-descriptor * transmission is updated so driver have to skip entire * chained buffers for the transmiited frame. In other * words, JME_TD_OWN bit is valid only at the first * descriptor of a multi-descriptor transmission. */ for (nsegs = 0; nsegs < txd->tx_ndesc; nsegs++) { sc->jme_rdata.jme_tx_ring[cons].flags = 0; JME_DESC_INC(cons, JME_TX_RING_CNT); } /* Reclaim transferred mbufs. */ bus_dmamap_sync(sc->jme_cdata.jme_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->jme_cdata.jme_tx_tag, txd->tx_dmamap); KASSERT(txd->tx_m != NULL, ("%s: freeing NULL mbuf!\n", __func__)); m_freem(txd->tx_m); txd->tx_m = NULL; sc->jme_cdata.jme_tx_cnt -= txd->tx_ndesc; KASSERT(sc->jme_cdata.jme_tx_cnt >= 0, ("%s: Active Tx desc counter was garbled\n", __func__)); txd->tx_ndesc = 0; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; } sc->jme_cdata.jme_tx_cons = cons; /* Unarm watchog timer when there is no pending descriptors in queue. */ if (sc->jme_cdata.jme_tx_cnt == 0) sc->jme_watchdog_timer = 0; bus_dmamap_sync(sc->jme_cdata.jme_tx_ring_tag, sc->jme_cdata.jme_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static __inline void jme_discard_rxbuf(struct jme_softc *sc, int cons) { struct jme_desc *desc; desc = &sc->jme_rdata.jme_rx_ring[cons]; desc->flags = htole32(JME_RD_OWN | JME_RD_INTR | JME_RD_64BIT); desc->buflen = htole32(MCLBYTES); } /* Receive a frame. */ static void jme_rxeof(struct jme_softc *sc) { struct ifnet *ifp; struct jme_desc *desc; struct jme_rxdesc *rxd; struct mbuf *mp, *m; uint32_t flags, status; int cons, count, nsegs; JME_LOCK_ASSERT(sc); ifp = sc->jme_ifp; cons = sc->jme_cdata.jme_rx_cons; desc = &sc->jme_rdata.jme_rx_ring[cons]; flags = le32toh(desc->flags); status = le32toh(desc->buflen); nsegs = JME_RX_NSEGS(status); sc->jme_cdata.jme_rxlen = JME_RX_BYTES(status) - JME_RX_PAD_BYTES; if ((status & JME_RX_ERR_STAT) != 0) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); jme_discard_rxbuf(sc, sc->jme_cdata.jme_rx_cons); #ifdef JME_SHOW_ERRORS device_printf(sc->jme_dev, "%s : receive error = 0x%b\n", __func__, JME_RX_ERR(status), JME_RX_ERR_BITS); #endif sc->jme_cdata.jme_rx_cons += nsegs; sc->jme_cdata.jme_rx_cons %= JME_RX_RING_CNT; return; } for (count = 0; count < nsegs; count++, JME_DESC_INC(cons, JME_RX_RING_CNT)) { rxd = &sc->jme_cdata.jme_rxdesc[cons]; mp = rxd->rx_m; /* Add a new receive buffer to the ring. */ if (jme_newbuf(sc, rxd) != 0) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); /* Reuse buffer. */ for (; count < nsegs; count++) { jme_discard_rxbuf(sc, cons); JME_DESC_INC(cons, JME_RX_RING_CNT); } if (sc->jme_cdata.jme_rxhead != NULL) { m_freem(sc->jme_cdata.jme_rxhead); JME_RXCHAIN_RESET(sc); } break; } /* * Assume we've received a full sized frame. * Actual size is fixed when we encounter the end of * multi-segmented frame. */ mp->m_len = MCLBYTES; /* Chain received mbufs. */ if (sc->jme_cdata.jme_rxhead == NULL) { sc->jme_cdata.jme_rxhead = mp; sc->jme_cdata.jme_rxtail = mp; } else { /* * Receive processor can receive a maximum frame * size of 65535 bytes. */ mp->m_flags &= ~M_PKTHDR; sc->jme_cdata.jme_rxtail->m_next = mp; sc->jme_cdata.jme_rxtail = mp; } if (count == nsegs - 1) { /* Last desc. for this frame. */ m = sc->jme_cdata.jme_rxhead; m->m_flags |= M_PKTHDR; m->m_pkthdr.len = sc->jme_cdata.jme_rxlen; if (nsegs > 1) { /* Set first mbuf size. */ m->m_len = MCLBYTES - JME_RX_PAD_BYTES; /* Set last mbuf size. */ mp->m_len = sc->jme_cdata.jme_rxlen - ((MCLBYTES - JME_RX_PAD_BYTES) + (MCLBYTES * (nsegs - 2))); } else m->m_len = sc->jme_cdata.jme_rxlen; m->m_pkthdr.rcvif = ifp; /* * Account for 10bytes auto padding which is used * to align IP header on 32bit boundary. Also note, * CRC bytes is automatically removed by the * hardware. */ m->m_data += JME_RX_PAD_BYTES; /* Set checksum information. */ if ((ifp->if_capenable & IFCAP_RXCSUM) != 0 && (flags & JME_RD_IPV4) != 0) { m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED; if ((flags & JME_RD_IPCSUM) != 0) m->m_pkthdr.csum_flags |= CSUM_IP_VALID; if (((flags & JME_RD_MORE_FRAG) == 0) && ((flags & (JME_RD_TCP | JME_RD_TCPCSUM)) == (JME_RD_TCP | JME_RD_TCPCSUM) || (flags & (JME_RD_UDP | JME_RD_UDPCSUM)) == (JME_RD_UDP | JME_RD_UDPCSUM))) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } } /* Check for VLAN tagged packets. */ if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0 && (flags & JME_RD_VLAN_TAG) != 0) { m->m_pkthdr.ether_vtag = flags & JME_RD_VLAN_MASK; m->m_flags |= M_VLANTAG; } if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); /* Pass it on. */ JME_UNLOCK(sc); (*ifp->if_input)(ifp, m); JME_LOCK(sc); /* Reset mbuf chains. */ JME_RXCHAIN_RESET(sc); } } sc->jme_cdata.jme_rx_cons += nsegs; sc->jme_cdata.jme_rx_cons %= JME_RX_RING_CNT; } static int jme_rxintr(struct jme_softc *sc, int count) { struct jme_desc *desc; int nsegs, prog, pktlen; bus_dmamap_sync(sc->jme_cdata.jme_rx_ring_tag, sc->jme_cdata.jme_rx_ring_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); for (prog = 0; count > 0; prog++) { desc = &sc->jme_rdata.jme_rx_ring[sc->jme_cdata.jme_rx_cons]; if ((le32toh(desc->flags) & JME_RD_OWN) == JME_RD_OWN) break; if ((le32toh(desc->buflen) & JME_RD_VALID) == 0) break; nsegs = JME_RX_NSEGS(le32toh(desc->buflen)); /* * Check number of segments against received bytes. * Non-matching value would indicate that hardware * is still trying to update Rx descriptors. I'm not * sure whether this check is needed. */ pktlen = JME_RX_BYTES(le32toh(desc->buflen)); if (nsegs != ((pktlen + (MCLBYTES - 1)) / MCLBYTES)) break; prog++; /* Received a frame. */ jme_rxeof(sc); count -= nsegs; } if (prog > 0) bus_dmamap_sync(sc->jme_cdata.jme_rx_ring_tag, sc->jme_cdata.jme_rx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (count > 0 ? 0 : EAGAIN); } static void jme_tick(void *arg) { struct jme_softc *sc; struct mii_data *mii; sc = (struct jme_softc *)arg; JME_LOCK_ASSERT(sc); mii = device_get_softc(sc->jme_miibus); mii_tick(mii); /* * Reclaim Tx buffers that have been completed. It's not * needed here but it would release allocated mbuf chains * faster and limit the maximum delay to a hz. */ jme_txeof(sc); jme_stats_update(sc); jme_watchdog(sc); callout_reset(&sc->jme_tick_ch, hz, jme_tick, sc); } static void jme_reset(struct jme_softc *sc) { uint32_t ghc, gpreg; /* Stop receiver, transmitter. */ jme_stop_rx(sc); jme_stop_tx(sc); /* Reset controller. */ CSR_WRITE_4(sc, JME_GHC, GHC_RESET); CSR_READ_4(sc, JME_GHC); DELAY(10); /* * Workaround Rx FIFO overruns seen under certain conditions. * Explicitly synchorize TX/RX clock. TX/RX clock should be * enabled only after enabling TX/RX MACs. */ if ((sc->jme_flags & (JME_FLAG_TXCLK | JME_FLAG_RXCLK)) != 0) { /* Disable TX clock. */ CSR_WRITE_4(sc, JME_GHC, GHC_RESET | GHC_TX_MAC_CLK_DIS); /* Disable RX clock. */ gpreg = CSR_READ_4(sc, JME_GPREG1); CSR_WRITE_4(sc, JME_GPREG1, gpreg | GPREG1_RX_MAC_CLK_DIS); gpreg = CSR_READ_4(sc, JME_GPREG1); /* De-assert RESET but still disable TX clock. */ CSR_WRITE_4(sc, JME_GHC, GHC_TX_MAC_CLK_DIS); ghc = CSR_READ_4(sc, JME_GHC); /* Enable TX clock. */ CSR_WRITE_4(sc, JME_GHC, ghc & ~GHC_TX_MAC_CLK_DIS); /* Enable RX clock. */ CSR_WRITE_4(sc, JME_GPREG1, gpreg & ~GPREG1_RX_MAC_CLK_DIS); CSR_READ_4(sc, JME_GPREG1); /* Disable TX/RX clock again. */ CSR_WRITE_4(sc, JME_GHC, GHC_TX_MAC_CLK_DIS); CSR_WRITE_4(sc, JME_GPREG1, gpreg | GPREG1_RX_MAC_CLK_DIS); } else CSR_WRITE_4(sc, JME_GHC, 0); CSR_READ_4(sc, JME_GHC); DELAY(10); } static void jme_init(void *xsc) { struct jme_softc *sc; sc = (struct jme_softc *)xsc; JME_LOCK(sc); jme_init_locked(sc); JME_UNLOCK(sc); } static void jme_init_locked(struct jme_softc *sc) { struct ifnet *ifp; struct mii_data *mii; bus_addr_t paddr; uint32_t reg; int error; JME_LOCK_ASSERT(sc); ifp = sc->jme_ifp; mii = device_get_softc(sc->jme_miibus); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) return; /* * Cancel any pending I/O. */ jme_stop(sc); /* * Reset the chip to a known state. */ jme_reset(sc); /* Init descriptors. */ error = jme_init_rx_ring(sc); if (error != 0) { device_printf(sc->jme_dev, "%s: initialization failed: no memory for Rx buffers.\n", __func__); jme_stop(sc); return; } jme_init_tx_ring(sc); /* Initialize shadow status block. */ jme_init_ssb(sc); /* Reprogram the station address. */ jme_set_macaddr(sc, IF_LLADDR(sc->jme_ifp)); /* * Configure Tx queue. * Tx priority queue weight value : 0 * Tx FIFO threshold for processing next packet : 16QW * Maximum Tx DMA length : 512 * Allow Tx DMA burst. */ sc->jme_txcsr = TXCSR_TXQ_N_SEL(TXCSR_TXQ0); sc->jme_txcsr |= TXCSR_TXQ_WEIGHT(TXCSR_TXQ_WEIGHT_MIN); sc->jme_txcsr |= TXCSR_FIFO_THRESH_16QW; sc->jme_txcsr |= sc->jme_tx_dma_size; sc->jme_txcsr |= TXCSR_DMA_BURST; CSR_WRITE_4(sc, JME_TXCSR, sc->jme_txcsr); /* Set Tx descriptor counter. */ CSR_WRITE_4(sc, JME_TXQDC, JME_TX_RING_CNT); /* Set Tx ring address to the hardware. */ paddr = JME_TX_RING_ADDR(sc, 0); CSR_WRITE_4(sc, JME_TXDBA_HI, JME_ADDR_HI(paddr)); CSR_WRITE_4(sc, JME_TXDBA_LO, JME_ADDR_LO(paddr)); /* Configure TxMAC parameters. */ reg = TXMAC_IFG1_DEFAULT | TXMAC_IFG2_DEFAULT | TXMAC_IFG_ENB; reg |= TXMAC_THRESH_1_PKT; reg |= TXMAC_CRC_ENB | TXMAC_PAD_ENB; CSR_WRITE_4(sc, JME_TXMAC, reg); /* * Configure Rx queue. * FIFO full threshold for transmitting Tx pause packet : 128T * FIFO threshold for processing next packet : 128QW * Rx queue 0 select * Max Rx DMA length : 128 * Rx descriptor retry : 32 * Rx descriptor retry time gap : 256ns * Don't receive runt/bad frame. */ sc->jme_rxcsr = RXCSR_FIFO_FTHRESH_128T; /* * Since Rx FIFO size is 4K bytes, receiving frames larger * than 4K bytes will suffer from Rx FIFO overruns. So * decrease FIFO threshold to reduce the FIFO overruns for * frames larger than 4000 bytes. * For best performance of standard MTU sized frames use * maximum allowable FIFO threshold, 128QW. Note these do * not hold on chip full mask verion >=2. For these * controllers 64QW and 128QW are not valid value. */ if (CHIPMODE_REVFM(sc->jme_chip_rev) >= 2) sc->jme_rxcsr |= RXCSR_FIFO_THRESH_16QW; else { if ((ifp->if_mtu + ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN + ETHER_CRC_LEN) > JME_RX_FIFO_SIZE) sc->jme_rxcsr |= RXCSR_FIFO_THRESH_16QW; else sc->jme_rxcsr |= RXCSR_FIFO_THRESH_128QW; } sc->jme_rxcsr |= sc->jme_rx_dma_size | RXCSR_RXQ_N_SEL(RXCSR_RXQ0); sc->jme_rxcsr |= RXCSR_DESC_RT_CNT(RXCSR_DESC_RT_CNT_DEFAULT); sc->jme_rxcsr |= RXCSR_DESC_RT_GAP_256 & RXCSR_DESC_RT_GAP_MASK; CSR_WRITE_4(sc, JME_RXCSR, sc->jme_rxcsr); /* Set Rx descriptor counter. */ CSR_WRITE_4(sc, JME_RXQDC, JME_RX_RING_CNT); /* Set Rx ring address to the hardware. */ paddr = JME_RX_RING_ADDR(sc, 0); CSR_WRITE_4(sc, JME_RXDBA_HI, JME_ADDR_HI(paddr)); CSR_WRITE_4(sc, JME_RXDBA_LO, JME_ADDR_LO(paddr)); /* Clear receive filter. */ CSR_WRITE_4(sc, JME_RXMAC, 0); /* Set up the receive filter. */ jme_set_filter(sc); jme_set_vlan(sc); /* * Disable all WOL bits as WOL can interfere normal Rx * operation. Also clear WOL detection status bits. */ reg = CSR_READ_4(sc, JME_PMCS); reg &= ~PMCS_WOL_ENB_MASK; CSR_WRITE_4(sc, JME_PMCS, reg); reg = CSR_READ_4(sc, JME_RXMAC); /* * Pad 10bytes right before received frame. This will greatly * help Rx performance on strict-alignment architectures as * it does not need to copy the frame to align the payload. */ reg |= RXMAC_PAD_10BYTES; if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) reg |= RXMAC_CSUM_ENB; CSR_WRITE_4(sc, JME_RXMAC, reg); /* Configure general purpose reg0 */ reg = CSR_READ_4(sc, JME_GPREG0); reg &= ~GPREG0_PCC_UNIT_MASK; /* Set PCC timer resolution to micro-seconds unit. */ reg |= GPREG0_PCC_UNIT_US; /* * Disable all shadow register posting as we have to read * JME_INTR_STATUS register in jme_int_task. Also it seems * that it's hard to synchronize interrupt status between * hardware and software with shadow posting due to * requirements of bus_dmamap_sync(9). */ reg |= GPREG0_SH_POST_DW7_DIS | GPREG0_SH_POST_DW6_DIS | GPREG0_SH_POST_DW5_DIS | GPREG0_SH_POST_DW4_DIS | GPREG0_SH_POST_DW3_DIS | GPREG0_SH_POST_DW2_DIS | GPREG0_SH_POST_DW1_DIS | GPREG0_SH_POST_DW0_DIS; /* Disable posting of DW0. */ reg &= ~GPREG0_POST_DW0_ENB; /* Clear PME message. */ reg &= ~GPREG0_PME_ENB; /* Set PHY address. */ reg &= ~GPREG0_PHY_ADDR_MASK; reg |= sc->jme_phyaddr; CSR_WRITE_4(sc, JME_GPREG0, reg); /* Configure Tx queue 0 packet completion coalescing. */ reg = (sc->jme_tx_coal_to << PCCTX_COAL_TO_SHIFT) & PCCTX_COAL_TO_MASK; reg |= (sc->jme_tx_coal_pkt << PCCTX_COAL_PKT_SHIFT) & PCCTX_COAL_PKT_MASK; reg |= PCCTX_COAL_TXQ0; CSR_WRITE_4(sc, JME_PCCTX, reg); /* Configure Rx queue 0 packet completion coalescing. */ reg = (sc->jme_rx_coal_to << PCCRX_COAL_TO_SHIFT) & PCCRX_COAL_TO_MASK; reg |= (sc->jme_rx_coal_pkt << PCCRX_COAL_PKT_SHIFT) & PCCRX_COAL_PKT_MASK; CSR_WRITE_4(sc, JME_PCCRX0, reg); /* * Configure PCD(Packet Completion Deferring). It seems PCD * generates an interrupt when the time interval between two * back-to-back incoming/outgoing packet is long enough for * it to reach its timer value 0. The arrival of new packets * after timer has started causes the PCD timer to restart. * Unfortunately, it's not clear how PCD is useful at this * moment, so just use the same of PCC parameters. */ if ((sc->jme_flags & JME_FLAG_PCCPCD) != 0) { sc->jme_rx_pcd_to = sc->jme_rx_coal_to; if (sc->jme_rx_coal_to > PCDRX_TO_MAX) sc->jme_rx_pcd_to = PCDRX_TO_MAX; sc->jme_tx_pcd_to = sc->jme_tx_coal_to; if (sc->jme_tx_coal_to > PCDTX_TO_MAX) sc->jme_tx_pcd_to = PCDTX_TO_MAX; reg = sc->jme_rx_pcd_to << PCDRX0_TO_THROTTLE_SHIFT; reg |= sc->jme_rx_pcd_to << PCDRX0_TO_SHIFT; CSR_WRITE_4(sc, PCDRX_REG(0), reg); reg = sc->jme_tx_pcd_to << PCDTX_TO_THROTTLE_SHIFT; reg |= sc->jme_tx_pcd_to << PCDTX_TO_SHIFT; CSR_WRITE_4(sc, JME_PCDTX, reg); } /* Configure shadow status block but don't enable posting. */ paddr = sc->jme_rdata.jme_ssb_block_paddr; CSR_WRITE_4(sc, JME_SHBASE_ADDR_HI, JME_ADDR_HI(paddr)); CSR_WRITE_4(sc, JME_SHBASE_ADDR_LO, JME_ADDR_LO(paddr)); /* Disable Timer 1 and Timer 2. */ CSR_WRITE_4(sc, JME_TIMER1, 0); CSR_WRITE_4(sc, JME_TIMER2, 0); /* Configure retry transmit period, retry limit value. */ CSR_WRITE_4(sc, JME_TXTRHD, ((TXTRHD_RT_PERIOD_DEFAULT << TXTRHD_RT_PERIOD_SHIFT) & TXTRHD_RT_PERIOD_MASK) | ((TXTRHD_RT_LIMIT_DEFAULT << TXTRHD_RT_LIMIT_SHIFT) & TXTRHD_RT_LIMIT_SHIFT)); /* Disable RSS. */ CSR_WRITE_4(sc, JME_RSSC, RSSC_DIS_RSS); /* Initialize the interrupt mask. */ CSR_WRITE_4(sc, JME_INTR_MASK_SET, JME_INTRS); CSR_WRITE_4(sc, JME_INTR_STATUS, 0xFFFFFFFF); /* * Enabling Tx/Rx DMA engines and Rx queue processing is * done after detection of valid link in jme_link_task. */ sc->jme_flags &= ~JME_FLAG_LINK; /* Set the current media. */ mii_mediachg(mii); callout_reset(&sc->jme_tick_ch, hz, jme_tick, sc); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; } static void jme_stop(struct jme_softc *sc) { struct ifnet *ifp; struct jme_txdesc *txd; struct jme_rxdesc *rxd; int i; JME_LOCK_ASSERT(sc); /* * Mark the interface down and cancel the watchdog timer. */ ifp = sc->jme_ifp; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc->jme_flags &= ~JME_FLAG_LINK; callout_stop(&sc->jme_tick_ch); sc->jme_watchdog_timer = 0; /* * Disable interrupts. */ CSR_WRITE_4(sc, JME_INTR_MASK_CLR, JME_INTRS); CSR_WRITE_4(sc, JME_INTR_STATUS, 0xFFFFFFFF); /* Disable updating shadow status block. */ CSR_WRITE_4(sc, JME_SHBASE_ADDR_LO, CSR_READ_4(sc, JME_SHBASE_ADDR_LO) & ~SHBASE_POST_ENB); /* Stop receiver, transmitter. */ jme_stop_rx(sc); jme_stop_tx(sc); /* Reclaim Rx/Tx buffers that have been completed. */ jme_rxintr(sc, JME_RX_RING_CNT); if (sc->jme_cdata.jme_rxhead != NULL) m_freem(sc->jme_cdata.jme_rxhead); JME_RXCHAIN_RESET(sc); jme_txeof(sc); /* * Free RX and TX mbufs still in the queues. */ for (i = 0; i < JME_RX_RING_CNT; i++) { rxd = &sc->jme_cdata.jme_rxdesc[i]; if (rxd->rx_m != NULL) { bus_dmamap_sync(sc->jme_cdata.jme_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->jme_cdata.jme_rx_tag, rxd->rx_dmamap); m_freem(rxd->rx_m); rxd->rx_m = NULL; } } for (i = 0; i < JME_TX_RING_CNT; i++) { txd = &sc->jme_cdata.jme_txdesc[i]; if (txd->tx_m != NULL) { bus_dmamap_sync(sc->jme_cdata.jme_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->jme_cdata.jme_tx_tag, txd->tx_dmamap); m_freem(txd->tx_m); txd->tx_m = NULL; txd->tx_ndesc = 0; } } jme_stats_update(sc); jme_stats_save(sc); } static void jme_stop_tx(struct jme_softc *sc) { uint32_t reg; int i; reg = CSR_READ_4(sc, JME_TXCSR); if ((reg & TXCSR_TX_ENB) == 0) return; reg &= ~TXCSR_TX_ENB; CSR_WRITE_4(sc, JME_TXCSR, reg); for (i = JME_TIMEOUT; i > 0; i--) { DELAY(1); if ((CSR_READ_4(sc, JME_TXCSR) & TXCSR_TX_ENB) == 0) break; } if (i == 0) device_printf(sc->jme_dev, "stopping transmitter timeout!\n"); } static void jme_stop_rx(struct jme_softc *sc) { uint32_t reg; int i; reg = CSR_READ_4(sc, JME_RXCSR); if ((reg & RXCSR_RX_ENB) == 0) return; reg &= ~RXCSR_RX_ENB; CSR_WRITE_4(sc, JME_RXCSR, reg); for (i = JME_TIMEOUT; i > 0; i--) { DELAY(1); if ((CSR_READ_4(sc, JME_RXCSR) & RXCSR_RX_ENB) == 0) break; } if (i == 0) device_printf(sc->jme_dev, "stopping recevier timeout!\n"); } static void jme_init_tx_ring(struct jme_softc *sc) { struct jme_ring_data *rd; struct jme_txdesc *txd; int i; sc->jme_cdata.jme_tx_prod = 0; sc->jme_cdata.jme_tx_cons = 0; sc->jme_cdata.jme_tx_cnt = 0; rd = &sc->jme_rdata; bzero(rd->jme_tx_ring, JME_TX_RING_SIZE); for (i = 0; i < JME_TX_RING_CNT; i++) { txd = &sc->jme_cdata.jme_txdesc[i]; txd->tx_m = NULL; txd->tx_desc = &rd->jme_tx_ring[i]; txd->tx_ndesc = 0; } bus_dmamap_sync(sc->jme_cdata.jme_tx_ring_tag, sc->jme_cdata.jme_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static void jme_init_ssb(struct jme_softc *sc) { struct jme_ring_data *rd; rd = &sc->jme_rdata; bzero(rd->jme_ssb_block, JME_SSB_SIZE); bus_dmamap_sync(sc->jme_cdata.jme_ssb_tag, sc->jme_cdata.jme_ssb_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static int jme_init_rx_ring(struct jme_softc *sc) { struct jme_ring_data *rd; struct jme_rxdesc *rxd; int i; sc->jme_cdata.jme_rx_cons = 0; JME_RXCHAIN_RESET(sc); sc->jme_morework = 0; rd = &sc->jme_rdata; bzero(rd->jme_rx_ring, JME_RX_RING_SIZE); for (i = 0; i < JME_RX_RING_CNT; i++) { rxd = &sc->jme_cdata.jme_rxdesc[i]; rxd->rx_m = NULL; rxd->rx_desc = &rd->jme_rx_ring[i]; if (jme_newbuf(sc, rxd) != 0) return (ENOBUFS); } bus_dmamap_sync(sc->jme_cdata.jme_rx_ring_tag, sc->jme_cdata.jme_rx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (0); } static int jme_newbuf(struct jme_softc *sc, struct jme_rxdesc *rxd) { struct jme_desc *desc; struct mbuf *m; bus_dma_segment_t segs[1]; bus_dmamap_t map; int nsegs; m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) return (ENOBUFS); /* * JMC250 has 64bit boundary alignment limitation so jme(4) * takes advantage of 10 bytes padding feature of hardware * in order not to copy entire frame to align IP header on * 32bit boundary. */ m->m_len = m->m_pkthdr.len = MCLBYTES; if (bus_dmamap_load_mbuf_sg(sc->jme_cdata.jme_rx_tag, sc->jme_cdata.jme_rx_sparemap, m, segs, &nsegs, 0) != 0) { m_freem(m); return (ENOBUFS); } KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); if (rxd->rx_m != NULL) { bus_dmamap_sync(sc->jme_cdata.jme_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->jme_cdata.jme_rx_tag, rxd->rx_dmamap); } map = rxd->rx_dmamap; rxd->rx_dmamap = sc->jme_cdata.jme_rx_sparemap; sc->jme_cdata.jme_rx_sparemap = map; bus_dmamap_sync(sc->jme_cdata.jme_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_PREREAD); rxd->rx_m = m; desc = rxd->rx_desc; desc->buflen = htole32(segs[0].ds_len); desc->addr_lo = htole32(JME_ADDR_LO(segs[0].ds_addr)); desc->addr_hi = htole32(JME_ADDR_HI(segs[0].ds_addr)); desc->flags = htole32(JME_RD_OWN | JME_RD_INTR | JME_RD_64BIT); return (0); } static void jme_set_vlan(struct jme_softc *sc) { struct ifnet *ifp; uint32_t reg; JME_LOCK_ASSERT(sc); ifp = sc->jme_ifp; reg = CSR_READ_4(sc, JME_RXMAC); reg &= ~RXMAC_VLAN_ENB; if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0) reg |= RXMAC_VLAN_ENB; CSR_WRITE_4(sc, JME_RXMAC, reg); } static void jme_set_filter(struct jme_softc *sc) { struct ifnet *ifp; struct ifmultiaddr *ifma; uint32_t crc; uint32_t mchash[2]; uint32_t rxcfg; JME_LOCK_ASSERT(sc); ifp = sc->jme_ifp; rxcfg = CSR_READ_4(sc, JME_RXMAC); rxcfg &= ~ (RXMAC_BROADCAST | RXMAC_PROMISC | RXMAC_MULTICAST | RXMAC_ALLMULTI); /* Always accept frames destined to our station address. */ rxcfg |= RXMAC_UNICAST; if ((ifp->if_flags & IFF_BROADCAST) != 0) rxcfg |= RXMAC_BROADCAST; if ((ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI)) != 0) { if ((ifp->if_flags & IFF_PROMISC) != 0) rxcfg |= RXMAC_PROMISC; if ((ifp->if_flags & IFF_ALLMULTI) != 0) rxcfg |= RXMAC_ALLMULTI; CSR_WRITE_4(sc, JME_MAR0, 0xFFFFFFFF); CSR_WRITE_4(sc, JME_MAR1, 0xFFFFFFFF); CSR_WRITE_4(sc, JME_RXMAC, rxcfg); return; } /* * Set up the multicast address filter by passing all multicast * addresses through a CRC generator, and then using the low-order * 6 bits as an index into the 64 bit multicast hash table. The * high order bits select the register, while the rest of the bits * select the bit within the register. */ rxcfg |= RXMAC_MULTICAST; bzero(mchash, sizeof(mchash)); if_maddr_rlock(ifp); TAILQ_FOREACH(ifma, &sc->jme_ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; crc = ether_crc32_be(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN); /* Just want the 6 least significant bits. */ crc &= 0x3f; /* Set the corresponding bit in the hash table. */ mchash[crc >> 5] |= 1 << (crc & 0x1f); } if_maddr_runlock(ifp); CSR_WRITE_4(sc, JME_MAR0, mchash[0]); CSR_WRITE_4(sc, JME_MAR1, mchash[1]); CSR_WRITE_4(sc, JME_RXMAC, rxcfg); } static void jme_stats_clear(struct jme_softc *sc) { JME_LOCK_ASSERT(sc); if ((sc->jme_flags & JME_FLAG_HWMIB) == 0) return; /* Disable and clear counters. */ CSR_WRITE_4(sc, JME_STATCSR, 0xFFFFFFFF); /* Activate hw counters. */ CSR_WRITE_4(sc, JME_STATCSR, 0); CSR_READ_4(sc, JME_STATCSR); bzero(&sc->jme_stats, sizeof(struct jme_hw_stats)); } static void jme_stats_save(struct jme_softc *sc) { JME_LOCK_ASSERT(sc); if ((sc->jme_flags & JME_FLAG_HWMIB) == 0) return; /* Save current counters. */ bcopy(&sc->jme_stats, &sc->jme_ostats, sizeof(struct jme_hw_stats)); /* Disable and clear counters. */ CSR_WRITE_4(sc, JME_STATCSR, 0xFFFFFFFF); } static void jme_stats_update(struct jme_softc *sc) { struct jme_hw_stats *stat, *ostat; uint32_t reg; JME_LOCK_ASSERT(sc); if ((sc->jme_flags & JME_FLAG_HWMIB) == 0) return; stat = &sc->jme_stats; ostat = &sc->jme_ostats; stat->tx_good_frames = CSR_READ_4(sc, JME_STAT_TXGOOD); stat->rx_good_frames = CSR_READ_4(sc, JME_STAT_RXGOOD); reg = CSR_READ_4(sc, JME_STAT_CRCMII); stat->rx_crc_errs = (reg & STAT_RX_CRC_ERR_MASK) >> STAT_RX_CRC_ERR_SHIFT; stat->rx_mii_errs = (reg & STAT_RX_MII_ERR_MASK) >> STAT_RX_MII_ERR_SHIFT; reg = CSR_READ_4(sc, JME_STAT_RXERR); stat->rx_fifo_oflows = (reg & STAT_RXERR_OFLOW_MASK) >> STAT_RXERR_OFLOW_SHIFT; stat->rx_desc_empty = (reg & STAT_RXERR_MPTY_MASK) >> STAT_RXERR_MPTY_SHIFT; reg = CSR_READ_4(sc, JME_STAT_FAIL); stat->rx_bad_frames = (reg & STAT_FAIL_RX_MASK) >> STAT_FAIL_RX_SHIFT; stat->tx_bad_frames = (reg & STAT_FAIL_TX_MASK) >> STAT_FAIL_TX_SHIFT; /* Account for previous counters. */ stat->rx_good_frames += ostat->rx_good_frames; stat->rx_crc_errs += ostat->rx_crc_errs; stat->rx_mii_errs += ostat->rx_mii_errs; stat->rx_fifo_oflows += ostat->rx_fifo_oflows; stat->rx_desc_empty += ostat->rx_desc_empty; stat->rx_bad_frames += ostat->rx_bad_frames; stat->tx_good_frames += ostat->tx_good_frames; stat->tx_bad_frames += ostat->tx_bad_frames; } static void jme_phy_down(struct jme_softc *sc) { uint32_t reg; jme_miibus_writereg(sc->jme_dev, sc->jme_phyaddr, MII_BMCR, BMCR_PDOWN); if (CHIPMODE_REVFM(sc->jme_chip_rev) >= 5) { reg = CSR_READ_4(sc, JME_PHYPOWDN); reg |= 0x0000000F; CSR_WRITE_4(sc, JME_PHYPOWDN, reg); reg = pci_read_config(sc->jme_dev, JME_PCI_PE1, 4); reg &= ~PE1_GIGA_PDOWN_MASK; reg |= PE1_GIGA_PDOWN_D3; pci_write_config(sc->jme_dev, JME_PCI_PE1, reg, 4); } } static void jme_phy_up(struct jme_softc *sc) { uint32_t reg; uint16_t bmcr; bmcr = jme_miibus_readreg(sc->jme_dev, sc->jme_phyaddr, MII_BMCR); bmcr &= ~BMCR_PDOWN; jme_miibus_writereg(sc->jme_dev, sc->jme_phyaddr, MII_BMCR, bmcr); if (CHIPMODE_REVFM(sc->jme_chip_rev) >= 5) { reg = CSR_READ_4(sc, JME_PHYPOWDN); reg &= ~0x0000000F; CSR_WRITE_4(sc, JME_PHYPOWDN, reg); reg = pci_read_config(sc->jme_dev, JME_PCI_PE1, 4); reg &= ~PE1_GIGA_PDOWN_MASK; reg |= PE1_GIGA_PDOWN_DIS; pci_write_config(sc->jme_dev, JME_PCI_PE1, reg, 4); } } static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int low, int high) { int error, value; if (arg1 == NULL) return (EINVAL); value = *(int *)arg1; error = sysctl_handle_int(oidp, &value, 0, req); if (error || req->newptr == NULL) return (error); if (value < low || value > high) return (EINVAL); *(int *)arg1 = value; return (0); } static int sysctl_hw_jme_tx_coal_to(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, PCCTX_COAL_TO_MIN, PCCTX_COAL_TO_MAX)); } static int sysctl_hw_jme_tx_coal_pkt(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, PCCTX_COAL_PKT_MIN, PCCTX_COAL_PKT_MAX)); } static int sysctl_hw_jme_rx_coal_to(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, PCCRX_COAL_TO_MIN, PCCRX_COAL_TO_MAX)); } static int sysctl_hw_jme_rx_coal_pkt(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, PCCRX_COAL_PKT_MIN, PCCRX_COAL_PKT_MAX)); } static int sysctl_hw_jme_proc_limit(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, JME_PROC_MIN, JME_PROC_MAX)); } Index: head/sys/dev/msk/if_msk.c =================================================================== --- head/sys/dev/msk/if_msk.c (revision 295734) +++ head/sys/dev/msk/if_msk.c (revision 295735) @@ -1,4615 +1,4615 @@ /****************************************************************************** * * Name : sky2.c * Project: Gigabit Ethernet Driver for FreeBSD 5.x/6.x * Version: $Revision: 1.23 $ * Date : $Date: 2005/12/22 09:04:11 $ * Purpose: Main driver source file * *****************************************************************************/ /****************************************************************************** * * LICENSE: * Copyright (C) Marvell International Ltd. and/or its affiliates * * The computer program files contained in this folder ("Files") * are provided to you under the BSD-type license terms provided * below, and any use of such Files and any derivative works * thereof created by you shall be governed by the following terms * and conditions: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - 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. * - Neither the name of Marvell nor the names of its contributors * may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * /LICENSE * *****************************************************************************/ /*- * Copyright (c) 1997, 1998, 1999, 2000 * 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 Bill Paul OR THE VOICES IN HIS HEAD * 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. */ /*- * Copyright (c) 2003 Nathan L. Binkert * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Device driver for the Marvell Yukon II Ethernet controller. * Due to lack of documentation, this driver is based on the code from * sk(4) and Marvell's myk(4) driver for FreeBSD 5.x. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MODULE_DEPEND(msk, pci, 1, 1, 1); MODULE_DEPEND(msk, ether, 1, 1, 1); MODULE_DEPEND(msk, miibus, 1, 1, 1); /* "device miibus" required. See GENERIC if you get errors here. */ #include "miibus_if.h" /* Tunables. */ static int msi_disable = 0; TUNABLE_INT("hw.msk.msi_disable", &msi_disable); static int legacy_intr = 0; TUNABLE_INT("hw.msk.legacy_intr", &legacy_intr); static int jumbo_disable = 0; TUNABLE_INT("hw.msk.jumbo_disable", &jumbo_disable); #define MSK_CSUM_FEATURES (CSUM_TCP | CSUM_UDP) /* * Devices supported by this driver. */ static const struct msk_product { uint16_t msk_vendorid; uint16_t msk_deviceid; const char *msk_name; } msk_products[] = { { VENDORID_SK, DEVICEID_SK_YUKON2, "SK-9Sxx Gigabit Ethernet" }, { VENDORID_SK, DEVICEID_SK_YUKON2_EXPR, "SK-9Exx Gigabit Ethernet"}, { VENDORID_MARVELL, DEVICEID_MRVL_8021CU, "Marvell Yukon 88E8021CU Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8021X, "Marvell Yukon 88E8021 SX/LX Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8022CU, "Marvell Yukon 88E8022CU Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8022X, "Marvell Yukon 88E8022 SX/LX Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8061CU, "Marvell Yukon 88E8061CU Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8061X, "Marvell Yukon 88E8061 SX/LX Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8062CU, "Marvell Yukon 88E8062CU Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8062X, "Marvell Yukon 88E8062 SX/LX Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8035, "Marvell Yukon 88E8035 Fast Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8036, "Marvell Yukon 88E8036 Fast Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8038, "Marvell Yukon 88E8038 Fast Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8039, "Marvell Yukon 88E8039 Fast Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8040, "Marvell Yukon 88E8040 Fast Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8040T, "Marvell Yukon 88E8040T Fast Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8042, "Marvell Yukon 88E8042 Fast Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_8048, "Marvell Yukon 88E8048 Fast Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_4361, "Marvell Yukon 88E8050 Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_4360, "Marvell Yukon 88E8052 Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_4362, "Marvell Yukon 88E8053 Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_4363, "Marvell Yukon 88E8055 Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_4364, "Marvell Yukon 88E8056 Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_4365, "Marvell Yukon 88E8070 Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_436A, "Marvell Yukon 88E8058 Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_436B, "Marvell Yukon 88E8071 Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_436C, "Marvell Yukon 88E8072 Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_436D, "Marvell Yukon 88E8055 Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_4370, "Marvell Yukon 88E8075 Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_4380, "Marvell Yukon 88E8057 Gigabit Ethernet" }, { VENDORID_MARVELL, DEVICEID_MRVL_4381, "Marvell Yukon 88E8059 Gigabit Ethernet" }, { VENDORID_DLINK, DEVICEID_DLINK_DGE550SX, "D-Link 550SX Gigabit Ethernet" }, { VENDORID_DLINK, DEVICEID_DLINK_DGE560SX, "D-Link 560SX Gigabit Ethernet" }, { VENDORID_DLINK, DEVICEID_DLINK_DGE560T, "D-Link 560T Gigabit Ethernet" } }; static const char *model_name[] = { "Yukon XL", "Yukon EC Ultra", "Yukon EX", "Yukon EC", "Yukon FE", "Yukon FE+", "Yukon Supreme", "Yukon Ultra 2", "Yukon Unknown", "Yukon Optima", }; static int mskc_probe(device_t); static int mskc_attach(device_t); static int mskc_detach(device_t); static int mskc_shutdown(device_t); static int mskc_setup_rambuffer(struct msk_softc *); static int mskc_suspend(device_t); static int mskc_resume(device_t); static bus_dma_tag_t mskc_get_dma_tag(device_t, device_t); static void mskc_reset(struct msk_softc *); static int msk_probe(device_t); static int msk_attach(device_t); static int msk_detach(device_t); static void msk_tick(void *); static void msk_intr(void *); static void msk_intr_phy(struct msk_if_softc *); static void msk_intr_gmac(struct msk_if_softc *); static __inline void msk_rxput(struct msk_if_softc *); static int msk_handle_events(struct msk_softc *); static void msk_handle_hwerr(struct msk_if_softc *, uint32_t); static void msk_intr_hwerr(struct msk_softc *); #ifndef __NO_STRICT_ALIGNMENT static __inline void msk_fixup_rx(struct mbuf *); #endif static __inline void msk_rxcsum(struct msk_if_softc *, uint32_t, struct mbuf *); static void msk_rxeof(struct msk_if_softc *, uint32_t, uint32_t, int); static void msk_jumbo_rxeof(struct msk_if_softc *, uint32_t, uint32_t, int); static void msk_txeof(struct msk_if_softc *, int); static int msk_encap(struct msk_if_softc *, struct mbuf **); static void msk_start(struct ifnet *); static void msk_start_locked(struct ifnet *); static int msk_ioctl(struct ifnet *, u_long, caddr_t); static void msk_set_prefetch(struct msk_softc *, int, bus_addr_t, uint32_t); static void msk_set_rambuffer(struct msk_if_softc *); static void msk_set_tx_stfwd(struct msk_if_softc *); static void msk_init(void *); static void msk_init_locked(struct msk_if_softc *); static void msk_stop(struct msk_if_softc *); static void msk_watchdog(struct msk_if_softc *); static int msk_mediachange(struct ifnet *); static void msk_mediastatus(struct ifnet *, struct ifmediareq *); static void msk_phy_power(struct msk_softc *, int); static void msk_dmamap_cb(void *, bus_dma_segment_t *, int, int); static int msk_status_dma_alloc(struct msk_softc *); static void msk_status_dma_free(struct msk_softc *); static int msk_txrx_dma_alloc(struct msk_if_softc *); static int msk_rx_dma_jalloc(struct msk_if_softc *); static void msk_txrx_dma_free(struct msk_if_softc *); static void msk_rx_dma_jfree(struct msk_if_softc *); static int msk_rx_fill(struct msk_if_softc *, int); static int msk_init_rx_ring(struct msk_if_softc *); static int msk_init_jumbo_rx_ring(struct msk_if_softc *); static void msk_init_tx_ring(struct msk_if_softc *); static __inline void msk_discard_rxbuf(struct msk_if_softc *, int); static __inline void msk_discard_jumbo_rxbuf(struct msk_if_softc *, int); static int msk_newbuf(struct msk_if_softc *, int); static int msk_jumbo_newbuf(struct msk_if_softc *, int); static int msk_phy_readreg(struct msk_if_softc *, int, int); static int msk_phy_writereg(struct msk_if_softc *, int, int, int); static int msk_miibus_readreg(device_t, int, int); static int msk_miibus_writereg(device_t, int, int, int); static void msk_miibus_statchg(device_t); static void msk_rxfilter(struct msk_if_softc *); static void msk_setvlan(struct msk_if_softc *, struct ifnet *); static void msk_stats_clear(struct msk_if_softc *); static void msk_stats_update(struct msk_if_softc *); static int msk_sysctl_stat32(SYSCTL_HANDLER_ARGS); static int msk_sysctl_stat64(SYSCTL_HANDLER_ARGS); static void msk_sysctl_node(struct msk_if_softc *); static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int, int); static int sysctl_hw_msk_proc_limit(SYSCTL_HANDLER_ARGS); static device_method_t mskc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mskc_probe), DEVMETHOD(device_attach, mskc_attach), DEVMETHOD(device_detach, mskc_detach), DEVMETHOD(device_suspend, mskc_suspend), DEVMETHOD(device_resume, mskc_resume), DEVMETHOD(device_shutdown, mskc_shutdown), DEVMETHOD(bus_get_dma_tag, mskc_get_dma_tag), DEVMETHOD_END }; static driver_t mskc_driver = { "mskc", mskc_methods, sizeof(struct msk_softc) }; static devclass_t mskc_devclass; static device_method_t msk_methods[] = { /* Device interface */ DEVMETHOD(device_probe, msk_probe), DEVMETHOD(device_attach, msk_attach), DEVMETHOD(device_detach, msk_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* MII interface */ DEVMETHOD(miibus_readreg, msk_miibus_readreg), DEVMETHOD(miibus_writereg, msk_miibus_writereg), DEVMETHOD(miibus_statchg, msk_miibus_statchg), DEVMETHOD_END }; static driver_t msk_driver = { "msk", msk_methods, sizeof(struct msk_if_softc) }; static devclass_t msk_devclass; DRIVER_MODULE(mskc, pci, mskc_driver, mskc_devclass, NULL, NULL); DRIVER_MODULE(msk, mskc, msk_driver, msk_devclass, NULL, NULL); DRIVER_MODULE(miibus, msk, miibus_driver, miibus_devclass, NULL, NULL); static struct resource_spec msk_res_spec_io[] = { { SYS_RES_IOPORT, PCIR_BAR(1), RF_ACTIVE }, { -1, 0, 0 } }; static struct resource_spec msk_res_spec_mem[] = { { SYS_RES_MEMORY, PCIR_BAR(0), RF_ACTIVE }, { -1, 0, 0 } }; static struct resource_spec msk_irq_spec_legacy[] = { { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0, 0 } }; static struct resource_spec msk_irq_spec_msi[] = { { SYS_RES_IRQ, 1, RF_ACTIVE }, { -1, 0, 0 } }; static int msk_miibus_readreg(device_t dev, int phy, int reg) { struct msk_if_softc *sc_if; sc_if = device_get_softc(dev); return (msk_phy_readreg(sc_if, phy, reg)); } static int msk_phy_readreg(struct msk_if_softc *sc_if, int phy, int reg) { struct msk_softc *sc; int i, val; sc = sc_if->msk_softc; GMAC_WRITE_2(sc, sc_if->msk_port, GM_SMI_CTRL, GM_SMI_CT_PHY_AD(phy) | GM_SMI_CT_REG_AD(reg) | GM_SMI_CT_OP_RD); for (i = 0; i < MSK_TIMEOUT; i++) { DELAY(1); val = GMAC_READ_2(sc, sc_if->msk_port, GM_SMI_CTRL); if ((val & GM_SMI_CT_RD_VAL) != 0) { val = GMAC_READ_2(sc, sc_if->msk_port, GM_SMI_DATA); break; } } if (i == MSK_TIMEOUT) { if_printf(sc_if->msk_ifp, "phy failed to come ready\n"); val = 0; } return (val); } static int msk_miibus_writereg(device_t dev, int phy, int reg, int val) { struct msk_if_softc *sc_if; sc_if = device_get_softc(dev); return (msk_phy_writereg(sc_if, phy, reg, val)); } static int msk_phy_writereg(struct msk_if_softc *sc_if, int phy, int reg, int val) { struct msk_softc *sc; int i; sc = sc_if->msk_softc; GMAC_WRITE_2(sc, sc_if->msk_port, GM_SMI_DATA, val); GMAC_WRITE_2(sc, sc_if->msk_port, GM_SMI_CTRL, GM_SMI_CT_PHY_AD(phy) | GM_SMI_CT_REG_AD(reg)); for (i = 0; i < MSK_TIMEOUT; i++) { DELAY(1); if ((GMAC_READ_2(sc, sc_if->msk_port, GM_SMI_CTRL) & GM_SMI_CT_BUSY) == 0) break; } if (i == MSK_TIMEOUT) if_printf(sc_if->msk_ifp, "phy write timeout\n"); return (0); } static void msk_miibus_statchg(device_t dev) { struct msk_softc *sc; struct msk_if_softc *sc_if; struct mii_data *mii; struct ifnet *ifp; uint32_t gmac; sc_if = device_get_softc(dev); sc = sc_if->msk_softc; MSK_IF_LOCK_ASSERT(sc_if); mii = device_get_softc(sc_if->msk_miibus); ifp = sc_if->msk_ifp; if (mii == NULL || ifp == NULL || (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; sc_if->msk_flags &= ~MSK_FLAG_LINK; if ((mii->mii_media_status & (IFM_AVALID | IFM_ACTIVE)) == (IFM_AVALID | IFM_ACTIVE)) { switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: sc_if->msk_flags |= MSK_FLAG_LINK; break; case IFM_1000_T: case IFM_1000_SX: case IFM_1000_LX: case IFM_1000_CX: if ((sc_if->msk_flags & MSK_FLAG_FASTETHER) == 0) sc_if->msk_flags |= MSK_FLAG_LINK; break; default: break; } } if ((sc_if->msk_flags & MSK_FLAG_LINK) != 0) { /* Enable Tx FIFO Underrun. */ CSR_WRITE_1(sc, MR_ADDR(sc_if->msk_port, GMAC_IRQ_MSK), GM_IS_TX_FF_UR | GM_IS_RX_FF_OR); /* * Because mii(4) notify msk(4) that it detected link status * change, there is no need to enable automatic * speed/flow-control/duplex updates. */ gmac = GM_GPCR_AU_ALL_DIS; switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_1000_SX: case IFM_1000_T: gmac |= GM_GPCR_SPEED_1000; break; case IFM_100_TX: gmac |= GM_GPCR_SPEED_100; break; case IFM_10_T: break; } if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) == 0) gmac |= GM_GPCR_FC_RX_DIS; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) == 0) gmac |= GM_GPCR_FC_TX_DIS; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) gmac |= GM_GPCR_DUP_FULL; else gmac |= GM_GPCR_FC_RX_DIS | GM_GPCR_FC_TX_DIS; gmac |= GM_GPCR_RX_ENA | GM_GPCR_TX_ENA; GMAC_WRITE_2(sc, sc_if->msk_port, GM_GP_CTRL, gmac); /* Read again to ensure writing. */ GMAC_READ_2(sc, sc_if->msk_port, GM_GP_CTRL); gmac = GMC_PAUSE_OFF; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0) gmac = GMC_PAUSE_ON; } CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, GMAC_CTRL), gmac); /* Enable PHY interrupt for FIFO underrun/overflow. */ msk_phy_writereg(sc_if, PHY_ADDR_MARV, PHY_MARV_INT_MASK, PHY_M_IS_FIFO_ERROR); } else { /* * Link state changed to down. * Disable PHY interrupts. */ msk_phy_writereg(sc_if, PHY_ADDR_MARV, PHY_MARV_INT_MASK, 0); /* Disable Rx/Tx MAC. */ gmac = GMAC_READ_2(sc, sc_if->msk_port, GM_GP_CTRL); if ((gmac & (GM_GPCR_RX_ENA | GM_GPCR_TX_ENA)) != 0) { gmac &= ~(GM_GPCR_RX_ENA | GM_GPCR_TX_ENA); GMAC_WRITE_2(sc, sc_if->msk_port, GM_GP_CTRL, gmac); /* Read again to ensure writing. */ GMAC_READ_2(sc, sc_if->msk_port, GM_GP_CTRL); } } } static void msk_rxfilter(struct msk_if_softc *sc_if) { struct msk_softc *sc; struct ifnet *ifp; struct ifmultiaddr *ifma; uint32_t mchash[2]; uint32_t crc; uint16_t mode; sc = sc_if->msk_softc; MSK_IF_LOCK_ASSERT(sc_if); ifp = sc_if->msk_ifp; bzero(mchash, sizeof(mchash)); mode = GMAC_READ_2(sc, sc_if->msk_port, GM_RX_CTRL); if ((ifp->if_flags & IFF_PROMISC) != 0) mode &= ~(GM_RXCR_UCF_ENA | GM_RXCR_MCF_ENA); else if ((ifp->if_flags & IFF_ALLMULTI) != 0) { mode |= GM_RXCR_UCF_ENA | GM_RXCR_MCF_ENA; mchash[0] = 0xffff; mchash[1] = 0xffff; } else { mode |= GM_RXCR_UCF_ENA; if_maddr_rlock(ifp); TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; crc = ether_crc32_be(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN); /* Just want the 6 least significant bits. */ crc &= 0x3f; /* Set the corresponding bit in the hash table. */ mchash[crc >> 5] |= 1 << (crc & 0x1f); } if_maddr_runlock(ifp); if (mchash[0] != 0 || mchash[1] != 0) mode |= GM_RXCR_MCF_ENA; } GMAC_WRITE_2(sc, sc_if->msk_port, GM_MC_ADDR_H1, mchash[0] & 0xffff); GMAC_WRITE_2(sc, sc_if->msk_port, GM_MC_ADDR_H2, (mchash[0] >> 16) & 0xffff); GMAC_WRITE_2(sc, sc_if->msk_port, GM_MC_ADDR_H3, mchash[1] & 0xffff); GMAC_WRITE_2(sc, sc_if->msk_port, GM_MC_ADDR_H4, (mchash[1] >> 16) & 0xffff); GMAC_WRITE_2(sc, sc_if->msk_port, GM_RX_CTRL, mode); } static void msk_setvlan(struct msk_if_softc *sc_if, struct ifnet *ifp) { struct msk_softc *sc; sc = sc_if->msk_softc; if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0) { CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, RX_GMF_CTRL_T), RX_VLAN_STRIP_ON); CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, TX_GMF_CTRL_T), TX_VLAN_TAG_ON); } else { CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, RX_GMF_CTRL_T), RX_VLAN_STRIP_OFF); CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, TX_GMF_CTRL_T), TX_VLAN_TAG_OFF); } } static int msk_rx_fill(struct msk_if_softc *sc_if, int jumbo) { uint16_t idx; int i; if ((sc_if->msk_flags & MSK_FLAG_DESCV2) == 0 && (sc_if->msk_ifp->if_capenable & IFCAP_RXCSUM) != 0) { /* Wait until controller executes OP_TCPSTART command. */ for (i = 100; i > 0; i--) { DELAY(100); idx = CSR_READ_2(sc_if->msk_softc, Y2_PREF_Q_ADDR(sc_if->msk_rxq, PREF_UNIT_GET_IDX_REG)); if (idx != 0) break; } if (i == 0) { device_printf(sc_if->msk_if_dev, "prefetch unit stuck?\n"); return (ETIMEDOUT); } /* * Fill consumed LE with free buffer. This can be done * in Rx handler but we don't want to add special code * in fast handler. */ if (jumbo > 0) { if (msk_jumbo_newbuf(sc_if, 0) != 0) return (ENOBUFS); bus_dmamap_sync(sc_if->msk_cdata.msk_jumbo_rx_ring_tag, sc_if->msk_cdata.msk_jumbo_rx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } else { if (msk_newbuf(sc_if, 0) != 0) return (ENOBUFS); bus_dmamap_sync(sc_if->msk_cdata.msk_rx_ring_tag, sc_if->msk_cdata.msk_rx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } sc_if->msk_cdata.msk_rx_prod = 0; CSR_WRITE_2(sc_if->msk_softc, Y2_PREF_Q_ADDR(sc_if->msk_rxq, PREF_UNIT_PUT_IDX_REG), sc_if->msk_cdata.msk_rx_prod); } return (0); } static int msk_init_rx_ring(struct msk_if_softc *sc_if) { struct msk_ring_data *rd; struct msk_rxdesc *rxd; int i, nbuf, prod; MSK_IF_LOCK_ASSERT(sc_if); sc_if->msk_cdata.msk_rx_cons = 0; sc_if->msk_cdata.msk_rx_prod = 0; sc_if->msk_cdata.msk_rx_putwm = MSK_PUT_WM; rd = &sc_if->msk_rdata; bzero(rd->msk_rx_ring, sizeof(struct msk_rx_desc) * MSK_RX_RING_CNT); for (i = prod = 0; i < MSK_RX_RING_CNT; i++) { rxd = &sc_if->msk_cdata.msk_rxdesc[prod]; rxd->rx_m = NULL; rxd->rx_le = &rd->msk_rx_ring[prod]; MSK_INC(prod, MSK_RX_RING_CNT); } nbuf = MSK_RX_BUF_CNT; prod = 0; /* Have controller know how to compute Rx checksum. */ if ((sc_if->msk_flags & MSK_FLAG_DESCV2) == 0 && (sc_if->msk_ifp->if_capenable & IFCAP_RXCSUM) != 0) { #ifdef MSK_64BIT_DMA rxd = &sc_if->msk_cdata.msk_rxdesc[prod]; rxd->rx_m = NULL; rxd->rx_le = &rd->msk_rx_ring[prod]; rxd->rx_le->msk_addr = htole32(ETHER_HDR_LEN << 16 | ETHER_HDR_LEN); rxd->rx_le->msk_control = htole32(OP_TCPSTART | HW_OWNER); MSK_INC(prod, MSK_RX_RING_CNT); MSK_INC(sc_if->msk_cdata.msk_rx_cons, MSK_RX_RING_CNT); #endif rxd = &sc_if->msk_cdata.msk_rxdesc[prod]; rxd->rx_m = NULL; rxd->rx_le = &rd->msk_rx_ring[prod]; rxd->rx_le->msk_addr = htole32(ETHER_HDR_LEN << 16 | ETHER_HDR_LEN); rxd->rx_le->msk_control = htole32(OP_TCPSTART | HW_OWNER); MSK_INC(prod, MSK_RX_RING_CNT); MSK_INC(sc_if->msk_cdata.msk_rx_cons, MSK_RX_RING_CNT); nbuf--; } for (i = 0; i < nbuf; i++) { if (msk_newbuf(sc_if, prod) != 0) return (ENOBUFS); MSK_RX_INC(prod, MSK_RX_RING_CNT); } bus_dmamap_sync(sc_if->msk_cdata.msk_rx_ring_tag, sc_if->msk_cdata.msk_rx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); /* Update prefetch unit. */ sc_if->msk_cdata.msk_rx_prod = prod; CSR_WRITE_2(sc_if->msk_softc, Y2_PREF_Q_ADDR(sc_if->msk_rxq, PREF_UNIT_PUT_IDX_REG), (sc_if->msk_cdata.msk_rx_prod + MSK_RX_RING_CNT - 1) % MSK_RX_RING_CNT); if (msk_rx_fill(sc_if, 0) != 0) return (ENOBUFS); return (0); } static int msk_init_jumbo_rx_ring(struct msk_if_softc *sc_if) { struct msk_ring_data *rd; struct msk_rxdesc *rxd; int i, nbuf, prod; MSK_IF_LOCK_ASSERT(sc_if); sc_if->msk_cdata.msk_rx_cons = 0; sc_if->msk_cdata.msk_rx_prod = 0; sc_if->msk_cdata.msk_rx_putwm = MSK_PUT_WM; rd = &sc_if->msk_rdata; bzero(rd->msk_jumbo_rx_ring, sizeof(struct msk_rx_desc) * MSK_JUMBO_RX_RING_CNT); for (i = prod = 0; i < MSK_JUMBO_RX_RING_CNT; i++) { rxd = &sc_if->msk_cdata.msk_jumbo_rxdesc[prod]; rxd->rx_m = NULL; rxd->rx_le = &rd->msk_jumbo_rx_ring[prod]; MSK_INC(prod, MSK_JUMBO_RX_RING_CNT); } nbuf = MSK_RX_BUF_CNT; prod = 0; /* Have controller know how to compute Rx checksum. */ if ((sc_if->msk_flags & MSK_FLAG_DESCV2) == 0 && (sc_if->msk_ifp->if_capenable & IFCAP_RXCSUM) != 0) { #ifdef MSK_64BIT_DMA rxd = &sc_if->msk_cdata.msk_jumbo_rxdesc[prod]; rxd->rx_m = NULL; rxd->rx_le = &rd->msk_jumbo_rx_ring[prod]; rxd->rx_le->msk_addr = htole32(ETHER_HDR_LEN << 16 | ETHER_HDR_LEN); rxd->rx_le->msk_control = htole32(OP_TCPSTART | HW_OWNER); MSK_INC(prod, MSK_JUMBO_RX_RING_CNT); MSK_INC(sc_if->msk_cdata.msk_rx_cons, MSK_JUMBO_RX_RING_CNT); #endif rxd = &sc_if->msk_cdata.msk_jumbo_rxdesc[prod]; rxd->rx_m = NULL; rxd->rx_le = &rd->msk_jumbo_rx_ring[prod]; rxd->rx_le->msk_addr = htole32(ETHER_HDR_LEN << 16 | ETHER_HDR_LEN); rxd->rx_le->msk_control = htole32(OP_TCPSTART | HW_OWNER); MSK_INC(prod, MSK_JUMBO_RX_RING_CNT); MSK_INC(sc_if->msk_cdata.msk_rx_cons, MSK_JUMBO_RX_RING_CNT); nbuf--; } for (i = 0; i < nbuf; i++) { if (msk_jumbo_newbuf(sc_if, prod) != 0) return (ENOBUFS); MSK_RX_INC(prod, MSK_JUMBO_RX_RING_CNT); } bus_dmamap_sync(sc_if->msk_cdata.msk_jumbo_rx_ring_tag, sc_if->msk_cdata.msk_jumbo_rx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); /* Update prefetch unit. */ sc_if->msk_cdata.msk_rx_prod = prod; CSR_WRITE_2(sc_if->msk_softc, Y2_PREF_Q_ADDR(sc_if->msk_rxq, PREF_UNIT_PUT_IDX_REG), (sc_if->msk_cdata.msk_rx_prod + MSK_JUMBO_RX_RING_CNT - 1) % MSK_JUMBO_RX_RING_CNT); if (msk_rx_fill(sc_if, 1) != 0) return (ENOBUFS); return (0); } static void msk_init_tx_ring(struct msk_if_softc *sc_if) { struct msk_ring_data *rd; struct msk_txdesc *txd; int i; sc_if->msk_cdata.msk_tso_mtu = 0; sc_if->msk_cdata.msk_last_csum = 0; sc_if->msk_cdata.msk_tx_prod = 0; sc_if->msk_cdata.msk_tx_cons = 0; sc_if->msk_cdata.msk_tx_cnt = 0; sc_if->msk_cdata.msk_tx_high_addr = 0; rd = &sc_if->msk_rdata; bzero(rd->msk_tx_ring, sizeof(struct msk_tx_desc) * MSK_TX_RING_CNT); for (i = 0; i < MSK_TX_RING_CNT; i++) { txd = &sc_if->msk_cdata.msk_txdesc[i]; txd->tx_m = NULL; txd->tx_le = &rd->msk_tx_ring[i]; } bus_dmamap_sync(sc_if->msk_cdata.msk_tx_ring_tag, sc_if->msk_cdata.msk_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static __inline void msk_discard_rxbuf(struct msk_if_softc *sc_if, int idx) { struct msk_rx_desc *rx_le; struct msk_rxdesc *rxd; struct mbuf *m; #ifdef MSK_64BIT_DMA rxd = &sc_if->msk_cdata.msk_rxdesc[idx]; rx_le = rxd->rx_le; rx_le->msk_control = htole32(OP_ADDR64 | HW_OWNER); MSK_INC(idx, MSK_RX_RING_CNT); #endif rxd = &sc_if->msk_cdata.msk_rxdesc[idx]; m = rxd->rx_m; rx_le = rxd->rx_le; rx_le->msk_control = htole32(m->m_len | OP_PACKET | HW_OWNER); } static __inline void msk_discard_jumbo_rxbuf(struct msk_if_softc *sc_if, int idx) { struct msk_rx_desc *rx_le; struct msk_rxdesc *rxd; struct mbuf *m; #ifdef MSK_64BIT_DMA rxd = &sc_if->msk_cdata.msk_jumbo_rxdesc[idx]; rx_le = rxd->rx_le; rx_le->msk_control = htole32(OP_ADDR64 | HW_OWNER); MSK_INC(idx, MSK_JUMBO_RX_RING_CNT); #endif rxd = &sc_if->msk_cdata.msk_jumbo_rxdesc[idx]; m = rxd->rx_m; rx_le = rxd->rx_le; rx_le->msk_control = htole32(m->m_len | OP_PACKET | HW_OWNER); } static int msk_newbuf(struct msk_if_softc *sc_if, int idx) { struct msk_rx_desc *rx_le; struct msk_rxdesc *rxd; struct mbuf *m; bus_dma_segment_t segs[1]; bus_dmamap_t map; int nsegs; m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) return (ENOBUFS); m->m_len = m->m_pkthdr.len = MCLBYTES; if ((sc_if->msk_flags & MSK_FLAG_RAMBUF) == 0) m_adj(m, ETHER_ALIGN); #ifndef __NO_STRICT_ALIGNMENT else m_adj(m, MSK_RX_BUF_ALIGN); #endif if (bus_dmamap_load_mbuf_sg(sc_if->msk_cdata.msk_rx_tag, sc_if->msk_cdata.msk_rx_sparemap, m, segs, &nsegs, BUS_DMA_NOWAIT) != 0) { m_freem(m); return (ENOBUFS); } KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); rxd = &sc_if->msk_cdata.msk_rxdesc[idx]; #ifdef MSK_64BIT_DMA rx_le = rxd->rx_le; rx_le->msk_addr = htole32(MSK_ADDR_HI(segs[0].ds_addr)); rx_le->msk_control = htole32(OP_ADDR64 | HW_OWNER); MSK_INC(idx, MSK_RX_RING_CNT); rxd = &sc_if->msk_cdata.msk_rxdesc[idx]; #endif if (rxd->rx_m != NULL) { bus_dmamap_sync(sc_if->msk_cdata.msk_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc_if->msk_cdata.msk_rx_tag, rxd->rx_dmamap); rxd->rx_m = NULL; } map = rxd->rx_dmamap; rxd->rx_dmamap = sc_if->msk_cdata.msk_rx_sparemap; sc_if->msk_cdata.msk_rx_sparemap = map; bus_dmamap_sync(sc_if->msk_cdata.msk_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_PREREAD); rxd->rx_m = m; rx_le = rxd->rx_le; rx_le->msk_addr = htole32(MSK_ADDR_LO(segs[0].ds_addr)); rx_le->msk_control = htole32(segs[0].ds_len | OP_PACKET | HW_OWNER); return (0); } static int msk_jumbo_newbuf(struct msk_if_softc *sc_if, int idx) { struct msk_rx_desc *rx_le; struct msk_rxdesc *rxd; struct mbuf *m; bus_dma_segment_t segs[1]; bus_dmamap_t map; int nsegs; m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, MJUM9BYTES); if (m == NULL) return (ENOBUFS); m->m_len = m->m_pkthdr.len = MJUM9BYTES; if ((sc_if->msk_flags & MSK_FLAG_RAMBUF) == 0) m_adj(m, ETHER_ALIGN); #ifndef __NO_STRICT_ALIGNMENT else m_adj(m, MSK_RX_BUF_ALIGN); #endif if (bus_dmamap_load_mbuf_sg(sc_if->msk_cdata.msk_jumbo_rx_tag, sc_if->msk_cdata.msk_jumbo_rx_sparemap, m, segs, &nsegs, BUS_DMA_NOWAIT) != 0) { m_freem(m); return (ENOBUFS); } KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); rxd = &sc_if->msk_cdata.msk_jumbo_rxdesc[idx]; #ifdef MSK_64BIT_DMA rx_le = rxd->rx_le; rx_le->msk_addr = htole32(MSK_ADDR_HI(segs[0].ds_addr)); rx_le->msk_control = htole32(OP_ADDR64 | HW_OWNER); MSK_INC(idx, MSK_JUMBO_RX_RING_CNT); rxd = &sc_if->msk_cdata.msk_jumbo_rxdesc[idx]; #endif if (rxd->rx_m != NULL) { bus_dmamap_sync(sc_if->msk_cdata.msk_jumbo_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc_if->msk_cdata.msk_jumbo_rx_tag, rxd->rx_dmamap); rxd->rx_m = NULL; } map = rxd->rx_dmamap; rxd->rx_dmamap = sc_if->msk_cdata.msk_jumbo_rx_sparemap; sc_if->msk_cdata.msk_jumbo_rx_sparemap = map; bus_dmamap_sync(sc_if->msk_cdata.msk_jumbo_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_PREREAD); rxd->rx_m = m; rx_le = rxd->rx_le; rx_le->msk_addr = htole32(MSK_ADDR_LO(segs[0].ds_addr)); rx_le->msk_control = htole32(segs[0].ds_len | OP_PACKET | HW_OWNER); return (0); } /* * Set media options. */ static int msk_mediachange(struct ifnet *ifp) { struct msk_if_softc *sc_if; struct mii_data *mii; int error; sc_if = ifp->if_softc; MSK_IF_LOCK(sc_if); mii = device_get_softc(sc_if->msk_miibus); error = mii_mediachg(mii); MSK_IF_UNLOCK(sc_if); return (error); } /* * Report current media status. */ static void msk_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr) { struct msk_if_softc *sc_if; struct mii_data *mii; sc_if = ifp->if_softc; MSK_IF_LOCK(sc_if); if ((ifp->if_flags & IFF_UP) == 0) { MSK_IF_UNLOCK(sc_if); return; } mii = device_get_softc(sc_if->msk_miibus); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; MSK_IF_UNLOCK(sc_if); } static int msk_ioctl(struct ifnet *ifp, u_long command, caddr_t data) { struct msk_if_softc *sc_if; struct ifreq *ifr; struct mii_data *mii; int error, mask, reinit; sc_if = ifp->if_softc; ifr = (struct ifreq *)data; error = 0; switch(command) { case SIOCSIFMTU: MSK_IF_LOCK(sc_if); if (ifr->ifr_mtu > MSK_JUMBO_MTU || ifr->ifr_mtu < ETHERMIN) error = EINVAL; else if (ifp->if_mtu != ifr->ifr_mtu) { if (ifr->ifr_mtu > ETHERMTU) { if ((sc_if->msk_flags & MSK_FLAG_JUMBO) == 0) { error = EINVAL; MSK_IF_UNLOCK(sc_if); break; } if ((sc_if->msk_flags & MSK_FLAG_JUMBO_NOCSUM) != 0) { ifp->if_hwassist &= ~(MSK_CSUM_FEATURES | CSUM_TSO); ifp->if_capenable &= ~(IFCAP_TSO4 | IFCAP_TXCSUM); VLAN_CAPABILITIES(ifp); } } ifp->if_mtu = ifr->ifr_mtu; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; msk_init_locked(sc_if); } } MSK_IF_UNLOCK(sc_if); break; case SIOCSIFFLAGS: MSK_IF_LOCK(sc_if); if ((ifp->if_flags & IFF_UP) != 0) { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0 && ((ifp->if_flags ^ sc_if->msk_if_flags) & (IFF_PROMISC | IFF_ALLMULTI)) != 0) msk_rxfilter(sc_if); else if ((sc_if->msk_flags & MSK_FLAG_DETACH) == 0) msk_init_locked(sc_if); } else if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) msk_stop(sc_if); sc_if->msk_if_flags = ifp->if_flags; MSK_IF_UNLOCK(sc_if); break; case SIOCADDMULTI: case SIOCDELMULTI: MSK_IF_LOCK(sc_if); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) msk_rxfilter(sc_if); MSK_IF_UNLOCK(sc_if); break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: mii = device_get_softc(sc_if->msk_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); break; case SIOCSIFCAP: reinit = 0; MSK_IF_LOCK(sc_if); mask = ifr->ifr_reqcap ^ ifp->if_capenable; if ((mask & IFCAP_TXCSUM) != 0 && (IFCAP_TXCSUM & ifp->if_capabilities) != 0) { ifp->if_capenable ^= IFCAP_TXCSUM; if ((IFCAP_TXCSUM & ifp->if_capenable) != 0) ifp->if_hwassist |= MSK_CSUM_FEATURES; else ifp->if_hwassist &= ~MSK_CSUM_FEATURES; } if ((mask & IFCAP_RXCSUM) != 0 && (IFCAP_RXCSUM & ifp->if_capabilities) != 0) { ifp->if_capenable ^= IFCAP_RXCSUM; if ((sc_if->msk_flags & MSK_FLAG_DESCV2) == 0) reinit = 1; } if ((mask & IFCAP_VLAN_HWCSUM) != 0 && (IFCAP_VLAN_HWCSUM & ifp->if_capabilities) != 0) ifp->if_capenable ^= IFCAP_VLAN_HWCSUM; if ((mask & IFCAP_TSO4) != 0 && (IFCAP_TSO4 & ifp->if_capabilities) != 0) { ifp->if_capenable ^= IFCAP_TSO4; if ((IFCAP_TSO4 & ifp->if_capenable) != 0) ifp->if_hwassist |= CSUM_TSO; else ifp->if_hwassist &= ~CSUM_TSO; } if ((mask & IFCAP_VLAN_HWTSO) != 0 && (IFCAP_VLAN_HWTSO & ifp->if_capabilities) != 0) ifp->if_capenable ^= IFCAP_VLAN_HWTSO; if ((mask & IFCAP_VLAN_HWTAGGING) != 0 && (IFCAP_VLAN_HWTAGGING & ifp->if_capabilities) != 0) { ifp->if_capenable ^= IFCAP_VLAN_HWTAGGING; if ((IFCAP_VLAN_HWTAGGING & ifp->if_capenable) == 0) ifp->if_capenable &= ~(IFCAP_VLAN_HWTSO | IFCAP_VLAN_HWCSUM); msk_setvlan(sc_if, ifp); } if (ifp->if_mtu > ETHERMTU && (sc_if->msk_flags & MSK_FLAG_JUMBO_NOCSUM) != 0) { ifp->if_hwassist &= ~(MSK_CSUM_FEATURES | CSUM_TSO); ifp->if_capenable &= ~(IFCAP_TSO4 | IFCAP_TXCSUM); } VLAN_CAPABILITIES(ifp); if (reinit > 0 && (ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; msk_init_locked(sc_if); } MSK_IF_UNLOCK(sc_if); break; default: error = ether_ioctl(ifp, command, data); break; } return (error); } static int mskc_probe(device_t dev) { const struct msk_product *mp; uint16_t vendor, devid; int i; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); mp = msk_products; for (i = 0; i < nitems(msk_products); i++, mp++) { if (vendor == mp->msk_vendorid && devid == mp->msk_deviceid) { device_set_desc(dev, mp->msk_name); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int mskc_setup_rambuffer(struct msk_softc *sc) { int next; int i; /* Get adapter SRAM size. */ sc->msk_ramsize = CSR_READ_1(sc, B2_E_0) * 4; if (bootverbose) device_printf(sc->msk_dev, "RAM buffer size : %dKB\n", sc->msk_ramsize); if (sc->msk_ramsize == 0) return (0); sc->msk_pflags |= MSK_FLAG_RAMBUF; /* * Give receiver 2/3 of memory and round down to the multiple * of 1024. Tx/Rx RAM buffer size of Yukon II should be multiple * of 1024. */ sc->msk_rxqsize = rounddown((sc->msk_ramsize * 1024 * 2) / 3, 1024); sc->msk_txqsize = (sc->msk_ramsize * 1024) - sc->msk_rxqsize; for (i = 0, next = 0; i < sc->msk_num_port; i++) { sc->msk_rxqstart[i] = next; sc->msk_rxqend[i] = next + sc->msk_rxqsize - 1; next = sc->msk_rxqend[i] + 1; sc->msk_txqstart[i] = next; sc->msk_txqend[i] = next + sc->msk_txqsize - 1; next = sc->msk_txqend[i] + 1; if (bootverbose) { device_printf(sc->msk_dev, "Port %d : Rx Queue %dKB(0x%08x:0x%08x)\n", i, sc->msk_rxqsize / 1024, sc->msk_rxqstart[i], sc->msk_rxqend[i]); device_printf(sc->msk_dev, "Port %d : Tx Queue %dKB(0x%08x:0x%08x)\n", i, sc->msk_txqsize / 1024, sc->msk_txqstart[i], sc->msk_txqend[i]); } } return (0); } static void msk_phy_power(struct msk_softc *sc, int mode) { uint32_t our, val; int i; switch (mode) { case MSK_PHY_POWERUP: /* Switch power to VCC (WA for VAUX problem). */ CSR_WRITE_1(sc, B0_POWER_CTRL, PC_VAUX_ENA | PC_VCC_ENA | PC_VAUX_OFF | PC_VCC_ON); /* Disable Core Clock Division, set Clock Select to 0. */ CSR_WRITE_4(sc, B2_Y2_CLK_CTRL, Y2_CLK_DIV_DIS); val = 0; if (sc->msk_hw_id == CHIP_ID_YUKON_XL && sc->msk_hw_rev > CHIP_REV_YU_XL_A1) { /* Enable bits are inverted. */ val = Y2_PCI_CLK_LNK1_DIS | Y2_COR_CLK_LNK1_DIS | Y2_CLK_GAT_LNK1_DIS | Y2_PCI_CLK_LNK2_DIS | Y2_COR_CLK_LNK2_DIS | Y2_CLK_GAT_LNK2_DIS; } /* * Enable PCI & Core Clock, enable clock gating for both Links. */ CSR_WRITE_1(sc, B2_Y2_CLK_GATE, val); our = CSR_PCI_READ_4(sc, PCI_OUR_REG_1); our &= ~(PCI_Y2_PHY1_POWD | PCI_Y2_PHY2_POWD); if (sc->msk_hw_id == CHIP_ID_YUKON_XL) { if (sc->msk_hw_rev > CHIP_REV_YU_XL_A1) { /* Deassert Low Power for 1st PHY. */ our |= PCI_Y2_PHY1_COMA; if (sc->msk_num_port > 1) our |= PCI_Y2_PHY2_COMA; } } if (sc->msk_hw_id == CHIP_ID_YUKON_EC_U || sc->msk_hw_id == CHIP_ID_YUKON_EX || sc->msk_hw_id >= CHIP_ID_YUKON_FE_P) { val = CSR_PCI_READ_4(sc, PCI_OUR_REG_4); val &= (PCI_FORCE_ASPM_REQUEST | PCI_ASPM_GPHY_LINK_DOWN | PCI_ASPM_INT_FIFO_EMPTY | PCI_ASPM_CLKRUN_REQUEST); /* Set all bits to 0 except bits 15..12. */ CSR_PCI_WRITE_4(sc, PCI_OUR_REG_4, val); val = CSR_PCI_READ_4(sc, PCI_OUR_REG_5); val &= PCI_CTL_TIM_VMAIN_AV_MSK; CSR_PCI_WRITE_4(sc, PCI_OUR_REG_5, val); CSR_PCI_WRITE_4(sc, PCI_CFG_REG_1, 0); CSR_WRITE_2(sc, B0_CTST, Y2_HW_WOL_ON); /* * Disable status race, workaround for * Yukon EC Ultra & Yukon EX. */ val = CSR_READ_4(sc, B2_GP_IO); val |= GLB_GPIO_STAT_RACE_DIS; CSR_WRITE_4(sc, B2_GP_IO, val); CSR_READ_4(sc, B2_GP_IO); } /* Release PHY from PowerDown/COMA mode. */ CSR_PCI_WRITE_4(sc, PCI_OUR_REG_1, our); for (i = 0; i < sc->msk_num_port; i++) { CSR_WRITE_2(sc, MR_ADDR(i, GMAC_LINK_CTRL), GMLC_RST_SET); CSR_WRITE_2(sc, MR_ADDR(i, GMAC_LINK_CTRL), GMLC_RST_CLR); } break; case MSK_PHY_POWERDOWN: val = CSR_PCI_READ_4(sc, PCI_OUR_REG_1); val |= PCI_Y2_PHY1_POWD | PCI_Y2_PHY2_POWD; if (sc->msk_hw_id == CHIP_ID_YUKON_XL && sc->msk_hw_rev > CHIP_REV_YU_XL_A1) { val &= ~PCI_Y2_PHY1_COMA; if (sc->msk_num_port > 1) val &= ~PCI_Y2_PHY2_COMA; } CSR_PCI_WRITE_4(sc, PCI_OUR_REG_1, val); val = Y2_PCI_CLK_LNK1_DIS | Y2_COR_CLK_LNK1_DIS | Y2_CLK_GAT_LNK1_DIS | Y2_PCI_CLK_LNK2_DIS | Y2_COR_CLK_LNK2_DIS | Y2_CLK_GAT_LNK2_DIS; if (sc->msk_hw_id == CHIP_ID_YUKON_XL && sc->msk_hw_rev > CHIP_REV_YU_XL_A1) { /* Enable bits are inverted. */ val = 0; } /* * Disable PCI & Core Clock, disable clock gating for * both Links. */ CSR_WRITE_1(sc, B2_Y2_CLK_GATE, val); CSR_WRITE_1(sc, B0_POWER_CTRL, PC_VAUX_ENA | PC_VCC_ENA | PC_VAUX_ON | PC_VCC_OFF); break; default: break; } } static void mskc_reset(struct msk_softc *sc) { bus_addr_t addr; uint16_t status; uint32_t val; int i, initram; /* Disable ASF. */ if (sc->msk_hw_id >= CHIP_ID_YUKON_XL && sc->msk_hw_id <= CHIP_ID_YUKON_SUPR) { if (sc->msk_hw_id == CHIP_ID_YUKON_EX || sc->msk_hw_id == CHIP_ID_YUKON_SUPR) { CSR_WRITE_4(sc, B28_Y2_CPU_WDOG, 0); status = CSR_READ_2(sc, B28_Y2_ASF_HCU_CCSR); /* Clear AHB bridge & microcontroller reset. */ status &= ~(Y2_ASF_HCU_CCSR_AHB_RST | Y2_ASF_HCU_CCSR_CPU_RST_MODE); /* Clear ASF microcontroller state. */ status &= ~Y2_ASF_HCU_CCSR_UC_STATE_MSK; status &= ~Y2_ASF_HCU_CCSR_CPU_CLK_DIVIDE_MSK; CSR_WRITE_2(sc, B28_Y2_ASF_HCU_CCSR, status); CSR_WRITE_4(sc, B28_Y2_CPU_WDOG, 0); } else CSR_WRITE_1(sc, B28_Y2_ASF_STAT_CMD, Y2_ASF_RESET); CSR_WRITE_2(sc, B0_CTST, Y2_ASF_DISABLE); /* * Since we disabled ASF, S/W reset is required for * Power Management. */ CSR_WRITE_2(sc, B0_CTST, CS_RST_SET); CSR_WRITE_2(sc, B0_CTST, CS_RST_CLR); } /* Clear all error bits in the PCI status register. */ status = pci_read_config(sc->msk_dev, PCIR_STATUS, 2); CSR_WRITE_1(sc, B2_TST_CTRL1, TST_CFG_WRITE_ON); pci_write_config(sc->msk_dev, PCIR_STATUS, status | PCIM_STATUS_PERR | PCIM_STATUS_SERR | PCIM_STATUS_RMABORT | PCIM_STATUS_RTABORT | PCIM_STATUS_MDPERR, 2); CSR_WRITE_2(sc, B0_CTST, CS_MRST_CLR); switch (sc->msk_bustype) { case MSK_PEX_BUS: /* Clear all PEX errors. */ CSR_PCI_WRITE_4(sc, PEX_UNC_ERR_STAT, 0xffffffff); val = CSR_PCI_READ_4(sc, PEX_UNC_ERR_STAT); if ((val & PEX_RX_OV) != 0) { sc->msk_intrmask &= ~Y2_IS_HW_ERR; sc->msk_intrhwemask &= ~Y2_IS_PCI_EXP; } break; case MSK_PCI_BUS: case MSK_PCIX_BUS: /* Set Cache Line Size to 2(8bytes) if configured to 0. */ val = pci_read_config(sc->msk_dev, PCIR_CACHELNSZ, 1); if (val == 0) pci_write_config(sc->msk_dev, PCIR_CACHELNSZ, 2, 1); if (sc->msk_bustype == MSK_PCIX_BUS) { /* Set Cache Line Size opt. */ val = pci_read_config(sc->msk_dev, PCI_OUR_REG_1, 4); val |= PCI_CLS_OPT; pci_write_config(sc->msk_dev, PCI_OUR_REG_1, val, 4); } break; } /* Set PHY power state. */ msk_phy_power(sc, MSK_PHY_POWERUP); /* Reset GPHY/GMAC Control */ for (i = 0; i < sc->msk_num_port; i++) { /* GPHY Control reset. */ CSR_WRITE_1(sc, MR_ADDR(i, GPHY_CTRL), GPC_RST_SET); CSR_WRITE_1(sc, MR_ADDR(i, GPHY_CTRL), GPC_RST_CLR); /* GMAC Control reset. */ CSR_WRITE_4(sc, MR_ADDR(i, GMAC_CTRL), GMC_RST_SET); CSR_WRITE_4(sc, MR_ADDR(i, GMAC_CTRL), GMC_RST_CLR); CSR_WRITE_4(sc, MR_ADDR(i, GMAC_CTRL), GMC_F_LOOPB_OFF); if (sc->msk_hw_id == CHIP_ID_YUKON_EX || sc->msk_hw_id == CHIP_ID_YUKON_SUPR) CSR_WRITE_4(sc, MR_ADDR(i, GMAC_CTRL), GMC_BYP_MACSECRX_ON | GMC_BYP_MACSECTX_ON | GMC_BYP_RETR_ON); } if (sc->msk_hw_id == CHIP_ID_YUKON_SUPR && sc->msk_hw_rev > CHIP_REV_YU_SU_B0) CSR_PCI_WRITE_4(sc, PCI_OUR_REG_3, PCI_CLK_MACSEC_DIS); if (sc->msk_hw_id == CHIP_ID_YUKON_OPT && sc->msk_hw_rev == 0) { /* Disable PCIe PHY powerdown(reg 0x80, bit7). */ CSR_WRITE_4(sc, Y2_PEX_PHY_DATA, (0x0080 << 16) | 0x0080); } CSR_WRITE_1(sc, B2_TST_CTRL1, TST_CFG_WRITE_OFF); /* LED On. */ CSR_WRITE_2(sc, B0_CTST, Y2_LED_STAT_ON); /* Clear TWSI IRQ. */ CSR_WRITE_4(sc, B2_I2C_IRQ, I2C_CLR_IRQ); /* Turn off hardware timer. */ CSR_WRITE_1(sc, B2_TI_CTRL, TIM_STOP); CSR_WRITE_1(sc, B2_TI_CTRL, TIM_CLR_IRQ); /* Turn off descriptor polling. */ CSR_WRITE_1(sc, B28_DPT_CTRL, DPT_STOP); /* Turn off time stamps. */ CSR_WRITE_1(sc, GMAC_TI_ST_CTRL, GMT_ST_STOP); CSR_WRITE_1(sc, GMAC_TI_ST_CTRL, GMT_ST_CLR_IRQ); initram = 0; if (sc->msk_hw_id == CHIP_ID_YUKON_XL || sc->msk_hw_id == CHIP_ID_YUKON_EC || sc->msk_hw_id == CHIP_ID_YUKON_FE) initram++; /* Configure timeout values. */ for (i = 0; initram > 0 && i < sc->msk_num_port; i++) { CSR_WRITE_2(sc, SELECT_RAM_BUFFER(i, B3_RI_CTRL), RI_RST_SET); CSR_WRITE_2(sc, SELECT_RAM_BUFFER(i, B3_RI_CTRL), RI_RST_CLR); CSR_WRITE_1(sc, SELECT_RAM_BUFFER(i, B3_RI_WTO_R1), MSK_RI_TO_53); CSR_WRITE_1(sc, SELECT_RAM_BUFFER(i, B3_RI_WTO_XA1), MSK_RI_TO_53); CSR_WRITE_1(sc, SELECT_RAM_BUFFER(i, B3_RI_WTO_XS1), MSK_RI_TO_53); CSR_WRITE_1(sc, SELECT_RAM_BUFFER(i, B3_RI_RTO_R1), MSK_RI_TO_53); CSR_WRITE_1(sc, SELECT_RAM_BUFFER(i, B3_RI_RTO_XA1), MSK_RI_TO_53); CSR_WRITE_1(sc, SELECT_RAM_BUFFER(i, B3_RI_RTO_XS1), MSK_RI_TO_53); CSR_WRITE_1(sc, SELECT_RAM_BUFFER(i, B3_RI_WTO_R2), MSK_RI_TO_53); CSR_WRITE_1(sc, SELECT_RAM_BUFFER(i, B3_RI_WTO_XA2), MSK_RI_TO_53); CSR_WRITE_1(sc, SELECT_RAM_BUFFER(i, B3_RI_WTO_XS2), MSK_RI_TO_53); CSR_WRITE_1(sc, SELECT_RAM_BUFFER(i, B3_RI_RTO_R2), MSK_RI_TO_53); CSR_WRITE_1(sc, SELECT_RAM_BUFFER(i, B3_RI_RTO_XA2), MSK_RI_TO_53); CSR_WRITE_1(sc, SELECT_RAM_BUFFER(i, B3_RI_RTO_XS2), MSK_RI_TO_53); } /* Disable all interrupts. */ CSR_WRITE_4(sc, B0_HWE_IMSK, 0); CSR_READ_4(sc, B0_HWE_IMSK); CSR_WRITE_4(sc, B0_IMSK, 0); CSR_READ_4(sc, B0_IMSK); /* * On dual port PCI-X card, there is an problem where status * can be received out of order due to split transactions. */ if (sc->msk_pcixcap != 0 && sc->msk_num_port > 1) { uint16_t pcix_cmd; pcix_cmd = pci_read_config(sc->msk_dev, sc->msk_pcixcap + PCIXR_COMMAND, 2); /* Clear Max Outstanding Split Transactions. */ pcix_cmd &= ~PCIXM_COMMAND_MAX_SPLITS; CSR_WRITE_1(sc, B2_TST_CTRL1, TST_CFG_WRITE_ON); pci_write_config(sc->msk_dev, sc->msk_pcixcap + PCIXR_COMMAND, pcix_cmd, 2); CSR_WRITE_1(sc, B2_TST_CTRL1, TST_CFG_WRITE_OFF); } if (sc->msk_expcap != 0) { /* Change Max. Read Request Size to 2048 bytes. */ if (pci_get_max_read_req(sc->msk_dev) == 512) pci_set_max_read_req(sc->msk_dev, 2048); } /* Clear status list. */ bzero(sc->msk_stat_ring, sizeof(struct msk_stat_desc) * sc->msk_stat_count); sc->msk_stat_cons = 0; bus_dmamap_sync(sc->msk_stat_tag, sc->msk_stat_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); CSR_WRITE_4(sc, STAT_CTRL, SC_STAT_RST_SET); CSR_WRITE_4(sc, STAT_CTRL, SC_STAT_RST_CLR); /* Set the status list base address. */ addr = sc->msk_stat_ring_paddr; CSR_WRITE_4(sc, STAT_LIST_ADDR_LO, MSK_ADDR_LO(addr)); CSR_WRITE_4(sc, STAT_LIST_ADDR_HI, MSK_ADDR_HI(addr)); /* Set the status list last index. */ CSR_WRITE_2(sc, STAT_LAST_IDX, sc->msk_stat_count - 1); if (sc->msk_hw_id == CHIP_ID_YUKON_EC && sc->msk_hw_rev == CHIP_REV_YU_EC_A1) { /* WA for dev. #4.3 */ CSR_WRITE_2(sc, STAT_TX_IDX_TH, ST_TXTH_IDX_MASK); /* WA for dev. #4.18 */ CSR_WRITE_1(sc, STAT_FIFO_WM, 0x21); CSR_WRITE_1(sc, STAT_FIFO_ISR_WM, 0x07); } else { CSR_WRITE_2(sc, STAT_TX_IDX_TH, 0x0a); CSR_WRITE_1(sc, STAT_FIFO_WM, 0x10); if (sc->msk_hw_id == CHIP_ID_YUKON_XL && sc->msk_hw_rev == CHIP_REV_YU_XL_A0) CSR_WRITE_1(sc, STAT_FIFO_ISR_WM, 0x04); else CSR_WRITE_1(sc, STAT_FIFO_ISR_WM, 0x10); CSR_WRITE_4(sc, STAT_ISR_TIMER_INI, 0x0190); } /* * Use default value for STAT_ISR_TIMER_INI, STAT_LEV_TIMER_INI. */ CSR_WRITE_4(sc, STAT_TX_TIMER_INI, MSK_USECS(sc, 1000)); /* Enable status unit. */ CSR_WRITE_4(sc, STAT_CTRL, SC_STAT_OP_ON); CSR_WRITE_1(sc, STAT_TX_TIMER_CTRL, TIM_START); CSR_WRITE_1(sc, STAT_LEV_TIMER_CTRL, TIM_START); CSR_WRITE_1(sc, STAT_ISR_TIMER_CTRL, TIM_START); } static int msk_probe(device_t dev) { struct msk_softc *sc; char desc[100]; sc = device_get_softc(device_get_parent(dev)); /* * Not much to do here. We always know there will be * at least one GMAC present, and if there are two, * mskc_attach() will create a second device instance * for us. */ snprintf(desc, sizeof(desc), "Marvell Technology Group Ltd. %s Id 0x%02x Rev 0x%02x", model_name[sc->msk_hw_id - CHIP_ID_YUKON_XL], sc->msk_hw_id, sc->msk_hw_rev); device_set_desc_copy(dev, desc); return (BUS_PROBE_DEFAULT); } static int msk_attach(device_t dev) { struct msk_softc *sc; struct msk_if_softc *sc_if; struct ifnet *ifp; struct msk_mii_data *mmd; int i, port, error; uint8_t eaddr[6]; if (dev == NULL) return (EINVAL); error = 0; sc_if = device_get_softc(dev); sc = device_get_softc(device_get_parent(dev)); mmd = device_get_ivars(dev); port = mmd->port; sc_if->msk_if_dev = dev; sc_if->msk_port = port; sc_if->msk_softc = sc; sc_if->msk_flags = sc->msk_pflags; sc->msk_if[port] = sc_if; /* Setup Tx/Rx queue register offsets. */ if (port == MSK_PORT_A) { sc_if->msk_txq = Q_XA1; sc_if->msk_txsq = Q_XS1; sc_if->msk_rxq = Q_R1; } else { sc_if->msk_txq = Q_XA2; sc_if->msk_txsq = Q_XS2; sc_if->msk_rxq = Q_R2; } callout_init_mtx(&sc_if->msk_tick_ch, &sc_if->msk_softc->msk_mtx, 0); msk_sysctl_node(sc_if); - if ((error = msk_txrx_dma_alloc(sc_if) != 0)) + if ((error = msk_txrx_dma_alloc(sc_if)) != 0) goto fail; msk_rx_dma_jalloc(sc_if); ifp = sc_if->msk_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(sc_if->msk_if_dev, "can not if_alloc()\n"); error = ENOSPC; goto fail; } ifp->if_softc = sc_if; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_capabilities = IFCAP_TXCSUM | IFCAP_TSO4; /* * Enable Rx checksum offloading if controller supports * new descriptor formant and controller is not Yukon XL. */ if ((sc_if->msk_flags & MSK_FLAG_DESCV2) == 0 && sc->msk_hw_id != CHIP_ID_YUKON_XL) ifp->if_capabilities |= IFCAP_RXCSUM; if ((sc_if->msk_flags & MSK_FLAG_DESCV2) != 0 && (sc_if->msk_flags & MSK_FLAG_NORX_CSUM) == 0) ifp->if_capabilities |= IFCAP_RXCSUM; ifp->if_hwassist = MSK_CSUM_FEATURES | CSUM_TSO; ifp->if_capenable = ifp->if_capabilities; ifp->if_ioctl = msk_ioctl; ifp->if_start = msk_start; ifp->if_init = msk_init; IFQ_SET_MAXLEN(&ifp->if_snd, MSK_TX_RING_CNT - 1); ifp->if_snd.ifq_drv_maxlen = MSK_TX_RING_CNT - 1; IFQ_SET_READY(&ifp->if_snd); /* * Get station address for this interface. Note that * dual port cards actually come with three station * addresses: one for each port, plus an extra. The * extra one is used by the SysKonnect driver software * as a 'virtual' station address for when both ports * are operating in failover mode. Currently we don't * use this extra address. */ MSK_IF_LOCK(sc_if); for (i = 0; i < ETHER_ADDR_LEN; i++) eaddr[i] = CSR_READ_1(sc, B2_MAC_1 + (port * 8) + i); /* * Call MI attach routine. Can't hold locks when calling into ether_*. */ MSK_IF_UNLOCK(sc_if); ether_ifattach(ifp, eaddr); MSK_IF_LOCK(sc_if); /* VLAN capability setup */ ifp->if_capabilities |= IFCAP_VLAN_MTU; if ((sc_if->msk_flags & MSK_FLAG_NOHWVLAN) == 0) { /* * Due to Tx checksum offload hardware bugs, msk(4) manually * computes checksum for short frames. For VLAN tagged frames * this workaround does not work so disable checksum offload * for VLAN interface. */ ifp->if_capabilities |= IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_HWTSO; /* * Enable Rx checksum offloading for VLAN tagged frames * if controller support new descriptor format. */ if ((sc_if->msk_flags & MSK_FLAG_DESCV2) != 0 && (sc_if->msk_flags & MSK_FLAG_NORX_CSUM) == 0) ifp->if_capabilities |= IFCAP_VLAN_HWCSUM; } ifp->if_capenable = ifp->if_capabilities; /* * Disable RX checksum offloading on controllers that don't use * new descriptor format but give chance to enable it. */ if ((sc_if->msk_flags & MSK_FLAG_DESCV2) == 0) ifp->if_capenable &= ~IFCAP_RXCSUM; /* * Tell the upper layer(s) we support long frames. * Must appear after the call to ether_ifattach() because * ether_ifattach() sets ifi_hdrlen to the default value. */ ifp->if_hdrlen = sizeof(struct ether_vlan_header); /* * Do miibus setup. */ MSK_IF_UNLOCK(sc_if); error = mii_attach(dev, &sc_if->msk_miibus, ifp, msk_mediachange, msk_mediastatus, BMSR_DEFCAPMASK, PHY_ADDR_MARV, MII_OFFSET_ANY, mmd->mii_flags); if (error != 0) { device_printf(sc_if->msk_if_dev, "attaching PHYs failed\n"); ether_ifdetach(ifp); error = ENXIO; goto fail; } fail: if (error != 0) { /* Access should be ok even though lock has been dropped */ sc->msk_if[port] = NULL; msk_detach(dev); } return (error); } /* * Attach the interface. Allocate softc structures, do ifmedia * setup and ethernet/BPF attach. */ static int mskc_attach(device_t dev) { struct msk_softc *sc; struct msk_mii_data *mmd; int error, msic, msir, reg; sc = device_get_softc(dev); sc->msk_dev = dev; mtx_init(&sc->msk_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); /* * Map control/status registers. */ pci_enable_busmaster(dev); /* Allocate I/O resource */ #ifdef MSK_USEIOSPACE sc->msk_res_spec = msk_res_spec_io; #else sc->msk_res_spec = msk_res_spec_mem; #endif sc->msk_irq_spec = msk_irq_spec_legacy; error = bus_alloc_resources(dev, sc->msk_res_spec, sc->msk_res); if (error) { if (sc->msk_res_spec == msk_res_spec_mem) sc->msk_res_spec = msk_res_spec_io; else sc->msk_res_spec = msk_res_spec_mem; error = bus_alloc_resources(dev, sc->msk_res_spec, sc->msk_res); if (error) { device_printf(dev, "couldn't allocate %s resources\n", sc->msk_res_spec == msk_res_spec_mem ? "memory" : "I/O"); mtx_destroy(&sc->msk_mtx); return (ENXIO); } } /* Enable all clocks before accessing any registers. */ CSR_PCI_WRITE_4(sc, PCI_OUR_REG_3, 0); CSR_WRITE_2(sc, B0_CTST, CS_RST_CLR); sc->msk_hw_id = CSR_READ_1(sc, B2_CHIP_ID); sc->msk_hw_rev = (CSR_READ_1(sc, B2_MAC_CFG) >> 4) & 0x0f; /* Bail out if chip is not recognized. */ if (sc->msk_hw_id < CHIP_ID_YUKON_XL || sc->msk_hw_id > CHIP_ID_YUKON_OPT || sc->msk_hw_id == CHIP_ID_YUKON_UNKNOWN) { device_printf(dev, "unknown device: id=0x%02x, rev=0x%02x\n", sc->msk_hw_id, sc->msk_hw_rev); mtx_destroy(&sc->msk_mtx); return (ENXIO); } SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "process_limit", CTLTYPE_INT | CTLFLAG_RW, &sc->msk_process_limit, 0, sysctl_hw_msk_proc_limit, "I", "max number of Rx events to process"); sc->msk_process_limit = MSK_PROC_DEFAULT; error = resource_int_value(device_get_name(dev), device_get_unit(dev), "process_limit", &sc->msk_process_limit); if (error == 0) { if (sc->msk_process_limit < MSK_PROC_MIN || sc->msk_process_limit > MSK_PROC_MAX) { device_printf(dev, "process_limit value out of range; " "using default: %d\n", MSK_PROC_DEFAULT); sc->msk_process_limit = MSK_PROC_DEFAULT; } } sc->msk_int_holdoff = MSK_INT_HOLDOFF_DEFAULT; SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "int_holdoff", CTLFLAG_RW, &sc->msk_int_holdoff, 0, "Maximum number of time to delay interrupts"); resource_int_value(device_get_name(dev), device_get_unit(dev), "int_holdoff", &sc->msk_int_holdoff); sc->msk_pmd = CSR_READ_1(sc, B2_PMD_TYP); /* Check number of MACs. */ sc->msk_num_port = 1; if ((CSR_READ_1(sc, B2_Y2_HW_RES) & CFG_DUAL_MAC_MSK) == CFG_DUAL_MAC_MSK) { if (!(CSR_READ_1(sc, B2_Y2_CLK_GATE) & Y2_STATUS_LNK2_INAC)) sc->msk_num_port++; } /* Check bus type. */ if (pci_find_cap(sc->msk_dev, PCIY_EXPRESS, ®) == 0) { sc->msk_bustype = MSK_PEX_BUS; sc->msk_expcap = reg; } else if (pci_find_cap(sc->msk_dev, PCIY_PCIX, ®) == 0) { sc->msk_bustype = MSK_PCIX_BUS; sc->msk_pcixcap = reg; } else sc->msk_bustype = MSK_PCI_BUS; switch (sc->msk_hw_id) { case CHIP_ID_YUKON_EC: sc->msk_clock = 125; /* 125 MHz */ sc->msk_pflags |= MSK_FLAG_JUMBO; break; case CHIP_ID_YUKON_EC_U: sc->msk_clock = 125; /* 125 MHz */ sc->msk_pflags |= MSK_FLAG_JUMBO | MSK_FLAG_JUMBO_NOCSUM; break; case CHIP_ID_YUKON_EX: sc->msk_clock = 125; /* 125 MHz */ sc->msk_pflags |= MSK_FLAG_JUMBO | MSK_FLAG_DESCV2 | MSK_FLAG_AUTOTX_CSUM; /* * Yukon Extreme seems to have silicon bug for * automatic Tx checksum calculation capability. */ if (sc->msk_hw_rev == CHIP_REV_YU_EX_B0) sc->msk_pflags &= ~MSK_FLAG_AUTOTX_CSUM; /* * Yukon Extreme A0 could not use store-and-forward * for jumbo frames, so disable Tx checksum * offloading for jumbo frames. */ if (sc->msk_hw_rev == CHIP_REV_YU_EX_A0) sc->msk_pflags |= MSK_FLAG_JUMBO_NOCSUM; break; case CHIP_ID_YUKON_FE: sc->msk_clock = 100; /* 100 MHz */ sc->msk_pflags |= MSK_FLAG_FASTETHER; break; case CHIP_ID_YUKON_FE_P: sc->msk_clock = 50; /* 50 MHz */ sc->msk_pflags |= MSK_FLAG_FASTETHER | MSK_FLAG_DESCV2 | MSK_FLAG_AUTOTX_CSUM; if (sc->msk_hw_rev == CHIP_REV_YU_FE_P_A0) { /* * XXX * FE+ A0 has status LE writeback bug so msk(4) * does not rely on status word of received frame * in msk_rxeof() which in turn disables all * hardware assistance bits reported by the status * word as well as validity of the received frame. * Just pass received frames to upper stack with * minimal test and let upper stack handle them. */ sc->msk_pflags |= MSK_FLAG_NOHWVLAN | MSK_FLAG_NORXCHK | MSK_FLAG_NORX_CSUM; } break; case CHIP_ID_YUKON_XL: sc->msk_clock = 156; /* 156 MHz */ sc->msk_pflags |= MSK_FLAG_JUMBO; break; case CHIP_ID_YUKON_SUPR: sc->msk_clock = 125; /* 125 MHz */ sc->msk_pflags |= MSK_FLAG_JUMBO | MSK_FLAG_DESCV2 | MSK_FLAG_AUTOTX_CSUM; break; case CHIP_ID_YUKON_UL_2: sc->msk_clock = 125; /* 125 MHz */ sc->msk_pflags |= MSK_FLAG_JUMBO; break; case CHIP_ID_YUKON_OPT: sc->msk_clock = 125; /* 125 MHz */ sc->msk_pflags |= MSK_FLAG_JUMBO | MSK_FLAG_DESCV2; break; default: sc->msk_clock = 156; /* 156 MHz */ break; } /* Allocate IRQ resources. */ msic = pci_msi_count(dev); if (bootverbose) device_printf(dev, "MSI count : %d\n", msic); if (legacy_intr != 0) msi_disable = 1; if (msi_disable == 0 && msic > 0) { msir = 1; if (pci_alloc_msi(dev, &msir) == 0) { if (msir == 1) { sc->msk_pflags |= MSK_FLAG_MSI; sc->msk_irq_spec = msk_irq_spec_msi; } else pci_release_msi(dev); } } error = bus_alloc_resources(dev, sc->msk_irq_spec, sc->msk_irq); if (error) { device_printf(dev, "couldn't allocate IRQ resources\n"); goto fail; } if ((error = msk_status_dma_alloc(sc)) != 0) goto fail; /* Set base interrupt mask. */ sc->msk_intrmask = Y2_IS_HW_ERR | Y2_IS_STAT_BMU; sc->msk_intrhwemask = Y2_IS_TIST_OV | Y2_IS_MST_ERR | Y2_IS_IRQ_STAT | Y2_IS_PCI_EXP | Y2_IS_PCI_NEXP; /* Reset the adapter. */ mskc_reset(sc); if ((error = mskc_setup_rambuffer(sc)) != 0) goto fail; sc->msk_devs[MSK_PORT_A] = device_add_child(dev, "msk", -1); if (sc->msk_devs[MSK_PORT_A] == NULL) { device_printf(dev, "failed to add child for PORT_A\n"); error = ENXIO; goto fail; } mmd = malloc(sizeof(struct msk_mii_data), M_DEVBUF, M_WAITOK | M_ZERO); if (mmd == NULL) { device_printf(dev, "failed to allocate memory for " "ivars of PORT_A\n"); error = ENXIO; goto fail; } mmd->port = MSK_PORT_A; mmd->pmd = sc->msk_pmd; mmd->mii_flags |= MIIF_DOPAUSE; if (sc->msk_pmd == 'L' || sc->msk_pmd == 'S') mmd->mii_flags |= MIIF_HAVEFIBER; if (sc->msk_pmd == 'P') mmd->mii_flags |= MIIF_HAVEFIBER | MIIF_MACPRIV0; device_set_ivars(sc->msk_devs[MSK_PORT_A], mmd); if (sc->msk_num_port > 1) { sc->msk_devs[MSK_PORT_B] = device_add_child(dev, "msk", -1); if (sc->msk_devs[MSK_PORT_B] == NULL) { device_printf(dev, "failed to add child for PORT_B\n"); error = ENXIO; goto fail; } mmd = malloc(sizeof(struct msk_mii_data), M_DEVBUF, M_WAITOK | M_ZERO); if (mmd == NULL) { device_printf(dev, "failed to allocate memory for " "ivars of PORT_B\n"); error = ENXIO; goto fail; } mmd->port = MSK_PORT_B; mmd->pmd = sc->msk_pmd; if (sc->msk_pmd == 'L' || sc->msk_pmd == 'S') mmd->mii_flags |= MIIF_HAVEFIBER; if (sc->msk_pmd == 'P') mmd->mii_flags |= MIIF_HAVEFIBER | MIIF_MACPRIV0; device_set_ivars(sc->msk_devs[MSK_PORT_B], mmd); } error = bus_generic_attach(dev); if (error) { device_printf(dev, "failed to attach port(s)\n"); goto fail; } /* Hook interrupt last to avoid having to lock softc. */ error = bus_setup_intr(dev, sc->msk_irq[0], INTR_TYPE_NET | INTR_MPSAFE, NULL, msk_intr, sc, &sc->msk_intrhand); if (error != 0) { device_printf(dev, "couldn't set up interrupt handler\n"); goto fail; } fail: if (error != 0) mskc_detach(dev); return (error); } /* * Shutdown hardware and free up resources. This can be called any * time after the mutex has been initialized. It is called in both * the error case in attach and the normal detach case so it needs * to be careful about only freeing resources that have actually been * allocated. */ static int msk_detach(device_t dev) { struct msk_softc *sc; struct msk_if_softc *sc_if; struct ifnet *ifp; sc_if = device_get_softc(dev); KASSERT(mtx_initialized(&sc_if->msk_softc->msk_mtx), ("msk mutex not initialized in msk_detach")); MSK_IF_LOCK(sc_if); ifp = sc_if->msk_ifp; if (device_is_attached(dev)) { /* XXX */ sc_if->msk_flags |= MSK_FLAG_DETACH; msk_stop(sc_if); /* Can't hold locks while calling detach. */ MSK_IF_UNLOCK(sc_if); callout_drain(&sc_if->msk_tick_ch); if (ifp) ether_ifdetach(ifp); MSK_IF_LOCK(sc_if); } /* * We're generally called from mskc_detach() which is using * device_delete_child() to get to here. It's already trashed * miibus for us, so don't do it here or we'll panic. * * if (sc_if->msk_miibus != NULL) { * device_delete_child(dev, sc_if->msk_miibus); * sc_if->msk_miibus = NULL; * } */ msk_rx_dma_jfree(sc_if); msk_txrx_dma_free(sc_if); bus_generic_detach(dev); if (ifp) if_free(ifp); sc = sc_if->msk_softc; sc->msk_if[sc_if->msk_port] = NULL; MSK_IF_UNLOCK(sc_if); return (0); } static int mskc_detach(device_t dev) { struct msk_softc *sc; sc = device_get_softc(dev); KASSERT(mtx_initialized(&sc->msk_mtx), ("msk mutex not initialized")); if (device_is_alive(dev)) { if (sc->msk_devs[MSK_PORT_A] != NULL) { free(device_get_ivars(sc->msk_devs[MSK_PORT_A]), M_DEVBUF); device_delete_child(dev, sc->msk_devs[MSK_PORT_A]); } if (sc->msk_devs[MSK_PORT_B] != NULL) { free(device_get_ivars(sc->msk_devs[MSK_PORT_B]), M_DEVBUF); device_delete_child(dev, sc->msk_devs[MSK_PORT_B]); } bus_generic_detach(dev); } /* Disable all interrupts. */ CSR_WRITE_4(sc, B0_IMSK, 0); CSR_READ_4(sc, B0_IMSK); CSR_WRITE_4(sc, B0_HWE_IMSK, 0); CSR_READ_4(sc, B0_HWE_IMSK); /* LED Off. */ CSR_WRITE_2(sc, B0_CTST, Y2_LED_STAT_OFF); /* Put hardware reset. */ CSR_WRITE_2(sc, B0_CTST, CS_RST_SET); msk_status_dma_free(sc); if (sc->msk_intrhand) { bus_teardown_intr(dev, sc->msk_irq[0], sc->msk_intrhand); sc->msk_intrhand = NULL; } bus_release_resources(dev, sc->msk_irq_spec, sc->msk_irq); if ((sc->msk_pflags & MSK_FLAG_MSI) != 0) pci_release_msi(dev); bus_release_resources(dev, sc->msk_res_spec, sc->msk_res); mtx_destroy(&sc->msk_mtx); return (0); } static bus_dma_tag_t mskc_get_dma_tag(device_t bus, device_t child __unused) { return (bus_get_dma_tag(bus)); } struct msk_dmamap_arg { bus_addr_t msk_busaddr; }; static void msk_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct msk_dmamap_arg *ctx; if (error != 0) return; ctx = arg; ctx->msk_busaddr = segs[0].ds_addr; } /* Create status DMA region. */ static int msk_status_dma_alloc(struct msk_softc *sc) { struct msk_dmamap_arg ctx; bus_size_t stat_sz; int count, error; /* * It seems controller requires number of status LE entries * is power of 2 and the maximum number of status LE entries * is 4096. For dual-port controllers, the number of status * LE entries should be large enough to hold both port's * status updates. */ count = 3 * MSK_RX_RING_CNT + MSK_TX_RING_CNT; count = imin(4096, roundup2(count, 1024)); sc->msk_stat_count = count; stat_sz = count * sizeof(struct msk_stat_desc); error = bus_dma_tag_create( bus_get_dma_tag(sc->msk_dev), /* parent */ MSK_STAT_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ stat_sz, /* maxsize */ 1, /* nsegments */ stat_sz, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->msk_stat_tag); if (error != 0) { device_printf(sc->msk_dev, "failed to create status DMA tag\n"); return (error); } /* Allocate DMA'able memory and load the DMA map for status ring. */ error = bus_dmamem_alloc(sc->msk_stat_tag, (void **)&sc->msk_stat_ring, BUS_DMA_WAITOK | BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->msk_stat_map); if (error != 0) { device_printf(sc->msk_dev, "failed to allocate DMA'able memory for status ring\n"); return (error); } ctx.msk_busaddr = 0; error = bus_dmamap_load(sc->msk_stat_tag, sc->msk_stat_map, sc->msk_stat_ring, stat_sz, msk_dmamap_cb, &ctx, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc->msk_dev, "failed to load DMA'able memory for status ring\n"); return (error); } sc->msk_stat_ring_paddr = ctx.msk_busaddr; return (0); } static void msk_status_dma_free(struct msk_softc *sc) { /* Destroy status block. */ if (sc->msk_stat_tag) { if (sc->msk_stat_ring_paddr) { bus_dmamap_unload(sc->msk_stat_tag, sc->msk_stat_map); sc->msk_stat_ring_paddr = 0; } if (sc->msk_stat_ring) { bus_dmamem_free(sc->msk_stat_tag, sc->msk_stat_ring, sc->msk_stat_map); sc->msk_stat_ring = NULL; } bus_dma_tag_destroy(sc->msk_stat_tag); sc->msk_stat_tag = NULL; } } static int msk_txrx_dma_alloc(struct msk_if_softc *sc_if) { struct msk_dmamap_arg ctx; struct msk_txdesc *txd; struct msk_rxdesc *rxd; bus_size_t rxalign; int error, i; /* Create parent DMA tag. */ error = bus_dma_tag_create( bus_get_dma_tag(sc_if->msk_if_dev), /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ 0, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc_if->msk_cdata.msk_parent_tag); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to create parent DMA tag\n"); goto fail; } /* Create tag for Tx ring. */ error = bus_dma_tag_create(sc_if->msk_cdata.msk_parent_tag,/* parent */ MSK_RING_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MSK_TX_RING_SZ, /* maxsize */ 1, /* nsegments */ MSK_TX_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc_if->msk_cdata.msk_tx_ring_tag); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to create Tx ring DMA tag\n"); goto fail; } /* Create tag for Rx ring. */ error = bus_dma_tag_create(sc_if->msk_cdata.msk_parent_tag,/* parent */ MSK_RING_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MSK_RX_RING_SZ, /* maxsize */ 1, /* nsegments */ MSK_RX_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc_if->msk_cdata.msk_rx_ring_tag); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to create Rx ring DMA tag\n"); goto fail; } /* Create tag for Tx buffers. */ error = bus_dma_tag_create(sc_if->msk_cdata.msk_parent_tag,/* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MSK_TSO_MAXSIZE, /* maxsize */ MSK_MAXTXSEGS, /* nsegments */ MSK_TSO_MAXSGSIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc_if->msk_cdata.msk_tx_tag); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to create Tx DMA tag\n"); goto fail; } rxalign = 1; /* * Workaround hardware hang which seems to happen when Rx buffer * is not aligned on multiple of FIFO word(8 bytes). */ if ((sc_if->msk_flags & MSK_FLAG_RAMBUF) != 0) rxalign = MSK_RX_BUF_ALIGN; /* Create tag for Rx buffers. */ error = bus_dma_tag_create(sc_if->msk_cdata.msk_parent_tag,/* parent */ rxalign, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, /* maxsize */ 1, /* nsegments */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc_if->msk_cdata.msk_rx_tag); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to create Rx DMA tag\n"); goto fail; } /* Allocate DMA'able memory and load the DMA map for Tx ring. */ error = bus_dmamem_alloc(sc_if->msk_cdata.msk_tx_ring_tag, (void **)&sc_if->msk_rdata.msk_tx_ring, BUS_DMA_WAITOK | BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc_if->msk_cdata.msk_tx_ring_map); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to allocate DMA'able memory for Tx ring\n"); goto fail; } ctx.msk_busaddr = 0; error = bus_dmamap_load(sc_if->msk_cdata.msk_tx_ring_tag, sc_if->msk_cdata.msk_tx_ring_map, sc_if->msk_rdata.msk_tx_ring, MSK_TX_RING_SZ, msk_dmamap_cb, &ctx, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to load DMA'able memory for Tx ring\n"); goto fail; } sc_if->msk_rdata.msk_tx_ring_paddr = ctx.msk_busaddr; /* Allocate DMA'able memory and load the DMA map for Rx ring. */ error = bus_dmamem_alloc(sc_if->msk_cdata.msk_rx_ring_tag, (void **)&sc_if->msk_rdata.msk_rx_ring, BUS_DMA_WAITOK | BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc_if->msk_cdata.msk_rx_ring_map); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to allocate DMA'able memory for Rx ring\n"); goto fail; } ctx.msk_busaddr = 0; error = bus_dmamap_load(sc_if->msk_cdata.msk_rx_ring_tag, sc_if->msk_cdata.msk_rx_ring_map, sc_if->msk_rdata.msk_rx_ring, MSK_RX_RING_SZ, msk_dmamap_cb, &ctx, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to load DMA'able memory for Rx ring\n"); goto fail; } sc_if->msk_rdata.msk_rx_ring_paddr = ctx.msk_busaddr; /* Create DMA maps for Tx buffers. */ for (i = 0; i < MSK_TX_RING_CNT; i++) { txd = &sc_if->msk_cdata.msk_txdesc[i]; txd->tx_m = NULL; txd->tx_dmamap = NULL; error = bus_dmamap_create(sc_if->msk_cdata.msk_tx_tag, 0, &txd->tx_dmamap); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to create Tx dmamap\n"); goto fail; } } /* Create DMA maps for Rx buffers. */ if ((error = bus_dmamap_create(sc_if->msk_cdata.msk_rx_tag, 0, &sc_if->msk_cdata.msk_rx_sparemap)) != 0) { device_printf(sc_if->msk_if_dev, "failed to create spare Rx dmamap\n"); goto fail; } for (i = 0; i < MSK_RX_RING_CNT; i++) { rxd = &sc_if->msk_cdata.msk_rxdesc[i]; rxd->rx_m = NULL; rxd->rx_dmamap = NULL; error = bus_dmamap_create(sc_if->msk_cdata.msk_rx_tag, 0, &rxd->rx_dmamap); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to create Rx dmamap\n"); goto fail; } } fail: return (error); } static int msk_rx_dma_jalloc(struct msk_if_softc *sc_if) { struct msk_dmamap_arg ctx; struct msk_rxdesc *jrxd; bus_size_t rxalign; int error, i; if (jumbo_disable != 0 || (sc_if->msk_flags & MSK_FLAG_JUMBO) == 0) { sc_if->msk_flags &= ~MSK_FLAG_JUMBO; device_printf(sc_if->msk_if_dev, "disabling jumbo frame support\n"); return (0); } /* Create tag for jumbo Rx ring. */ error = bus_dma_tag_create(sc_if->msk_cdata.msk_parent_tag,/* parent */ MSK_RING_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MSK_JUMBO_RX_RING_SZ, /* maxsize */ 1, /* nsegments */ MSK_JUMBO_RX_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc_if->msk_cdata.msk_jumbo_rx_ring_tag); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to create jumbo Rx ring DMA tag\n"); goto jumbo_fail; } rxalign = 1; /* * Workaround hardware hang which seems to happen when Rx buffer * is not aligned on multiple of FIFO word(8 bytes). */ if ((sc_if->msk_flags & MSK_FLAG_RAMBUF) != 0) rxalign = MSK_RX_BUF_ALIGN; /* Create tag for jumbo Rx buffers. */ error = bus_dma_tag_create(sc_if->msk_cdata.msk_parent_tag,/* parent */ rxalign, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MJUM9BYTES, /* maxsize */ 1, /* nsegments */ MJUM9BYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc_if->msk_cdata.msk_jumbo_rx_tag); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to create jumbo Rx DMA tag\n"); goto jumbo_fail; } /* Allocate DMA'able memory and load the DMA map for jumbo Rx ring. */ error = bus_dmamem_alloc(sc_if->msk_cdata.msk_jumbo_rx_ring_tag, (void **)&sc_if->msk_rdata.msk_jumbo_rx_ring, BUS_DMA_WAITOK | BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc_if->msk_cdata.msk_jumbo_rx_ring_map); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to allocate DMA'able memory for jumbo Rx ring\n"); goto jumbo_fail; } ctx.msk_busaddr = 0; error = bus_dmamap_load(sc_if->msk_cdata.msk_jumbo_rx_ring_tag, sc_if->msk_cdata.msk_jumbo_rx_ring_map, sc_if->msk_rdata.msk_jumbo_rx_ring, MSK_JUMBO_RX_RING_SZ, msk_dmamap_cb, &ctx, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to load DMA'able memory for jumbo Rx ring\n"); goto jumbo_fail; } sc_if->msk_rdata.msk_jumbo_rx_ring_paddr = ctx.msk_busaddr; /* Create DMA maps for jumbo Rx buffers. */ if ((error = bus_dmamap_create(sc_if->msk_cdata.msk_jumbo_rx_tag, 0, &sc_if->msk_cdata.msk_jumbo_rx_sparemap)) != 0) { device_printf(sc_if->msk_if_dev, "failed to create spare jumbo Rx dmamap\n"); goto jumbo_fail; } for (i = 0; i < MSK_JUMBO_RX_RING_CNT; i++) { jrxd = &sc_if->msk_cdata.msk_jumbo_rxdesc[i]; jrxd->rx_m = NULL; jrxd->rx_dmamap = NULL; error = bus_dmamap_create(sc_if->msk_cdata.msk_jumbo_rx_tag, 0, &jrxd->rx_dmamap); if (error != 0) { device_printf(sc_if->msk_if_dev, "failed to create jumbo Rx dmamap\n"); goto jumbo_fail; } } return (0); jumbo_fail: msk_rx_dma_jfree(sc_if); device_printf(sc_if->msk_if_dev, "disabling jumbo frame support " "due to resource shortage\n"); sc_if->msk_flags &= ~MSK_FLAG_JUMBO; return (error); } static void msk_txrx_dma_free(struct msk_if_softc *sc_if) { struct msk_txdesc *txd; struct msk_rxdesc *rxd; int i; /* Tx ring. */ if (sc_if->msk_cdata.msk_tx_ring_tag) { if (sc_if->msk_rdata.msk_tx_ring_paddr) bus_dmamap_unload(sc_if->msk_cdata.msk_tx_ring_tag, sc_if->msk_cdata.msk_tx_ring_map); if (sc_if->msk_rdata.msk_tx_ring) bus_dmamem_free(sc_if->msk_cdata.msk_tx_ring_tag, sc_if->msk_rdata.msk_tx_ring, sc_if->msk_cdata.msk_tx_ring_map); sc_if->msk_rdata.msk_tx_ring = NULL; sc_if->msk_rdata.msk_tx_ring_paddr = 0; bus_dma_tag_destroy(sc_if->msk_cdata.msk_tx_ring_tag); sc_if->msk_cdata.msk_tx_ring_tag = NULL; } /* Rx ring. */ if (sc_if->msk_cdata.msk_rx_ring_tag) { if (sc_if->msk_rdata.msk_rx_ring_paddr) bus_dmamap_unload(sc_if->msk_cdata.msk_rx_ring_tag, sc_if->msk_cdata.msk_rx_ring_map); if (sc_if->msk_rdata.msk_rx_ring) bus_dmamem_free(sc_if->msk_cdata.msk_rx_ring_tag, sc_if->msk_rdata.msk_rx_ring, sc_if->msk_cdata.msk_rx_ring_map); sc_if->msk_rdata.msk_rx_ring = NULL; sc_if->msk_rdata.msk_rx_ring_paddr = 0; bus_dma_tag_destroy(sc_if->msk_cdata.msk_rx_ring_tag); sc_if->msk_cdata.msk_rx_ring_tag = NULL; } /* Tx buffers. */ if (sc_if->msk_cdata.msk_tx_tag) { for (i = 0; i < MSK_TX_RING_CNT; i++) { txd = &sc_if->msk_cdata.msk_txdesc[i]; if (txd->tx_dmamap) { bus_dmamap_destroy(sc_if->msk_cdata.msk_tx_tag, txd->tx_dmamap); txd->tx_dmamap = NULL; } } bus_dma_tag_destroy(sc_if->msk_cdata.msk_tx_tag); sc_if->msk_cdata.msk_tx_tag = NULL; } /* Rx buffers. */ if (sc_if->msk_cdata.msk_rx_tag) { for (i = 0; i < MSK_RX_RING_CNT; i++) { rxd = &sc_if->msk_cdata.msk_rxdesc[i]; if (rxd->rx_dmamap) { bus_dmamap_destroy(sc_if->msk_cdata.msk_rx_tag, rxd->rx_dmamap); rxd->rx_dmamap = NULL; } } if (sc_if->msk_cdata.msk_rx_sparemap) { bus_dmamap_destroy(sc_if->msk_cdata.msk_rx_tag, sc_if->msk_cdata.msk_rx_sparemap); sc_if->msk_cdata.msk_rx_sparemap = 0; } bus_dma_tag_destroy(sc_if->msk_cdata.msk_rx_tag); sc_if->msk_cdata.msk_rx_tag = NULL; } if (sc_if->msk_cdata.msk_parent_tag) { bus_dma_tag_destroy(sc_if->msk_cdata.msk_parent_tag); sc_if->msk_cdata.msk_parent_tag = NULL; } } static void msk_rx_dma_jfree(struct msk_if_softc *sc_if) { struct msk_rxdesc *jrxd; int i; /* Jumbo Rx ring. */ if (sc_if->msk_cdata.msk_jumbo_rx_ring_tag) { if (sc_if->msk_rdata.msk_jumbo_rx_ring_paddr) bus_dmamap_unload(sc_if->msk_cdata.msk_jumbo_rx_ring_tag, sc_if->msk_cdata.msk_jumbo_rx_ring_map); if (sc_if->msk_rdata.msk_jumbo_rx_ring) bus_dmamem_free(sc_if->msk_cdata.msk_jumbo_rx_ring_tag, sc_if->msk_rdata.msk_jumbo_rx_ring, sc_if->msk_cdata.msk_jumbo_rx_ring_map); sc_if->msk_rdata.msk_jumbo_rx_ring = NULL; sc_if->msk_rdata.msk_jumbo_rx_ring_paddr = 0; bus_dma_tag_destroy(sc_if->msk_cdata.msk_jumbo_rx_ring_tag); sc_if->msk_cdata.msk_jumbo_rx_ring_tag = NULL; } /* Jumbo Rx buffers. */ if (sc_if->msk_cdata.msk_jumbo_rx_tag) { for (i = 0; i < MSK_JUMBO_RX_RING_CNT; i++) { jrxd = &sc_if->msk_cdata.msk_jumbo_rxdesc[i]; if (jrxd->rx_dmamap) { bus_dmamap_destroy( sc_if->msk_cdata.msk_jumbo_rx_tag, jrxd->rx_dmamap); jrxd->rx_dmamap = NULL; } } if (sc_if->msk_cdata.msk_jumbo_rx_sparemap) { bus_dmamap_destroy(sc_if->msk_cdata.msk_jumbo_rx_tag, sc_if->msk_cdata.msk_jumbo_rx_sparemap); sc_if->msk_cdata.msk_jumbo_rx_sparemap = 0; } bus_dma_tag_destroy(sc_if->msk_cdata.msk_jumbo_rx_tag); sc_if->msk_cdata.msk_jumbo_rx_tag = NULL; } } static int msk_encap(struct msk_if_softc *sc_if, struct mbuf **m_head) { struct msk_txdesc *txd, *txd_last; struct msk_tx_desc *tx_le; struct mbuf *m; bus_dmamap_t map; bus_dma_segment_t txsegs[MSK_MAXTXSEGS]; uint32_t control, csum, prod, si; uint16_t offset, tcp_offset, tso_mtu; int error, i, nseg, tso; MSK_IF_LOCK_ASSERT(sc_if); tcp_offset = offset = 0; m = *m_head; if (((sc_if->msk_flags & MSK_FLAG_AUTOTX_CSUM) == 0 && (m->m_pkthdr.csum_flags & MSK_CSUM_FEATURES) != 0) || ((sc_if->msk_flags & MSK_FLAG_DESCV2) == 0 && (m->m_pkthdr.csum_flags & CSUM_TSO) != 0)) { /* * Since mbuf has no protocol specific structure information * in it we have to inspect protocol information here to * setup TSO and checksum offload. I don't know why Marvell * made a such decision in chip design because other GigE * hardwares normally takes care of all these chores in * hardware. However, TSO performance of Yukon II is very * good such that it's worth to implement it. */ struct ether_header *eh; struct ip *ip; struct tcphdr *tcp; if (M_WRITABLE(m) == 0) { /* Get a writable copy. */ m = m_dup(*m_head, M_NOWAIT); m_freem(*m_head); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } *m_head = m; } offset = sizeof(struct ether_header); m = m_pullup(m, offset); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } eh = mtod(m, struct ether_header *); /* Check if hardware VLAN insertion is off. */ if (eh->ether_type == htons(ETHERTYPE_VLAN)) { offset = sizeof(struct ether_vlan_header); m = m_pullup(m, offset); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } } m = m_pullup(m, offset + sizeof(struct ip)); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } ip = (struct ip *)(mtod(m, char *) + offset); offset += (ip->ip_hl << 2); tcp_offset = offset; if ((m->m_pkthdr.csum_flags & CSUM_TSO) != 0) { m = m_pullup(m, offset + sizeof(struct tcphdr)); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } tcp = (struct tcphdr *)(mtod(m, char *) + offset); offset += (tcp->th_off << 2); } else if ((sc_if->msk_flags & MSK_FLAG_AUTOTX_CSUM) == 0 && (m->m_pkthdr.len < MSK_MIN_FRAMELEN) && (m->m_pkthdr.csum_flags & CSUM_TCP) != 0) { /* * It seems that Yukon II has Tx checksum offload bug * for small TCP packets that's less than 60 bytes in * size (e.g. TCP window probe packet, pure ACK packet). * Common work around like padding with zeros to make * the frame minimum ethernet frame size didn't work at * all. * Instead of disabling checksum offload completely we * resort to S/W checksum routine when we encounter * short TCP frames. * Short UDP packets appear to be handled correctly by * Yukon II. Also I assume this bug does not happen on * controllers that use newer descriptor format or * automatic Tx checksum calculation. */ m = m_pullup(m, offset + sizeof(struct tcphdr)); if (m == NULL) { *m_head = NULL; return (ENOBUFS); } *(uint16_t *)(m->m_data + offset + m->m_pkthdr.csum_data) = in_cksum_skip(m, m->m_pkthdr.len, offset); m->m_pkthdr.csum_flags &= ~CSUM_TCP; } *m_head = m; } prod = sc_if->msk_cdata.msk_tx_prod; txd = &sc_if->msk_cdata.msk_txdesc[prod]; txd_last = txd; map = txd->tx_dmamap; error = bus_dmamap_load_mbuf_sg(sc_if->msk_cdata.msk_tx_tag, map, *m_head, txsegs, &nseg, BUS_DMA_NOWAIT); if (error == EFBIG) { m = m_collapse(*m_head, M_NOWAIT, MSK_MAXTXSEGS); if (m == NULL) { m_freem(*m_head); *m_head = NULL; return (ENOBUFS); } *m_head = m; error = bus_dmamap_load_mbuf_sg(sc_if->msk_cdata.msk_tx_tag, map, *m_head, txsegs, &nseg, BUS_DMA_NOWAIT); if (error != 0) { m_freem(*m_head); *m_head = NULL; return (error); } } else if (error != 0) return (error); if (nseg == 0) { m_freem(*m_head); *m_head = NULL; return (EIO); } /* Check number of available descriptors. */ if (sc_if->msk_cdata.msk_tx_cnt + nseg >= (MSK_TX_RING_CNT - MSK_RESERVED_TX_DESC_CNT)) { bus_dmamap_unload(sc_if->msk_cdata.msk_tx_tag, map); return (ENOBUFS); } control = 0; tso = 0; tx_le = NULL; /* Check TSO support. */ if ((m->m_pkthdr.csum_flags & CSUM_TSO) != 0) { if ((sc_if->msk_flags & MSK_FLAG_DESCV2) != 0) tso_mtu = m->m_pkthdr.tso_segsz; else tso_mtu = offset + m->m_pkthdr.tso_segsz; if (tso_mtu != sc_if->msk_cdata.msk_tso_mtu) { tx_le = &sc_if->msk_rdata.msk_tx_ring[prod]; tx_le->msk_addr = htole32(tso_mtu); if ((sc_if->msk_flags & MSK_FLAG_DESCV2) != 0) tx_le->msk_control = htole32(OP_MSS | HW_OWNER); else tx_le->msk_control = htole32(OP_LRGLEN | HW_OWNER); sc_if->msk_cdata.msk_tx_cnt++; MSK_INC(prod, MSK_TX_RING_CNT); sc_if->msk_cdata.msk_tso_mtu = tso_mtu; } tso++; } /* Check if we have a VLAN tag to insert. */ if ((m->m_flags & M_VLANTAG) != 0) { if (tx_le == NULL) { tx_le = &sc_if->msk_rdata.msk_tx_ring[prod]; tx_le->msk_addr = htole32(0); tx_le->msk_control = htole32(OP_VLAN | HW_OWNER | htons(m->m_pkthdr.ether_vtag)); sc_if->msk_cdata.msk_tx_cnt++; MSK_INC(prod, MSK_TX_RING_CNT); } else { tx_le->msk_control |= htole32(OP_VLAN | htons(m->m_pkthdr.ether_vtag)); } control |= INS_VLAN; } /* Check if we have to handle checksum offload. */ if (tso == 0 && (m->m_pkthdr.csum_flags & MSK_CSUM_FEATURES) != 0) { if ((sc_if->msk_flags & MSK_FLAG_AUTOTX_CSUM) != 0) control |= CALSUM; else { control |= CALSUM | WR_SUM | INIT_SUM | LOCK_SUM; if ((m->m_pkthdr.csum_flags & CSUM_UDP) != 0) control |= UDPTCP; /* Checksum write position. */ csum = (tcp_offset + m->m_pkthdr.csum_data) & 0xffff; /* Checksum start position. */ csum |= (uint32_t)tcp_offset << 16; if (csum != sc_if->msk_cdata.msk_last_csum) { tx_le = &sc_if->msk_rdata.msk_tx_ring[prod]; tx_le->msk_addr = htole32(csum); tx_le->msk_control = htole32(1 << 16 | (OP_TCPLISW | HW_OWNER)); sc_if->msk_cdata.msk_tx_cnt++; MSK_INC(prod, MSK_TX_RING_CNT); sc_if->msk_cdata.msk_last_csum = csum; } } } #ifdef MSK_64BIT_DMA if (MSK_ADDR_HI(txsegs[0].ds_addr) != sc_if->msk_cdata.msk_tx_high_addr) { sc_if->msk_cdata.msk_tx_high_addr = MSK_ADDR_HI(txsegs[0].ds_addr); tx_le = &sc_if->msk_rdata.msk_tx_ring[prod]; tx_le->msk_addr = htole32(MSK_ADDR_HI(txsegs[0].ds_addr)); tx_le->msk_control = htole32(OP_ADDR64 | HW_OWNER); sc_if->msk_cdata.msk_tx_cnt++; MSK_INC(prod, MSK_TX_RING_CNT); } #endif si = prod; tx_le = &sc_if->msk_rdata.msk_tx_ring[prod]; tx_le->msk_addr = htole32(MSK_ADDR_LO(txsegs[0].ds_addr)); if (tso == 0) tx_le->msk_control = htole32(txsegs[0].ds_len | control | OP_PACKET); else tx_le->msk_control = htole32(txsegs[0].ds_len | control | OP_LARGESEND); sc_if->msk_cdata.msk_tx_cnt++; MSK_INC(prod, MSK_TX_RING_CNT); for (i = 1; i < nseg; i++) { tx_le = &sc_if->msk_rdata.msk_tx_ring[prod]; #ifdef MSK_64BIT_DMA if (MSK_ADDR_HI(txsegs[i].ds_addr) != sc_if->msk_cdata.msk_tx_high_addr) { sc_if->msk_cdata.msk_tx_high_addr = MSK_ADDR_HI(txsegs[i].ds_addr); tx_le = &sc_if->msk_rdata.msk_tx_ring[prod]; tx_le->msk_addr = htole32(MSK_ADDR_HI(txsegs[i].ds_addr)); tx_le->msk_control = htole32(OP_ADDR64 | HW_OWNER); sc_if->msk_cdata.msk_tx_cnt++; MSK_INC(prod, MSK_TX_RING_CNT); tx_le = &sc_if->msk_rdata.msk_tx_ring[prod]; } #endif tx_le->msk_addr = htole32(MSK_ADDR_LO(txsegs[i].ds_addr)); tx_le->msk_control = htole32(txsegs[i].ds_len | control | OP_BUFFER | HW_OWNER); sc_if->msk_cdata.msk_tx_cnt++; MSK_INC(prod, MSK_TX_RING_CNT); } /* Update producer index. */ sc_if->msk_cdata.msk_tx_prod = prod; /* Set EOP on the last descriptor. */ prod = (prod + MSK_TX_RING_CNT - 1) % MSK_TX_RING_CNT; tx_le = &sc_if->msk_rdata.msk_tx_ring[prod]; tx_le->msk_control |= htole32(EOP); /* Turn the first descriptor ownership to hardware. */ tx_le = &sc_if->msk_rdata.msk_tx_ring[si]; tx_le->msk_control |= htole32(HW_OWNER); txd = &sc_if->msk_cdata.msk_txdesc[prod]; map = txd_last->tx_dmamap; txd_last->tx_dmamap = txd->tx_dmamap; txd->tx_dmamap = map; txd->tx_m = m; /* Sync descriptors. */ bus_dmamap_sync(sc_if->msk_cdata.msk_tx_tag, map, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(sc_if->msk_cdata.msk_tx_ring_tag, sc_if->msk_cdata.msk_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (0); } static void msk_start(struct ifnet *ifp) { struct msk_if_softc *sc_if; sc_if = ifp->if_softc; MSK_IF_LOCK(sc_if); msk_start_locked(ifp); MSK_IF_UNLOCK(sc_if); } static void msk_start_locked(struct ifnet *ifp) { struct msk_if_softc *sc_if; struct mbuf *m_head; int enq; sc_if = ifp->if_softc; MSK_IF_LOCK_ASSERT(sc_if); if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING || (sc_if->msk_flags & MSK_FLAG_LINK) == 0) return; for (enq = 0; !IFQ_DRV_IS_EMPTY(&ifp->if_snd) && sc_if->msk_cdata.msk_tx_cnt < (MSK_TX_RING_CNT - MSK_RESERVED_TX_DESC_CNT); ) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; /* * Pack the data into the transmit ring. If we * don't have room, set the OACTIVE flag and wait * for the NIC to drain the ring. */ if (msk_encap(sc_if, &m_head) != 0) { if (m_head == NULL) break; IFQ_DRV_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } enq++; /* * If there's a BPF listener, bounce a copy of this frame * to him. */ ETHER_BPF_MTAP(ifp, m_head); } if (enq > 0) { /* Transmit */ CSR_WRITE_2(sc_if->msk_softc, Y2_PREF_Q_ADDR(sc_if->msk_txq, PREF_UNIT_PUT_IDX_REG), sc_if->msk_cdata.msk_tx_prod); /* Set a timeout in case the chip goes out to lunch. */ sc_if->msk_watchdog_timer = MSK_TX_TIMEOUT; } } static void msk_watchdog(struct msk_if_softc *sc_if) { struct ifnet *ifp; MSK_IF_LOCK_ASSERT(sc_if); if (sc_if->msk_watchdog_timer == 0 || --sc_if->msk_watchdog_timer) return; ifp = sc_if->msk_ifp; if ((sc_if->msk_flags & MSK_FLAG_LINK) == 0) { if (bootverbose) if_printf(sc_if->msk_ifp, "watchdog timeout " "(missed link)\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; msk_init_locked(sc_if); return; } if_printf(ifp, "watchdog timeout\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; msk_init_locked(sc_if); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) msk_start_locked(ifp); } static int mskc_shutdown(device_t dev) { struct msk_softc *sc; int i; sc = device_get_softc(dev); MSK_LOCK(sc); for (i = 0; i < sc->msk_num_port; i++) { if (sc->msk_if[i] != NULL && sc->msk_if[i]->msk_ifp != NULL && ((sc->msk_if[i]->msk_ifp->if_drv_flags & IFF_DRV_RUNNING) != 0)) msk_stop(sc->msk_if[i]); } MSK_UNLOCK(sc); /* Put hardware reset. */ CSR_WRITE_2(sc, B0_CTST, CS_RST_SET); return (0); } static int mskc_suspend(device_t dev) { struct msk_softc *sc; int i; sc = device_get_softc(dev); MSK_LOCK(sc); for (i = 0; i < sc->msk_num_port; i++) { if (sc->msk_if[i] != NULL && sc->msk_if[i]->msk_ifp != NULL && ((sc->msk_if[i]->msk_ifp->if_drv_flags & IFF_DRV_RUNNING) != 0)) msk_stop(sc->msk_if[i]); } /* Disable all interrupts. */ CSR_WRITE_4(sc, B0_IMSK, 0); CSR_READ_4(sc, B0_IMSK); CSR_WRITE_4(sc, B0_HWE_IMSK, 0); CSR_READ_4(sc, B0_HWE_IMSK); msk_phy_power(sc, MSK_PHY_POWERDOWN); /* Put hardware reset. */ CSR_WRITE_2(sc, B0_CTST, CS_RST_SET); sc->msk_pflags |= MSK_FLAG_SUSPEND; MSK_UNLOCK(sc); return (0); } static int mskc_resume(device_t dev) { struct msk_softc *sc; int i; sc = device_get_softc(dev); MSK_LOCK(sc); CSR_PCI_WRITE_4(sc, PCI_OUR_REG_3, 0); mskc_reset(sc); for (i = 0; i < sc->msk_num_port; i++) { if (sc->msk_if[i] != NULL && sc->msk_if[i]->msk_ifp != NULL && ((sc->msk_if[i]->msk_ifp->if_flags & IFF_UP) != 0)) { sc->msk_if[i]->msk_ifp->if_drv_flags &= ~IFF_DRV_RUNNING; msk_init_locked(sc->msk_if[i]); } } sc->msk_pflags &= ~MSK_FLAG_SUSPEND; MSK_UNLOCK(sc); return (0); } #ifndef __NO_STRICT_ALIGNMENT static __inline void msk_fixup_rx(struct mbuf *m) { int i; uint16_t *src, *dst; src = mtod(m, uint16_t *); dst = src - 3; for (i = 0; i < (m->m_len / sizeof(uint16_t) + 1); i++) *dst++ = *src++; m->m_data -= (MSK_RX_BUF_ALIGN - ETHER_ALIGN); } #endif static __inline void msk_rxcsum(struct msk_if_softc *sc_if, uint32_t control, struct mbuf *m) { struct ether_header *eh; struct ip *ip; struct udphdr *uh; int32_t hlen, len, pktlen, temp32; uint16_t csum, *opts; if ((sc_if->msk_flags & MSK_FLAG_DESCV2) != 0) { if ((control & (CSS_IPV4 | CSS_IPFRAG)) == CSS_IPV4) { m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED; if ((control & CSS_IPV4_CSUM_OK) != 0) m->m_pkthdr.csum_flags |= CSUM_IP_VALID; if ((control & (CSS_TCP | CSS_UDP)) != 0 && (control & (CSS_TCPUDP_CSUM_OK)) != 0) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } } return; } /* * Marvell Yukon controllers that support OP_RXCHKS has known * to have various Rx checksum offloading bugs. These * controllers can be configured to compute simple checksum * at two different positions. So we can compute IP and TCP/UDP * checksum at the same time. We intentionally have controller * compute TCP/UDP checksum twice by specifying the same * checksum start position and compare the result. If the value * is different it would indicate the hardware logic was wrong. */ if ((sc_if->msk_csum & 0xFFFF) != (sc_if->msk_csum >> 16)) { if (bootverbose) device_printf(sc_if->msk_if_dev, "Rx checksum value mismatch!\n"); return; } pktlen = m->m_pkthdr.len; if (pktlen < sizeof(struct ether_header) + sizeof(struct ip)) return; eh = mtod(m, struct ether_header *); if (eh->ether_type != htons(ETHERTYPE_IP)) return; ip = (struct ip *)(eh + 1); if (ip->ip_v != IPVERSION) return; hlen = ip->ip_hl << 2; pktlen -= sizeof(struct ether_header); if (hlen < sizeof(struct ip)) return; if (ntohs(ip->ip_len) < hlen) return; if (ntohs(ip->ip_len) != pktlen) return; if (ip->ip_off & htons(IP_MF | IP_OFFMASK)) return; /* can't handle fragmented packet. */ switch (ip->ip_p) { case IPPROTO_TCP: if (pktlen < (hlen + sizeof(struct tcphdr))) return; break; case IPPROTO_UDP: if (pktlen < (hlen + sizeof(struct udphdr))) return; uh = (struct udphdr *)((caddr_t)ip + hlen); if (uh->uh_sum == 0) return; /* no checksum */ break; default: return; } csum = bswap16(sc_if->msk_csum & 0xFFFF); /* Checksum fixup for IP options. */ len = hlen - sizeof(struct ip); if (len > 0) { opts = (uint16_t *)(ip + 1); for (; len > 0; len -= sizeof(uint16_t), opts++) { temp32 = csum - *opts; temp32 = (temp32 >> 16) + (temp32 & 65535); csum = temp32 & 65535; } } m->m_pkthdr.csum_flags |= CSUM_DATA_VALID; m->m_pkthdr.csum_data = csum; } static void msk_rxeof(struct msk_if_softc *sc_if, uint32_t status, uint32_t control, int len) { struct mbuf *m; struct ifnet *ifp; struct msk_rxdesc *rxd; int cons, rxlen; ifp = sc_if->msk_ifp; MSK_IF_LOCK_ASSERT(sc_if); cons = sc_if->msk_cdata.msk_rx_cons; do { rxlen = status >> 16; if ((status & GMR_FS_VLAN) != 0 && (ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0) rxlen -= ETHER_VLAN_ENCAP_LEN; if ((sc_if->msk_flags & MSK_FLAG_NORXCHK) != 0) { /* * For controllers that returns bogus status code * just do minimal check and let upper stack * handle this frame. */ if (len > MSK_MAX_FRAMELEN || len < ETHER_HDR_LEN) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); msk_discard_rxbuf(sc_if, cons); break; } } else if (len > sc_if->msk_framesize || ((status & GMR_FS_ANY_ERR) != 0) || ((status & GMR_FS_RX_OK) == 0) || (rxlen != len)) { /* Don't count flow-control packet as errors. */ if ((status & GMR_FS_GOOD_FC) == 0) if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); msk_discard_rxbuf(sc_if, cons); break; } #ifdef MSK_64BIT_DMA rxd = &sc_if->msk_cdata.msk_rxdesc[(cons + 1) % MSK_RX_RING_CNT]; #else rxd = &sc_if->msk_cdata.msk_rxdesc[cons]; #endif m = rxd->rx_m; if (msk_newbuf(sc_if, cons) != 0) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); /* Reuse old buffer. */ msk_discard_rxbuf(sc_if, cons); break; } m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = m->m_len = len; #ifndef __NO_STRICT_ALIGNMENT if ((sc_if->msk_flags & MSK_FLAG_RAMBUF) != 0) msk_fixup_rx(m); #endif if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) msk_rxcsum(sc_if, control, m); /* Check for VLAN tagged packets. */ if ((status & GMR_FS_VLAN) != 0 && (ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0) { m->m_pkthdr.ether_vtag = sc_if->msk_vtag; m->m_flags |= M_VLANTAG; } MSK_IF_UNLOCK(sc_if); (*ifp->if_input)(ifp, m); MSK_IF_LOCK(sc_if); } while (0); MSK_RX_INC(sc_if->msk_cdata.msk_rx_cons, MSK_RX_RING_CNT); MSK_RX_INC(sc_if->msk_cdata.msk_rx_prod, MSK_RX_RING_CNT); } static void msk_jumbo_rxeof(struct msk_if_softc *sc_if, uint32_t status, uint32_t control, int len) { struct mbuf *m; struct ifnet *ifp; struct msk_rxdesc *jrxd; int cons, rxlen; ifp = sc_if->msk_ifp; MSK_IF_LOCK_ASSERT(sc_if); cons = sc_if->msk_cdata.msk_rx_cons; do { rxlen = status >> 16; if ((status & GMR_FS_VLAN) != 0 && (ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0) rxlen -= ETHER_VLAN_ENCAP_LEN; if (len > sc_if->msk_framesize || ((status & GMR_FS_ANY_ERR) != 0) || ((status & GMR_FS_RX_OK) == 0) || (rxlen != len)) { /* Don't count flow-control packet as errors. */ if ((status & GMR_FS_GOOD_FC) == 0) if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); msk_discard_jumbo_rxbuf(sc_if, cons); break; } #ifdef MSK_64BIT_DMA jrxd = &sc_if->msk_cdata.msk_jumbo_rxdesc[(cons + 1) % MSK_JUMBO_RX_RING_CNT]; #else jrxd = &sc_if->msk_cdata.msk_jumbo_rxdesc[cons]; #endif m = jrxd->rx_m; if (msk_jumbo_newbuf(sc_if, cons) != 0) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); /* Reuse old buffer. */ msk_discard_jumbo_rxbuf(sc_if, cons); break; } m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = m->m_len = len; #ifndef __NO_STRICT_ALIGNMENT if ((sc_if->msk_flags & MSK_FLAG_RAMBUF) != 0) msk_fixup_rx(m); #endif if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) msk_rxcsum(sc_if, control, m); /* Check for VLAN tagged packets. */ if ((status & GMR_FS_VLAN) != 0 && (ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0) { m->m_pkthdr.ether_vtag = sc_if->msk_vtag; m->m_flags |= M_VLANTAG; } MSK_IF_UNLOCK(sc_if); (*ifp->if_input)(ifp, m); MSK_IF_LOCK(sc_if); } while (0); MSK_RX_INC(sc_if->msk_cdata.msk_rx_cons, MSK_JUMBO_RX_RING_CNT); MSK_RX_INC(sc_if->msk_cdata.msk_rx_prod, MSK_JUMBO_RX_RING_CNT); } static void msk_txeof(struct msk_if_softc *sc_if, int idx) { struct msk_txdesc *txd; struct msk_tx_desc *cur_tx; struct ifnet *ifp; uint32_t control; int cons, prog; MSK_IF_LOCK_ASSERT(sc_if); ifp = sc_if->msk_ifp; bus_dmamap_sync(sc_if->msk_cdata.msk_tx_ring_tag, sc_if->msk_cdata.msk_tx_ring_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); /* * Go through our tx ring and free mbufs for those * frames that have been sent. */ cons = sc_if->msk_cdata.msk_tx_cons; prog = 0; for (; cons != idx; MSK_INC(cons, MSK_TX_RING_CNT)) { if (sc_if->msk_cdata.msk_tx_cnt <= 0) break; prog++; cur_tx = &sc_if->msk_rdata.msk_tx_ring[cons]; control = le32toh(cur_tx->msk_control); sc_if->msk_cdata.msk_tx_cnt--; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; if ((control & EOP) == 0) continue; txd = &sc_if->msk_cdata.msk_txdesc[cons]; bus_dmamap_sync(sc_if->msk_cdata.msk_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc_if->msk_cdata.msk_tx_tag, txd->tx_dmamap); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); KASSERT(txd->tx_m != NULL, ("%s: freeing NULL mbuf!", __func__)); m_freem(txd->tx_m); txd->tx_m = NULL; } if (prog > 0) { sc_if->msk_cdata.msk_tx_cons = cons; if (sc_if->msk_cdata.msk_tx_cnt == 0) sc_if->msk_watchdog_timer = 0; /* No need to sync LEs as we didn't update LEs. */ } } static void msk_tick(void *xsc_if) { struct msk_if_softc *sc_if; struct mii_data *mii; sc_if = xsc_if; MSK_IF_LOCK_ASSERT(sc_if); mii = device_get_softc(sc_if->msk_miibus); mii_tick(mii); if ((sc_if->msk_flags & MSK_FLAG_LINK) == 0) msk_miibus_statchg(sc_if->msk_if_dev); msk_handle_events(sc_if->msk_softc); msk_watchdog(sc_if); callout_reset(&sc_if->msk_tick_ch, hz, msk_tick, sc_if); } static void msk_intr_phy(struct msk_if_softc *sc_if) { uint16_t status; msk_phy_readreg(sc_if, PHY_ADDR_MARV, PHY_MARV_INT_STAT); status = msk_phy_readreg(sc_if, PHY_ADDR_MARV, PHY_MARV_INT_STAT); /* Handle FIFO Underrun/Overflow? */ if ((status & PHY_M_IS_FIFO_ERROR)) device_printf(sc_if->msk_if_dev, "PHY FIFO underrun/overflow.\n"); } static void msk_intr_gmac(struct msk_if_softc *sc_if) { struct msk_softc *sc; uint8_t status; sc = sc_if->msk_softc; status = CSR_READ_1(sc, MR_ADDR(sc_if->msk_port, GMAC_IRQ_SRC)); /* GMAC Rx FIFO overrun. */ if ((status & GM_IS_RX_FF_OR) != 0) CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, RX_GMF_CTRL_T), GMF_CLI_RX_FO); /* GMAC Tx FIFO underrun. */ if ((status & GM_IS_TX_FF_UR) != 0) { CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, TX_GMF_CTRL_T), GMF_CLI_TX_FU); device_printf(sc_if->msk_if_dev, "Tx FIFO underrun!\n"); /* * XXX * In case of Tx underrun, we may need to flush/reset * Tx MAC but that would also require resynchronization * with status LEs. Reinitializing status LEs would * affect other port in dual MAC configuration so it * should be avoided as possible as we can. * Due to lack of documentation it's all vague guess but * it needs more investigation. */ } } static void msk_handle_hwerr(struct msk_if_softc *sc_if, uint32_t status) { struct msk_softc *sc; sc = sc_if->msk_softc; if ((status & Y2_IS_PAR_RD1) != 0) { device_printf(sc_if->msk_if_dev, "RAM buffer read parity error\n"); /* Clear IRQ. */ CSR_WRITE_2(sc, SELECT_RAM_BUFFER(sc_if->msk_port, B3_RI_CTRL), RI_CLR_RD_PERR); } if ((status & Y2_IS_PAR_WR1) != 0) { device_printf(sc_if->msk_if_dev, "RAM buffer write parity error\n"); /* Clear IRQ. */ CSR_WRITE_2(sc, SELECT_RAM_BUFFER(sc_if->msk_port, B3_RI_CTRL), RI_CLR_WR_PERR); } if ((status & Y2_IS_PAR_MAC1) != 0) { device_printf(sc_if->msk_if_dev, "Tx MAC parity error\n"); /* Clear IRQ. */ CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, TX_GMF_CTRL_T), GMF_CLI_TX_PE); } if ((status & Y2_IS_PAR_RX1) != 0) { device_printf(sc_if->msk_if_dev, "Rx parity error\n"); /* Clear IRQ. */ CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_rxq, Q_CSR), BMU_CLR_IRQ_PAR); } if ((status & (Y2_IS_TCP_TXS1 | Y2_IS_TCP_TXA1)) != 0) { device_printf(sc_if->msk_if_dev, "TCP segmentation error\n"); /* Clear IRQ. */ CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_txq, Q_CSR), BMU_CLR_IRQ_TCP); } } static void msk_intr_hwerr(struct msk_softc *sc) { uint32_t status; uint32_t tlphead[4]; status = CSR_READ_4(sc, B0_HWE_ISRC); /* Time Stamp timer overflow. */ if ((status & Y2_IS_TIST_OV) != 0) CSR_WRITE_1(sc, GMAC_TI_ST_CTRL, GMT_ST_CLR_IRQ); if ((status & Y2_IS_PCI_NEXP) != 0) { /* * PCI Express Error occured which is not described in PEX * spec. * This error is also mapped either to Master Abort( * Y2_IS_MST_ERR) or Target Abort (Y2_IS_IRQ_STAT) bit and * can only be cleared there. */ device_printf(sc->msk_dev, "PCI Express protocol violation error\n"); } if ((status & (Y2_IS_MST_ERR | Y2_IS_IRQ_STAT)) != 0) { uint16_t v16; if ((status & Y2_IS_MST_ERR) != 0) device_printf(sc->msk_dev, "unexpected IRQ Status error\n"); else device_printf(sc->msk_dev, "unexpected IRQ Master error\n"); /* Reset all bits in the PCI status register. */ v16 = pci_read_config(sc->msk_dev, PCIR_STATUS, 2); CSR_WRITE_1(sc, B2_TST_CTRL1, TST_CFG_WRITE_ON); pci_write_config(sc->msk_dev, PCIR_STATUS, v16 | PCIM_STATUS_PERR | PCIM_STATUS_SERR | PCIM_STATUS_RMABORT | PCIM_STATUS_RTABORT | PCIM_STATUS_MDPERR, 2); CSR_WRITE_1(sc, B2_TST_CTRL1, TST_CFG_WRITE_OFF); } /* Check for PCI Express Uncorrectable Error. */ if ((status & Y2_IS_PCI_EXP) != 0) { uint32_t v32; /* * On PCI Express bus bridges are called root complexes (RC). * PCI Express errors are recognized by the root complex too, * which requests the system to handle the problem. After * error occurrence it may be that no access to the adapter * may be performed any longer. */ v32 = CSR_PCI_READ_4(sc, PEX_UNC_ERR_STAT); if ((v32 & PEX_UNSUP_REQ) != 0) { /* Ignore unsupported request error. */ device_printf(sc->msk_dev, "Uncorrectable PCI Express error\n"); } if ((v32 & (PEX_FATAL_ERRORS | PEX_POIS_TLP)) != 0) { int i; /* Get TLP header form Log Registers. */ for (i = 0; i < 4; i++) tlphead[i] = CSR_PCI_READ_4(sc, PEX_HEADER_LOG + i * 4); /* Check for vendor defined broadcast message. */ if (!(tlphead[0] == 0x73004001 && tlphead[1] == 0x7f)) { sc->msk_intrhwemask &= ~Y2_IS_PCI_EXP; CSR_WRITE_4(sc, B0_HWE_IMSK, sc->msk_intrhwemask); CSR_READ_4(sc, B0_HWE_IMSK); } } /* Clear the interrupt. */ CSR_WRITE_1(sc, B2_TST_CTRL1, TST_CFG_WRITE_ON); CSR_PCI_WRITE_4(sc, PEX_UNC_ERR_STAT, 0xffffffff); CSR_WRITE_1(sc, B2_TST_CTRL1, TST_CFG_WRITE_OFF); } if ((status & Y2_HWE_L1_MASK) != 0 && sc->msk_if[MSK_PORT_A] != NULL) msk_handle_hwerr(sc->msk_if[MSK_PORT_A], status); if ((status & Y2_HWE_L2_MASK) != 0 && sc->msk_if[MSK_PORT_B] != NULL) msk_handle_hwerr(sc->msk_if[MSK_PORT_B], status >> 8); } static __inline void msk_rxput(struct msk_if_softc *sc_if) { struct msk_softc *sc; sc = sc_if->msk_softc; if (sc_if->msk_framesize > (MCLBYTES - MSK_RX_BUF_ALIGN)) bus_dmamap_sync( sc_if->msk_cdata.msk_jumbo_rx_ring_tag, sc_if->msk_cdata.msk_jumbo_rx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); else bus_dmamap_sync( sc_if->msk_cdata.msk_rx_ring_tag, sc_if->msk_cdata.msk_rx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); CSR_WRITE_2(sc, Y2_PREF_Q_ADDR(sc_if->msk_rxq, PREF_UNIT_PUT_IDX_REG), sc_if->msk_cdata.msk_rx_prod); } static int msk_handle_events(struct msk_softc *sc) { struct msk_if_softc *sc_if; int rxput[2]; struct msk_stat_desc *sd; uint32_t control, status; int cons, len, port, rxprog; if (sc->msk_stat_cons == CSR_READ_2(sc, STAT_PUT_IDX)) return (0); /* Sync status LEs. */ bus_dmamap_sync(sc->msk_stat_tag, sc->msk_stat_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); rxput[MSK_PORT_A] = rxput[MSK_PORT_B] = 0; rxprog = 0; cons = sc->msk_stat_cons; for (;;) { sd = &sc->msk_stat_ring[cons]; control = le32toh(sd->msk_control); if ((control & HW_OWNER) == 0) break; control &= ~HW_OWNER; sd->msk_control = htole32(control); status = le32toh(sd->msk_status); len = control & STLE_LEN_MASK; port = (control >> 16) & 0x01; sc_if = sc->msk_if[port]; if (sc_if == NULL) { device_printf(sc->msk_dev, "invalid port opcode " "0x%08x\n", control & STLE_OP_MASK); continue; } switch (control & STLE_OP_MASK) { case OP_RXVLAN: sc_if->msk_vtag = ntohs(len); break; case OP_RXCHKSVLAN: sc_if->msk_vtag = ntohs(len); /* FALLTHROUGH */ case OP_RXCHKS: sc_if->msk_csum = status; break; case OP_RXSTAT: if (!(sc_if->msk_ifp->if_drv_flags & IFF_DRV_RUNNING)) break; if (sc_if->msk_framesize > (MCLBYTES - MSK_RX_BUF_ALIGN)) msk_jumbo_rxeof(sc_if, status, control, len); else msk_rxeof(sc_if, status, control, len); rxprog++; /* * Because there is no way to sync single Rx LE * put the DMA sync operation off until the end of * event processing. */ rxput[port]++; /* Update prefetch unit if we've passed water mark. */ if (rxput[port] >= sc_if->msk_cdata.msk_rx_putwm) { msk_rxput(sc_if); rxput[port] = 0; } break; case OP_TXINDEXLE: if (sc->msk_if[MSK_PORT_A] != NULL) msk_txeof(sc->msk_if[MSK_PORT_A], status & STLE_TXA1_MSKL); if (sc->msk_if[MSK_PORT_B] != NULL) msk_txeof(sc->msk_if[MSK_PORT_B], ((status & STLE_TXA2_MSKL) >> STLE_TXA2_SHIFTL) | ((len & STLE_TXA2_MSKH) << STLE_TXA2_SHIFTH)); break; default: device_printf(sc->msk_dev, "unhandled opcode 0x%08x\n", control & STLE_OP_MASK); break; } MSK_INC(cons, sc->msk_stat_count); if (rxprog > sc->msk_process_limit) break; } sc->msk_stat_cons = cons; bus_dmamap_sync(sc->msk_stat_tag, sc->msk_stat_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); if (rxput[MSK_PORT_A] > 0) msk_rxput(sc->msk_if[MSK_PORT_A]); if (rxput[MSK_PORT_B] > 0) msk_rxput(sc->msk_if[MSK_PORT_B]); return (sc->msk_stat_cons != CSR_READ_2(sc, STAT_PUT_IDX)); } static void msk_intr(void *xsc) { struct msk_softc *sc; struct msk_if_softc *sc_if0, *sc_if1; struct ifnet *ifp0, *ifp1; uint32_t status; int domore; sc = xsc; MSK_LOCK(sc); /* Reading B0_Y2_SP_ISRC2 masks further interrupts. */ status = CSR_READ_4(sc, B0_Y2_SP_ISRC2); if (status == 0 || status == 0xffffffff || (sc->msk_pflags & MSK_FLAG_SUSPEND) != 0 || (status & sc->msk_intrmask) == 0) { CSR_WRITE_4(sc, B0_Y2_SP_ICR, 2); MSK_UNLOCK(sc); return; } sc_if0 = sc->msk_if[MSK_PORT_A]; sc_if1 = sc->msk_if[MSK_PORT_B]; ifp0 = ifp1 = NULL; if (sc_if0 != NULL) ifp0 = sc_if0->msk_ifp; if (sc_if1 != NULL) ifp1 = sc_if1->msk_ifp; if ((status & Y2_IS_IRQ_PHY1) != 0 && sc_if0 != NULL) msk_intr_phy(sc_if0); if ((status & Y2_IS_IRQ_PHY2) != 0 && sc_if1 != NULL) msk_intr_phy(sc_if1); if ((status & Y2_IS_IRQ_MAC1) != 0 && sc_if0 != NULL) msk_intr_gmac(sc_if0); if ((status & Y2_IS_IRQ_MAC2) != 0 && sc_if1 != NULL) msk_intr_gmac(sc_if1); if ((status & (Y2_IS_CHK_RX1 | Y2_IS_CHK_RX2)) != 0) { device_printf(sc->msk_dev, "Rx descriptor error\n"); sc->msk_intrmask &= ~(Y2_IS_CHK_RX1 | Y2_IS_CHK_RX2); CSR_WRITE_4(sc, B0_IMSK, sc->msk_intrmask); CSR_READ_4(sc, B0_IMSK); } if ((status & (Y2_IS_CHK_TXA1 | Y2_IS_CHK_TXA2)) != 0) { device_printf(sc->msk_dev, "Tx descriptor error\n"); sc->msk_intrmask &= ~(Y2_IS_CHK_TXA1 | Y2_IS_CHK_TXA2); CSR_WRITE_4(sc, B0_IMSK, sc->msk_intrmask); CSR_READ_4(sc, B0_IMSK); } if ((status & Y2_IS_HW_ERR) != 0) msk_intr_hwerr(sc); domore = msk_handle_events(sc); if ((status & Y2_IS_STAT_BMU) != 0 && domore == 0) CSR_WRITE_4(sc, STAT_CTRL, SC_STAT_CLR_IRQ); /* Reenable interrupts. */ CSR_WRITE_4(sc, B0_Y2_SP_ICR, 2); if (ifp0 != NULL && (ifp0->if_drv_flags & IFF_DRV_RUNNING) != 0 && !IFQ_DRV_IS_EMPTY(&ifp0->if_snd)) msk_start_locked(ifp0); if (ifp1 != NULL && (ifp1->if_drv_flags & IFF_DRV_RUNNING) != 0 && !IFQ_DRV_IS_EMPTY(&ifp1->if_snd)) msk_start_locked(ifp1); MSK_UNLOCK(sc); } static void msk_set_tx_stfwd(struct msk_if_softc *sc_if) { struct msk_softc *sc; struct ifnet *ifp; ifp = sc_if->msk_ifp; sc = sc_if->msk_softc; if ((sc->msk_hw_id == CHIP_ID_YUKON_EX && sc->msk_hw_rev != CHIP_REV_YU_EX_A0) || sc->msk_hw_id >= CHIP_ID_YUKON_SUPR) { CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, TX_GMF_CTRL_T), TX_STFW_ENA); } else { if (ifp->if_mtu > ETHERMTU) { /* Set Tx GMAC FIFO Almost Empty Threshold. */ CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, TX_GMF_AE_THR), MSK_ECU_JUMBO_WM << 16 | MSK_ECU_AE_THR); /* Disable Store & Forward mode for Tx. */ CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, TX_GMF_CTRL_T), TX_STFW_DIS); } else { CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, TX_GMF_CTRL_T), TX_STFW_ENA); } } } static void msk_init(void *xsc) { struct msk_if_softc *sc_if = xsc; MSK_IF_LOCK(sc_if); msk_init_locked(sc_if); MSK_IF_UNLOCK(sc_if); } static void msk_init_locked(struct msk_if_softc *sc_if) { struct msk_softc *sc; struct ifnet *ifp; struct mii_data *mii; uint8_t *eaddr; uint16_t gmac; uint32_t reg; int error; MSK_IF_LOCK_ASSERT(sc_if); ifp = sc_if->msk_ifp; sc = sc_if->msk_softc; mii = device_get_softc(sc_if->msk_miibus); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) return; error = 0; /* Cancel pending I/O and free all Rx/Tx buffers. */ msk_stop(sc_if); if (ifp->if_mtu < ETHERMTU) sc_if->msk_framesize = ETHERMTU; else sc_if->msk_framesize = ifp->if_mtu; sc_if->msk_framesize += ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN; if (ifp->if_mtu > ETHERMTU && (sc_if->msk_flags & MSK_FLAG_JUMBO_NOCSUM) != 0) { ifp->if_hwassist &= ~(MSK_CSUM_FEATURES | CSUM_TSO); ifp->if_capenable &= ~(IFCAP_TSO4 | IFCAP_TXCSUM); } /* GMAC Control reset. */ CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, GMAC_CTRL), GMC_RST_SET); CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, GMAC_CTRL), GMC_RST_CLR); CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, GMAC_CTRL), GMC_F_LOOPB_OFF); if (sc->msk_hw_id == CHIP_ID_YUKON_EX || sc->msk_hw_id == CHIP_ID_YUKON_SUPR) CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, GMAC_CTRL), GMC_BYP_MACSECRX_ON | GMC_BYP_MACSECTX_ON | GMC_BYP_RETR_ON); /* * Initialize GMAC first such that speed/duplex/flow-control * parameters are renegotiated when interface is brought up. */ GMAC_WRITE_2(sc, sc_if->msk_port, GM_GP_CTRL, 0); /* Dummy read the Interrupt Source Register. */ CSR_READ_1(sc, MR_ADDR(sc_if->msk_port, GMAC_IRQ_SRC)); /* Clear MIB stats. */ msk_stats_clear(sc_if); /* Disable FCS. */ GMAC_WRITE_2(sc, sc_if->msk_port, GM_RX_CTRL, GM_RXCR_CRC_DIS); /* Setup Transmit Control Register. */ GMAC_WRITE_2(sc, sc_if->msk_port, GM_TX_CTRL, TX_COL_THR(TX_COL_DEF)); /* Setup Transmit Flow Control Register. */ GMAC_WRITE_2(sc, sc_if->msk_port, GM_TX_FLOW_CTRL, 0xffff); /* Setup Transmit Parameter Register. */ GMAC_WRITE_2(sc, sc_if->msk_port, GM_TX_PARAM, TX_JAM_LEN_VAL(TX_JAM_LEN_DEF) | TX_JAM_IPG_VAL(TX_JAM_IPG_DEF) | TX_IPG_JAM_DATA(TX_IPG_JAM_DEF) | TX_BACK_OFF_LIM(TX_BOF_LIM_DEF)); gmac = DATA_BLIND_VAL(DATA_BLIND_DEF) | GM_SMOD_VLAN_ENA | IPG_DATA_VAL(IPG_DATA_DEF); if (ifp->if_mtu > ETHERMTU) gmac |= GM_SMOD_JUMBO_ENA; GMAC_WRITE_2(sc, sc_if->msk_port, GM_SERIAL_MODE, gmac); /* Set station address. */ eaddr = IF_LLADDR(ifp); GMAC_WRITE_2(sc, sc_if->msk_port, GM_SRC_ADDR_1L, eaddr[0] | (eaddr[1] << 8)); GMAC_WRITE_2(sc, sc_if->msk_port, GM_SRC_ADDR_1M, eaddr[2] | (eaddr[3] << 8)); GMAC_WRITE_2(sc, sc_if->msk_port, GM_SRC_ADDR_1H, eaddr[4] | (eaddr[5] << 8)); GMAC_WRITE_2(sc, sc_if->msk_port, GM_SRC_ADDR_2L, eaddr[0] | (eaddr[1] << 8)); GMAC_WRITE_2(sc, sc_if->msk_port, GM_SRC_ADDR_2M, eaddr[2] | (eaddr[3] << 8)); GMAC_WRITE_2(sc, sc_if->msk_port, GM_SRC_ADDR_2H, eaddr[4] | (eaddr[5] << 8)); /* Disable interrupts for counter overflows. */ GMAC_WRITE_2(sc, sc_if->msk_port, GM_TX_IRQ_MSK, 0); GMAC_WRITE_2(sc, sc_if->msk_port, GM_RX_IRQ_MSK, 0); GMAC_WRITE_2(sc, sc_if->msk_port, GM_TR_IRQ_MSK, 0); /* Configure Rx MAC FIFO. */ CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, RX_GMF_CTRL_T), GMF_RST_SET); CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, RX_GMF_CTRL_T), GMF_RST_CLR); reg = GMF_OPER_ON | GMF_RX_F_FL_ON; if (sc->msk_hw_id == CHIP_ID_YUKON_FE_P || sc->msk_hw_id == CHIP_ID_YUKON_EX) reg |= GMF_RX_OVER_ON; CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, RX_GMF_CTRL_T), reg); /* Set receive filter. */ msk_rxfilter(sc_if); if (sc->msk_hw_id == CHIP_ID_YUKON_XL) { /* Clear flush mask - HW bug. */ CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, RX_GMF_FL_MSK), 0); } else { /* Flush Rx MAC FIFO on any flow control or error. */ CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, RX_GMF_FL_MSK), GMR_FS_ANY_ERR); } /* * Set Rx FIFO flush threshold to 64 bytes + 1 FIFO word * due to hardware hang on receipt of pause frames. */ reg = RX_GMF_FL_THR_DEF + 1; /* Another magic for Yukon FE+ - From Linux. */ if (sc->msk_hw_id == CHIP_ID_YUKON_FE_P && sc->msk_hw_rev == CHIP_REV_YU_FE_P_A0) reg = 0x178; CSR_WRITE_2(sc, MR_ADDR(sc_if->msk_port, RX_GMF_FL_THR), reg); /* Configure Tx MAC FIFO. */ CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, TX_GMF_CTRL_T), GMF_RST_SET); CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, TX_GMF_CTRL_T), GMF_RST_CLR); CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, TX_GMF_CTRL_T), GMF_OPER_ON); /* Configure hardware VLAN tag insertion/stripping. */ msk_setvlan(sc_if, ifp); if ((sc_if->msk_flags & MSK_FLAG_RAMBUF) == 0) { /* Set Rx Pause threshold. */ CSR_WRITE_2(sc, MR_ADDR(sc_if->msk_port, RX_GMF_LP_THR), MSK_ECU_LLPP); CSR_WRITE_2(sc, MR_ADDR(sc_if->msk_port, RX_GMF_UP_THR), MSK_ECU_ULPP); /* Configure store-and-forward for Tx. */ msk_set_tx_stfwd(sc_if); } if (sc->msk_hw_id == CHIP_ID_YUKON_FE_P && sc->msk_hw_rev == CHIP_REV_YU_FE_P_A0) { /* Disable dynamic watermark - from Linux. */ reg = CSR_READ_4(sc, MR_ADDR(sc_if->msk_port, TX_GMF_EA)); reg &= ~0x03; CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, TX_GMF_EA), reg); } /* * Disable Force Sync bit and Alloc bit in Tx RAM interface * arbiter as we don't use Sync Tx queue. */ CSR_WRITE_1(sc, MR_ADDR(sc_if->msk_port, TXA_CTRL), TXA_DIS_FSYNC | TXA_DIS_ALLOC | TXA_STOP_RC); /* Enable the RAM Interface Arbiter. */ CSR_WRITE_1(sc, MR_ADDR(sc_if->msk_port, TXA_CTRL), TXA_ENA_ARB); /* Setup RAM buffer. */ msk_set_rambuffer(sc_if); /* Disable Tx sync Queue. */ CSR_WRITE_1(sc, RB_ADDR(sc_if->msk_txsq, RB_CTRL), RB_RST_SET); /* Setup Tx Queue Bus Memory Interface. */ CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_txq, Q_CSR), BMU_CLR_RESET); CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_txq, Q_CSR), BMU_OPER_INIT); CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_txq, Q_CSR), BMU_FIFO_OP_ON); CSR_WRITE_2(sc, Q_ADDR(sc_if->msk_txq, Q_WM), MSK_BMU_TX_WM); switch (sc->msk_hw_id) { case CHIP_ID_YUKON_EC_U: if (sc->msk_hw_rev == CHIP_REV_YU_EC_U_A0) { /* Fix for Yukon-EC Ultra: set BMU FIFO level */ CSR_WRITE_2(sc, Q_ADDR(sc_if->msk_txq, Q_AL), MSK_ECU_TXFF_LEV); } break; case CHIP_ID_YUKON_EX: /* * Yukon Extreme seems to have silicon bug for * automatic Tx checksum calculation capability. */ if (sc->msk_hw_rev == CHIP_REV_YU_EX_B0) CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_txq, Q_F), F_TX_CHK_AUTO_OFF); break; } /* Setup Rx Queue Bus Memory Interface. */ CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_rxq, Q_CSR), BMU_CLR_RESET); CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_rxq, Q_CSR), BMU_OPER_INIT); CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_rxq, Q_CSR), BMU_FIFO_OP_ON); CSR_WRITE_2(sc, Q_ADDR(sc_if->msk_rxq, Q_WM), MSK_BMU_RX_WM); if (sc->msk_hw_id == CHIP_ID_YUKON_EC_U && sc->msk_hw_rev >= CHIP_REV_YU_EC_U_A1) { /* MAC Rx RAM Read is controlled by hardware. */ CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_rxq, Q_F), F_M_RX_RAM_DIS); } msk_set_prefetch(sc, sc_if->msk_txq, sc_if->msk_rdata.msk_tx_ring_paddr, MSK_TX_RING_CNT - 1); msk_init_tx_ring(sc_if); /* Disable Rx checksum offload and RSS hash. */ reg = BMU_DIS_RX_RSS_HASH; if ((sc_if->msk_flags & MSK_FLAG_DESCV2) == 0 && (ifp->if_capenable & IFCAP_RXCSUM) != 0) reg |= BMU_ENA_RX_CHKSUM; else reg |= BMU_DIS_RX_CHKSUM; CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_rxq, Q_CSR), reg); if (sc_if->msk_framesize > (MCLBYTES - MSK_RX_BUF_ALIGN)) { msk_set_prefetch(sc, sc_if->msk_rxq, sc_if->msk_rdata.msk_jumbo_rx_ring_paddr, MSK_JUMBO_RX_RING_CNT - 1); error = msk_init_jumbo_rx_ring(sc_if); } else { msk_set_prefetch(sc, sc_if->msk_rxq, sc_if->msk_rdata.msk_rx_ring_paddr, MSK_RX_RING_CNT - 1); error = msk_init_rx_ring(sc_if); } if (error != 0) { device_printf(sc_if->msk_if_dev, "initialization failed: no memory for Rx buffers\n"); msk_stop(sc_if); return; } if (sc->msk_hw_id == CHIP_ID_YUKON_EX || sc->msk_hw_id == CHIP_ID_YUKON_SUPR) { /* Disable flushing of non-ASF packets. */ CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, RX_GMF_CTRL_T), GMF_RX_MACSEC_FLUSH_OFF); } /* Configure interrupt handling. */ if (sc_if->msk_port == MSK_PORT_A) { sc->msk_intrmask |= Y2_IS_PORT_A; sc->msk_intrhwemask |= Y2_HWE_L1_MASK; } else { sc->msk_intrmask |= Y2_IS_PORT_B; sc->msk_intrhwemask |= Y2_HWE_L2_MASK; } /* Configure IRQ moderation mask. */ CSR_WRITE_4(sc, B2_IRQM_MSK, sc->msk_intrmask); if (sc->msk_int_holdoff > 0) { /* Configure initial IRQ moderation timer value. */ CSR_WRITE_4(sc, B2_IRQM_INI, MSK_USECS(sc, sc->msk_int_holdoff)); CSR_WRITE_4(sc, B2_IRQM_VAL, MSK_USECS(sc, sc->msk_int_holdoff)); /* Start IRQ moderation. */ CSR_WRITE_1(sc, B2_IRQM_CTRL, TIM_START); } CSR_WRITE_4(sc, B0_HWE_IMSK, sc->msk_intrhwemask); CSR_READ_4(sc, B0_HWE_IMSK); CSR_WRITE_4(sc, B0_IMSK, sc->msk_intrmask); CSR_READ_4(sc, B0_IMSK); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc_if->msk_flags &= ~MSK_FLAG_LINK; mii_mediachg(mii); callout_reset(&sc_if->msk_tick_ch, hz, msk_tick, sc_if); } static void msk_set_rambuffer(struct msk_if_softc *sc_if) { struct msk_softc *sc; int ltpp, utpp; sc = sc_if->msk_softc; if ((sc_if->msk_flags & MSK_FLAG_RAMBUF) == 0) return; /* Setup Rx Queue. */ CSR_WRITE_1(sc, RB_ADDR(sc_if->msk_rxq, RB_CTRL), RB_RST_CLR); CSR_WRITE_4(sc, RB_ADDR(sc_if->msk_rxq, RB_START), sc->msk_rxqstart[sc_if->msk_port] / 8); CSR_WRITE_4(sc, RB_ADDR(sc_if->msk_rxq, RB_END), sc->msk_rxqend[sc_if->msk_port] / 8); CSR_WRITE_4(sc, RB_ADDR(sc_if->msk_rxq, RB_WP), sc->msk_rxqstart[sc_if->msk_port] / 8); CSR_WRITE_4(sc, RB_ADDR(sc_if->msk_rxq, RB_RP), sc->msk_rxqstart[sc_if->msk_port] / 8); utpp = (sc->msk_rxqend[sc_if->msk_port] + 1 - sc->msk_rxqstart[sc_if->msk_port] - MSK_RB_ULPP) / 8; ltpp = (sc->msk_rxqend[sc_if->msk_port] + 1 - sc->msk_rxqstart[sc_if->msk_port] - MSK_RB_LLPP_B) / 8; if (sc->msk_rxqsize < MSK_MIN_RXQ_SIZE) ltpp += (MSK_RB_LLPP_B - MSK_RB_LLPP_S) / 8; CSR_WRITE_4(sc, RB_ADDR(sc_if->msk_rxq, RB_RX_UTPP), utpp); CSR_WRITE_4(sc, RB_ADDR(sc_if->msk_rxq, RB_RX_LTPP), ltpp); /* Set Rx priority(RB_RX_UTHP/RB_RX_LTHP) thresholds? */ CSR_WRITE_1(sc, RB_ADDR(sc_if->msk_rxq, RB_CTRL), RB_ENA_OP_MD); CSR_READ_1(sc, RB_ADDR(sc_if->msk_rxq, RB_CTRL)); /* Setup Tx Queue. */ CSR_WRITE_1(sc, RB_ADDR(sc_if->msk_txq, RB_CTRL), RB_RST_CLR); CSR_WRITE_4(sc, RB_ADDR(sc_if->msk_txq, RB_START), sc->msk_txqstart[sc_if->msk_port] / 8); CSR_WRITE_4(sc, RB_ADDR(sc_if->msk_txq, RB_END), sc->msk_txqend[sc_if->msk_port] / 8); CSR_WRITE_4(sc, RB_ADDR(sc_if->msk_txq, RB_WP), sc->msk_txqstart[sc_if->msk_port] / 8); CSR_WRITE_4(sc, RB_ADDR(sc_if->msk_txq, RB_RP), sc->msk_txqstart[sc_if->msk_port] / 8); /* Enable Store & Forward for Tx side. */ CSR_WRITE_1(sc, RB_ADDR(sc_if->msk_txq, RB_CTRL), RB_ENA_STFWD); CSR_WRITE_1(sc, RB_ADDR(sc_if->msk_txq, RB_CTRL), RB_ENA_OP_MD); CSR_READ_1(sc, RB_ADDR(sc_if->msk_txq, RB_CTRL)); } static void msk_set_prefetch(struct msk_softc *sc, int qaddr, bus_addr_t addr, uint32_t count) { /* Reset the prefetch unit. */ CSR_WRITE_4(sc, Y2_PREF_Q_ADDR(qaddr, PREF_UNIT_CTRL_REG), PREF_UNIT_RST_SET); CSR_WRITE_4(sc, Y2_PREF_Q_ADDR(qaddr, PREF_UNIT_CTRL_REG), PREF_UNIT_RST_CLR); /* Set LE base address. */ CSR_WRITE_4(sc, Y2_PREF_Q_ADDR(qaddr, PREF_UNIT_ADDR_LOW_REG), MSK_ADDR_LO(addr)); CSR_WRITE_4(sc, Y2_PREF_Q_ADDR(qaddr, PREF_UNIT_ADDR_HI_REG), MSK_ADDR_HI(addr)); /* Set the list last index. */ CSR_WRITE_2(sc, Y2_PREF_Q_ADDR(qaddr, PREF_UNIT_LAST_IDX_REG), count); /* Turn on prefetch unit. */ CSR_WRITE_4(sc, Y2_PREF_Q_ADDR(qaddr, PREF_UNIT_CTRL_REG), PREF_UNIT_OP_ON); /* Dummy read to ensure write. */ CSR_READ_4(sc, Y2_PREF_Q_ADDR(qaddr, PREF_UNIT_CTRL_REG)); } static void msk_stop(struct msk_if_softc *sc_if) { struct msk_softc *sc; struct msk_txdesc *txd; struct msk_rxdesc *rxd; struct msk_rxdesc *jrxd; struct ifnet *ifp; uint32_t val; int i; MSK_IF_LOCK_ASSERT(sc_if); sc = sc_if->msk_softc; ifp = sc_if->msk_ifp; callout_stop(&sc_if->msk_tick_ch); sc_if->msk_watchdog_timer = 0; /* Disable interrupts. */ if (sc_if->msk_port == MSK_PORT_A) { sc->msk_intrmask &= ~Y2_IS_PORT_A; sc->msk_intrhwemask &= ~Y2_HWE_L1_MASK; } else { sc->msk_intrmask &= ~Y2_IS_PORT_B; sc->msk_intrhwemask &= ~Y2_HWE_L2_MASK; } CSR_WRITE_4(sc, B0_HWE_IMSK, sc->msk_intrhwemask); CSR_READ_4(sc, B0_HWE_IMSK); CSR_WRITE_4(sc, B0_IMSK, sc->msk_intrmask); CSR_READ_4(sc, B0_IMSK); /* Disable Tx/Rx MAC. */ val = GMAC_READ_2(sc, sc_if->msk_port, GM_GP_CTRL); val &= ~(GM_GPCR_RX_ENA | GM_GPCR_TX_ENA); GMAC_WRITE_2(sc, sc_if->msk_port, GM_GP_CTRL, val); /* Read again to ensure writing. */ GMAC_READ_2(sc, sc_if->msk_port, GM_GP_CTRL); /* Update stats and clear counters. */ msk_stats_update(sc_if); /* Stop Tx BMU. */ CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_txq, Q_CSR), BMU_STOP); val = CSR_READ_4(sc, Q_ADDR(sc_if->msk_txq, Q_CSR)); for (i = 0; i < MSK_TIMEOUT; i++) { if ((val & (BMU_STOP | BMU_IDLE)) == 0) { CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_txq, Q_CSR), BMU_STOP); val = CSR_READ_4(sc, Q_ADDR(sc_if->msk_txq, Q_CSR)); } else break; DELAY(1); } if (i == MSK_TIMEOUT) device_printf(sc_if->msk_if_dev, "Tx BMU stop failed\n"); CSR_WRITE_1(sc, RB_ADDR(sc_if->msk_txq, RB_CTRL), RB_RST_SET | RB_DIS_OP_MD); /* Disable all GMAC interrupt. */ CSR_WRITE_1(sc, MR_ADDR(sc_if->msk_port, GMAC_IRQ_MSK), 0); /* Disable PHY interrupt. */ msk_phy_writereg(sc_if, PHY_ADDR_MARV, PHY_MARV_INT_MASK, 0); /* Disable the RAM Interface Arbiter. */ CSR_WRITE_1(sc, MR_ADDR(sc_if->msk_port, TXA_CTRL), TXA_DIS_ARB); /* Reset the PCI FIFO of the async Tx queue */ CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_txq, Q_CSR), BMU_RST_SET | BMU_FIFO_RST); /* Reset the Tx prefetch units. */ CSR_WRITE_4(sc, Y2_PREF_Q_ADDR(sc_if->msk_txq, PREF_UNIT_CTRL_REG), PREF_UNIT_RST_SET); /* Reset the RAM Buffer async Tx queue. */ CSR_WRITE_1(sc, RB_ADDR(sc_if->msk_txq, RB_CTRL), RB_RST_SET); /* Reset Tx MAC FIFO. */ CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, TX_GMF_CTRL_T), GMF_RST_SET); /* Set Pause Off. */ CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, GMAC_CTRL), GMC_PAUSE_OFF); /* * The Rx Stop command will not work for Yukon-2 if the BMU does not * reach the end of packet and since we can't make sure that we have * incoming data, we must reset the BMU while it is not during a DMA * transfer. Since it is possible that the Rx path is still active, * the Rx RAM buffer will be stopped first, so any possible incoming * data will not trigger a DMA. After the RAM buffer is stopped, the * BMU is polled until any DMA in progress is ended and only then it * will be reset. */ /* Disable the RAM Buffer receive queue. */ CSR_WRITE_1(sc, RB_ADDR(sc_if->msk_rxq, RB_CTRL), RB_DIS_OP_MD); for (i = 0; i < MSK_TIMEOUT; i++) { if (CSR_READ_1(sc, RB_ADDR(sc_if->msk_rxq, Q_RSL)) == CSR_READ_1(sc, RB_ADDR(sc_if->msk_rxq, Q_RL))) break; DELAY(1); } if (i == MSK_TIMEOUT) device_printf(sc_if->msk_if_dev, "Rx BMU stop failed\n"); CSR_WRITE_4(sc, Q_ADDR(sc_if->msk_rxq, Q_CSR), BMU_RST_SET | BMU_FIFO_RST); /* Reset the Rx prefetch unit. */ CSR_WRITE_4(sc, Y2_PREF_Q_ADDR(sc_if->msk_rxq, PREF_UNIT_CTRL_REG), PREF_UNIT_RST_SET); /* Reset the RAM Buffer receive queue. */ CSR_WRITE_1(sc, RB_ADDR(sc_if->msk_rxq, RB_CTRL), RB_RST_SET); /* Reset Rx MAC FIFO. */ CSR_WRITE_4(sc, MR_ADDR(sc_if->msk_port, RX_GMF_CTRL_T), GMF_RST_SET); /* Free Rx and Tx mbufs still in the queues. */ for (i = 0; i < MSK_RX_RING_CNT; i++) { rxd = &sc_if->msk_cdata.msk_rxdesc[i]; if (rxd->rx_m != NULL) { bus_dmamap_sync(sc_if->msk_cdata.msk_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc_if->msk_cdata.msk_rx_tag, rxd->rx_dmamap); m_freem(rxd->rx_m); rxd->rx_m = NULL; } } for (i = 0; i < MSK_JUMBO_RX_RING_CNT; i++) { jrxd = &sc_if->msk_cdata.msk_jumbo_rxdesc[i]; if (jrxd->rx_m != NULL) { bus_dmamap_sync(sc_if->msk_cdata.msk_jumbo_rx_tag, jrxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc_if->msk_cdata.msk_jumbo_rx_tag, jrxd->rx_dmamap); m_freem(jrxd->rx_m); jrxd->rx_m = NULL; } } for (i = 0; i < MSK_TX_RING_CNT; i++) { txd = &sc_if->msk_cdata.msk_txdesc[i]; if (txd->tx_m != NULL) { bus_dmamap_sync(sc_if->msk_cdata.msk_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc_if->msk_cdata.msk_tx_tag, txd->tx_dmamap); m_freem(txd->tx_m); txd->tx_m = NULL; } } /* * Mark the interface down. */ ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc_if->msk_flags &= ~MSK_FLAG_LINK; } /* * When GM_PAR_MIB_CLR bit of GM_PHY_ADDR is set, reading lower * counter clears high 16 bits of the counter such that accessing * lower 16 bits should be the last operation. */ #define MSK_READ_MIB32(x, y) \ (((uint32_t)GMAC_READ_2(sc, x, (y) + 4)) << 16) + \ (uint32_t)GMAC_READ_2(sc, x, y) #define MSK_READ_MIB64(x, y) \ (((uint64_t)MSK_READ_MIB32(x, (y) + 8)) << 32) + \ (uint64_t)MSK_READ_MIB32(x, y) static void msk_stats_clear(struct msk_if_softc *sc_if) { struct msk_softc *sc; uint32_t reg; uint16_t gmac; int i; MSK_IF_LOCK_ASSERT(sc_if); sc = sc_if->msk_softc; /* Set MIB Clear Counter Mode. */ gmac = GMAC_READ_2(sc, sc_if->msk_port, GM_PHY_ADDR); GMAC_WRITE_2(sc, sc_if->msk_port, GM_PHY_ADDR, gmac | GM_PAR_MIB_CLR); /* Read all MIB Counters with Clear Mode set. */ for (i = GM_RXF_UC_OK; i <= GM_TXE_FIFO_UR; i += sizeof(uint32_t)) reg = MSK_READ_MIB32(sc_if->msk_port, i); /* Clear MIB Clear Counter Mode. */ gmac &= ~GM_PAR_MIB_CLR; GMAC_WRITE_2(sc, sc_if->msk_port, GM_PHY_ADDR, gmac); } static void msk_stats_update(struct msk_if_softc *sc_if) { struct msk_softc *sc; struct ifnet *ifp; struct msk_hw_stats *stats; uint16_t gmac; uint32_t reg; MSK_IF_LOCK_ASSERT(sc_if); ifp = sc_if->msk_ifp; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; sc = sc_if->msk_softc; stats = &sc_if->msk_stats; /* Set MIB Clear Counter Mode. */ gmac = GMAC_READ_2(sc, sc_if->msk_port, GM_PHY_ADDR); GMAC_WRITE_2(sc, sc_if->msk_port, GM_PHY_ADDR, gmac | GM_PAR_MIB_CLR); /* Rx stats. */ stats->rx_ucast_frames += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_UC_OK); stats->rx_bcast_frames += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_BC_OK); stats->rx_pause_frames += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_MPAUSE); stats->rx_mcast_frames += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_MC_OK); stats->rx_crc_errs += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_FCS_ERR); reg = MSK_READ_MIB32(sc_if->msk_port, GM_RXF_SPARE1); stats->rx_good_octets += MSK_READ_MIB64(sc_if->msk_port, GM_RXO_OK_LO); stats->rx_bad_octets += MSK_READ_MIB64(sc_if->msk_port, GM_RXO_ERR_LO); stats->rx_runts += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_SHT); stats->rx_runt_errs += MSK_READ_MIB32(sc_if->msk_port, GM_RXE_FRAG); stats->rx_pkts_64 += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_64B); stats->rx_pkts_65_127 += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_127B); stats->rx_pkts_128_255 += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_255B); stats->rx_pkts_256_511 += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_511B); stats->rx_pkts_512_1023 += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_1023B); stats->rx_pkts_1024_1518 += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_1518B); stats->rx_pkts_1519_max += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_MAX_SZ); stats->rx_pkts_too_long += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_LNG_ERR); stats->rx_pkts_jabbers += MSK_READ_MIB32(sc_if->msk_port, GM_RXF_JAB_PKT); reg = MSK_READ_MIB32(sc_if->msk_port, GM_RXF_SPARE2); stats->rx_fifo_oflows += MSK_READ_MIB32(sc_if->msk_port, GM_RXE_FIFO_OV); reg = MSK_READ_MIB32(sc_if->msk_port, GM_RXF_SPARE3); /* Tx stats. */ stats->tx_ucast_frames += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_UC_OK); stats->tx_bcast_frames += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_BC_OK); stats->tx_pause_frames += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_MPAUSE); stats->tx_mcast_frames += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_MC_OK); stats->tx_octets += MSK_READ_MIB64(sc_if->msk_port, GM_TXO_OK_LO); stats->tx_pkts_64 += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_64B); stats->tx_pkts_65_127 += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_127B); stats->tx_pkts_128_255 += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_255B); stats->tx_pkts_256_511 += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_511B); stats->tx_pkts_512_1023 += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_1023B); stats->tx_pkts_1024_1518 += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_1518B); stats->tx_pkts_1519_max += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_MAX_SZ); reg = MSK_READ_MIB32(sc_if->msk_port, GM_TXF_SPARE1); stats->tx_colls += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_COL); stats->tx_late_colls += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_LAT_COL); stats->tx_excess_colls += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_ABO_COL); stats->tx_multi_colls += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_MUL_COL); stats->tx_single_colls += MSK_READ_MIB32(sc_if->msk_port, GM_TXF_SNG_COL); stats->tx_underflows += MSK_READ_MIB32(sc_if->msk_port, GM_TXE_FIFO_UR); /* Clear MIB Clear Counter Mode. */ gmac &= ~GM_PAR_MIB_CLR; GMAC_WRITE_2(sc, sc_if->msk_port, GM_PHY_ADDR, gmac); } static int msk_sysctl_stat32(SYSCTL_HANDLER_ARGS) { struct msk_softc *sc; struct msk_if_softc *sc_if; uint32_t result, *stat; int off; sc_if = (struct msk_if_softc *)arg1; sc = sc_if->msk_softc; off = arg2; stat = (uint32_t *)((uint8_t *)&sc_if->msk_stats + off); MSK_IF_LOCK(sc_if); result = MSK_READ_MIB32(sc_if->msk_port, GM_MIB_CNT_BASE + off * 2); result += *stat; MSK_IF_UNLOCK(sc_if); return (sysctl_handle_int(oidp, &result, 0, req)); } static int msk_sysctl_stat64(SYSCTL_HANDLER_ARGS) { struct msk_softc *sc; struct msk_if_softc *sc_if; uint64_t result, *stat; int off; sc_if = (struct msk_if_softc *)arg1; sc = sc_if->msk_softc; off = arg2; stat = (uint64_t *)((uint8_t *)&sc_if->msk_stats + off); MSK_IF_LOCK(sc_if); result = MSK_READ_MIB64(sc_if->msk_port, GM_MIB_CNT_BASE + off * 2); result += *stat; MSK_IF_UNLOCK(sc_if); return (sysctl_handle_64(oidp, &result, 0, req)); } #undef MSK_READ_MIB32 #undef MSK_READ_MIB64 #define MSK_SYSCTL_STAT32(sc, c, o, p, n, d) \ SYSCTL_ADD_PROC(c, p, OID_AUTO, o, CTLTYPE_UINT | CTLFLAG_RD, \ sc, offsetof(struct msk_hw_stats, n), msk_sysctl_stat32, \ "IU", d) #define MSK_SYSCTL_STAT64(sc, c, o, p, n, d) \ SYSCTL_ADD_PROC(c, p, OID_AUTO, o, CTLTYPE_U64 | CTLFLAG_RD, \ sc, offsetof(struct msk_hw_stats, n), msk_sysctl_stat64, \ "QU", d) static void msk_sysctl_node(struct msk_if_softc *sc_if) { struct sysctl_ctx_list *ctx; struct sysctl_oid_list *child, *schild; struct sysctl_oid *tree; ctx = device_get_sysctl_ctx(sc_if->msk_if_dev); child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc_if->msk_if_dev)); tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", CTLFLAG_RD, NULL, "MSK Statistics"); schild = child = SYSCTL_CHILDREN(tree); tree = SYSCTL_ADD_NODE(ctx, schild, OID_AUTO, "rx", CTLFLAG_RD, NULL, "MSK RX Statistics"); child = SYSCTL_CHILDREN(tree); MSK_SYSCTL_STAT32(sc_if, ctx, "ucast_frames", child, rx_ucast_frames, "Good unicast frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "bcast_frames", child, rx_bcast_frames, "Good broadcast frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "pause_frames", child, rx_pause_frames, "Pause frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "mcast_frames", child, rx_mcast_frames, "Multicast frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "crc_errs", child, rx_crc_errs, "CRC errors"); MSK_SYSCTL_STAT64(sc_if, ctx, "good_octets", child, rx_good_octets, "Good octets"); MSK_SYSCTL_STAT64(sc_if, ctx, "bad_octets", child, rx_bad_octets, "Bad octets"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_64", child, rx_pkts_64, "64 bytes frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_65_127", child, rx_pkts_65_127, "65 to 127 bytes frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_128_255", child, rx_pkts_128_255, "128 to 255 bytes frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_256_511", child, rx_pkts_256_511, "256 to 511 bytes frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_512_1023", child, rx_pkts_512_1023, "512 to 1023 bytes frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_1024_1518", child, rx_pkts_1024_1518, "1024 to 1518 bytes frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_1519_max", child, rx_pkts_1519_max, "1519 to max frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_too_long", child, rx_pkts_too_long, "frames too long"); MSK_SYSCTL_STAT32(sc_if, ctx, "jabbers", child, rx_pkts_jabbers, "Jabber errors"); MSK_SYSCTL_STAT32(sc_if, ctx, "overflows", child, rx_fifo_oflows, "FIFO overflows"); tree = SYSCTL_ADD_NODE(ctx, schild, OID_AUTO, "tx", CTLFLAG_RD, NULL, "MSK TX Statistics"); child = SYSCTL_CHILDREN(tree); MSK_SYSCTL_STAT32(sc_if, ctx, "ucast_frames", child, tx_ucast_frames, "Unicast frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "bcast_frames", child, tx_bcast_frames, "Broadcast frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "pause_frames", child, tx_pause_frames, "Pause frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "mcast_frames", child, tx_mcast_frames, "Multicast frames"); MSK_SYSCTL_STAT64(sc_if, ctx, "octets", child, tx_octets, "Octets"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_64", child, tx_pkts_64, "64 bytes frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_65_127", child, tx_pkts_65_127, "65 to 127 bytes frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_128_255", child, tx_pkts_128_255, "128 to 255 bytes frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_256_511", child, tx_pkts_256_511, "256 to 511 bytes frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_512_1023", child, tx_pkts_512_1023, "512 to 1023 bytes frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_1024_1518", child, tx_pkts_1024_1518, "1024 to 1518 bytes frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "frames_1519_max", child, tx_pkts_1519_max, "1519 to max frames"); MSK_SYSCTL_STAT32(sc_if, ctx, "colls", child, tx_colls, "Collisions"); MSK_SYSCTL_STAT32(sc_if, ctx, "late_colls", child, tx_late_colls, "Late collisions"); MSK_SYSCTL_STAT32(sc_if, ctx, "excess_colls", child, tx_excess_colls, "Excessive collisions"); MSK_SYSCTL_STAT32(sc_if, ctx, "multi_colls", child, tx_multi_colls, "Multiple collisions"); MSK_SYSCTL_STAT32(sc_if, ctx, "single_colls", child, tx_single_colls, "Single collisions"); MSK_SYSCTL_STAT32(sc_if, ctx, "underflows", child, tx_underflows, "FIFO underflows"); } #undef MSK_SYSCTL_STAT32 #undef MSK_SYSCTL_STAT64 static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int low, int high) { int error, value; if (!arg1) return (EINVAL); value = *(int *)arg1; error = sysctl_handle_int(oidp, &value, 0, req); if (error || !req->newptr) return (error); if (value < low || value > high) return (EINVAL); *(int *)arg1 = value; return (0); } static int sysctl_hw_msk_proc_limit(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, MSK_PROC_MIN, MSK_PROC_MAX)); } Index: head/sys/dev/stge/if_stge.c =================================================================== --- head/sys/dev/stge/if_stge.c (revision 295734) +++ head/sys/dev/stge/if_stge.c (revision 295735) @@ -1,2605 +1,2605 @@ /* $NetBSD: if_stge.c,v 1.32 2005/12/11 12:22:49 christos Exp $ */ /*- * Copyright (c) 2001 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Jason R. Thorpe. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Device driver for the Sundance Tech. TC9021 10/100/1000 * Ethernet controller. */ #include __FBSDID("$FreeBSD$"); #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_device_polling.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define STGE_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP) MODULE_DEPEND(stge, pci, 1, 1, 1); MODULE_DEPEND(stge, ether, 1, 1, 1); MODULE_DEPEND(stge, miibus, 1, 1, 1); /* "device miibus" required. See GENERIC if you get errors here. */ #include "miibus_if.h" /* * Devices supported by this driver. */ static const struct stge_product { uint16_t stge_vendorid; uint16_t stge_deviceid; const char *stge_name; } stge_products[] = { { VENDOR_SUNDANCETI, DEVICEID_SUNDANCETI_ST1023, "Sundance ST-1023 Gigabit Ethernet" }, { VENDOR_SUNDANCETI, DEVICEID_SUNDANCETI_ST2021, "Sundance ST-2021 Gigabit Ethernet" }, { VENDOR_TAMARACK, DEVICEID_TAMARACK_TC9021, "Tamarack TC9021 Gigabit Ethernet" }, { VENDOR_TAMARACK, DEVICEID_TAMARACK_TC9021_ALT, "Tamarack TC9021 Gigabit Ethernet" }, /* * The Sundance sample boards use the Sundance vendor ID, * but the Tamarack product ID. */ { VENDOR_SUNDANCETI, DEVICEID_TAMARACK_TC9021, "Sundance TC9021 Gigabit Ethernet" }, { VENDOR_SUNDANCETI, DEVICEID_TAMARACK_TC9021_ALT, "Sundance TC9021 Gigabit Ethernet" }, { VENDOR_DLINK, DEVICEID_DLINK_DL4000, "D-Link DL-4000 Gigabit Ethernet" }, { VENDOR_ANTARES, DEVICEID_ANTARES_TC9021, "Antares Gigabit Ethernet" } }; static int stge_probe(device_t); static int stge_attach(device_t); static int stge_detach(device_t); static int stge_shutdown(device_t); static int stge_suspend(device_t); static int stge_resume(device_t); static int stge_encap(struct stge_softc *, struct mbuf **); static void stge_start(struct ifnet *); static void stge_start_locked(struct ifnet *); static void stge_watchdog(struct stge_softc *); static int stge_ioctl(struct ifnet *, u_long, caddr_t); static void stge_init(void *); static void stge_init_locked(struct stge_softc *); static void stge_vlan_setup(struct stge_softc *); static void stge_stop(struct stge_softc *); static void stge_start_tx(struct stge_softc *); static void stge_start_rx(struct stge_softc *); static void stge_stop_tx(struct stge_softc *); static void stge_stop_rx(struct stge_softc *); static void stge_reset(struct stge_softc *, uint32_t); static int stge_eeprom_wait(struct stge_softc *); static void stge_read_eeprom(struct stge_softc *, int, uint16_t *); static void stge_tick(void *); static void stge_stats_update(struct stge_softc *); static void stge_set_filter(struct stge_softc *); static void stge_set_multi(struct stge_softc *); static void stge_link_task(void *, int); static void stge_intr(void *); static __inline int stge_tx_error(struct stge_softc *); static void stge_txeof(struct stge_softc *); static int stge_rxeof(struct stge_softc *); static __inline void stge_discard_rxbuf(struct stge_softc *, int); static int stge_newbuf(struct stge_softc *, int); #ifndef __NO_STRICT_ALIGNMENT static __inline struct mbuf *stge_fixup_rx(struct stge_softc *, struct mbuf *); #endif static int stge_miibus_readreg(device_t, int, int); static int stge_miibus_writereg(device_t, int, int, int); static void stge_miibus_statchg(device_t); static int stge_mediachange(struct ifnet *); static void stge_mediastatus(struct ifnet *, struct ifmediareq *); static void stge_dmamap_cb(void *, bus_dma_segment_t *, int, int); static int stge_dma_alloc(struct stge_softc *); static void stge_dma_free(struct stge_softc *); static void stge_dma_wait(struct stge_softc *); static void stge_init_tx_ring(struct stge_softc *); static int stge_init_rx_ring(struct stge_softc *); #ifdef DEVICE_POLLING static int stge_poll(struct ifnet *, enum poll_cmd, int); #endif static void stge_setwol(struct stge_softc *); static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int, int); static int sysctl_hw_stge_rxint_nframe(SYSCTL_HANDLER_ARGS); static int sysctl_hw_stge_rxint_dmawait(SYSCTL_HANDLER_ARGS); /* * MII bit-bang glue */ static uint32_t stge_mii_bitbang_read(device_t); static void stge_mii_bitbang_write(device_t, uint32_t); static const struct mii_bitbang_ops stge_mii_bitbang_ops = { stge_mii_bitbang_read, stge_mii_bitbang_write, { PC_MgmtData, /* MII_BIT_MDO */ PC_MgmtData, /* MII_BIT_MDI */ PC_MgmtClk, /* MII_BIT_MDC */ PC_MgmtDir, /* MII_BIT_DIR_HOST_PHY */ 0, /* MII_BIT_DIR_PHY_HOST */ } }; static device_method_t stge_methods[] = { /* Device interface */ DEVMETHOD(device_probe, stge_probe), DEVMETHOD(device_attach, stge_attach), DEVMETHOD(device_detach, stge_detach), DEVMETHOD(device_shutdown, stge_shutdown), DEVMETHOD(device_suspend, stge_suspend), DEVMETHOD(device_resume, stge_resume), /* MII interface */ DEVMETHOD(miibus_readreg, stge_miibus_readreg), DEVMETHOD(miibus_writereg, stge_miibus_writereg), DEVMETHOD(miibus_statchg, stge_miibus_statchg), DEVMETHOD_END }; static driver_t stge_driver = { "stge", stge_methods, sizeof(struct stge_softc) }; static devclass_t stge_devclass; DRIVER_MODULE(stge, pci, stge_driver, stge_devclass, 0, 0); DRIVER_MODULE(miibus, stge, miibus_driver, miibus_devclass, 0, 0); static struct resource_spec stge_res_spec_io[] = { { SYS_RES_IOPORT, PCIR_BAR(0), RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0, 0 } }; static struct resource_spec stge_res_spec_mem[] = { { SYS_RES_MEMORY, PCIR_BAR(1), RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0, 0 } }; /* * stge_mii_bitbang_read: [mii bit-bang interface function] * * Read the MII serial port for the MII bit-bang module. */ static uint32_t stge_mii_bitbang_read(device_t dev) { struct stge_softc *sc; uint32_t val; sc = device_get_softc(dev); val = CSR_READ_1(sc, STGE_PhyCtrl); CSR_BARRIER(sc, STGE_PhyCtrl, 1, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); return (val); } /* * stge_mii_bitbang_write: [mii big-bang interface function] * * Write the MII serial port for the MII bit-bang module. */ static void stge_mii_bitbang_write(device_t dev, uint32_t val) { struct stge_softc *sc; sc = device_get_softc(dev); CSR_WRITE_1(sc, STGE_PhyCtrl, val); CSR_BARRIER(sc, STGE_PhyCtrl, 1, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); } /* * sc_miibus_readreg: [mii interface function] * * Read a PHY register on the MII of the TC9021. */ static int stge_miibus_readreg(device_t dev, int phy, int reg) { struct stge_softc *sc; int error, val; sc = device_get_softc(dev); if (reg == STGE_PhyCtrl) { /* XXX allow ip1000phy read STGE_PhyCtrl register. */ STGE_MII_LOCK(sc); error = CSR_READ_1(sc, STGE_PhyCtrl); STGE_MII_UNLOCK(sc); return (error); } STGE_MII_LOCK(sc); val = mii_bitbang_readreg(dev, &stge_mii_bitbang_ops, phy, reg); STGE_MII_UNLOCK(sc); return (val); } /* * stge_miibus_writereg: [mii interface function] * * Write a PHY register on the MII of the TC9021. */ static int stge_miibus_writereg(device_t dev, int phy, int reg, int val) { struct stge_softc *sc; sc = device_get_softc(dev); STGE_MII_LOCK(sc); mii_bitbang_writereg(dev, &stge_mii_bitbang_ops, phy, reg, val); STGE_MII_UNLOCK(sc); return (0); } /* * stge_miibus_statchg: [mii interface function] * * Callback from MII layer when media changes. */ static void stge_miibus_statchg(device_t dev) { struct stge_softc *sc; sc = device_get_softc(dev); taskqueue_enqueue(taskqueue_swi, &sc->sc_link_task); } /* * stge_mediastatus: [ifmedia interface function] * * Get the current interface media status. */ static void stge_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr) { struct stge_softc *sc; struct mii_data *mii; sc = ifp->if_softc; mii = device_get_softc(sc->sc_miibus); mii_pollstat(mii); ifmr->ifm_status = mii->mii_media_status; ifmr->ifm_active = mii->mii_media_active; } /* * stge_mediachange: [ifmedia interface function] * * Set hardware to newly-selected media. */ static int stge_mediachange(struct ifnet *ifp) { struct stge_softc *sc; struct mii_data *mii; sc = ifp->if_softc; mii = device_get_softc(sc->sc_miibus); mii_mediachg(mii); return (0); } static int stge_eeprom_wait(struct stge_softc *sc) { int i; for (i = 0; i < STGE_TIMEOUT; i++) { DELAY(1000); if ((CSR_READ_2(sc, STGE_EepromCtrl) & EC_EepromBusy) == 0) return (0); } return (1); } /* * stge_read_eeprom: * * Read data from the serial EEPROM. */ static void stge_read_eeprom(struct stge_softc *sc, int offset, uint16_t *data) { if (stge_eeprom_wait(sc)) device_printf(sc->sc_dev, "EEPROM failed to come ready\n"); CSR_WRITE_2(sc, STGE_EepromCtrl, EC_EepromAddress(offset) | EC_EepromOpcode(EC_OP_RR)); if (stge_eeprom_wait(sc)) device_printf(sc->sc_dev, "EEPROM read timed out\n"); *data = CSR_READ_2(sc, STGE_EepromData); } static int stge_probe(device_t dev) { const struct stge_product *sp; int i; uint16_t vendor, devid; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); sp = stge_products; for (i = 0; i < sizeof(stge_products)/sizeof(stge_products[0]); i++, sp++) { if (vendor == sp->stge_vendorid && devid == sp->stge_deviceid) { device_set_desc(dev, sp->stge_name); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int stge_attach(device_t dev) { struct stge_softc *sc; struct ifnet *ifp; uint8_t enaddr[ETHER_ADDR_LEN]; int error, flags, i; uint16_t cmd; uint32_t val; error = 0; sc = device_get_softc(dev); sc->sc_dev = dev; mtx_init(&sc->sc_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); mtx_init(&sc->sc_mii_mtx, "stge_mii_mutex", NULL, MTX_DEF); callout_init_mtx(&sc->sc_tick_ch, &sc->sc_mtx, 0); TASK_INIT(&sc->sc_link_task, 0, stge_link_task, sc); /* * Map the device. */ pci_enable_busmaster(dev); cmd = pci_read_config(dev, PCIR_COMMAND, 2); val = pci_read_config(dev, PCIR_BAR(1), 4); if (PCI_BAR_IO(val)) sc->sc_spec = stge_res_spec_mem; else { val = pci_read_config(dev, PCIR_BAR(0), 4); if (!PCI_BAR_IO(val)) { device_printf(sc->sc_dev, "couldn't locate IO BAR\n"); error = ENXIO; goto fail; } sc->sc_spec = stge_res_spec_io; } error = bus_alloc_resources(dev, sc->sc_spec, sc->sc_res); if (error != 0) { device_printf(dev, "couldn't allocate %s resources\n", sc->sc_spec == stge_res_spec_mem ? "memory" : "I/O"); goto fail; } sc->sc_rev = pci_get_revid(dev); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rxint_nframe", CTLTYPE_INT|CTLFLAG_RW, &sc->sc_rxint_nframe, 0, sysctl_hw_stge_rxint_nframe, "I", "stge rx interrupt nframe"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rxint_dmawait", CTLTYPE_INT|CTLFLAG_RW, &sc->sc_rxint_dmawait, 0, sysctl_hw_stge_rxint_dmawait, "I", "stge rx interrupt dmawait"); /* Pull in device tunables. */ sc->sc_rxint_nframe = STGE_RXINT_NFRAME_DEFAULT; error = resource_int_value(device_get_name(dev), device_get_unit(dev), "rxint_nframe", &sc->sc_rxint_nframe); if (error == 0) { if (sc->sc_rxint_nframe < STGE_RXINT_NFRAME_MIN || sc->sc_rxint_nframe > STGE_RXINT_NFRAME_MAX) { device_printf(dev, "rxint_nframe value out of range; " "using default: %d\n", STGE_RXINT_NFRAME_DEFAULT); sc->sc_rxint_nframe = STGE_RXINT_NFRAME_DEFAULT; } } sc->sc_rxint_dmawait = STGE_RXINT_DMAWAIT_DEFAULT; error = resource_int_value(device_get_name(dev), device_get_unit(dev), "rxint_dmawait", &sc->sc_rxint_dmawait); if (error == 0) { if (sc->sc_rxint_dmawait < STGE_RXINT_DMAWAIT_MIN || sc->sc_rxint_dmawait > STGE_RXINT_DMAWAIT_MAX) { device_printf(dev, "rxint_dmawait value out of range; " "using default: %d\n", STGE_RXINT_DMAWAIT_DEFAULT); sc->sc_rxint_dmawait = STGE_RXINT_DMAWAIT_DEFAULT; } } - if ((error = stge_dma_alloc(sc) != 0)) + if ((error = stge_dma_alloc(sc)) != 0) goto fail; /* * Determine if we're copper or fiber. It affects how we * reset the card. */ if (CSR_READ_4(sc, STGE_AsicCtrl) & AC_PhyMedia) sc->sc_usefiber = 1; else sc->sc_usefiber = 0; /* Load LED configuration from EEPROM. */ stge_read_eeprom(sc, STGE_EEPROM_LEDMode, &sc->sc_led); /* * Reset the chip to a known state. */ STGE_LOCK(sc); stge_reset(sc, STGE_RESET_FULL); STGE_UNLOCK(sc); /* * Reading the station address from the EEPROM doesn't seem * to work, at least on my sample boards. Instead, since * the reset sequence does AutoInit, read it from the station * address registers. For Sundance 1023 you can only read it * from EEPROM. */ if (pci_get_device(dev) != DEVICEID_SUNDANCETI_ST1023) { uint16_t v; v = CSR_READ_2(sc, STGE_StationAddress0); enaddr[0] = v & 0xff; enaddr[1] = v >> 8; v = CSR_READ_2(sc, STGE_StationAddress1); enaddr[2] = v & 0xff; enaddr[3] = v >> 8; v = CSR_READ_2(sc, STGE_StationAddress2); enaddr[4] = v & 0xff; enaddr[5] = v >> 8; sc->sc_stge1023 = 0; } else { uint16_t myaddr[ETHER_ADDR_LEN / 2]; for (i = 0; i sc_stge1023 = 1; } ifp = sc->sc_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(sc->sc_dev, "failed to if_alloc()\n"); error = ENXIO; goto fail; } ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = stge_ioctl; ifp->if_start = stge_start; ifp->if_init = stge_init; ifp->if_snd.ifq_drv_maxlen = STGE_TX_RING_CNT - 1; IFQ_SET_MAXLEN(&ifp->if_snd, ifp->if_snd.ifq_drv_maxlen); IFQ_SET_READY(&ifp->if_snd); /* Revision B3 and earlier chips have checksum bug. */ if (sc->sc_rev >= 0x0c) { ifp->if_hwassist = STGE_CSUM_FEATURES; ifp->if_capabilities = IFCAP_HWCSUM; } else { ifp->if_hwassist = 0; ifp->if_capabilities = 0; } ifp->if_capabilities |= IFCAP_WOL_MAGIC; ifp->if_capenable = ifp->if_capabilities; /* * Read some important bits from the PhyCtrl register. */ sc->sc_PhyCtrl = CSR_READ_1(sc, STGE_PhyCtrl) & (PC_PhyDuplexPolarity | PC_PhyLnkPolarity); /* Set up MII bus. */ flags = MIIF_DOPAUSE; if (sc->sc_rev >= 0x40 && sc->sc_rev <= 0x4e) flags |= MIIF_MACPRIV0; error = mii_attach(sc->sc_dev, &sc->sc_miibus, ifp, stge_mediachange, stge_mediastatus, BMSR_DEFCAPMASK, MII_PHY_ANY, MII_OFFSET_ANY, flags); if (error != 0) { device_printf(sc->sc_dev, "attaching PHYs failed\n"); goto fail; } ether_ifattach(ifp, enaddr); /* VLAN capability setup */ ifp->if_capabilities |= IFCAP_VLAN_MTU | IFCAP_VLAN_HWTAGGING; if (sc->sc_rev >= 0x0c) ifp->if_capabilities |= IFCAP_VLAN_HWCSUM; ifp->if_capenable = ifp->if_capabilities; #ifdef DEVICE_POLLING ifp->if_capabilities |= IFCAP_POLLING; #endif /* * Tell the upper layer(s) we support long frames. * Must appear after the call to ether_ifattach() because * ether_ifattach() sets ifi_hdrlen to the default value. */ ifp->if_hdrlen = sizeof(struct ether_vlan_header); /* * The manual recommends disabling early transmit, so we * do. It's disabled anyway, if using IP checksumming, * since the entire packet must be in the FIFO in order * for the chip to perform the checksum. */ sc->sc_txthresh = 0x0fff; /* * Disable MWI if the PCI layer tells us to. */ sc->sc_DMACtrl = 0; if ((cmd & PCIM_CMD_MWRICEN) == 0) sc->sc_DMACtrl |= DMAC_MWIDisable; /* * Hookup IRQ */ error = bus_setup_intr(dev, sc->sc_res[1], INTR_TYPE_NET | INTR_MPSAFE, NULL, stge_intr, sc, &sc->sc_ih); if (error != 0) { ether_ifdetach(ifp); device_printf(sc->sc_dev, "couldn't set up IRQ\n"); sc->sc_ifp = NULL; goto fail; } fail: if (error != 0) stge_detach(dev); return (error); } static int stge_detach(device_t dev) { struct stge_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); ifp = sc->sc_ifp; #ifdef DEVICE_POLLING if (ifp && ifp->if_capenable & IFCAP_POLLING) ether_poll_deregister(ifp); #endif if (device_is_attached(dev)) { STGE_LOCK(sc); /* XXX */ sc->sc_detach = 1; stge_stop(sc); STGE_UNLOCK(sc); callout_drain(&sc->sc_tick_ch); taskqueue_drain(taskqueue_swi, &sc->sc_link_task); ether_ifdetach(ifp); } if (sc->sc_miibus != NULL) { device_delete_child(dev, sc->sc_miibus); sc->sc_miibus = NULL; } bus_generic_detach(dev); stge_dma_free(sc); if (ifp != NULL) { if_free(ifp); sc->sc_ifp = NULL; } if (sc->sc_ih) { bus_teardown_intr(dev, sc->sc_res[1], sc->sc_ih); sc->sc_ih = NULL; } bus_release_resources(dev, sc->sc_spec, sc->sc_res); mtx_destroy(&sc->sc_mii_mtx); mtx_destroy(&sc->sc_mtx); return (0); } struct stge_dmamap_arg { bus_addr_t stge_busaddr; }; static void stge_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct stge_dmamap_arg *ctx; if (error != 0) return; ctx = (struct stge_dmamap_arg *)arg; ctx->stge_busaddr = segs[0].ds_addr; } static int stge_dma_alloc(struct stge_softc *sc) { struct stge_dmamap_arg ctx; struct stge_txdesc *txd; struct stge_rxdesc *rxd; int error, i; /* create parent tag. */ error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev),/* parent */ 1, 0, /* algnmnt, boundary */ STGE_DMA_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ 0, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->sc_cdata.stge_parent_tag); if (error != 0) { device_printf(sc->sc_dev, "failed to create parent DMA tag\n"); goto fail; } /* create tag for Tx ring. */ error = bus_dma_tag_create(sc->sc_cdata.stge_parent_tag,/* parent */ STGE_RING_ALIGN, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ STGE_TX_RING_SZ, /* maxsize */ 1, /* nsegments */ STGE_TX_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->sc_cdata.stge_tx_ring_tag); if (error != 0) { device_printf(sc->sc_dev, "failed to allocate Tx ring DMA tag\n"); goto fail; } /* create tag for Rx ring. */ error = bus_dma_tag_create(sc->sc_cdata.stge_parent_tag,/* parent */ STGE_RING_ALIGN, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ STGE_RX_RING_SZ, /* maxsize */ 1, /* nsegments */ STGE_RX_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->sc_cdata.stge_rx_ring_tag); if (error != 0) { device_printf(sc->sc_dev, "failed to allocate Rx ring DMA tag\n"); goto fail; } /* create tag for Tx buffers. */ error = bus_dma_tag_create(sc->sc_cdata.stge_parent_tag,/* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES * STGE_MAXTXSEGS, /* maxsize */ STGE_MAXTXSEGS, /* nsegments */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->sc_cdata.stge_tx_tag); if (error != 0) { device_printf(sc->sc_dev, "failed to allocate Tx DMA tag\n"); goto fail; } /* create tag for Rx buffers. */ error = bus_dma_tag_create(sc->sc_cdata.stge_parent_tag,/* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, /* maxsize */ 1, /* nsegments */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->sc_cdata.stge_rx_tag); if (error != 0) { device_printf(sc->sc_dev, "failed to allocate Rx DMA tag\n"); goto fail; } /* allocate DMA'able memory and load the DMA map for Tx ring. */ error = bus_dmamem_alloc(sc->sc_cdata.stge_tx_ring_tag, (void **)&sc->sc_rdata.stge_tx_ring, BUS_DMA_NOWAIT | BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->sc_cdata.stge_tx_ring_map); if (error != 0) { device_printf(sc->sc_dev, "failed to allocate DMA'able memory for Tx ring\n"); goto fail; } ctx.stge_busaddr = 0; error = bus_dmamap_load(sc->sc_cdata.stge_tx_ring_tag, sc->sc_cdata.stge_tx_ring_map, sc->sc_rdata.stge_tx_ring, STGE_TX_RING_SZ, stge_dmamap_cb, &ctx, BUS_DMA_NOWAIT); if (error != 0 || ctx.stge_busaddr == 0) { device_printf(sc->sc_dev, "failed to load DMA'able memory for Tx ring\n"); goto fail; } sc->sc_rdata.stge_tx_ring_paddr = ctx.stge_busaddr; /* allocate DMA'able memory and load the DMA map for Rx ring. */ error = bus_dmamem_alloc(sc->sc_cdata.stge_rx_ring_tag, (void **)&sc->sc_rdata.stge_rx_ring, BUS_DMA_NOWAIT | BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->sc_cdata.stge_rx_ring_map); if (error != 0) { device_printf(sc->sc_dev, "failed to allocate DMA'able memory for Rx ring\n"); goto fail; } ctx.stge_busaddr = 0; error = bus_dmamap_load(sc->sc_cdata.stge_rx_ring_tag, sc->sc_cdata.stge_rx_ring_map, sc->sc_rdata.stge_rx_ring, STGE_RX_RING_SZ, stge_dmamap_cb, &ctx, BUS_DMA_NOWAIT); if (error != 0 || ctx.stge_busaddr == 0) { device_printf(sc->sc_dev, "failed to load DMA'able memory for Rx ring\n"); goto fail; } sc->sc_rdata.stge_rx_ring_paddr = ctx.stge_busaddr; /* create DMA maps for Tx buffers. */ for (i = 0; i < STGE_TX_RING_CNT; i++) { txd = &sc->sc_cdata.stge_txdesc[i]; txd->tx_m = NULL; txd->tx_dmamap = 0; error = bus_dmamap_create(sc->sc_cdata.stge_tx_tag, 0, &txd->tx_dmamap); if (error != 0) { device_printf(sc->sc_dev, "failed to create Tx dmamap\n"); goto fail; } } /* create DMA maps for Rx buffers. */ if ((error = bus_dmamap_create(sc->sc_cdata.stge_rx_tag, 0, &sc->sc_cdata.stge_rx_sparemap)) != 0) { device_printf(sc->sc_dev, "failed to create spare Rx dmamap\n"); goto fail; } for (i = 0; i < STGE_RX_RING_CNT; i++) { rxd = &sc->sc_cdata.stge_rxdesc[i]; rxd->rx_m = NULL; rxd->rx_dmamap = 0; error = bus_dmamap_create(sc->sc_cdata.stge_rx_tag, 0, &rxd->rx_dmamap); if (error != 0) { device_printf(sc->sc_dev, "failed to create Rx dmamap\n"); goto fail; } } fail: return (error); } static void stge_dma_free(struct stge_softc *sc) { struct stge_txdesc *txd; struct stge_rxdesc *rxd; int i; /* Tx ring */ if (sc->sc_cdata.stge_tx_ring_tag) { if (sc->sc_rdata.stge_tx_ring_paddr) bus_dmamap_unload(sc->sc_cdata.stge_tx_ring_tag, sc->sc_cdata.stge_tx_ring_map); if (sc->sc_rdata.stge_tx_ring) bus_dmamem_free(sc->sc_cdata.stge_tx_ring_tag, sc->sc_rdata.stge_tx_ring, sc->sc_cdata.stge_tx_ring_map); sc->sc_rdata.stge_tx_ring = NULL; sc->sc_rdata.stge_tx_ring_paddr = 0; bus_dma_tag_destroy(sc->sc_cdata.stge_tx_ring_tag); sc->sc_cdata.stge_tx_ring_tag = NULL; } /* Rx ring */ if (sc->sc_cdata.stge_rx_ring_tag) { if (sc->sc_rdata.stge_rx_ring_paddr) bus_dmamap_unload(sc->sc_cdata.stge_rx_ring_tag, sc->sc_cdata.stge_rx_ring_map); if (sc->sc_rdata.stge_rx_ring) bus_dmamem_free(sc->sc_cdata.stge_rx_ring_tag, sc->sc_rdata.stge_rx_ring, sc->sc_cdata.stge_rx_ring_map); sc->sc_rdata.stge_rx_ring = NULL; sc->sc_rdata.stge_rx_ring_paddr = 0; bus_dma_tag_destroy(sc->sc_cdata.stge_rx_ring_tag); sc->sc_cdata.stge_rx_ring_tag = NULL; } /* Tx buffers */ if (sc->sc_cdata.stge_tx_tag) { for (i = 0; i < STGE_TX_RING_CNT; i++) { txd = &sc->sc_cdata.stge_txdesc[i]; if (txd->tx_dmamap) { bus_dmamap_destroy(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap); txd->tx_dmamap = 0; } } bus_dma_tag_destroy(sc->sc_cdata.stge_tx_tag); sc->sc_cdata.stge_tx_tag = NULL; } /* Rx buffers */ if (sc->sc_cdata.stge_rx_tag) { for (i = 0; i < STGE_RX_RING_CNT; i++) { rxd = &sc->sc_cdata.stge_rxdesc[i]; if (rxd->rx_dmamap) { bus_dmamap_destroy(sc->sc_cdata.stge_rx_tag, rxd->rx_dmamap); rxd->rx_dmamap = 0; } } if (sc->sc_cdata.stge_rx_sparemap) { bus_dmamap_destroy(sc->sc_cdata.stge_rx_tag, sc->sc_cdata.stge_rx_sparemap); sc->sc_cdata.stge_rx_sparemap = 0; } bus_dma_tag_destroy(sc->sc_cdata.stge_rx_tag); sc->sc_cdata.stge_rx_tag = NULL; } if (sc->sc_cdata.stge_parent_tag) { bus_dma_tag_destroy(sc->sc_cdata.stge_parent_tag); sc->sc_cdata.stge_parent_tag = NULL; } } /* * stge_shutdown: * * Make sure the interface is stopped at reboot time. */ static int stge_shutdown(device_t dev) { return (stge_suspend(dev)); } static void stge_setwol(struct stge_softc *sc) { struct ifnet *ifp; uint8_t v; STGE_LOCK_ASSERT(sc); ifp = sc->sc_ifp; v = CSR_READ_1(sc, STGE_WakeEvent); /* Disable all WOL bits. */ v &= ~(WE_WakePktEnable | WE_MagicPktEnable | WE_LinkEventEnable | WE_WakeOnLanEnable); if ((ifp->if_capenable & IFCAP_WOL_MAGIC) != 0) v |= WE_MagicPktEnable | WE_WakeOnLanEnable; CSR_WRITE_1(sc, STGE_WakeEvent, v); /* Reset Tx and prevent transmission. */ CSR_WRITE_4(sc, STGE_AsicCtrl, CSR_READ_4(sc, STGE_AsicCtrl) | AC_TxReset); /* * TC9021 automatically reset link speed to 100Mbps when it's put * into sleep so there is no need to try to resetting link speed. */ } static int stge_suspend(device_t dev) { struct stge_softc *sc; sc = device_get_softc(dev); STGE_LOCK(sc); stge_stop(sc); sc->sc_suspended = 1; stge_setwol(sc); STGE_UNLOCK(sc); return (0); } static int stge_resume(device_t dev) { struct stge_softc *sc; struct ifnet *ifp; uint8_t v; sc = device_get_softc(dev); STGE_LOCK(sc); /* * Clear WOL bits, so special frames wouldn't interfere * normal Rx operation anymore. */ v = CSR_READ_1(sc, STGE_WakeEvent); v &= ~(WE_WakePktEnable | WE_MagicPktEnable | WE_LinkEventEnable | WE_WakeOnLanEnable); CSR_WRITE_1(sc, STGE_WakeEvent, v); ifp = sc->sc_ifp; if (ifp->if_flags & IFF_UP) stge_init_locked(sc); sc->sc_suspended = 0; STGE_UNLOCK(sc); return (0); } static void stge_dma_wait(struct stge_softc *sc) { int i; for (i = 0; i < STGE_TIMEOUT; i++) { DELAY(2); if ((CSR_READ_4(sc, STGE_DMACtrl) & DMAC_TxDMAInProg) == 0) break; } if (i == STGE_TIMEOUT) device_printf(sc->sc_dev, "DMA wait timed out\n"); } static int stge_encap(struct stge_softc *sc, struct mbuf **m_head) { struct stge_txdesc *txd; struct stge_tfd *tfd; struct mbuf *m; bus_dma_segment_t txsegs[STGE_MAXTXSEGS]; int error, i, nsegs, si; uint64_t csum_flags, tfc; STGE_LOCK_ASSERT(sc); if ((txd = STAILQ_FIRST(&sc->sc_cdata.stge_txfreeq)) == NULL) return (ENOBUFS); error = bus_dmamap_load_mbuf_sg(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap, *m_head, txsegs, &nsegs, 0); if (error == EFBIG) { m = m_collapse(*m_head, M_NOWAIT, STGE_MAXTXSEGS); if (m == NULL) { m_freem(*m_head); *m_head = NULL; return (ENOMEM); } *m_head = m; error = bus_dmamap_load_mbuf_sg(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap, *m_head, txsegs, &nsegs, 0); if (error != 0) { m_freem(*m_head); *m_head = NULL; return (error); } } else if (error != 0) return (error); if (nsegs == 0) { m_freem(*m_head); *m_head = NULL; return (EIO); } m = *m_head; csum_flags = 0; if ((m->m_pkthdr.csum_flags & STGE_CSUM_FEATURES) != 0) { if (m->m_pkthdr.csum_flags & CSUM_IP) csum_flags |= TFD_IPChecksumEnable; if (m->m_pkthdr.csum_flags & CSUM_TCP) csum_flags |= TFD_TCPChecksumEnable; else if (m->m_pkthdr.csum_flags & CSUM_UDP) csum_flags |= TFD_UDPChecksumEnable; } si = sc->sc_cdata.stge_tx_prod; tfd = &sc->sc_rdata.stge_tx_ring[si]; for (i = 0; i < nsegs; i++) tfd->tfd_frags[i].frag_word0 = htole64(FRAG_ADDR(txsegs[i].ds_addr) | FRAG_LEN(txsegs[i].ds_len)); sc->sc_cdata.stge_tx_cnt++; tfc = TFD_FrameId(si) | TFD_WordAlign(TFD_WordAlign_disable) | TFD_FragCount(nsegs) | csum_flags; if (sc->sc_cdata.stge_tx_cnt >= STGE_TX_HIWAT) tfc |= TFD_TxDMAIndicate; /* Update producer index. */ sc->sc_cdata.stge_tx_prod = (si + 1) % STGE_TX_RING_CNT; /* Check if we have a VLAN tag to insert. */ if (m->m_flags & M_VLANTAG) tfc |= (TFD_VLANTagInsert | TFD_VID(m->m_pkthdr.ether_vtag)); tfd->tfd_control = htole64(tfc); /* Update Tx Queue. */ STAILQ_REMOVE_HEAD(&sc->sc_cdata.stge_txfreeq, tx_q); STAILQ_INSERT_TAIL(&sc->sc_cdata.stge_txbusyq, txd, tx_q); txd->tx_m = m; /* Sync descriptors. */ bus_dmamap_sync(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(sc->sc_cdata.stge_tx_ring_tag, sc->sc_cdata.stge_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (0); } /* * stge_start: [ifnet interface function] * * Start packet transmission on the interface. */ static void stge_start(struct ifnet *ifp) { struct stge_softc *sc; sc = ifp->if_softc; STGE_LOCK(sc); stge_start_locked(ifp); STGE_UNLOCK(sc); } static void stge_start_locked(struct ifnet *ifp) { struct stge_softc *sc; struct mbuf *m_head; int enq; sc = ifp->if_softc; STGE_LOCK_ASSERT(sc); if ((ifp->if_drv_flags & (IFF_DRV_RUNNING|IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING || sc->sc_link == 0) return; for (enq = 0; !IFQ_DRV_IS_EMPTY(&ifp->if_snd); ) { if (sc->sc_cdata.stge_tx_cnt >= STGE_TX_HIWAT) { ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; /* * Pack the data into the transmit ring. If we * don't have room, set the OACTIVE flag and wait * for the NIC to drain the ring. */ if (stge_encap(sc, &m_head)) { if (m_head == NULL) break; IFQ_DRV_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } enq++; /* * If there's a BPF listener, bounce a copy of this frame * to him. */ ETHER_BPF_MTAP(ifp, m_head); } if (enq > 0) { /* Transmit */ CSR_WRITE_4(sc, STGE_DMACtrl, DMAC_TxDMAPollNow); /* Set a timeout in case the chip goes out to lunch. */ sc->sc_watchdog_timer = 5; } } /* * stge_watchdog: * * Watchdog timer handler. */ static void stge_watchdog(struct stge_softc *sc) { struct ifnet *ifp; STGE_LOCK_ASSERT(sc); if (sc->sc_watchdog_timer == 0 || --sc->sc_watchdog_timer) return; ifp = sc->sc_ifp; if_printf(sc->sc_ifp, "device timeout\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; stge_init_locked(sc); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) stge_start_locked(ifp); } /* * stge_ioctl: [ifnet interface function] * * Handle control requests from the operator. */ static int stge_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct stge_softc *sc; struct ifreq *ifr; struct mii_data *mii; int error, mask; sc = ifp->if_softc; ifr = (struct ifreq *)data; error = 0; switch (cmd) { case SIOCSIFMTU: if (ifr->ifr_mtu < ETHERMIN || ifr->ifr_mtu > STGE_JUMBO_MTU) error = EINVAL; else if (ifp->if_mtu != ifr->ifr_mtu) { ifp->if_mtu = ifr->ifr_mtu; STGE_LOCK(sc); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; stge_init_locked(sc); } STGE_UNLOCK(sc); } break; case SIOCSIFFLAGS: STGE_LOCK(sc); if ((ifp->if_flags & IFF_UP) != 0) { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { if (((ifp->if_flags ^ sc->sc_if_flags) & IFF_PROMISC) != 0) stge_set_filter(sc); } else { if (sc->sc_detach == 0) stge_init_locked(sc); } } else { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) stge_stop(sc); } sc->sc_if_flags = ifp->if_flags; STGE_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: STGE_LOCK(sc); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) stge_set_multi(sc); STGE_UNLOCK(sc); break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: mii = device_get_softc(sc->sc_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); break; case SIOCSIFCAP: mask = ifr->ifr_reqcap ^ ifp->if_capenable; #ifdef DEVICE_POLLING if ((mask & IFCAP_POLLING) != 0) { if ((ifr->ifr_reqcap & IFCAP_POLLING) != 0) { error = ether_poll_register(stge_poll, ifp); if (error != 0) break; STGE_LOCK(sc); CSR_WRITE_2(sc, STGE_IntEnable, 0); ifp->if_capenable |= IFCAP_POLLING; STGE_UNLOCK(sc); } else { error = ether_poll_deregister(ifp); if (error != 0) break; STGE_LOCK(sc); CSR_WRITE_2(sc, STGE_IntEnable, sc->sc_IntEnable); ifp->if_capenable &= ~IFCAP_POLLING; STGE_UNLOCK(sc); } } #endif if ((mask & IFCAP_HWCSUM) != 0) { ifp->if_capenable ^= IFCAP_HWCSUM; if ((IFCAP_HWCSUM & ifp->if_capenable) != 0 && (IFCAP_HWCSUM & ifp->if_capabilities) != 0) ifp->if_hwassist = STGE_CSUM_FEATURES; else ifp->if_hwassist = 0; } if ((mask & IFCAP_WOL) != 0 && (ifp->if_capabilities & IFCAP_WOL) != 0) { if ((mask & IFCAP_WOL_MAGIC) != 0) ifp->if_capenable ^= IFCAP_WOL_MAGIC; } if ((mask & IFCAP_VLAN_HWTAGGING) != 0) { ifp->if_capenable ^= IFCAP_VLAN_HWTAGGING; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { STGE_LOCK(sc); stge_vlan_setup(sc); STGE_UNLOCK(sc); } } VLAN_CAPABILITIES(ifp); break; default: error = ether_ioctl(ifp, cmd, data); break; } return (error); } static void stge_link_task(void *arg, int pending) { struct stge_softc *sc; struct mii_data *mii; uint32_t v, ac; int i; sc = (struct stge_softc *)arg; STGE_LOCK(sc); mii = device_get_softc(sc->sc_miibus); if (mii->mii_media_status & IFM_ACTIVE) { if (IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) sc->sc_link = 1; } else sc->sc_link = 0; sc->sc_MACCtrl = 0; if (((mii->mii_media_active & IFM_GMASK) & IFM_FDX) != 0) sc->sc_MACCtrl |= MC_DuplexSelect; if (((mii->mii_media_active & IFM_GMASK) & IFM_ETH_RXPAUSE) != 0) sc->sc_MACCtrl |= MC_RxFlowControlEnable; if (((mii->mii_media_active & IFM_GMASK) & IFM_ETH_TXPAUSE) != 0) sc->sc_MACCtrl |= MC_TxFlowControlEnable; /* * Update STGE_MACCtrl register depending on link status. * (duplex, flow control etc) */ v = ac = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; v &= ~(MC_DuplexSelect|MC_RxFlowControlEnable|MC_TxFlowControlEnable); v |= sc->sc_MACCtrl; CSR_WRITE_4(sc, STGE_MACCtrl, v); if (((ac ^ sc->sc_MACCtrl) & MC_DuplexSelect) != 0) { /* Duplex setting changed, reset Tx/Rx functions. */ ac = CSR_READ_4(sc, STGE_AsicCtrl); ac |= AC_TxReset | AC_RxReset; CSR_WRITE_4(sc, STGE_AsicCtrl, ac); for (i = 0; i < STGE_TIMEOUT; i++) { DELAY(100); if ((CSR_READ_4(sc, STGE_AsicCtrl) & AC_ResetBusy) == 0) break; } if (i == STGE_TIMEOUT) device_printf(sc->sc_dev, "reset failed to complete\n"); } STGE_UNLOCK(sc); } static __inline int stge_tx_error(struct stge_softc *sc) { uint32_t txstat; int error; for (error = 0;;) { txstat = CSR_READ_4(sc, STGE_TxStatus); if ((txstat & TS_TxComplete) == 0) break; /* Tx underrun */ if ((txstat & TS_TxUnderrun) != 0) { /* * XXX * There should be a more better way to recover * from Tx underrun instead of a full reset. */ if (sc->sc_nerr++ < STGE_MAXERR) device_printf(sc->sc_dev, "Tx underrun, " "resetting...\n"); if (sc->sc_nerr == STGE_MAXERR) device_printf(sc->sc_dev, "too many errors; " "not reporting any more\n"); error = -1; break; } /* Maximum/Late collisions, Re-enable Tx MAC. */ if ((txstat & (TS_MaxCollisions|TS_LateCollision)) != 0) CSR_WRITE_4(sc, STGE_MACCtrl, (CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK) | MC_TxEnable); } return (error); } /* * stge_intr: * * Interrupt service routine. */ static void stge_intr(void *arg) { struct stge_softc *sc; struct ifnet *ifp; int reinit; uint16_t status; sc = (struct stge_softc *)arg; ifp = sc->sc_ifp; STGE_LOCK(sc); #ifdef DEVICE_POLLING if ((ifp->if_capenable & IFCAP_POLLING) != 0) goto done_locked; #endif status = CSR_READ_2(sc, STGE_IntStatus); if (sc->sc_suspended || (status & IS_InterruptStatus) == 0) goto done_locked; /* Disable interrupts. */ for (reinit = 0;;) { status = CSR_READ_2(sc, STGE_IntStatusAck); status &= sc->sc_IntEnable; if (status == 0) break; /* Host interface errors. */ if ((status & IS_HostError) != 0) { device_printf(sc->sc_dev, "Host interface error, resetting...\n"); reinit = 1; goto force_init; } /* Receive interrupts. */ if ((status & IS_RxDMAComplete) != 0) { stge_rxeof(sc); if ((status & IS_RFDListEnd) != 0) CSR_WRITE_4(sc, STGE_DMACtrl, DMAC_RxDMAPollNow); } /* Transmit interrupts. */ if ((status & (IS_TxDMAComplete | IS_TxComplete)) != 0) stge_txeof(sc); /* Transmission errors.*/ if ((status & IS_TxComplete) != 0) { if ((reinit = stge_tx_error(sc)) != 0) break; } } force_init: if (reinit != 0) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; stge_init_locked(sc); } /* Re-enable interrupts. */ CSR_WRITE_2(sc, STGE_IntEnable, sc->sc_IntEnable); /* Try to get more packets going. */ if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) stge_start_locked(ifp); done_locked: STGE_UNLOCK(sc); } /* * stge_txeof: * * Helper; handle transmit interrupts. */ static void stge_txeof(struct stge_softc *sc) { struct ifnet *ifp; struct stge_txdesc *txd; uint64_t control; int cons; STGE_LOCK_ASSERT(sc); ifp = sc->sc_ifp; txd = STAILQ_FIRST(&sc->sc_cdata.stge_txbusyq); if (txd == NULL) return; bus_dmamap_sync(sc->sc_cdata.stge_tx_ring_tag, sc->sc_cdata.stge_tx_ring_map, BUS_DMASYNC_POSTREAD); /* * Go through our Tx list and free mbufs for those * frames which have been transmitted. */ for (cons = sc->sc_cdata.stge_tx_cons;; cons = (cons + 1) % STGE_TX_RING_CNT) { if (sc->sc_cdata.stge_tx_cnt <= 0) break; control = le64toh(sc->sc_rdata.stge_tx_ring[cons].tfd_control); if ((control & TFD_TFDDone) == 0) break; sc->sc_cdata.stge_tx_cnt--; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; bus_dmamap_sync(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap); /* Output counter is updated with statistics register */ m_freem(txd->tx_m); txd->tx_m = NULL; STAILQ_REMOVE_HEAD(&sc->sc_cdata.stge_txbusyq, tx_q); STAILQ_INSERT_TAIL(&sc->sc_cdata.stge_txfreeq, txd, tx_q); txd = STAILQ_FIRST(&sc->sc_cdata.stge_txbusyq); } sc->sc_cdata.stge_tx_cons = cons; if (sc->sc_cdata.stge_tx_cnt == 0) sc->sc_watchdog_timer = 0; bus_dmamap_sync(sc->sc_cdata.stge_tx_ring_tag, sc->sc_cdata.stge_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static __inline void stge_discard_rxbuf(struct stge_softc *sc, int idx) { struct stge_rfd *rfd; rfd = &sc->sc_rdata.stge_rx_ring[idx]; rfd->rfd_status = 0; } #ifndef __NO_STRICT_ALIGNMENT /* * It seems that TC9021's DMA engine has alignment restrictions in * DMA scatter operations. The first DMA segment has no address * alignment restrictins but the rest should be aligned on 4(?) bytes * boundary. Otherwise it would corrupt random memory. Since we don't * know which one is used for the first segment in advance we simply * don't align at all. * To avoid copying over an entire frame to align, we allocate a new * mbuf and copy ethernet header to the new mbuf. The new mbuf is * prepended into the existing mbuf chain. */ static __inline struct mbuf * stge_fixup_rx(struct stge_softc *sc, struct mbuf *m) { struct mbuf *n; n = NULL; if (m->m_len <= (MCLBYTES - ETHER_HDR_LEN)) { bcopy(m->m_data, m->m_data + ETHER_HDR_LEN, m->m_len); m->m_data += ETHER_HDR_LEN; n = m; } else { MGETHDR(n, M_NOWAIT, MT_DATA); if (n != NULL) { bcopy(m->m_data, n->m_data, ETHER_HDR_LEN); m->m_data += ETHER_HDR_LEN; m->m_len -= ETHER_HDR_LEN; n->m_len = ETHER_HDR_LEN; M_MOVE_PKTHDR(n, m); n->m_next = m; } else m_freem(m); } return (n); } #endif /* * stge_rxeof: * * Helper; handle receive interrupts. */ static int stge_rxeof(struct stge_softc *sc) { struct ifnet *ifp; struct stge_rxdesc *rxd; struct mbuf *mp, *m; uint64_t status64; uint32_t status; int cons, prog, rx_npkts; STGE_LOCK_ASSERT(sc); rx_npkts = 0; ifp = sc->sc_ifp; bus_dmamap_sync(sc->sc_cdata.stge_rx_ring_tag, sc->sc_cdata.stge_rx_ring_map, BUS_DMASYNC_POSTREAD); prog = 0; for (cons = sc->sc_cdata.stge_rx_cons; prog < STGE_RX_RING_CNT; prog++, cons = (cons + 1) % STGE_RX_RING_CNT) { status64 = le64toh(sc->sc_rdata.stge_rx_ring[cons].rfd_status); status = RFD_RxStatus(status64); if ((status & RFD_RFDDone) == 0) break; #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) { if (sc->sc_cdata.stge_rxcycles <= 0) break; sc->sc_cdata.stge_rxcycles--; } #endif prog++; rxd = &sc->sc_cdata.stge_rxdesc[cons]; mp = rxd->rx_m; /* * If the packet had an error, drop it. Note we count * the error later in the periodic stats update. */ if ((status & RFD_FrameEnd) != 0 && (status & (RFD_RxFIFOOverrun | RFD_RxRuntFrame | RFD_RxAlignmentError | RFD_RxFCSError | RFD_RxLengthError)) != 0) { stge_discard_rxbuf(sc, cons); if (sc->sc_cdata.stge_rxhead != NULL) { m_freem(sc->sc_cdata.stge_rxhead); STGE_RXCHAIN_RESET(sc); } continue; } /* * Add a new receive buffer to the ring. */ if (stge_newbuf(sc, cons) != 0) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); stge_discard_rxbuf(sc, cons); if (sc->sc_cdata.stge_rxhead != NULL) { m_freem(sc->sc_cdata.stge_rxhead); STGE_RXCHAIN_RESET(sc); } continue; } if ((status & RFD_FrameEnd) != 0) mp->m_len = RFD_RxDMAFrameLen(status) - sc->sc_cdata.stge_rxlen; sc->sc_cdata.stge_rxlen += mp->m_len; /* Chain mbufs. */ if (sc->sc_cdata.stge_rxhead == NULL) { sc->sc_cdata.stge_rxhead = mp; sc->sc_cdata.stge_rxtail = mp; } else { mp->m_flags &= ~M_PKTHDR; sc->sc_cdata.stge_rxtail->m_next = mp; sc->sc_cdata.stge_rxtail = mp; } if ((status & RFD_FrameEnd) != 0) { m = sc->sc_cdata.stge_rxhead; m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = sc->sc_cdata.stge_rxlen; if (m->m_pkthdr.len > sc->sc_if_framesize) { m_freem(m); STGE_RXCHAIN_RESET(sc); continue; } /* * Set the incoming checksum information for * the packet. */ if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) { if ((status & RFD_IPDetected) != 0) { m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED; if ((status & RFD_IPError) == 0) m->m_pkthdr.csum_flags |= CSUM_IP_VALID; } if (((status & RFD_TCPDetected) != 0 && (status & RFD_TCPError) == 0) || ((status & RFD_UDPDetected) != 0 && (status & RFD_UDPError) == 0)) { m->m_pkthdr.csum_flags |= (CSUM_DATA_VALID | CSUM_PSEUDO_HDR); m->m_pkthdr.csum_data = 0xffff; } } #ifndef __NO_STRICT_ALIGNMENT if (sc->sc_if_framesize > (MCLBYTES - ETHER_ALIGN)) { if ((m = stge_fixup_rx(sc, m)) == NULL) { STGE_RXCHAIN_RESET(sc); continue; } } #endif /* Check for VLAN tagged packets. */ if ((status & RFD_VLANDetected) != 0 && (ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0) { m->m_pkthdr.ether_vtag = RFD_TCI(status64); m->m_flags |= M_VLANTAG; } STGE_UNLOCK(sc); /* Pass it on. */ (*ifp->if_input)(ifp, m); STGE_LOCK(sc); rx_npkts++; STGE_RXCHAIN_RESET(sc); } } if (prog > 0) { /* Update the consumer index. */ sc->sc_cdata.stge_rx_cons = cons; bus_dmamap_sync(sc->sc_cdata.stge_rx_ring_tag, sc->sc_cdata.stge_rx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } return (rx_npkts); } #ifdef DEVICE_POLLING static int stge_poll(struct ifnet *ifp, enum poll_cmd cmd, int count) { struct stge_softc *sc; uint16_t status; int rx_npkts; rx_npkts = 0; sc = ifp->if_softc; STGE_LOCK(sc); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { STGE_UNLOCK(sc); return (rx_npkts); } sc->sc_cdata.stge_rxcycles = count; rx_npkts = stge_rxeof(sc); stge_txeof(sc); if (cmd == POLL_AND_CHECK_STATUS) { status = CSR_READ_2(sc, STGE_IntStatus); status &= sc->sc_IntEnable; if (status != 0) { if ((status & IS_HostError) != 0) { device_printf(sc->sc_dev, "Host interface error, resetting...\n"); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; stge_init_locked(sc); } if ((status & IS_TxComplete) != 0) { if (stge_tx_error(sc) != 0) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; stge_init_locked(sc); } } } } if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) stge_start_locked(ifp); STGE_UNLOCK(sc); return (rx_npkts); } #endif /* DEVICE_POLLING */ /* * stge_tick: * * One second timer, used to tick the MII. */ static void stge_tick(void *arg) { struct stge_softc *sc; struct mii_data *mii; sc = (struct stge_softc *)arg; STGE_LOCK_ASSERT(sc); mii = device_get_softc(sc->sc_miibus); mii_tick(mii); /* Update statistics counters. */ stge_stats_update(sc); /* * Relcaim any pending Tx descriptors to release mbufs in a * timely manner as we don't generate Tx completion interrupts * for every frame. This limits the delay to a maximum of one * second. */ if (sc->sc_cdata.stge_tx_cnt != 0) stge_txeof(sc); stge_watchdog(sc); callout_reset(&sc->sc_tick_ch, hz, stge_tick, sc); } /* * stge_stats_update: * * Read the TC9021 statistics counters. */ static void stge_stats_update(struct stge_softc *sc) { struct ifnet *ifp; STGE_LOCK_ASSERT(sc); ifp = sc->sc_ifp; CSR_READ_4(sc,STGE_OctetRcvOk); if_inc_counter(ifp, IFCOUNTER_IPACKETS, CSR_READ_4(sc, STGE_FramesRcvdOk)); if_inc_counter(ifp, IFCOUNTER_IERRORS, CSR_READ_2(sc, STGE_FramesLostRxErrors)); CSR_READ_4(sc, STGE_OctetXmtdOk); if_inc_counter(ifp, IFCOUNTER_OPACKETS, CSR_READ_4(sc, STGE_FramesXmtdOk)); if_inc_counter(ifp, IFCOUNTER_COLLISIONS, CSR_READ_4(sc, STGE_LateCollisions) + CSR_READ_4(sc, STGE_MultiColFrames) + CSR_READ_4(sc, STGE_SingleColFrames)); if_inc_counter(ifp, IFCOUNTER_OERRORS, CSR_READ_2(sc, STGE_FramesAbortXSColls) + CSR_READ_2(sc, STGE_FramesWEXDeferal)); } /* * stge_reset: * * Perform a soft reset on the TC9021. */ static void stge_reset(struct stge_softc *sc, uint32_t how) { uint32_t ac; uint8_t v; int i, dv; STGE_LOCK_ASSERT(sc); dv = 5000; ac = CSR_READ_4(sc, STGE_AsicCtrl); switch (how) { case STGE_RESET_TX: ac |= AC_TxReset | AC_FIFO; dv = 100; break; case STGE_RESET_RX: ac |= AC_RxReset | AC_FIFO; dv = 100; break; case STGE_RESET_FULL: default: /* * Only assert RstOut if we're fiber. We need GMII clocks * to be present in order for the reset to complete on fiber * cards. */ ac |= AC_GlobalReset | AC_RxReset | AC_TxReset | AC_DMA | AC_FIFO | AC_Network | AC_Host | AC_AutoInit | (sc->sc_usefiber ? AC_RstOut : 0); break; } CSR_WRITE_4(sc, STGE_AsicCtrl, ac); /* Account for reset problem at 10Mbps. */ DELAY(dv); for (i = 0; i < STGE_TIMEOUT; i++) { if ((CSR_READ_4(sc, STGE_AsicCtrl) & AC_ResetBusy) == 0) break; DELAY(dv); } if (i == STGE_TIMEOUT) device_printf(sc->sc_dev, "reset failed to complete\n"); /* Set LED, from Linux IPG driver. */ ac = CSR_READ_4(sc, STGE_AsicCtrl); ac &= ~(AC_LEDMode | AC_LEDSpeed | AC_LEDModeBit1); if ((sc->sc_led & 0x01) != 0) ac |= AC_LEDMode; if ((sc->sc_led & 0x03) != 0) ac |= AC_LEDModeBit1; if ((sc->sc_led & 0x08) != 0) ac |= AC_LEDSpeed; CSR_WRITE_4(sc, STGE_AsicCtrl, ac); /* Set PHY, from Linux IPG driver */ v = CSR_READ_1(sc, STGE_PhySet); v &= ~(PS_MemLenb9b | PS_MemLen | PS_NonCompdet); v |= ((sc->sc_led & 0x70) >> 4); CSR_WRITE_1(sc, STGE_PhySet, v); } /* * stge_init: [ ifnet interface function ] * * Initialize the interface. */ static void stge_init(void *xsc) { struct stge_softc *sc; sc = (struct stge_softc *)xsc; STGE_LOCK(sc); stge_init_locked(sc); STGE_UNLOCK(sc); } static void stge_init_locked(struct stge_softc *sc) { struct ifnet *ifp; struct mii_data *mii; uint16_t eaddr[3]; uint32_t v; int error; STGE_LOCK_ASSERT(sc); ifp = sc->sc_ifp; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) return; mii = device_get_softc(sc->sc_miibus); /* * Cancel any pending I/O. */ stge_stop(sc); /* * Reset the chip to a known state. */ stge_reset(sc, STGE_RESET_FULL); /* Init descriptors. */ error = stge_init_rx_ring(sc); if (error != 0) { device_printf(sc->sc_dev, "initialization failed: no memory for rx buffers\n"); stge_stop(sc); goto out; } stge_init_tx_ring(sc); /* Set the station address. */ bcopy(IF_LLADDR(ifp), eaddr, ETHER_ADDR_LEN); CSR_WRITE_2(sc, STGE_StationAddress0, htole16(eaddr[0])); CSR_WRITE_2(sc, STGE_StationAddress1, htole16(eaddr[1])); CSR_WRITE_2(sc, STGE_StationAddress2, htole16(eaddr[2])); /* * Set the statistics masks. Disable all the RMON stats, * and disable selected stats in the non-RMON stats registers. */ CSR_WRITE_4(sc, STGE_RMONStatisticsMask, 0xffffffff); CSR_WRITE_4(sc, STGE_StatisticsMask, (1U << 1) | (1U << 2) | (1U << 3) | (1U << 4) | (1U << 5) | (1U << 6) | (1U << 7) | (1U << 8) | (1U << 9) | (1U << 10) | (1U << 13) | (1U << 14) | (1U << 15) | (1U << 19) | (1U << 20) | (1U << 21)); /* Set up the receive filter. */ stge_set_filter(sc); /* Program multicast filter. */ stge_set_multi(sc); /* * Give the transmit and receive ring to the chip. */ CSR_WRITE_4(sc, STGE_TFDListPtrHi, STGE_ADDR_HI(STGE_TX_RING_ADDR(sc, 0))); CSR_WRITE_4(sc, STGE_TFDListPtrLo, STGE_ADDR_LO(STGE_TX_RING_ADDR(sc, 0))); CSR_WRITE_4(sc, STGE_RFDListPtrHi, STGE_ADDR_HI(STGE_RX_RING_ADDR(sc, 0))); CSR_WRITE_4(sc, STGE_RFDListPtrLo, STGE_ADDR_LO(STGE_RX_RING_ADDR(sc, 0))); /* * Initialize the Tx auto-poll period. It's OK to make this number * large (255 is the max, but we use 127) -- we explicitly kick the * transmit engine when there's actually a packet. */ CSR_WRITE_1(sc, STGE_TxDMAPollPeriod, 127); /* ..and the Rx auto-poll period. */ CSR_WRITE_1(sc, STGE_RxDMAPollPeriod, 1); /* Initialize the Tx start threshold. */ CSR_WRITE_2(sc, STGE_TxStartThresh, sc->sc_txthresh); /* Rx DMA thresholds, from Linux */ CSR_WRITE_1(sc, STGE_RxDMABurstThresh, 0x30); CSR_WRITE_1(sc, STGE_RxDMAUrgentThresh, 0x30); /* Rx early threhold, from Linux */ CSR_WRITE_2(sc, STGE_RxEarlyThresh, 0x7ff); /* Tx DMA thresholds, from Linux */ CSR_WRITE_1(sc, STGE_TxDMABurstThresh, 0x30); CSR_WRITE_1(sc, STGE_TxDMAUrgentThresh, 0x04); /* * Initialize the Rx DMA interrupt control register. We * request an interrupt after every incoming packet, but * defer it for sc_rxint_dmawait us. When the number of * interrupts pending reaches STGE_RXINT_NFRAME, we stop * deferring the interrupt, and signal it immediately. */ CSR_WRITE_4(sc, STGE_RxDMAIntCtrl, RDIC_RxFrameCount(sc->sc_rxint_nframe) | RDIC_RxDMAWaitTime(STGE_RXINT_USECS2TICK(sc->sc_rxint_dmawait))); /* * Initialize the interrupt mask. */ sc->sc_IntEnable = IS_HostError | IS_TxComplete | IS_TxDMAComplete | IS_RxDMAComplete | IS_RFDListEnd; #ifdef DEVICE_POLLING /* Disable interrupts if we are polling. */ if ((ifp->if_capenable & IFCAP_POLLING) != 0) CSR_WRITE_2(sc, STGE_IntEnable, 0); else #endif CSR_WRITE_2(sc, STGE_IntEnable, sc->sc_IntEnable); /* * Configure the DMA engine. * XXX Should auto-tune TxBurstLimit. */ CSR_WRITE_4(sc, STGE_DMACtrl, sc->sc_DMACtrl | DMAC_TxBurstLimit(3)); /* * Send a PAUSE frame when we reach 29,696 bytes in the Rx * FIFO, and send an un-PAUSE frame when we reach 3056 bytes * in the Rx FIFO. */ CSR_WRITE_2(sc, STGE_FlowOnTresh, 29696 / 16); CSR_WRITE_2(sc, STGE_FlowOffThresh, 3056 / 16); /* * Set the maximum frame size. */ sc->sc_if_framesize = ifp->if_mtu + ETHER_HDR_LEN + ETHER_CRC_LEN; CSR_WRITE_2(sc, STGE_MaxFrameSize, sc->sc_if_framesize); /* * Initialize MacCtrl -- do it before setting the media, * as setting the media will actually program the register. * * Note: We have to poke the IFS value before poking * anything else. */ /* Tx/Rx MAC should be disabled before programming IFS.*/ CSR_WRITE_4(sc, STGE_MACCtrl, MC_IFSSelect(MC_IFS96bit)); stge_vlan_setup(sc); if (sc->sc_rev >= 6) { /* >= B.2 */ /* Multi-frag frame bug work-around. */ CSR_WRITE_2(sc, STGE_DebugCtrl, CSR_READ_2(sc, STGE_DebugCtrl) | 0x0200); /* Tx Poll Now bug work-around. */ CSR_WRITE_2(sc, STGE_DebugCtrl, CSR_READ_2(sc, STGE_DebugCtrl) | 0x0010); /* Tx Poll Now bug work-around. */ CSR_WRITE_2(sc, STGE_DebugCtrl, CSR_READ_2(sc, STGE_DebugCtrl) | 0x0020); } v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; v |= MC_StatisticsEnable | MC_TxEnable | MC_RxEnable; CSR_WRITE_4(sc, STGE_MACCtrl, v); /* * It seems that transmitting frames without checking the state of * Rx/Tx MAC wedge the hardware. */ stge_start_tx(sc); stge_start_rx(sc); sc->sc_link = 0; /* * Set the current media. */ mii_mediachg(mii); /* * Start the one second MII clock. */ callout_reset(&sc->sc_tick_ch, hz, stge_tick, sc); /* * ...all done! */ ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; out: if (error != 0) device_printf(sc->sc_dev, "interface not running\n"); } static void stge_vlan_setup(struct stge_softc *sc) { struct ifnet *ifp; uint32_t v; ifp = sc->sc_ifp; /* * The NIC always copy a VLAN tag regardless of STGE_MACCtrl * MC_AutoVLANuntagging bit. * MC_AutoVLANtagging bit selects which VLAN source to use * between STGE_VLANTag and TFC. However TFC TFD_VLANTagInsert * bit has priority over MC_AutoVLANtagging bit. So we always * use TFC instead of STGE_VLANTag register. */ v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0) v |= MC_AutoVLANuntagging; else v &= ~MC_AutoVLANuntagging; CSR_WRITE_4(sc, STGE_MACCtrl, v); } /* * Stop transmission on the interface. */ static void stge_stop(struct stge_softc *sc) { struct ifnet *ifp; struct stge_txdesc *txd; struct stge_rxdesc *rxd; uint32_t v; int i; STGE_LOCK_ASSERT(sc); /* * Stop the one second clock. */ callout_stop(&sc->sc_tick_ch); sc->sc_watchdog_timer = 0; /* * Disable interrupts. */ CSR_WRITE_2(sc, STGE_IntEnable, 0); /* * Stop receiver, transmitter, and stats update. */ stge_stop_rx(sc); stge_stop_tx(sc); v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; v |= MC_StatisticsDisable; CSR_WRITE_4(sc, STGE_MACCtrl, v); /* * Stop the transmit and receive DMA. */ stge_dma_wait(sc); CSR_WRITE_4(sc, STGE_TFDListPtrHi, 0); CSR_WRITE_4(sc, STGE_TFDListPtrLo, 0); CSR_WRITE_4(sc, STGE_RFDListPtrHi, 0); CSR_WRITE_4(sc, STGE_RFDListPtrLo, 0); /* * Free RX and TX mbufs still in the queues. */ for (i = 0; i < STGE_RX_RING_CNT; i++) { rxd = &sc->sc_cdata.stge_rxdesc[i]; if (rxd->rx_m != NULL) { bus_dmamap_sync(sc->sc_cdata.stge_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->sc_cdata.stge_rx_tag, rxd->rx_dmamap); m_freem(rxd->rx_m); rxd->rx_m = NULL; } } for (i = 0; i < STGE_TX_RING_CNT; i++) { txd = &sc->sc_cdata.stge_txdesc[i]; if (txd->tx_m != NULL) { bus_dmamap_sync(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap); m_freem(txd->tx_m); txd->tx_m = NULL; } } /* * Mark the interface down and cancel the watchdog timer. */ ifp = sc->sc_ifp; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc->sc_link = 0; } static void stge_start_tx(struct stge_softc *sc) { uint32_t v; int i; v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_TxEnabled) != 0) return; v |= MC_TxEnable; CSR_WRITE_4(sc, STGE_MACCtrl, v); CSR_WRITE_1(sc, STGE_TxDMAPollPeriod, 127); for (i = STGE_TIMEOUT; i > 0; i--) { DELAY(10); v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_TxEnabled) != 0) break; } if (i == 0) device_printf(sc->sc_dev, "Starting Tx MAC timed out\n"); } static void stge_start_rx(struct stge_softc *sc) { uint32_t v; int i; v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_RxEnabled) != 0) return; v |= MC_RxEnable; CSR_WRITE_4(sc, STGE_MACCtrl, v); CSR_WRITE_1(sc, STGE_RxDMAPollPeriod, 1); for (i = STGE_TIMEOUT; i > 0; i--) { DELAY(10); v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_RxEnabled) != 0) break; } if (i == 0) device_printf(sc->sc_dev, "Starting Rx MAC timed out\n"); } static void stge_stop_tx(struct stge_softc *sc) { uint32_t v; int i; v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_TxEnabled) == 0) return; v |= MC_TxDisable; CSR_WRITE_4(sc, STGE_MACCtrl, v); for (i = STGE_TIMEOUT; i > 0; i--) { DELAY(10); v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_TxEnabled) == 0) break; } if (i == 0) device_printf(sc->sc_dev, "Stopping Tx MAC timed out\n"); } static void stge_stop_rx(struct stge_softc *sc) { uint32_t v; int i; v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_RxEnabled) == 0) return; v |= MC_RxDisable; CSR_WRITE_4(sc, STGE_MACCtrl, v); for (i = STGE_TIMEOUT; i > 0; i--) { DELAY(10); v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_RxEnabled) == 0) break; } if (i == 0) device_printf(sc->sc_dev, "Stopping Rx MAC timed out\n"); } static void stge_init_tx_ring(struct stge_softc *sc) { struct stge_ring_data *rd; struct stge_txdesc *txd; bus_addr_t addr; int i; STAILQ_INIT(&sc->sc_cdata.stge_txfreeq); STAILQ_INIT(&sc->sc_cdata.stge_txbusyq); sc->sc_cdata.stge_tx_prod = 0; sc->sc_cdata.stge_tx_cons = 0; sc->sc_cdata.stge_tx_cnt = 0; rd = &sc->sc_rdata; bzero(rd->stge_tx_ring, STGE_TX_RING_SZ); for (i = 0; i < STGE_TX_RING_CNT; i++) { if (i == (STGE_TX_RING_CNT - 1)) addr = STGE_TX_RING_ADDR(sc, 0); else addr = STGE_TX_RING_ADDR(sc, i + 1); rd->stge_tx_ring[i].tfd_next = htole64(addr); rd->stge_tx_ring[i].tfd_control = htole64(TFD_TFDDone); txd = &sc->sc_cdata.stge_txdesc[i]; STAILQ_INSERT_TAIL(&sc->sc_cdata.stge_txfreeq, txd, tx_q); } bus_dmamap_sync(sc->sc_cdata.stge_tx_ring_tag, sc->sc_cdata.stge_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static int stge_init_rx_ring(struct stge_softc *sc) { struct stge_ring_data *rd; bus_addr_t addr; int i; sc->sc_cdata.stge_rx_cons = 0; STGE_RXCHAIN_RESET(sc); rd = &sc->sc_rdata; bzero(rd->stge_rx_ring, STGE_RX_RING_SZ); for (i = 0; i < STGE_RX_RING_CNT; i++) { if (stge_newbuf(sc, i) != 0) return (ENOBUFS); if (i == (STGE_RX_RING_CNT - 1)) addr = STGE_RX_RING_ADDR(sc, 0); else addr = STGE_RX_RING_ADDR(sc, i + 1); rd->stge_rx_ring[i].rfd_next = htole64(addr); rd->stge_rx_ring[i].rfd_status = 0; } bus_dmamap_sync(sc->sc_cdata.stge_rx_ring_tag, sc->sc_cdata.stge_rx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (0); } /* * stge_newbuf: * * Add a receive buffer to the indicated descriptor. */ static int stge_newbuf(struct stge_softc *sc, int idx) { struct stge_rxdesc *rxd; struct stge_rfd *rfd; struct mbuf *m; bus_dma_segment_t segs[1]; bus_dmamap_t map; int nsegs; m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) return (ENOBUFS); m->m_len = m->m_pkthdr.len = MCLBYTES; /* * The hardware requires 4bytes aligned DMA address when JUMBO * frame is used. */ if (sc->sc_if_framesize <= (MCLBYTES - ETHER_ALIGN)) m_adj(m, ETHER_ALIGN); if (bus_dmamap_load_mbuf_sg(sc->sc_cdata.stge_rx_tag, sc->sc_cdata.stge_rx_sparemap, m, segs, &nsegs, 0) != 0) { m_freem(m); return (ENOBUFS); } KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); rxd = &sc->sc_cdata.stge_rxdesc[idx]; if (rxd->rx_m != NULL) { bus_dmamap_sync(sc->sc_cdata.stge_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->sc_cdata.stge_rx_tag, rxd->rx_dmamap); } map = rxd->rx_dmamap; rxd->rx_dmamap = sc->sc_cdata.stge_rx_sparemap; sc->sc_cdata.stge_rx_sparemap = map; bus_dmamap_sync(sc->sc_cdata.stge_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_PREREAD); rxd->rx_m = m; rfd = &sc->sc_rdata.stge_rx_ring[idx]; rfd->rfd_frag.frag_word0 = htole64(FRAG_ADDR(segs[0].ds_addr) | FRAG_LEN(segs[0].ds_len)); rfd->rfd_status = 0; return (0); } /* * stge_set_filter: * * Set up the receive filter. */ static void stge_set_filter(struct stge_softc *sc) { struct ifnet *ifp; uint16_t mode; STGE_LOCK_ASSERT(sc); ifp = sc->sc_ifp; mode = CSR_READ_2(sc, STGE_ReceiveMode); mode |= RM_ReceiveUnicast; if ((ifp->if_flags & IFF_BROADCAST) != 0) mode |= RM_ReceiveBroadcast; else mode &= ~RM_ReceiveBroadcast; if ((ifp->if_flags & IFF_PROMISC) != 0) mode |= RM_ReceiveAllFrames; else mode &= ~RM_ReceiveAllFrames; CSR_WRITE_2(sc, STGE_ReceiveMode, mode); } static void stge_set_multi(struct stge_softc *sc) { struct ifnet *ifp; struct ifmultiaddr *ifma; uint32_t crc; uint32_t mchash[2]; uint16_t mode; int count; STGE_LOCK_ASSERT(sc); ifp = sc->sc_ifp; mode = CSR_READ_2(sc, STGE_ReceiveMode); if ((ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI)) != 0) { if ((ifp->if_flags & IFF_PROMISC) != 0) mode |= RM_ReceiveAllFrames; else if ((ifp->if_flags & IFF_ALLMULTI) != 0) mode |= RM_ReceiveMulticast; CSR_WRITE_2(sc, STGE_ReceiveMode, mode); return; } /* clear existing filters. */ CSR_WRITE_4(sc, STGE_HashTable0, 0); CSR_WRITE_4(sc, STGE_HashTable1, 0); /* * Set up the multicast address filter by passing all multicast * addresses through a CRC generator, and then using the low-order * 6 bits as an index into the 64 bit multicast hash table. The * high order bits select the register, while the rest of the bits * select the bit within the register. */ bzero(mchash, sizeof(mchash)); count = 0; if_maddr_rlock(sc->sc_ifp); TAILQ_FOREACH(ifma, &sc->sc_ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; crc = ether_crc32_be(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN); /* Just want the 6 least significant bits. */ crc &= 0x3f; /* Set the corresponding bit in the hash table. */ mchash[crc >> 5] |= 1 << (crc & 0x1f); count++; } if_maddr_runlock(ifp); mode &= ~(RM_ReceiveMulticast | RM_ReceiveAllFrames); if (count > 0) mode |= RM_ReceiveMulticastHash; else mode &= ~RM_ReceiveMulticastHash; CSR_WRITE_4(sc, STGE_HashTable0, mchash[0]); CSR_WRITE_4(sc, STGE_HashTable1, mchash[1]); CSR_WRITE_2(sc, STGE_ReceiveMode, mode); } static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int low, int high) { int error, value; if (!arg1) return (EINVAL); value = *(int *)arg1; error = sysctl_handle_int(oidp, &value, 0, req); if (error || !req->newptr) return (error); if (value < low || value > high) return (EINVAL); *(int *)arg1 = value; return (0); } static int sysctl_hw_stge_rxint_nframe(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, STGE_RXINT_NFRAME_MIN, STGE_RXINT_NFRAME_MAX)); } static int sysctl_hw_stge_rxint_dmawait(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, STGE_RXINT_DMAWAIT_MIN, STGE_RXINT_DMAWAIT_MAX)); } Index: head/sys/dev/vte/if_vte.c =================================================================== --- head/sys/dev/vte/if_vte.c (revision 295734) +++ head/sys/dev/vte/if_vte.c (revision 295735) @@ -1,2065 +1,2065 @@ /*- * Copyright (c) 2010, Pyun YongHyeon * 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 unmodified, 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* Driver for DM&P Electronics, Inc, Vortex86 RDC R6040 FastEthernet. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* "device miibus" required. See GENERIC if you get errors here. */ #include "miibus_if.h" MODULE_DEPEND(vte, pci, 1, 1, 1); MODULE_DEPEND(vte, ether, 1, 1, 1); MODULE_DEPEND(vte, miibus, 1, 1, 1); /* Tunables. */ static int tx_deep_copy = 1; TUNABLE_INT("hw.vte.tx_deep_copy", &tx_deep_copy); /* * Devices supported by this driver. */ static const struct vte_ident vte_ident_table[] = { { VENDORID_RDC, DEVICEID_RDC_R6040, "RDC R6040 FastEthernet"}, { 0, 0, NULL} }; static int vte_attach(device_t); static int vte_detach(device_t); static int vte_dma_alloc(struct vte_softc *); static void vte_dma_free(struct vte_softc *); static void vte_dmamap_cb(void *, bus_dma_segment_t *, int, int); static struct vte_txdesc * vte_encap(struct vte_softc *, struct mbuf **); static const struct vte_ident * vte_find_ident(device_t); #ifndef __NO_STRICT_ALIGNMENT static struct mbuf * vte_fixup_rx(struct ifnet *, struct mbuf *); #endif static void vte_get_macaddr(struct vte_softc *); static void vte_init(void *); static void vte_init_locked(struct vte_softc *); static int vte_init_rx_ring(struct vte_softc *); static int vte_init_tx_ring(struct vte_softc *); static void vte_intr(void *); static int vte_ioctl(struct ifnet *, u_long, caddr_t); static uint64_t vte_get_counter(struct ifnet *, ift_counter); static void vte_mac_config(struct vte_softc *); static int vte_miibus_readreg(device_t, int, int); static void vte_miibus_statchg(device_t); static int vte_miibus_writereg(device_t, int, int, int); static int vte_mediachange(struct ifnet *); static int vte_mediachange_locked(struct ifnet *); static void vte_mediastatus(struct ifnet *, struct ifmediareq *); static int vte_newbuf(struct vte_softc *, struct vte_rxdesc *); static int vte_probe(device_t); static void vte_reset(struct vte_softc *); static int vte_resume(device_t); static void vte_rxeof(struct vte_softc *); static void vte_rxfilter(struct vte_softc *); static int vte_shutdown(device_t); static void vte_start(struct ifnet *); static void vte_start_locked(struct vte_softc *); static void vte_start_mac(struct vte_softc *); static void vte_stats_clear(struct vte_softc *); static void vte_stats_update(struct vte_softc *); static void vte_stop(struct vte_softc *); static void vte_stop_mac(struct vte_softc *); static int vte_suspend(device_t); static void vte_sysctl_node(struct vte_softc *); static void vte_tick(void *); static void vte_txeof(struct vte_softc *); static void vte_watchdog(struct vte_softc *); static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int, int); static int sysctl_hw_vte_int_mod(SYSCTL_HANDLER_ARGS); static device_method_t vte_methods[] = { /* Device interface. */ DEVMETHOD(device_probe, vte_probe), DEVMETHOD(device_attach, vte_attach), DEVMETHOD(device_detach, vte_detach), DEVMETHOD(device_shutdown, vte_shutdown), DEVMETHOD(device_suspend, vte_suspend), DEVMETHOD(device_resume, vte_resume), /* MII interface. */ DEVMETHOD(miibus_readreg, vte_miibus_readreg), DEVMETHOD(miibus_writereg, vte_miibus_writereg), DEVMETHOD(miibus_statchg, vte_miibus_statchg), DEVMETHOD_END }; static driver_t vte_driver = { "vte", vte_methods, sizeof(struct vte_softc) }; static devclass_t vte_devclass; DRIVER_MODULE(vte, pci, vte_driver, vte_devclass, 0, 0); DRIVER_MODULE(miibus, vte, miibus_driver, miibus_devclass, 0, 0); static int vte_miibus_readreg(device_t dev, int phy, int reg) { struct vte_softc *sc; int i; sc = device_get_softc(dev); CSR_WRITE_2(sc, VTE_MMDIO, MMDIO_READ | (phy << MMDIO_PHY_ADDR_SHIFT) | (reg << MMDIO_REG_ADDR_SHIFT)); for (i = VTE_PHY_TIMEOUT; i > 0; i--) { DELAY(5); if ((CSR_READ_2(sc, VTE_MMDIO) & MMDIO_READ) == 0) break; } if (i == 0) { device_printf(sc->vte_dev, "phy read timeout : %d\n", reg); return (0); } return (CSR_READ_2(sc, VTE_MMRD)); } static int vte_miibus_writereg(device_t dev, int phy, int reg, int val) { struct vte_softc *sc; int i; sc = device_get_softc(dev); CSR_WRITE_2(sc, VTE_MMWD, val); CSR_WRITE_2(sc, VTE_MMDIO, MMDIO_WRITE | (phy << MMDIO_PHY_ADDR_SHIFT) | (reg << MMDIO_REG_ADDR_SHIFT)); for (i = VTE_PHY_TIMEOUT; i > 0; i--) { DELAY(5); if ((CSR_READ_2(sc, VTE_MMDIO) & MMDIO_WRITE) == 0) break; } if (i == 0) device_printf(sc->vte_dev, "phy write timeout : %d\n", reg); return (0); } static void vte_miibus_statchg(device_t dev) { struct vte_softc *sc; struct mii_data *mii; struct ifnet *ifp; uint16_t val; sc = device_get_softc(dev); mii = device_get_softc(sc->vte_miibus); ifp = sc->vte_ifp; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; sc->vte_flags &= ~VTE_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: case IFM_100_TX: sc->vte_flags |= VTE_FLAG_LINK; break; default: break; } } /* Stop RX/TX MACs. */ vte_stop_mac(sc); /* Program MACs with resolved duplex and flow control. */ if ((sc->vte_flags & VTE_FLAG_LINK) != 0) { /* * Timer waiting time : (63 + TIMER * 64) MII clock. * MII clock : 25MHz(100Mbps) or 2.5MHz(10Mbps). */ if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX) val = 18 << VTE_IM_TIMER_SHIFT; else val = 1 << VTE_IM_TIMER_SHIFT; val |= sc->vte_int_rx_mod << VTE_IM_BUNDLE_SHIFT; /* 48.6us for 100Mbps, 50.8us for 10Mbps */ CSR_WRITE_2(sc, VTE_MRICR, val); if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX) val = 18 << VTE_IM_TIMER_SHIFT; else val = 1 << VTE_IM_TIMER_SHIFT; val |= sc->vte_int_tx_mod << VTE_IM_BUNDLE_SHIFT; /* 48.6us for 100Mbps, 50.8us for 10Mbps */ CSR_WRITE_2(sc, VTE_MTICR, val); vte_mac_config(sc); vte_start_mac(sc); } } static void vte_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr) { struct vte_softc *sc; struct mii_data *mii; sc = ifp->if_softc; VTE_LOCK(sc); if ((ifp->if_flags & IFF_UP) == 0) { VTE_UNLOCK(sc); return; } mii = device_get_softc(sc->vte_miibus); mii_pollstat(mii); ifmr->ifm_status = mii->mii_media_status; ifmr->ifm_active = mii->mii_media_active; VTE_UNLOCK(sc); } static int vte_mediachange(struct ifnet *ifp) { struct vte_softc *sc; int error; sc = ifp->if_softc; VTE_LOCK(sc); error = vte_mediachange_locked(ifp); VTE_UNLOCK(sc); return (error); } static int vte_mediachange_locked(struct ifnet *ifp) { struct vte_softc *sc; struct mii_data *mii; struct mii_softc *miisc; int error; sc = ifp->if_softc; mii = device_get_softc(sc->vte_miibus); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); return (error); } static const struct vte_ident * vte_find_ident(device_t dev) { const struct vte_ident *ident; uint16_t vendor, devid; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); for (ident = vte_ident_table; ident->name != NULL; ident++) { if (vendor == ident->vendorid && devid == ident->deviceid) return (ident); } return (NULL); } static int vte_probe(device_t dev) { const struct vte_ident *ident; ident = vte_find_ident(dev); if (ident != NULL) { device_set_desc(dev, ident->name); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static void vte_get_macaddr(struct vte_softc *sc) { uint16_t mid; /* * It seems there is no way to reload station address and * it is supposed to be set by BIOS. */ mid = CSR_READ_2(sc, VTE_MID0L); sc->vte_eaddr[0] = (mid >> 0) & 0xFF; sc->vte_eaddr[1] = (mid >> 8) & 0xFF; mid = CSR_READ_2(sc, VTE_MID0M); sc->vte_eaddr[2] = (mid >> 0) & 0xFF; sc->vte_eaddr[3] = (mid >> 8) & 0xFF; mid = CSR_READ_2(sc, VTE_MID0H); sc->vte_eaddr[4] = (mid >> 0) & 0xFF; sc->vte_eaddr[5] = (mid >> 8) & 0xFF; } static int vte_attach(device_t dev) { struct vte_softc *sc; struct ifnet *ifp; uint16_t macid; int error, rid; error = 0; sc = device_get_softc(dev); sc->vte_dev = dev; mtx_init(&sc->vte_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init_mtx(&sc->vte_tick_ch, &sc->vte_mtx, 0); sc->vte_ident = vte_find_ident(dev); /* Map the device. */ pci_enable_busmaster(dev); sc->vte_res_id = PCIR_BAR(1); sc->vte_res_type = SYS_RES_MEMORY; sc->vte_res = bus_alloc_resource_any(dev, sc->vte_res_type, &sc->vte_res_id, RF_ACTIVE); if (sc->vte_res == NULL) { sc->vte_res_id = PCIR_BAR(0); sc->vte_res_type = SYS_RES_IOPORT; sc->vte_res = bus_alloc_resource_any(dev, sc->vte_res_type, &sc->vte_res_id, RF_ACTIVE); if (sc->vte_res == NULL) { device_printf(dev, "cannot map memory/ports.\n"); mtx_destroy(&sc->vte_mtx); return (ENXIO); } } if (bootverbose) { device_printf(dev, "using %s space register mapping\n", sc->vte_res_type == SYS_RES_MEMORY ? "memory" : "I/O"); device_printf(dev, "MAC Identifier : 0x%04x\n", CSR_READ_2(sc, VTE_MACID)); macid = CSR_READ_2(sc, VTE_MACID_REV); device_printf(dev, "MAC Id. 0x%02x, Rev. 0x%02x\n", (macid & VTE_MACID_MASK) >> VTE_MACID_SHIFT, (macid & VTE_MACID_REV_MASK) >> VTE_MACID_REV_SHIFT); } rid = 0; sc->vte_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->vte_irq == NULL) { device_printf(dev, "cannot allocate IRQ resources.\n"); error = ENXIO; goto fail; } /* Reset the ethernet controller. */ vte_reset(sc); - if ((error = vte_dma_alloc(sc) != 0)) + if ((error = vte_dma_alloc(sc)) != 0) goto fail; /* Create device sysctl node. */ vte_sysctl_node(sc); /* Load station address. */ vte_get_macaddr(sc); ifp = sc->vte_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(dev, "cannot allocate ifnet structure.\n"); error = ENXIO; goto fail; } ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = vte_ioctl; ifp->if_start = vte_start; ifp->if_init = vte_init; ifp->if_get_counter = vte_get_counter; ifp->if_snd.ifq_drv_maxlen = VTE_TX_RING_CNT - 1; IFQ_SET_MAXLEN(&ifp->if_snd, ifp->if_snd.ifq_drv_maxlen); IFQ_SET_READY(&ifp->if_snd); /* * Set up MII bus. * BIOS would have initialized VTE_MPSCCR to catch PHY * status changes so driver may be able to extract * configured PHY address. Since it's common to see BIOS * fails to initialize the register(including the sample * board I have), let mii(4) probe it. This is more * reliable than relying on BIOS's initialization. * * Advertising flow control capability to mii(4) was * intentionally disabled due to severe problems in TX * pause frame generation. See vte_rxeof() for more * details. */ error = mii_attach(dev, &sc->vte_miibus, ifp, vte_mediachange, vte_mediastatus, BMSR_DEFCAPMASK, MII_PHY_ANY, MII_OFFSET_ANY, 0); if (error != 0) { device_printf(dev, "attaching PHYs failed\n"); goto fail; } ether_ifattach(ifp, sc->vte_eaddr); /* VLAN capability setup. */ ifp->if_capabilities |= IFCAP_VLAN_MTU; ifp->if_capenable = ifp->if_capabilities; /* Tell the upper layer we support VLAN over-sized frames. */ ifp->if_hdrlen = sizeof(struct ether_vlan_header); error = bus_setup_intr(dev, sc->vte_irq, INTR_TYPE_NET | INTR_MPSAFE, NULL, vte_intr, sc, &sc->vte_intrhand); if (error != 0) { device_printf(dev, "could not set up interrupt handler.\n"); ether_ifdetach(ifp); goto fail; } fail: if (error != 0) vte_detach(dev); return (error); } static int vte_detach(device_t dev) { struct vte_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); ifp = sc->vte_ifp; if (device_is_attached(dev)) { VTE_LOCK(sc); vte_stop(sc); VTE_UNLOCK(sc); callout_drain(&sc->vte_tick_ch); ether_ifdetach(ifp); } if (sc->vte_miibus != NULL) { device_delete_child(dev, sc->vte_miibus); sc->vte_miibus = NULL; } bus_generic_detach(dev); if (sc->vte_intrhand != NULL) { bus_teardown_intr(dev, sc->vte_irq, sc->vte_intrhand); sc->vte_intrhand = NULL; } if (sc->vte_irq != NULL) { bus_release_resource(dev, SYS_RES_IRQ, 0, sc->vte_irq); sc->vte_irq = NULL; } if (sc->vte_res != NULL) { bus_release_resource(dev, sc->vte_res_type, sc->vte_res_id, sc->vte_res); sc->vte_res = NULL; } if (ifp != NULL) { if_free(ifp); sc->vte_ifp = NULL; } vte_dma_free(sc); mtx_destroy(&sc->vte_mtx); return (0); } #define VTE_SYSCTL_STAT_ADD32(c, h, n, p, d) \ SYSCTL_ADD_UINT(c, h, OID_AUTO, n, CTLFLAG_RD, p, 0, d) static void vte_sysctl_node(struct vte_softc *sc) { struct sysctl_ctx_list *ctx; struct sysctl_oid_list *child, *parent; struct sysctl_oid *tree; struct vte_hw_stats *stats; int error; stats = &sc->vte_stats; ctx = device_get_sysctl_ctx(sc->vte_dev); child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->vte_dev)); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "int_rx_mod", CTLTYPE_INT | CTLFLAG_RW, &sc->vte_int_rx_mod, 0, sysctl_hw_vte_int_mod, "I", "vte RX interrupt moderation"); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "int_tx_mod", CTLTYPE_INT | CTLFLAG_RW, &sc->vte_int_tx_mod, 0, sysctl_hw_vte_int_mod, "I", "vte TX interrupt moderation"); /* Pull in device tunables. */ sc->vte_int_rx_mod = VTE_IM_RX_BUNDLE_DEFAULT; error = resource_int_value(device_get_name(sc->vte_dev), device_get_unit(sc->vte_dev), "int_rx_mod", &sc->vte_int_rx_mod); if (error == 0) { if (sc->vte_int_rx_mod < VTE_IM_BUNDLE_MIN || sc->vte_int_rx_mod > VTE_IM_BUNDLE_MAX) { device_printf(sc->vte_dev, "int_rx_mod value out of " "range; using default: %d\n", VTE_IM_RX_BUNDLE_DEFAULT); sc->vte_int_rx_mod = VTE_IM_RX_BUNDLE_DEFAULT; } } sc->vte_int_tx_mod = VTE_IM_TX_BUNDLE_DEFAULT; error = resource_int_value(device_get_name(sc->vte_dev), device_get_unit(sc->vte_dev), "int_tx_mod", &sc->vte_int_tx_mod); if (error == 0) { if (sc->vte_int_tx_mod < VTE_IM_BUNDLE_MIN || sc->vte_int_tx_mod > VTE_IM_BUNDLE_MAX) { device_printf(sc->vte_dev, "int_tx_mod value out of " "range; using default: %d\n", VTE_IM_TX_BUNDLE_DEFAULT); sc->vte_int_tx_mod = VTE_IM_TX_BUNDLE_DEFAULT; } } tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", CTLFLAG_RD, NULL, "VTE statistics"); parent = SYSCTL_CHILDREN(tree); /* RX statistics. */ tree = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "rx", CTLFLAG_RD, NULL, "RX MAC statistics"); child = SYSCTL_CHILDREN(tree); VTE_SYSCTL_STAT_ADD32(ctx, child, "good_frames", &stats->rx_frames, "Good frames"); VTE_SYSCTL_STAT_ADD32(ctx, child, "good_bcast_frames", &stats->rx_bcast_frames, "Good broadcast frames"); VTE_SYSCTL_STAT_ADD32(ctx, child, "good_mcast_frames", &stats->rx_mcast_frames, "Good multicast frames"); VTE_SYSCTL_STAT_ADD32(ctx, child, "runt", &stats->rx_runts, "Too short frames"); VTE_SYSCTL_STAT_ADD32(ctx, child, "crc_errs", &stats->rx_crcerrs, "CRC errors"); VTE_SYSCTL_STAT_ADD32(ctx, child, "long_frames", &stats->rx_long_frames, "Frames that have longer length than maximum packet length"); VTE_SYSCTL_STAT_ADD32(ctx, child, "fifo_full", &stats->rx_fifo_full, "FIFO full"); VTE_SYSCTL_STAT_ADD32(ctx, child, "desc_unavail", &stats->rx_desc_unavail, "Descriptor unavailable frames"); VTE_SYSCTL_STAT_ADD32(ctx, child, "pause_frames", &stats->rx_pause_frames, "Pause control frames"); /* TX statistics. */ tree = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "tx", CTLFLAG_RD, NULL, "TX MAC statistics"); child = SYSCTL_CHILDREN(tree); VTE_SYSCTL_STAT_ADD32(ctx, child, "good_frames", &stats->tx_frames, "Good frames"); VTE_SYSCTL_STAT_ADD32(ctx, child, "underruns", &stats->tx_underruns, "FIFO underruns"); VTE_SYSCTL_STAT_ADD32(ctx, child, "late_colls", &stats->tx_late_colls, "Late collisions"); VTE_SYSCTL_STAT_ADD32(ctx, child, "pause_frames", &stats->tx_pause_frames, "Pause control frames"); } #undef VTE_SYSCTL_STAT_ADD32 struct vte_dmamap_arg { bus_addr_t vte_busaddr; }; static void vte_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { struct vte_dmamap_arg *ctx; if (error != 0) return; KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); ctx = (struct vte_dmamap_arg *)arg; ctx->vte_busaddr = segs[0].ds_addr; } static int vte_dma_alloc(struct vte_softc *sc) { struct vte_txdesc *txd; struct vte_rxdesc *rxd; struct vte_dmamap_arg ctx; int error, i; /* Create parent DMA tag. */ error = bus_dma_tag_create( bus_get_dma_tag(sc->vte_dev), /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ 0, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->vte_cdata.vte_parent_tag); if (error != 0) { device_printf(sc->vte_dev, "could not create parent DMA tag.\n"); goto fail; } /* Create DMA tag for TX descriptor ring. */ error = bus_dma_tag_create( sc->vte_cdata.vte_parent_tag, /* parent */ VTE_TX_RING_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ VTE_TX_RING_SZ, /* maxsize */ 1, /* nsegments */ VTE_TX_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->vte_cdata.vte_tx_ring_tag); if (error != 0) { device_printf(sc->vte_dev, "could not create TX ring DMA tag.\n"); goto fail; } /* Create DMA tag for RX free descriptor ring. */ error = bus_dma_tag_create( sc->vte_cdata.vte_parent_tag, /* parent */ VTE_RX_RING_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ VTE_RX_RING_SZ, /* maxsize */ 1, /* nsegments */ VTE_RX_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->vte_cdata.vte_rx_ring_tag); if (error != 0) { device_printf(sc->vte_dev, "could not create RX ring DMA tag.\n"); goto fail; } /* Allocate DMA'able memory and load the DMA map for TX ring. */ error = bus_dmamem_alloc(sc->vte_cdata.vte_tx_ring_tag, (void **)&sc->vte_cdata.vte_tx_ring, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->vte_cdata.vte_tx_ring_map); if (error != 0) { device_printf(sc->vte_dev, "could not allocate DMA'able memory for TX ring.\n"); goto fail; } ctx.vte_busaddr = 0; error = bus_dmamap_load(sc->vte_cdata.vte_tx_ring_tag, sc->vte_cdata.vte_tx_ring_map, sc->vte_cdata.vte_tx_ring, VTE_TX_RING_SZ, vte_dmamap_cb, &ctx, 0); if (error != 0 || ctx.vte_busaddr == 0) { device_printf(sc->vte_dev, "could not load DMA'able memory for TX ring.\n"); goto fail; } sc->vte_cdata.vte_tx_ring_paddr = ctx.vte_busaddr; /* Allocate DMA'able memory and load the DMA map for RX ring. */ error = bus_dmamem_alloc(sc->vte_cdata.vte_rx_ring_tag, (void **)&sc->vte_cdata.vte_rx_ring, BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &sc->vte_cdata.vte_rx_ring_map); if (error != 0) { device_printf(sc->vte_dev, "could not allocate DMA'able memory for RX ring.\n"); goto fail; } ctx.vte_busaddr = 0; error = bus_dmamap_load(sc->vte_cdata.vte_rx_ring_tag, sc->vte_cdata.vte_rx_ring_map, sc->vte_cdata.vte_rx_ring, VTE_RX_RING_SZ, vte_dmamap_cb, &ctx, 0); if (error != 0 || ctx.vte_busaddr == 0) { device_printf(sc->vte_dev, "could not load DMA'able memory for RX ring.\n"); goto fail; } sc->vte_cdata.vte_rx_ring_paddr = ctx.vte_busaddr; /* Create TX buffer parent tag. */ error = bus_dma_tag_create( bus_get_dma_tag(sc->vte_dev), /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ 0, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->vte_cdata.vte_buffer_tag); if (error != 0) { device_printf(sc->vte_dev, "could not create parent buffer DMA tag.\n"); goto fail; } /* Create DMA tag for TX buffers. */ error = bus_dma_tag_create( sc->vte_cdata.vte_buffer_tag, /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, /* maxsize */ 1, /* nsegments */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->vte_cdata.vte_tx_tag); if (error != 0) { device_printf(sc->vte_dev, "could not create TX DMA tag.\n"); goto fail; } /* Create DMA tag for RX buffers. */ error = bus_dma_tag_create( sc->vte_cdata.vte_buffer_tag, /* parent */ VTE_RX_BUF_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, /* maxsize */ 1, /* nsegments */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->vte_cdata.vte_rx_tag); if (error != 0) { device_printf(sc->vte_dev, "could not create RX DMA tag.\n"); goto fail; } /* Create DMA maps for TX buffers. */ for (i = 0; i < VTE_TX_RING_CNT; i++) { txd = &sc->vte_cdata.vte_txdesc[i]; txd->tx_m = NULL; txd->tx_dmamap = NULL; error = bus_dmamap_create(sc->vte_cdata.vte_tx_tag, 0, &txd->tx_dmamap); if (error != 0) { device_printf(sc->vte_dev, "could not create TX dmamap.\n"); goto fail; } } /* Create DMA maps for RX buffers. */ if ((error = bus_dmamap_create(sc->vte_cdata.vte_rx_tag, 0, &sc->vte_cdata.vte_rx_sparemap)) != 0) { device_printf(sc->vte_dev, "could not create spare RX dmamap.\n"); goto fail; } for (i = 0; i < VTE_RX_RING_CNT; i++) { rxd = &sc->vte_cdata.vte_rxdesc[i]; rxd->rx_m = NULL; rxd->rx_dmamap = NULL; error = bus_dmamap_create(sc->vte_cdata.vte_rx_tag, 0, &rxd->rx_dmamap); if (error != 0) { device_printf(sc->vte_dev, "could not create RX dmamap.\n"); goto fail; } } fail: return (error); } static void vte_dma_free(struct vte_softc *sc) { struct vte_txdesc *txd; struct vte_rxdesc *rxd; int i; /* TX buffers. */ if (sc->vte_cdata.vte_tx_tag != NULL) { for (i = 0; i < VTE_TX_RING_CNT; i++) { txd = &sc->vte_cdata.vte_txdesc[i]; if (txd->tx_dmamap != NULL) { bus_dmamap_destroy(sc->vte_cdata.vte_tx_tag, txd->tx_dmamap); txd->tx_dmamap = NULL; } } bus_dma_tag_destroy(sc->vte_cdata.vte_tx_tag); sc->vte_cdata.vte_tx_tag = NULL; } /* RX buffers */ if (sc->vte_cdata.vte_rx_tag != NULL) { for (i = 0; i < VTE_RX_RING_CNT; i++) { rxd = &sc->vte_cdata.vte_rxdesc[i]; if (rxd->rx_dmamap != NULL) { bus_dmamap_destroy(sc->vte_cdata.vte_rx_tag, rxd->rx_dmamap); rxd->rx_dmamap = NULL; } } if (sc->vte_cdata.vte_rx_sparemap != NULL) { bus_dmamap_destroy(sc->vte_cdata.vte_rx_tag, sc->vte_cdata.vte_rx_sparemap); sc->vte_cdata.vte_rx_sparemap = NULL; } bus_dma_tag_destroy(sc->vte_cdata.vte_rx_tag); sc->vte_cdata.vte_rx_tag = NULL; } /* TX descriptor ring. */ if (sc->vte_cdata.vte_tx_ring_tag != NULL) { if (sc->vte_cdata.vte_tx_ring_paddr != 0) bus_dmamap_unload(sc->vte_cdata.vte_tx_ring_tag, sc->vte_cdata.vte_tx_ring_map); if (sc->vte_cdata.vte_tx_ring != NULL) bus_dmamem_free(sc->vte_cdata.vte_tx_ring_tag, sc->vte_cdata.vte_tx_ring, sc->vte_cdata.vte_tx_ring_map); sc->vte_cdata.vte_tx_ring = NULL; sc->vte_cdata.vte_tx_ring_paddr = 0; bus_dma_tag_destroy(sc->vte_cdata.vte_tx_ring_tag); sc->vte_cdata.vte_tx_ring_tag = NULL; } /* RX ring. */ if (sc->vte_cdata.vte_rx_ring_tag != NULL) { if (sc->vte_cdata.vte_rx_ring_paddr != 0) bus_dmamap_unload(sc->vte_cdata.vte_rx_ring_tag, sc->vte_cdata.vte_rx_ring_map); if (sc->vte_cdata.vte_rx_ring != NULL) bus_dmamem_free(sc->vte_cdata.vte_rx_ring_tag, sc->vte_cdata.vte_rx_ring, sc->vte_cdata.vte_rx_ring_map); sc->vte_cdata.vte_rx_ring = NULL; sc->vte_cdata.vte_rx_ring_paddr = 0; bus_dma_tag_destroy(sc->vte_cdata.vte_rx_ring_tag); sc->vte_cdata.vte_rx_ring_tag = NULL; } if (sc->vte_cdata.vte_buffer_tag != NULL) { bus_dma_tag_destroy(sc->vte_cdata.vte_buffer_tag); sc->vte_cdata.vte_buffer_tag = NULL; } if (sc->vte_cdata.vte_parent_tag != NULL) { bus_dma_tag_destroy(sc->vte_cdata.vte_parent_tag); sc->vte_cdata.vte_parent_tag = NULL; } } static int vte_shutdown(device_t dev) { return (vte_suspend(dev)); } static int vte_suspend(device_t dev) { struct vte_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); VTE_LOCK(sc); ifp = sc->vte_ifp; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) vte_stop(sc); VTE_UNLOCK(sc); return (0); } static int vte_resume(device_t dev) { struct vte_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); VTE_LOCK(sc); ifp = sc->vte_ifp; if ((ifp->if_flags & IFF_UP) != 0) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; vte_init_locked(sc); } VTE_UNLOCK(sc); return (0); } static struct vte_txdesc * vte_encap(struct vte_softc *sc, struct mbuf **m_head) { struct vte_txdesc *txd; struct mbuf *m, *n; bus_dma_segment_t txsegs[1]; int copy, error, nsegs, padlen; VTE_LOCK_ASSERT(sc); M_ASSERTPKTHDR((*m_head)); txd = &sc->vte_cdata.vte_txdesc[sc->vte_cdata.vte_tx_prod]; m = *m_head; /* * Controller doesn't auto-pad, so we have to make sure pad * short frames out to the minimum frame length. */ if (m->m_pkthdr.len < VTE_MIN_FRAMELEN) padlen = VTE_MIN_FRAMELEN - m->m_pkthdr.len; else padlen = 0; /* * Controller does not support multi-fragmented TX buffers. * Controller spends most of its TX processing time in * de-fragmenting TX buffers. Either faster CPU or more * advanced controller DMA engine is required to speed up * TX path processing. * To mitigate the de-fragmenting issue, perform deep copy * from fragmented mbuf chains to a pre-allocated mbuf * cluster with extra cost of kernel memory. For frames * that is composed of single TX buffer, the deep copy is * bypassed. */ if (tx_deep_copy != 0) { copy = 0; if (m->m_next != NULL) copy++; if (padlen > 0 && (M_WRITABLE(m) == 0 || padlen > M_TRAILINGSPACE(m))) copy++; if (copy != 0) { /* Avoid expensive m_defrag(9) and do deep copy. */ n = sc->vte_cdata.vte_txmbufs[sc->vte_cdata.vte_tx_prod]; m_copydata(m, 0, m->m_pkthdr.len, mtod(n, char *)); n->m_pkthdr.len = m->m_pkthdr.len; n->m_len = m->m_pkthdr.len; m = n; txd->tx_flags |= VTE_TXMBUF; } if (padlen > 0) { /* Zero out the bytes in the pad area. */ bzero(mtod(m, char *) + m->m_pkthdr.len, padlen); m->m_pkthdr.len += padlen; m->m_len = m->m_pkthdr.len; } } else { if (M_WRITABLE(m) == 0) { if (m->m_next != NULL || padlen > 0) { /* Get a writable copy. */ m = m_dup(*m_head, M_NOWAIT); /* Release original mbuf chains. */ m_freem(*m_head); if (m == NULL) { *m_head = NULL; return (NULL); } *m_head = m; } } if (m->m_next != NULL) { m = m_defrag(*m_head, M_NOWAIT); if (m == NULL) { m_freem(*m_head); *m_head = NULL; return (NULL); } *m_head = m; } if (padlen > 0) { if (M_TRAILINGSPACE(m) < padlen) { m = m_defrag(*m_head, M_NOWAIT); if (m == NULL) { m_freem(*m_head); *m_head = NULL; return (NULL); } *m_head = m; } /* Zero out the bytes in the pad area. */ bzero(mtod(m, char *) + m->m_pkthdr.len, padlen); m->m_pkthdr.len += padlen; m->m_len = m->m_pkthdr.len; } } error = bus_dmamap_load_mbuf_sg(sc->vte_cdata.vte_tx_tag, txd->tx_dmamap, m, txsegs, &nsegs, 0); if (error != 0) { txd->tx_flags &= ~VTE_TXMBUF; return (NULL); } KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); bus_dmamap_sync(sc->vte_cdata.vte_tx_tag, txd->tx_dmamap, BUS_DMASYNC_PREWRITE); txd->tx_desc->dtlen = htole16(VTE_TX_LEN(txsegs[0].ds_len)); txd->tx_desc->dtbp = htole32(txsegs[0].ds_addr); sc->vte_cdata.vte_tx_cnt++; /* Update producer index. */ VTE_DESC_INC(sc->vte_cdata.vte_tx_prod, VTE_TX_RING_CNT); /* Finally hand over ownership to controller. */ txd->tx_desc->dtst = htole16(VTE_DTST_TX_OWN); txd->tx_m = m; return (txd); } static void vte_start(struct ifnet *ifp) { struct vte_softc *sc; sc = ifp->if_softc; VTE_LOCK(sc); vte_start_locked(sc); VTE_UNLOCK(sc); } static void vte_start_locked(struct vte_softc *sc) { struct ifnet *ifp; struct vte_txdesc *txd; struct mbuf *m_head; int enq; ifp = sc->vte_ifp; if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING || (sc->vte_flags & VTE_FLAG_LINK) == 0) return; for (enq = 0; !IFQ_DRV_IS_EMPTY(&ifp->if_snd); ) { /* Reserve one free TX descriptor. */ if (sc->vte_cdata.vte_tx_cnt >= VTE_TX_RING_CNT - 1) { ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; /* * Pack the data into the transmit ring. If we * don't have room, set the OACTIVE flag and wait * for the NIC to drain the ring. */ if ((txd = vte_encap(sc, &m_head)) == NULL) { if (m_head != NULL) IFQ_DRV_PREPEND(&ifp->if_snd, m_head); break; } enq++; /* * If there's a BPF listener, bounce a copy of this frame * to him. */ ETHER_BPF_MTAP(ifp, m_head); /* Free consumed TX frame. */ if ((txd->tx_flags & VTE_TXMBUF) != 0) m_freem(m_head); } if (enq > 0) { bus_dmamap_sync(sc->vte_cdata.vte_tx_ring_tag, sc->vte_cdata.vte_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); CSR_WRITE_2(sc, VTE_TX_POLL, TX_POLL_START); sc->vte_watchdog_timer = VTE_TX_TIMEOUT; } } static void vte_watchdog(struct vte_softc *sc) { struct ifnet *ifp; VTE_LOCK_ASSERT(sc); if (sc->vte_watchdog_timer == 0 || --sc->vte_watchdog_timer) return; ifp = sc->vte_ifp; if_printf(sc->vte_ifp, "watchdog timeout -- resetting\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; vte_init_locked(sc); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) vte_start_locked(sc); } static int vte_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct vte_softc *sc; struct ifreq *ifr; struct mii_data *mii; int error; sc = ifp->if_softc; ifr = (struct ifreq *)data; error = 0; switch (cmd) { case SIOCSIFFLAGS: VTE_LOCK(sc); if ((ifp->if_flags & IFF_UP) != 0) { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0 && ((ifp->if_flags ^ sc->vte_if_flags) & (IFF_PROMISC | IFF_ALLMULTI)) != 0) vte_rxfilter(sc); else vte_init_locked(sc); } else if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) vte_stop(sc); sc->vte_if_flags = ifp->if_flags; VTE_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: VTE_LOCK(sc); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) vte_rxfilter(sc); VTE_UNLOCK(sc); break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: mii = device_get_softc(sc->vte_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); break; default: error = ether_ioctl(ifp, cmd, data); break; } return (error); } static void vte_mac_config(struct vte_softc *sc) { struct mii_data *mii; uint16_t mcr; VTE_LOCK_ASSERT(sc); mii = device_get_softc(sc->vte_miibus); mcr = CSR_READ_2(sc, VTE_MCR0); mcr &= ~(MCR0_FC_ENB | MCR0_FULL_DUPLEX); if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { mcr |= MCR0_FULL_DUPLEX; #ifdef notyet if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0) mcr |= MCR0_FC_ENB; /* * The data sheet is not clear whether the controller * honors received pause frames or not. The is no * separate control bit for RX pause frame so just * enable MCR0_FC_ENB bit. */ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0) mcr |= MCR0_FC_ENB; #endif } CSR_WRITE_2(sc, VTE_MCR0, mcr); } static void vte_stats_clear(struct vte_softc *sc) { /* Reading counter registers clears its contents. */ CSR_READ_2(sc, VTE_CNT_RX_DONE); CSR_READ_2(sc, VTE_CNT_MECNT0); CSR_READ_2(sc, VTE_CNT_MECNT1); CSR_READ_2(sc, VTE_CNT_MECNT2); CSR_READ_2(sc, VTE_CNT_MECNT3); CSR_READ_2(sc, VTE_CNT_TX_DONE); CSR_READ_2(sc, VTE_CNT_MECNT4); CSR_READ_2(sc, VTE_CNT_PAUSE); } static void vte_stats_update(struct vte_softc *sc) { struct vte_hw_stats *stat; uint16_t value; VTE_LOCK_ASSERT(sc); stat = &sc->vte_stats; CSR_READ_2(sc, VTE_MECISR); /* RX stats. */ stat->rx_frames += CSR_READ_2(sc, VTE_CNT_RX_DONE); value = CSR_READ_2(sc, VTE_CNT_MECNT0); stat->rx_bcast_frames += (value >> 8); stat->rx_mcast_frames += (value & 0xFF); value = CSR_READ_2(sc, VTE_CNT_MECNT1); stat->rx_runts += (value >> 8); stat->rx_crcerrs += (value & 0xFF); value = CSR_READ_2(sc, VTE_CNT_MECNT2); stat->rx_long_frames += (value & 0xFF); value = CSR_READ_2(sc, VTE_CNT_MECNT3); stat->rx_fifo_full += (value >> 8); stat->rx_desc_unavail += (value & 0xFF); /* TX stats. */ stat->tx_frames += CSR_READ_2(sc, VTE_CNT_TX_DONE); value = CSR_READ_2(sc, VTE_CNT_MECNT4); stat->tx_underruns += (value >> 8); stat->tx_late_colls += (value & 0xFF); value = CSR_READ_2(sc, VTE_CNT_PAUSE); stat->tx_pause_frames += (value >> 8); stat->rx_pause_frames += (value & 0xFF); } static uint64_t vte_get_counter(struct ifnet *ifp, ift_counter cnt) { struct vte_softc *sc; struct vte_hw_stats *stat; sc = if_getsoftc(ifp); stat = &sc->vte_stats; switch (cnt) { case IFCOUNTER_OPACKETS: return (stat->tx_frames); case IFCOUNTER_COLLISIONS: return (stat->tx_late_colls); case IFCOUNTER_OERRORS: return (stat->tx_late_colls + stat->tx_underruns); case IFCOUNTER_IPACKETS: return (stat->rx_frames); case IFCOUNTER_IERRORS: return (stat->rx_crcerrs + stat->rx_runts + stat->rx_long_frames + stat->rx_fifo_full); default: return (if_get_counter_default(ifp, cnt)); } } static void vte_intr(void *arg) { struct vte_softc *sc; struct ifnet *ifp; uint16_t status; int n; sc = (struct vte_softc *)arg; VTE_LOCK(sc); ifp = sc->vte_ifp; /* Reading VTE_MISR acknowledges interrupts. */ status = CSR_READ_2(sc, VTE_MISR); if ((status & VTE_INTRS) == 0) { /* Not ours. */ VTE_UNLOCK(sc); return; } /* Disable interrupts. */ CSR_WRITE_2(sc, VTE_MIER, 0); for (n = 8; (status & VTE_INTRS) != 0;) { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) break; if ((status & (MISR_RX_DONE | MISR_RX_DESC_UNAVAIL | MISR_RX_FIFO_FULL)) != 0) vte_rxeof(sc); if ((status & MISR_TX_DONE) != 0) vte_txeof(sc); if ((status & MISR_EVENT_CNT_OFLOW) != 0) vte_stats_update(sc); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) vte_start_locked(sc); if (--n > 0) status = CSR_READ_2(sc, VTE_MISR); else break; } if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { /* Re-enable interrupts. */ CSR_WRITE_2(sc, VTE_MIER, VTE_INTRS); } VTE_UNLOCK(sc); } static void vte_txeof(struct vte_softc *sc) { struct ifnet *ifp; struct vte_txdesc *txd; uint16_t status; int cons, prog; VTE_LOCK_ASSERT(sc); ifp = sc->vte_ifp; if (sc->vte_cdata.vte_tx_cnt == 0) return; bus_dmamap_sync(sc->vte_cdata.vte_tx_ring_tag, sc->vte_cdata.vte_tx_ring_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); cons = sc->vte_cdata.vte_tx_cons; /* * Go through our TX list and free mbufs for those * frames which have been transmitted. */ for (prog = 0; sc->vte_cdata.vte_tx_cnt > 0; prog++) { txd = &sc->vte_cdata.vte_txdesc[cons]; status = le16toh(txd->tx_desc->dtst); if ((status & VTE_DTST_TX_OWN) != 0) break; sc->vte_cdata.vte_tx_cnt--; /* Reclaim transmitted mbufs. */ bus_dmamap_sync(sc->vte_cdata.vte_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->vte_cdata.vte_tx_tag, txd->tx_dmamap); if ((txd->tx_flags & VTE_TXMBUF) == 0) m_freem(txd->tx_m); txd->tx_flags &= ~VTE_TXMBUF; txd->tx_m = NULL; prog++; VTE_DESC_INC(cons, VTE_TX_RING_CNT); } if (prog > 0) { ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc->vte_cdata.vte_tx_cons = cons; /* * Unarm watchdog timer only when there is no pending * frames in TX queue. */ if (sc->vte_cdata.vte_tx_cnt == 0) sc->vte_watchdog_timer = 0; } } static int vte_newbuf(struct vte_softc *sc, struct vte_rxdesc *rxd) { struct mbuf *m; bus_dma_segment_t segs[1]; bus_dmamap_t map; int nsegs; m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) return (ENOBUFS); m->m_len = m->m_pkthdr.len = MCLBYTES; m_adj(m, sizeof(uint32_t)); if (bus_dmamap_load_mbuf_sg(sc->vte_cdata.vte_rx_tag, sc->vte_cdata.vte_rx_sparemap, m, segs, &nsegs, 0) != 0) { m_freem(m); return (ENOBUFS); } KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); if (rxd->rx_m != NULL) { bus_dmamap_sync(sc->vte_cdata.vte_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->vte_cdata.vte_rx_tag, rxd->rx_dmamap); } map = rxd->rx_dmamap; rxd->rx_dmamap = sc->vte_cdata.vte_rx_sparemap; sc->vte_cdata.vte_rx_sparemap = map; bus_dmamap_sync(sc->vte_cdata.vte_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_PREREAD); rxd->rx_m = m; rxd->rx_desc->drbp = htole32(segs[0].ds_addr); rxd->rx_desc->drlen = htole16(VTE_RX_LEN(segs[0].ds_len)); rxd->rx_desc->drst = htole16(VTE_DRST_RX_OWN); return (0); } /* * It's not supposed to see this controller on strict-alignment * architectures but make it work for completeness. */ #ifndef __NO_STRICT_ALIGNMENT static struct mbuf * vte_fixup_rx(struct ifnet *ifp, struct mbuf *m) { uint16_t *src, *dst; int i; src = mtod(m, uint16_t *); dst = src - 1; for (i = 0; i < (m->m_len / sizeof(uint16_t) + 1); i++) *dst++ = *src++; m->m_data -= ETHER_ALIGN; return (m); } #endif static void vte_rxeof(struct vte_softc *sc) { struct ifnet *ifp; struct vte_rxdesc *rxd; struct mbuf *m; uint16_t status, total_len; int cons, prog; bus_dmamap_sync(sc->vte_cdata.vte_rx_ring_tag, sc->vte_cdata.vte_rx_ring_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); cons = sc->vte_cdata.vte_rx_cons; ifp = sc->vte_ifp; for (prog = 0; (ifp->if_drv_flags & IFF_DRV_RUNNING) != 0; prog++, VTE_DESC_INC(cons, VTE_RX_RING_CNT)) { rxd = &sc->vte_cdata.vte_rxdesc[cons]; status = le16toh(rxd->rx_desc->drst); if ((status & VTE_DRST_RX_OWN) != 0) break; total_len = VTE_RX_LEN(le16toh(rxd->rx_desc->drlen)); m = rxd->rx_m; if ((status & VTE_DRST_RX_OK) == 0) { /* Discard errored frame. */ rxd->rx_desc->drlen = htole16(MCLBYTES - sizeof(uint32_t)); rxd->rx_desc->drst = htole16(VTE_DRST_RX_OWN); continue; } if (vte_newbuf(sc, rxd) != 0) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); rxd->rx_desc->drlen = htole16(MCLBYTES - sizeof(uint32_t)); rxd->rx_desc->drst = htole16(VTE_DRST_RX_OWN); continue; } /* * It seems there is no way to strip FCS bytes. */ m->m_pkthdr.len = m->m_len = total_len - ETHER_CRC_LEN; m->m_pkthdr.rcvif = ifp; #ifndef __NO_STRICT_ALIGNMENT vte_fixup_rx(ifp, m); #endif VTE_UNLOCK(sc); (*ifp->if_input)(ifp, m); VTE_LOCK(sc); } if (prog > 0) { /* Update the consumer index. */ sc->vte_cdata.vte_rx_cons = cons; /* * Sync updated RX descriptors such that controller see * modified RX buffer addresses. */ bus_dmamap_sync(sc->vte_cdata.vte_rx_ring_tag, sc->vte_cdata.vte_rx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); #ifdef notyet /* * Update residue counter. Controller does not * keep track of number of available RX descriptors * such that driver should have to update VTE_MRDCR * to make controller know how many free RX * descriptors were added to controller. This is * a similar mechanism used in VIA velocity * controllers and it indicates controller just * polls OWN bit of current RX descriptor pointer. * A couple of severe issues were seen on sample * board where the controller continuously emits TX * pause frames once RX pause threshold crossed. * Once triggered it never recovered form that * state, I couldn't find a way to make it back to * work at least. This issue effectively * disconnected the system from network. Also, the * controller used 00:00:00:00:00:00 as source * station address of TX pause frame. Probably this * is one of reason why vendor recommends not to * enable flow control on R6040 controller. */ CSR_WRITE_2(sc, VTE_MRDCR, prog | (((VTE_RX_RING_CNT * 2) / 10) << VTE_MRDCR_RX_PAUSE_THRESH_SHIFT)); #endif } } static void vte_tick(void *arg) { struct vte_softc *sc; struct mii_data *mii; sc = (struct vte_softc *)arg; VTE_LOCK_ASSERT(sc); mii = device_get_softc(sc->vte_miibus); mii_tick(mii); vte_stats_update(sc); vte_txeof(sc); vte_watchdog(sc); callout_reset(&sc->vte_tick_ch, hz, vte_tick, sc); } static void vte_reset(struct vte_softc *sc) { uint16_t mcr; int i; mcr = CSR_READ_2(sc, VTE_MCR1); CSR_WRITE_2(sc, VTE_MCR1, mcr | MCR1_MAC_RESET); for (i = VTE_RESET_TIMEOUT; i > 0; i--) { DELAY(10); if ((CSR_READ_2(sc, VTE_MCR1) & MCR1_MAC_RESET) == 0) break; } if (i == 0) device_printf(sc->vte_dev, "reset timeout(0x%04x)!\n", mcr); /* * Follow the guide of vendor recommended way to reset MAC. * Vendor confirms relying on MCR1_MAC_RESET of VTE_MCR1 is * not reliable so manually reset internal state machine. */ CSR_WRITE_2(sc, VTE_MACSM, 0x0002); CSR_WRITE_2(sc, VTE_MACSM, 0); DELAY(5000); } static void vte_init(void *xsc) { struct vte_softc *sc; sc = (struct vte_softc *)xsc; VTE_LOCK(sc); vte_init_locked(sc); VTE_UNLOCK(sc); } static void vte_init_locked(struct vte_softc *sc) { struct ifnet *ifp; bus_addr_t paddr; uint8_t *eaddr; VTE_LOCK_ASSERT(sc); ifp = sc->vte_ifp; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) return; /* * Cancel any pending I/O. */ vte_stop(sc); /* * Reset the chip to a known state. */ vte_reset(sc); /* Initialize RX descriptors. */ if (vte_init_rx_ring(sc) != 0) { device_printf(sc->vte_dev, "no memory for RX buffers.\n"); vte_stop(sc); return; } if (vte_init_tx_ring(sc) != 0) { device_printf(sc->vte_dev, "no memory for TX buffers.\n"); vte_stop(sc); return; } /* * Reprogram the station address. Controller supports up * to 4 different station addresses so driver programs the * first station address as its own ethernet address and * configure the remaining three addresses as perfect * multicast addresses. */ eaddr = IF_LLADDR(sc->vte_ifp); CSR_WRITE_2(sc, VTE_MID0L, eaddr[1] << 8 | eaddr[0]); CSR_WRITE_2(sc, VTE_MID0M, eaddr[3] << 8 | eaddr[2]); CSR_WRITE_2(sc, VTE_MID0H, eaddr[5] << 8 | eaddr[4]); /* Set TX descriptor base addresses. */ paddr = sc->vte_cdata.vte_tx_ring_paddr; CSR_WRITE_2(sc, VTE_MTDSA1, paddr >> 16); CSR_WRITE_2(sc, VTE_MTDSA0, paddr & 0xFFFF); /* Set RX descriptor base addresses. */ paddr = sc->vte_cdata.vte_rx_ring_paddr; CSR_WRITE_2(sc, VTE_MRDSA1, paddr >> 16); CSR_WRITE_2(sc, VTE_MRDSA0, paddr & 0xFFFF); /* * Initialize RX descriptor residue counter and set RX * pause threshold to 20% of available RX descriptors. * See comments on vte_rxeof() for details on flow control * issues. */ CSR_WRITE_2(sc, VTE_MRDCR, (VTE_RX_RING_CNT & VTE_MRDCR_RESIDUE_MASK) | (((VTE_RX_RING_CNT * 2) / 10) << VTE_MRDCR_RX_PAUSE_THRESH_SHIFT)); /* * Always use maximum frame size that controller can * support. Otherwise received frames that has longer * frame length than vte(4) MTU would be silently dropped * in controller. This would break path-MTU discovery as * sender wouldn't get any responses from receiver. The * RX buffer size should be multiple of 4. * Note, jumbo frames are silently ignored by controller * and even MAC counters do not detect them. */ CSR_WRITE_2(sc, VTE_MRBSR, VTE_RX_BUF_SIZE_MAX); /* Configure FIFO. */ CSR_WRITE_2(sc, VTE_MBCR, MBCR_FIFO_XFER_LENGTH_16 | MBCR_TX_FIFO_THRESH_64 | MBCR_RX_FIFO_THRESH_16 | MBCR_SDRAM_BUS_REQ_TIMER_DEFAULT); /* * Configure TX/RX MACs. Actual resolved duplex and flow * control configuration is done after detecting a valid * link. Note, we don't generate early interrupt here * as well since FreeBSD does not have interrupt latency * problems like Windows. */ CSR_WRITE_2(sc, VTE_MCR0, MCR0_ACCPT_LONG_PKT); /* * We manually keep track of PHY status changes to * configure resolved duplex and flow control since only * duplex configuration can be automatically reflected to * MCR0. */ CSR_WRITE_2(sc, VTE_MCR1, MCR1_PKT_LENGTH_1537 | MCR1_EXCESS_COL_RETRY_16); /* Initialize RX filter. */ vte_rxfilter(sc); /* Disable TX/RX interrupt moderation control. */ CSR_WRITE_2(sc, VTE_MRICR, 0); CSR_WRITE_2(sc, VTE_MTICR, 0); /* Enable MAC event counter interrupts. */ CSR_WRITE_2(sc, VTE_MECIER, VTE_MECIER_INTRS); /* Clear MAC statistics. */ vte_stats_clear(sc); /* Acknowledge all pending interrupts and clear it. */ CSR_WRITE_2(sc, VTE_MIER, VTE_INTRS); CSR_WRITE_2(sc, VTE_MISR, 0); sc->vte_flags &= ~VTE_FLAG_LINK; /* Switch to the current media. */ vte_mediachange_locked(ifp); callout_reset(&sc->vte_tick_ch, hz, vte_tick, sc); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; } static void vte_stop(struct vte_softc *sc) { struct ifnet *ifp; struct vte_txdesc *txd; struct vte_rxdesc *rxd; int i; VTE_LOCK_ASSERT(sc); /* * Mark the interface down and cancel the watchdog timer. */ ifp = sc->vte_ifp; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc->vte_flags &= ~VTE_FLAG_LINK; callout_stop(&sc->vte_tick_ch); sc->vte_watchdog_timer = 0; vte_stats_update(sc); /* Disable interrupts. */ CSR_WRITE_2(sc, VTE_MIER, 0); CSR_WRITE_2(sc, VTE_MECIER, 0); /* Stop RX/TX MACs. */ vte_stop_mac(sc); /* Clear interrupts. */ CSR_READ_2(sc, VTE_MISR); /* * Free TX/RX mbufs still in the queues. */ for (i = 0; i < VTE_RX_RING_CNT; i++) { rxd = &sc->vte_cdata.vte_rxdesc[i]; if (rxd->rx_m != NULL) { bus_dmamap_sync(sc->vte_cdata.vte_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->vte_cdata.vte_rx_tag, rxd->rx_dmamap); m_freem(rxd->rx_m); rxd->rx_m = NULL; } } for (i = 0; i < VTE_TX_RING_CNT; i++) { txd = &sc->vte_cdata.vte_txdesc[i]; if (txd->tx_m != NULL) { bus_dmamap_sync(sc->vte_cdata.vte_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->vte_cdata.vte_tx_tag, txd->tx_dmamap); if ((txd->tx_flags & VTE_TXMBUF) == 0) m_freem(txd->tx_m); txd->tx_m = NULL; txd->tx_flags &= ~VTE_TXMBUF; } } /* Free TX mbuf pools used for deep copy. */ for (i = 0; i < VTE_TX_RING_CNT; i++) { if (sc->vte_cdata.vte_txmbufs[i] != NULL) { m_freem(sc->vte_cdata.vte_txmbufs[i]); sc->vte_cdata.vte_txmbufs[i] = NULL; } } } static void vte_start_mac(struct vte_softc *sc) { uint16_t mcr; int i; VTE_LOCK_ASSERT(sc); /* Enable RX/TX MACs. */ mcr = CSR_READ_2(sc, VTE_MCR0); if ((mcr & (MCR0_RX_ENB | MCR0_TX_ENB)) != (MCR0_RX_ENB | MCR0_TX_ENB)) { mcr |= MCR0_RX_ENB | MCR0_TX_ENB; CSR_WRITE_2(sc, VTE_MCR0, mcr); for (i = VTE_TIMEOUT; i > 0; i--) { mcr = CSR_READ_2(sc, VTE_MCR0); if ((mcr & (MCR0_RX_ENB | MCR0_TX_ENB)) == (MCR0_RX_ENB | MCR0_TX_ENB)) break; DELAY(10); } if (i == 0) device_printf(sc->vte_dev, "could not enable RX/TX MAC(0x%04x)!\n", mcr); } } static void vte_stop_mac(struct vte_softc *sc) { uint16_t mcr; int i; VTE_LOCK_ASSERT(sc); /* Disable RX/TX MACs. */ mcr = CSR_READ_2(sc, VTE_MCR0); if ((mcr & (MCR0_RX_ENB | MCR0_TX_ENB)) != 0) { mcr &= ~(MCR0_RX_ENB | MCR0_TX_ENB); CSR_WRITE_2(sc, VTE_MCR0, mcr); for (i = VTE_TIMEOUT; i > 0; i--) { mcr = CSR_READ_2(sc, VTE_MCR0); if ((mcr & (MCR0_RX_ENB | MCR0_TX_ENB)) == 0) break; DELAY(10); } if (i == 0) device_printf(sc->vte_dev, "could not disable RX/TX MAC(0x%04x)!\n", mcr); } } static int vte_init_tx_ring(struct vte_softc *sc) { struct vte_tx_desc *desc; struct vte_txdesc *txd; bus_addr_t addr; int i; VTE_LOCK_ASSERT(sc); sc->vte_cdata.vte_tx_prod = 0; sc->vte_cdata.vte_tx_cons = 0; sc->vte_cdata.vte_tx_cnt = 0; /* Pre-allocate TX mbufs for deep copy. */ if (tx_deep_copy != 0) { for (i = 0; i < VTE_TX_RING_CNT; i++) { sc->vte_cdata.vte_txmbufs[i] = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (sc->vte_cdata.vte_txmbufs[i] == NULL) return (ENOBUFS); sc->vte_cdata.vte_txmbufs[i]->m_pkthdr.len = MCLBYTES; sc->vte_cdata.vte_txmbufs[i]->m_len = MCLBYTES; } } desc = sc->vte_cdata.vte_tx_ring; bzero(desc, VTE_TX_RING_SZ); for (i = 0; i < VTE_TX_RING_CNT; i++) { txd = &sc->vte_cdata.vte_txdesc[i]; txd->tx_m = NULL; if (i != VTE_TX_RING_CNT - 1) addr = sc->vte_cdata.vte_tx_ring_paddr + sizeof(struct vte_tx_desc) * (i + 1); else addr = sc->vte_cdata.vte_tx_ring_paddr + sizeof(struct vte_tx_desc) * 0; desc = &sc->vte_cdata.vte_tx_ring[i]; desc->dtnp = htole32(addr); txd->tx_desc = desc; } bus_dmamap_sync(sc->vte_cdata.vte_tx_ring_tag, sc->vte_cdata.vte_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (0); } static int vte_init_rx_ring(struct vte_softc *sc) { struct vte_rx_desc *desc; struct vte_rxdesc *rxd; bus_addr_t addr; int i; VTE_LOCK_ASSERT(sc); sc->vte_cdata.vte_rx_cons = 0; desc = sc->vte_cdata.vte_rx_ring; bzero(desc, VTE_RX_RING_SZ); for (i = 0; i < VTE_RX_RING_CNT; i++) { rxd = &sc->vte_cdata.vte_rxdesc[i]; rxd->rx_m = NULL; if (i != VTE_RX_RING_CNT - 1) addr = sc->vte_cdata.vte_rx_ring_paddr + sizeof(struct vte_rx_desc) * (i + 1); else addr = sc->vte_cdata.vte_rx_ring_paddr + sizeof(struct vte_rx_desc) * 0; desc = &sc->vte_cdata.vte_rx_ring[i]; desc->drnp = htole32(addr); rxd->rx_desc = desc; if (vte_newbuf(sc, rxd) != 0) return (ENOBUFS); } bus_dmamap_sync(sc->vte_cdata.vte_rx_ring_tag, sc->vte_cdata.vte_rx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (0); } static void vte_rxfilter(struct vte_softc *sc) { struct ifnet *ifp; struct ifmultiaddr *ifma; uint8_t *eaddr; uint32_t crc; uint16_t rxfilt_perf[VTE_RXFILT_PERFECT_CNT][3]; uint16_t mchash[4], mcr; int i, nperf; VTE_LOCK_ASSERT(sc); ifp = sc->vte_ifp; bzero(mchash, sizeof(mchash)); for (i = 0; i < VTE_RXFILT_PERFECT_CNT; i++) { rxfilt_perf[i][0] = 0xFFFF; rxfilt_perf[i][1] = 0xFFFF; rxfilt_perf[i][2] = 0xFFFF; } mcr = CSR_READ_2(sc, VTE_MCR0); mcr &= ~(MCR0_PROMISC | MCR0_MULTICAST); mcr |= MCR0_BROADCAST_DIS; if ((ifp->if_flags & IFF_BROADCAST) != 0) mcr &= ~MCR0_BROADCAST_DIS; if ((ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI)) != 0) { if ((ifp->if_flags & IFF_PROMISC) != 0) mcr |= MCR0_PROMISC; if ((ifp->if_flags & IFF_ALLMULTI) != 0) mcr |= MCR0_MULTICAST; mchash[0] = 0xFFFF; mchash[1] = 0xFFFF; mchash[2] = 0xFFFF; mchash[3] = 0xFFFF; goto chipit; } nperf = 0; if_maddr_rlock(ifp); TAILQ_FOREACH(ifma, &sc->vte_ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; /* * Program the first 3 multicast groups into * the perfect filter. For all others, use the * hash table. */ if (nperf < VTE_RXFILT_PERFECT_CNT) { eaddr = LLADDR((struct sockaddr_dl *)ifma->ifma_addr); rxfilt_perf[nperf][0] = eaddr[1] << 8 | eaddr[0]; rxfilt_perf[nperf][1] = eaddr[3] << 8 | eaddr[2]; rxfilt_perf[nperf][2] = eaddr[5] << 8 | eaddr[4]; nperf++; continue; } crc = ether_crc32_be(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN); mchash[crc >> 30] |= 1 << ((crc >> 26) & 0x0F); } if_maddr_runlock(ifp); if (mchash[0] != 0 || mchash[1] != 0 || mchash[2] != 0 || mchash[3] != 0) mcr |= MCR0_MULTICAST; chipit: /* Program multicast hash table. */ CSR_WRITE_2(sc, VTE_MAR0, mchash[0]); CSR_WRITE_2(sc, VTE_MAR1, mchash[1]); CSR_WRITE_2(sc, VTE_MAR2, mchash[2]); CSR_WRITE_2(sc, VTE_MAR3, mchash[3]); /* Program perfect filter table. */ for (i = 0; i < VTE_RXFILT_PERFECT_CNT; i++) { CSR_WRITE_2(sc, VTE_RXFILTER_PEEFECT_BASE + 8 * i + 0, rxfilt_perf[i][0]); CSR_WRITE_2(sc, VTE_RXFILTER_PEEFECT_BASE + 8 * i + 2, rxfilt_perf[i][1]); CSR_WRITE_2(sc, VTE_RXFILTER_PEEFECT_BASE + 8 * i + 4, rxfilt_perf[i][2]); } CSR_WRITE_2(sc, VTE_MCR0, mcr); CSR_READ_2(sc, VTE_MCR0); } static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int low, int high) { int error, value; if (arg1 == NULL) return (EINVAL); value = *(int *)arg1; error = sysctl_handle_int(oidp, &value, 0, req); if (error || req->newptr == NULL) return (error); if (value < low || value > high) return (EINVAL); *(int *)arg1 = value; return (0); } static int sysctl_hw_vte_int_mod(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, VTE_IM_BUNDLE_MIN, VTE_IM_BUNDLE_MAX)); }