Author: colin Date: 2009-11-29 14:35:30 +0100 (Sun, 29 Nov 2009) New Revision: 34348 Changeset: http://dev.haiku-os.org/changeset/34348/haiku Added: haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/ haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/Jamfile haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/ haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/Jamfile haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/ haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/Jamfile haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/glue.c haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/if_alc.c haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/if_alcreg.h haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/if_alcvar.h haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/mii/ haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/mii/Jamfile haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/mii/ukphy.c haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/mii/ukphy_subr.c Modified: haiku/trunk/src/add-ons/kernel/drivers/network/Jamfile Log: * Adding driver for Atheros AR8131/AR8132 Gigabit/Fast Ethernet network cards. The source is based on the FreeBSD RELEASE_8_0_0 code, found in Haiku's freebsd vendor branch. This driver enables the network card in my EeePC 1005HA-M, for example. To compile it issue "jam atheros813x". * Introducing the new build target. Modified: haiku/trunk/src/add-ons/kernel/drivers/network/Jamfile =================================================================== --- haiku/trunk/src/add-ons/kernel/drivers/network/Jamfile 2009-11-29 12:40:32 UTC (rev 34347) +++ haiku/trunk/src/add-ons/kernel/drivers/network/Jamfile 2009-11-29 13:35:30 UTC (rev 34348) @@ -31,4 +31,7 @@ SubIncludeGPL HAIKU_TOP src add-ons kernel drivers network bcm440x ; SubIncludeGPL HAIKU_TOP src add-ons kernel drivers network bcm570x ; +# FreeBSD 8 drivers +SubInclude HAIKU_TOP src add-ons kernel drivers network atheros813x ; + SubInclude HAIKU_TOP src add-ons kernel drivers network wlan ; Added: haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/Jamfile =================================================================== --- haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/Jamfile (rev 0) +++ haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/Jamfile 2009-11-29 13:35:30 UTC (rev 34348) @@ -0,0 +1,3 @@ +SubDir HAIKU_TOP src add-ons kernel drivers network atheros813x ; + +SubInclude HAIKU_TOP src add-ons kernel drivers network atheros813x dev ; Added: haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/Jamfile =================================================================== --- haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/Jamfile (rev 0) +++ haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/Jamfile 2009-11-29 13:35:30 UTC (rev 34348) @@ -0,0 +1,4 @@ +SubDir HAIKU_TOP src add-ons kernel drivers network atheros813x dev ; + +SubInclude HAIKU_TOP src add-ons kernel drivers network atheros813x dev mii ; +SubInclude HAIKU_TOP src add-ons kernel drivers network atheros813x dev alc ; Added: haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/Jamfile =================================================================== --- haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/Jamfile (rev 0) +++ haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/Jamfile 2009-11-29 13:35:30 UTC (rev 34348) @@ -0,0 +1,19 @@ +SubDir HAIKU_TOP src add-ons kernel drivers network atheros813x dev alc ; + +SubDirCcFlags -Wall ; + +UseHeaders [ FDirName $(HAIKU_TOP) src libs compat freebsd_network compat ] : true ; + +UsePrivateHeaders net system ; +UsePrivateKernelHeaders ; + +UseHeaders [ FDirName $(SUBDIR) .. .. ] : true ; + +SubDirCcFlags [ FDefines _KERNEL=1 ] ; + +KernelAddon atheros813x : + if_alc.c + glue.c + : libfreebsd_network.a atheros813x_mii.a + ; + Added: haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/glue.c =================================================================== --- haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/glue.c (rev 0) +++ haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/glue.c 2009-11-29 13:35:30 UTC (rev 34348) @@ -0,0 +1,8 @@ +#include <sys/bus.h> + +HAIKU_FBSD_DRIVER_GLUE(atheros813x, alc, pci); + +HAIKU_FBSD_MII_DRIVER(ukphy); +HAIKU_DRIVER_REQUIREMENTS(FBSD_TASKQUEUES | FBSD_FAST_TASKQUEUE); +NO_HAIKU_CHECK_DISABLE_INTERRUPTS(); +NO_HAIKU_REENABLE_INTERRUPTS(); Added: haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/if_alc.c =================================================================== --- haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/if_alc.c (rev 0) +++ haiku/trunk/src/add-ons/kernel/drivers/network/atheros813x/dev/alc/if_alc.c 2009-11-29 13:35:30 UTC (rev 34348) @@ -0,0 +1,3512 @@ +/*- + * Copyright (c) 2009, Pyun YongHyeon <yongari@xxxxxxxxxxx> + * 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 AR8131/AR8132 PCIe Ethernet. */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/endian.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/rman.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/sysctl.h> +#include <sys/taskqueue.h> + +#include <net/bpf.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_dl.h> +#include <net/if_llc.h> +#include <net/if_media.h> +#include <net/if_types.h> +#include <net/if_vlan_var.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> + +#include <dev/mii/mii.h> +#include <dev/mii/miivar.h> + +#include <dev/pci/pcireg.h> +#include <dev/pci/pcivar.h> + +#include <machine/atomic.h> +#include <machine/bus.h> +#include <machine/in_cksum.h> + +#include <dev/alc/if_alcreg.h> +#include <dev/alc/if_alcvar.h> + +/* "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 +#ifndef IFCAP_VLAN_HWTSO +#define IFCAP_VLAN_HWTSO 0 +#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_dev { + uint16_t alc_vendorid; + uint16_t alc_deviceid; + const char *alc_name; +} alc_devs[] = { + { VENDORID_ATHEROS, DEVICEID_ATHEROS_AR8131, + "Atheros AR8131 PCIe Gigabit Ethernet" }, + { VENDORID_ATHEROS, DEVICEID_ATHEROS_AR8132, + "Atheros AR8132 PCIe Fast Ethernet" } +}; + +static void alc_aspm(struct alc_softc *); +static int alc_attach(device_t); +static int alc_check_boundary(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 int alc_encap(struct alc_softc *, struct mbuf **); +#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_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 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 int alc_mediachange(struct ifnet *); +static void alc_mediastatus(struct ifnet *, struct ifmediareq *); +static int alc_newbuf(struct alc_softc *, struct alc_rxdesc *); +static void alc_phy_down(struct alc_softc *); +static void alc_phy_reset(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 int alc_shutdown(device_t); +static void alc_start(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_tx_task(void *, int); +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; + uint32_t v; + int i; + + sc = device_get_softc(dev); + + if (phy != sc->alc_phyaddr) + 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 int +alc_miibus_writereg(device_t dev, int phy, int reg, int val) +{ + struct alc_softc *sc; + uint32_t v; + int i; + + sc = device_get_softc(dev); + + if (phy != sc->alc_phyaddr) + return (0); + + 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 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; + } + } + alc_stop_queue(sc); + /* 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); +} + +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); + ALC_UNLOCK(sc); + ifmr->ifm_status = mii->mii_media_status; + ifmr->ifm_active = mii->mii_media_active; +} + +static int +alc_mediachange(struct ifnet *ifp) +{ + struct alc_softc *sc; + struct mii_data *mii; + struct mii_softc *miisc; + int error; + + sc = ifp->if_softc; + ALC_LOCK(sc); + mii = device_get_softc(sc->alc_miibus); + if (mii->mii_instance != 0) { + LIST_FOREACH(miisc, &mii->mii_phys, mii_list) + mii_phy_reset(miisc); + } + error = mii_mediachg(mii); + ALC_UNLOCK(sc); + + return (error); +} + +static int +alc_probe(device_t dev) +{ + struct alc_dev *sp; + int i; + uint16_t vendor, devid; + + vendor = pci_get_vendor(dev); + devid = pci_get_device(dev); + sp = alc_devs; + for (i = 0; i < sizeof(alc_devs) / sizeof(alc_devs[0]); i++) { + if (vendor == sp->alc_vendorid && + devid == sp->alc_deviceid) { + device_set_desc(dev, sp->alc_name); + return (BUS_PROBE_DEFAULT); + } + sp++; + } + + return (ENXIO); +} + +static void +alc_get_macaddr(struct alc_softc *sc) +{ + uint32_t ea[2], opt; + int i; + + opt = CSR_READ_4(sc, ALC_OPT_CFG); + if ((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. + */ + 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); + } + 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 ((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); + } + + 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; + + /* 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) +{ + uint16_t data; + + /* Reset magic from Linux. */ + CSR_WRITE_2(sc, ALC_GPHY_CFG, + GPHY_CFG_HIB_EN | GPHY_CFG_HIB_PULSE | 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_HIB_EN | GPHY_CFG_HIB_PULSE | + GPHY_CFG_SEL_ANA_RESET); + CSR_READ_2(sc, ALC_GPHY_CFG); + DELAY(10 * 1000); + + /* 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); +} + +static void +alc_phy_down(struct alc_softc *sc) +{ + + /* Force PHY down. */ + CSR_WRITE_2(sc, ALC_GPHY_CFG, + GPHY_CFG_EXT_RESET | GPHY_CFG_HIB_EN | GPHY_CFG_HIB_PULSE | + GPHY_CFG_SEL_ANA_RESET | GPHY_CFG_PHY_IDDQ | GPHY_CFG_PWDOWN_HW); + DELAY(1000); +} + +static void +alc_aspm(struct alc_softc *sc) +{ + uint32_t pmcfg; + + ALC_LOCK_ASSERT(sc); + + pmcfg = CSR_READ_4(sc, ALC_PM_CFG); + pmcfg &= ~PM_CFG_SERDES_PD_EX_L1; + pmcfg |= PM_CFG_SERDES_BUDS_RX_L1_ENB; + pmcfg |= PM_CFG_SERDES_L1_ENB; + pmcfg &= ~PM_CFG_L1_ENTRY_TIMER_MASK; + pmcfg |= PM_CFG_MAC_ASPM_CHK; + if ((sc->alc_flags & ALC_FLAG_LINK) != 0) { + pmcfg |= PM_CFG_SERDES_PLL_L1_ENB; + pmcfg &= ~PM_CFG_CLK_SWH_L1; + pmcfg &= ~PM_CFG_ASPM_L1_ENB; + pmcfg &= ~PM_CFG_ASPM_L0S_ENB; + } else { + pmcfg &= ~PM_CFG_SERDES_PLL_L1_ENB; + pmcfg |= PM_CFG_CLK_SWH_L1; + pmcfg &= ~PM_CFG_ASPM_L1_ENB; + pmcfg &= ~PM_CFG_ASPM_L0S_ENB; + } + CSR_WRITE_4(sc, ALC_PM_CFG, pmcfg); +} + +static int +alc_attach(device_t dev) +{ + struct alc_softc *sc; + struct ifnet *ifp; + char *aspm_state[] = { "L0s/L1", "L0s", "L1", "L0s/l1" }; + uint16_t burst; + int base, error, i, msic, msixc, pmc, state; + uint32_t cap, ctl, val; + + error = 0; + sc = device_get_softc(dev); + sc->alc_dev = 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); + + /* 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; + + /* 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_extcap(dev, PCIY_EXPRESS, &base) == 0) { + sc->alc_flags |= ALC_FLAG_PCIE; + burst = CSR_READ_2(sc, base + PCIR_EXPRESS_DEVICE_CTL); + sc->alc_dma_rd_burst = + (burst & PCIM_EXP_CTL_MAX_READ_REQUEST) >> 12; + sc->alc_dma_wr_burst = (burst & PCIM_EXP_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]); + } + /* 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); + /* Disable ASPM L0S and L1. */ + cap = CSR_READ_2(sc, base + PCIR_EXPRESS_LINK_CAP); + if ((cap & PCIM_LINK_CAP_ASPM) != 0) { + ctl = CSR_READ_2(sc, base + PCIR_EXPRESS_LINK_CTL); + if ((ctl & 0x08) != 0) + sc->alc_rcb = DMA_CFG_RCB_128; + if (bootverbose) + device_printf(dev, "RCB %u bytes\n", + sc->alc_rcb == DMA_CFG_RCB_64 ? 64 : 128); + state = ctl & 0x03; + if (bootverbose) + device_printf(sc->alc_dev, "ASPM %s %s\n", + aspm_state[state], + state == 0 ? "disabled" : "enabled"); + if (state != 0) + alc_disable_l0s_l1(sc); + } + } + + /* Reset PHY. */ + alc_phy_reset(sc); + + /* Reset the ethernet controller. */ + alc_reset(sc); + + /* + * 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. + */ + if (pci_get_device(dev) == DEVICEID_ATHEROS_AR8132) + sc->alc_flags |= ALC_FLAG_FASTETHER | ALC_FLAG_JUMBO; + else + sc->alc_flags |= ALC_FLAG_JUMBO | ALC_FLAG_ASPM_MON; + /* + * It seems that AR8131/AR8132 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_rev = pci_get_revid(dev); + 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); + } + 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); + + /* 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 == ALC_MSIX_MESSAGES && + pci_alloc_msix(dev, &msixc) == 0) { + if (msic == ALC_MSIX_MESSAGES) { + 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 == ALC_MSI_MESSAGES && + pci_alloc_msi(dev, &msic) == 0) { + if (msic == ALC_MSI_MESSAGES) { + 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)) + 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_extcap(dev, PCIY_PMG, &pmc) == 0) + ifp->if_capabilities |= IFCAP_WOL_MAGIC | IFCAP_WOL_MCAST; + ifp->if_capenable = ifp->if_capabilities; + + /* Set up MII bus. */ + if ((error = mii_phy_probe(dev, &sc->alc_miibus, alc_mediachange, + alc_mediastatus)) != 0) { + device_printf(dev, "no PHY found!\n"); + goto fail; + } + + ether_ifattach(ifp, sc->alc_eaddr); + + /* VLAN capability setup. */ + ifp->if_capabilities |= IFCAP_VLAN_MTU; + ifp->if_capabilities |= IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_HWCSUM; + 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. + */ + ifp->if_capenable &= ~IFCAP_TXCSUM; + ifp->if_hwassist &= ~ALC_CSUM_FEATURES; + + /* Tell the upper layer(s) we support long frames. */ + ifp->if_data.ifi_hdrlen = sizeof(struct ether_vlan_header); + + /* Create local taskq. */ + TASK_INIT(&sc->alc_tx_task, 1, alc_tx_task, ifp); + 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)); + + 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)) { + ALC_LOCK(sc); + sc->alc_flags |= ALC_FLAG_DETACH; + alc_stop(sc); + ALC_UNLOCK(sc); + callout_drain(&sc->alc_tick_ch); + taskqueue_drain(sc->alc_tq, &sc->alc_int_task); + taskqueue_drain(sc->alc_tq, &sc->alc_tx_task); + ether_ifdetach(ifp); + } + + 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_QUAD(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"); [... truncated: 4205 lines follow ...]