svn commit: r193880 - in head/sys: amd64/conf boot/forth conf dev/alc i386/conf modules modules/alc

Pyun YongHyeon yongari at FreeBSD.org
Wed Jun 10 02:07:58 UTC 2009


Author: yongari
Date: Wed Jun 10 02:07:58 2009
New Revision: 193880
URL: http://svn.freebsd.org/changeset/base/193880

Log:
  Add alc(4), a driver for Atheros AR8131/AR8132 PCIe ethernet
  controller. These controllers are also known as L1C(AR8131) and
  L2C(AR8132) respectively. These controllers resembles the first
  generation controller L1 but usage of different descriptor format
  and new register mappings over L1 register space requires a new
  driver. There are a couple of registers I still don't understand
  but the driver seems to have no critical issues for performance and
  stability. Currently alc(4) supports the following hardware
  features.
    o MSI
    o TCP Segmentation offload
    o Hardware VLAN tag insertion/stripping
    o Tx/Rx interrupt moderation
    o Hardware statistics counters(dev.alc.%d.stats)
    o Jumbo frame
    o WOL
  AR8131/AR8132 also supports Tx checksum offloading but I disabled
  it due to stability issues. I'm not sure this comes from broken
  sample boards or hardware bugs. If you know your controller works
  without problems you can still enable it. The controller has a
  silicon bug for Rx checksum offloading, so the feature was not
  implemented.
  I'd like to say big thanks to Atheros. Atheros kindly sent sample
  boards to me and answered several questions I had.
  
  HW donated by:	Atheros Communications, Inc.

Added:
  head/sys/dev/alc/
  head/sys/dev/alc/if_alc.c   (contents, props changed)
  head/sys/dev/alc/if_alcreg.h   (contents, props changed)
  head/sys/dev/alc/if_alcvar.h   (contents, props changed)
  head/sys/modules/alc/
  head/sys/modules/alc/Makefile   (contents, props changed)
Modified:
  head/sys/amd64/conf/GENERIC
  head/sys/boot/forth/loader.conf
  head/sys/conf/NOTES
  head/sys/conf/files
  head/sys/i386/conf/GENERIC
  head/sys/modules/Makefile

Modified: head/sys/amd64/conf/GENERIC
==============================================================================
--- head/sys/amd64/conf/GENERIC	Wed Jun 10 01:26:25 2009	(r193879)
+++ head/sys/amd64/conf/GENERIC	Wed Jun 10 02:07:58 2009	(r193880)
@@ -213,6 +213,7 @@ device		vx		# 3Com 3c590, 3c595 (``Vorte
 device		miibus		# MII bus support
 device		ae		# Attansic/Atheros L2 FastEthernet
 device		age		# Attansic/Atheros L1 Gigabit Ethernet
+device		alc		# Atheros AR8131/AR8132 Ethernet
 device		ale		# Atheros AR8121/AR8113/AR8114 Ethernet
 device		bce		# Broadcom BCM5706/BCM5708 Gigabit Ethernet
 device		bfe		# Broadcom BCM440x 10/100 Ethernet

Modified: head/sys/boot/forth/loader.conf
==============================================================================
--- head/sys/boot/forth/loader.conf	Wed Jun 10 01:26:25 2009	(r193879)
+++ head/sys/boot/forth/loader.conf	Wed Jun 10 02:07:58 2009	(r193880)
@@ -210,6 +210,7 @@ pf_load="NO"			# packet filter
 miibus_load="NO"		# miibus support, needed for some drivers
 if_ae_load="NO"			# Attansic/Atheros L2 FastEthernet
 if_age_load="NO"		# Attansic/Atheros L1 Gigabit Ethernet
+if_alc_load="NO"		# Atheros AR8131/AR8132 Ethernet
 if_ale_load="NO"		# Atheros AR8121/AR8113/AR8114 Ethernet
 if_an_load="NO"			# Aironet 4500/4800 802.11 wireless NICs
 if_ar_load="NO"			# Digi SYNC/570i

Modified: head/sys/conf/NOTES
==============================================================================
--- head/sys/conf/NOTES	Wed Jun 10 01:26:25 2009	(r193879)
+++ head/sys/conf/NOTES	Wed Jun 10 02:07:58 2009	(r193880)
@@ -1754,6 +1754,7 @@ device		miibus
 #       L2 PCI-Express FastEthernet controllers.
 # age:  Support for gigabit ethernet adapters based on the Attansic/Atheros
 #       L1 PCI express gigabit ethernet controllers.
+# alc:  Support for Atheros AR8131/AR8132 PCIe ethernet controllers.
 # ale:  Support for Atheros AR8121/AR8113/AR8114 PCIe ethernet controllers.
 # bce:	Broadcom NetXtreme II (BCM5706/BCM5708) PCI/PCIe Gigabit Ethernet
 #       adapters.
@@ -1896,6 +1897,7 @@ device		xe
 # PCI Ethernet NICs that use the common MII bus controller code.
 device		ae		# Attansic/Atheros L2 FastEthernet
 device		age		# Attansic/Atheros L1 Gigabit Ethernet
+device		alc		# Atheros AR8131/AR8132 Ethernet
 device		ale		# Atheros AR8121/AR8113/AR8114 Ethernet
 device		bce		# Broadcom BCM5706/BCM5708 Gigabit Ethernet
 device		bfe		# Broadcom BCM440x 10/100 Ethernet

Modified: head/sys/conf/files
==============================================================================
--- head/sys/conf/files	Wed Jun 10 01:26:25 2009	(r193879)
+++ head/sys/conf/files	Wed Jun 10 02:07:58 2009	(r193880)
@@ -471,6 +471,7 @@ dev/aic7xxx/aic7xxx.c		optional ahc
 dev/aic7xxx/aic7xxx_93cx6.c	optional ahc
 dev/aic7xxx/aic7xxx_osm.c	optional ahc
 dev/aic7xxx/aic7xxx_pci.c	optional ahc pci
+dev/ale/if_alc.c		optional alc pci
 dev/ale/if_ale.c		optional ale pci
 dev/amd/amd.c			optional amd
 dev/amr/amr.c			optional amr

Added: head/sys/dev/alc/if_alc.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/dev/alc/if_alc.c	Wed Jun 10 02:07:58 2009	(r193880)
@@ -0,0 +1,3496 @@
+/*-
+ * Copyright (c) 2009, Pyun YongHyeon <yongari at FreeBSD.org>
+ * 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;
+		}
+	}
+	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");
+	/* 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",

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***


More information about the svn-src-all mailing list