svn commit: r184870 - in head/sys: amd64/conf boot/forth conf dev/ale i386/conf modules modules/ale

Pyun YongHyeon yongari at FreeBSD.org
Wed Nov 12 01:52:06 PST 2008


Author: yongari
Date: Wed Nov 12 09:52:06 2008
New Revision: 184870
URL: http://svn.freebsd.org/changeset/base/184870

Log:
  Add ale(4), a driver for Atheros AR8121/AR8113/AR8114 PCIe ethernet
  controller. The controller is also known as L1E(AR8121) and
  L2E(AR8113/AR8114). Unlike its predecessor Attansic L1,
  AR8121/AR8113/AR8114 uses completely different Rx logic such that
  it requires separate driver. Datasheet for AR81xx is not available
  to open source driver writers but it shares large part of Tx and
  PHY logic of L1. I still don't understand some part of register
  meaning and some MAC statistics counters but the driver seems to
  have no critical issues for performance and stability.
  
  The AR81xx requires copy operation to pass received frames to upper
  stack such that ale(4) consumes a lot of CPU cycles than that of
  other controller. A couple of silicon bugs also adds more CPU
  cycles to address the known hardware bug. However, if you have fast
  CPU you can still saturate the link.
  Currently ale(4) supports the following hardware features.
    - MSI.
    - TCP Segmentation offload.
    - Hardware VLAN tag insertion/stripping with checksum offload.
    - Tx TCP/UDP checksum offload and Rx IP/TCP/UDP checksum offload.
    - Tx/Rx interrupt moderation.
    - Hardware statistics counters.
    - Jumbo frame.
    - WOL.
  
  AR81xx PCIe ethernet controllers are mainly found on ASUS EeePC or
  P5Q series of ASUS motherboards. Special thanks to Jeremy Chadwick
  who sent the hardware to me. Without his donation writing a driver
  for AR81xx would never have been possible. Big thanks to all people
  who reported feedback or tested patches.
  
  HW donated by:	koitsu
  Tested by:	bsam, Joao Barros <joao.barros <> gmail DOT com >
  		Jan Henrik Sylvester <me <> janh DOT de >
  		Ivan Brawley < ivan <> brawley DOT id DOT au >,
  		CURRENT ML

Added:
  head/sys/dev/ale/
  head/sys/dev/ale/if_ale.c   (contents, props changed)
  head/sys/dev/ale/if_alereg.h   (contents, props changed)
  head/sys/dev/ale/if_alevar.h   (contents, props changed)
  head/sys/modules/ale/
  head/sys/modules/ale/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 Nov 12 09:38:18 2008	(r184869)
+++ head/sys/amd64/conf/GENERIC	Wed Nov 12 09:52:06 2008	(r184870)
@@ -202,6 +202,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		ale		# Atheros AR8121/AR8113/AR8114 Ethernet
 device		bce		# Broadcom BCM5706/BCM5708 Gigabit Ethernet
 device		bfe		# Broadcom BCM440x 10/100 Ethernet
 device		bge		# Broadcom BCM570xx Gigabit Ethernet

Modified: head/sys/boot/forth/loader.conf
==============================================================================
--- head/sys/boot/forth/loader.conf	Wed Nov 12 09:38:18 2008	(r184869)
+++ head/sys/boot/forth/loader.conf	Wed Nov 12 09:52:06 2008	(r184870)
@@ -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_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
 if_ath_load="NO"		# Atheros IEEE 802.11 wireless NICs

Modified: head/sys/conf/NOTES
==============================================================================
--- head/sys/conf/NOTES	Wed Nov 12 09:38:18 2008	(r184869)
+++ head/sys/conf/NOTES	Wed Nov 12 09:52:06 2008	(r184870)
@@ -1718,6 +1718,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.
+# ale:  Support for Atheros AR8121/AR8113/AR8114 PCIe ethernet controllers.
 # bce:	Broadcom NetXtreme II (BCM5706/BCM5708) PCI/PCIe Gigabit Ethernet
 #       adapters.
 # bfe:	Broadcom BCM4401 Ethernet adapter.
@@ -1859,6 +1860,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		ale		# Atheros AR8121/AR8113/AR8114 Ethernet
 device		bce		# Broadcom BCM5706/BCM5708 Gigabit Ethernet
 device		bfe		# Broadcom BCM440x 10/100 Ethernet
 device		bge		# Broadcom BCM570xx Gigabit Ethernet

Modified: head/sys/conf/files
==============================================================================
--- head/sys/conf/files	Wed Nov 12 09:38:18 2008	(r184869)
+++ head/sys/conf/files	Wed Nov 12 09:52:06 2008	(r184870)
@@ -448,6 +448,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_ale.c		optional ale pci
 dev/amd/amd.c			optional amd
 dev/amr/amr.c			optional amr
 dev/amr/amr_cam.c		optional amrp amr

Added: head/sys/dev/ale/if_ale.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/dev/ale/if_ale.c	Wed Nov 12 09:52:06 2008	(r184870)
@@ -0,0 +1,3075 @@
+/*-
+ * Copyright (c) 2008, 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 AR8121/AR8113/AR8114 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/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/module.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/ale/if_alereg.h>
+#include <dev/ale/if_alevar.h>
+
+/* "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)
+#ifndef	IFCAP_VLAN_HWTSO
+#define	IFCAP_VLAN_HWTSO	0
+#endif
+
+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 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_link_task(void *, int);
+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_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_tx_task(void *, int);
+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),
+
+	{ NULL, NULL }
+};
+
+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, 0, 0);
+DRIVER_MODULE(miibus, ale, miibus_driver, miibus_devclass, 0, 0);
+
+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);
+
+	if (phy != sc->ale_phyaddr)
+		return (0);
+
+	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);
+
+	if (phy != sc->ale_phyaddr)
+		return (0);
+
+	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;
+
+	sc = device_get_softc(dev);
+
+	taskqueue_enqueue(taskqueue_swi, &sc->ale_link_task);
+}
+
+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);
+	mii = device_get_softc(sc->ale_miibus);
+
+	mii_pollstat(mii);
+	ALE_UNLOCK(sc);
+	ifmr->ifm_status = mii->mii_media_status;
+	ifmr->ifm_active = mii->mii_media_active;
+}
+
+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);
+	if (mii->mii_instance != 0) {
+		LIST_FOREACH(miisc, &mii->mii_phys, mii_list)
+			mii_phy_reset(miisc);
+	}
+	error = mii_mediachg(mii);
+	ALE_UNLOCK(sc);
+
+	return (error);
+}
+
+static int
+ale_probe(device_t dev)
+{
+	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_extcap(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);
+}
+
+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);
+	TASK_INIT(&sc->ale_link_task, 0, ale_link_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 (msic == 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_extcap(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))
+		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_extcap(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. */
+	if ((error = mii_phy_probe(dev, &sc->ale_miibus, ale_mediachange,
+	    ale_mediastatus)) != 0) {
+		device_printf(dev, "no PHY found!\n");
+		goto fail;
+	}
+
+	ether_ifattach(ifp, sc->ale_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;
+
+	/* 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->ale_tx_task, 1, ale_tx_task, ifp);
+	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)) {
+		ALE_LOCK(sc);
+		sc->ale_flags |= ALE_FLAG_DETACH;
+		ale_stop(sc);
+		ALE_UNLOCK(sc);
+		callout_drain(&sc->ale_tick_ch);
+		taskqueue_drain(sc->ale_tq, &sc->ale_int_task);
+		taskqueue_drain(sc->ale_tq, &sc->ale_tx_task);
+		taskqueue_drain(taskqueue_swi, &sc->ale_link_task);
+		ether_ifdetach(ifp);
+	}
+
+	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 > 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, "abort",
+	    &stats->tx_abort, "Aborted frames due to 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])) ||

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


More information about the svn-src-head mailing list