Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 13 Jun 2017 18:46:29 +0000 (UTC)
From:      Zbigniew Bodek <zbb@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r319907 - in head/sys: arm/mv conf dev/neta
Message-ID:  <201706131846.v5DIkTA7030597@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: zbb
Date: Tue Jun 13 18:46:29 2017
New Revision: 319907
URL: https://svnweb.freebsd.org/changeset/base/319907

Log:
  Introduce Armada 38x/XP network controller support
  
  This patch contains a new driver for the network unit of Marvell
  Armada 38x/XP SoCs, called NETA. This support was thoroughly tested
  and optimised in terms of stability and performance. Additional
  hardware features, like Buffer Management (BM) or Parser and Classifier
  (PnC) will be progressively supported as needed.
  
  Submitted by: Fabien Thomas <fabien.thomas@stormshield.eu>
  	      Arnaud Ysmal <arnaud.ysmal@stormshield.eu>
  	      Zbigniew Bodek <zbb@semihalf.com>
  	      Michal Mazur <mkm@semihalf.com>
  	      Bartosz Szczepanek <bsz@semihalf.com>
  	      Marcin Wojtas <mw@semihalf.com>
  
  Obtained from:	Semihalf
  Sponsored by:	Stormshield (main development)
  		Netgate (cleanup and upstreaming)
  Differential revision: https://reviews.freebsd.org/D10706

Added:
  head/sys/dev/neta/
  head/sys/dev/neta/if_mvneta.c   (contents, props changed)
  head/sys/dev/neta/if_mvneta_fdt.c   (contents, props changed)
  head/sys/dev/neta/if_mvnetareg.h   (contents, props changed)
  head/sys/dev/neta/if_mvnetavar.h   (contents, props changed)
Modified:
  head/sys/arm/mv/files.mv
  head/sys/arm/mv/mv_common.c
  head/sys/arm/mv/mvwin.h
  head/sys/conf/options

Modified: head/sys/arm/mv/files.mv
==============================================================================
--- head/sys/arm/mv/files.mv	Tue Jun 13 18:35:14 2017	(r319906)
+++ head/sys/arm/mv/files.mv	Tue Jun 13 18:46:29 2017	(r319907)
@@ -24,6 +24,8 @@ arm/mv/timer.c			optional	!soc_mv_armada38x
 dev/cesa/cesa.c			optional	cesa
 dev/iicbus/twsi/mv_twsi.c	optional	twsi
 dev/mge/if_mge.c		optional	mge
+dev/neta/if_mvneta_fdt.c	optional	neta fdt
+dev/neta/if_mvneta.c		optional	neta mdio mii
 dev/nand/nfc_mv.c		optional	nand
 dev/mvs/mvs_soc.c		optional	mvs
 dev/uart/uart_dev_ns8250.c	optional	uart

Modified: head/sys/arm/mv/mv_common.c
==============================================================================
--- head/sys/arm/mv/mv_common.c	Tue Jun 13 18:35:14 2017	(r319906)
+++ head/sys/arm/mv/mv_common.c	Tue Jun 13 18:46:29 2017	(r319907)
@@ -96,6 +96,7 @@ static void decode_win_cesa_setup(u_long);
 static void decode_win_usb_setup(u_long);
 static void decode_win_usb3_setup(u_long);
 static void decode_win_eth_setup(u_long);
+static void decode_win_neta_setup(u_long);
 static void decode_win_sata_setup(u_long);
 static void decode_win_ahci_setup(u_long);
 static void decode_win_sdhci_setup(u_long);
@@ -107,6 +108,7 @@ static void decode_win_cesa_dump(u_long);
 static void decode_win_usb_dump(u_long);
 static void decode_win_usb3_dump(u_long);
 static void decode_win_eth_dump(u_long base);
+static void decode_win_neta_dump(u_long base);
 static void decode_win_idma_dump(u_long base);
 static void decode_win_xor_dump(u_long base);
 static void decode_win_ahci_dump(u_long base);
@@ -152,6 +154,7 @@ struct soc_node_spec {
 
 static struct soc_node_spec soc_nodes[] = {
 	{ "mrvl,ge", &decode_win_eth_setup, &decode_win_eth_dump },
+	{ "marvell,armada-370-neta", &decode_win_neta_setup, &decode_win_neta_dump },
 	{ "mrvl,usb-ehci", &decode_win_usb_setup, &decode_win_usb_dump },
 	{ "marvell,orion-ehci", &decode_win_usb_setup, &decode_win_usb_dump },
 	{ "marvell,armada-380-xhci", &decode_win_usb3_setup, &decode_win_usb3_dump },
@@ -1431,6 +1434,20 @@ decode_win_eth_setup(u_long base)
 				break;
 			}
 		}
+}
+
+static void
+decode_win_neta_dump(u_long base)
+{
+
+	decode_win_eth_dump(base + MV_WIN_NETA_OFFSET);
+}
+
+static void
+decode_win_neta_setup(u_long base)
+{
+
+	decode_win_eth_setup(base + MV_WIN_NETA_OFFSET);
 }
 
 static int

Modified: head/sys/arm/mv/mvwin.h
==============================================================================
--- head/sys/arm/mv/mvwin.h	Tue Jun 13 18:35:14 2017	(r319906)
+++ head/sys/arm/mv/mvwin.h	Tue Jun 13 18:46:29 2017	(r319907)
@@ -229,6 +229,9 @@
 #define	MV_WIN_USB3_BASE(n)		(0x8 * (n) + 0x4004)
 #define	MV_WIN_USB3_MAX			8
 
+#define	MV_WIN_NETA_OFFSET		0x2000
+#define	MV_WIN_NETA_BASE(n)		MV_WIN_ETH_BASE(n) + MV_WIN_NETA_OFFSET
+
 #define MV_WIN_ETH_BASE(n)		(0x8 * (n) + 0x200)
 #define MV_WIN_ETH_SIZE(n)		(0x8 * (n) + 0x204)
 #define MV_WIN_ETH_REMAP(n)		(0x4 * (n) + 0x280)
@@ -325,6 +328,7 @@
 /* IO Window Control Register fields */
 #define	IO_WIN_SIZE_SHIFT	16
 #define	IO_WIN_SIZE_MASK	0xFFFF
+#define	IO_WIN_COH_ATTR_MASK	(0xF << 12)
 #define	IO_WIN_ATTR_SHIFT	8
 #define	IO_WIN_ATTR_MASK	0xFF
 #define	IO_WIN_TGT_SHIFT	4

Modified: head/sys/conf/options
==============================================================================
--- head/sys/conf/options	Tue Jun 13 18:35:14 2017	(r319906)
+++ head/sys/conf/options	Tue Jun 13 18:46:29 2017	(r319907)
@@ -872,6 +872,10 @@ MWL_DIAGAPI		opt_mwl.h
 MWL_AGGR_SIZE		opt_mwl.h
 MWL_TX_NODROP		opt_mwl.h
 
+# Options for the Marvell NETA driver
+MVNETA_MULTIQUEUE	opt_mvneta.h
+MVNETA_KTR		opt_mvneta.h
+
 # Options for the Intel 802.11ac wireless driver
 IWM_DEBUG		opt_iwm.h
 

Added: head/sys/dev/neta/if_mvneta.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/dev/neta/if_mvneta.c	Tue Jun 13 18:46:29 2017	(r319907)
@@ -0,0 +1,3570 @@
+/*
+ * Copyright (c) 2017 Stormshield.
+ * Copyright (c) 2017 Semihalf.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "opt_platform.h"
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/endian.h>
+#include <sys/mbuf.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/smp.h>
+#include <sys/taskqueue.h>
+#ifdef MVNETA_KTR
+#include <sys/ktr.h>
+#endif
+
+#include <net/ethernet.h>
+#include <net/bpf.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+#include <net/if_vlan_var.h>
+
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/tcp_lro.h>
+
+#include <sys/sockio.h>
+#include <sys/bus.h>
+#include <machine/bus.h>
+#include <sys/rman.h>
+#include <machine/resource.h>
+
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/mdio/mdio.h>
+
+#include <arm/mv/mvreg.h>
+#include <arm/mv/mvvar.h>
+#include <arm/mv/mvwin.h>
+
+#include "if_mvnetareg.h"
+#include "if_mvnetavar.h"
+
+#include "miibus_if.h"
+#include "mdio_if.h"
+
+#ifdef MVNETA_DEBUG
+#define	STATIC /* nothing */
+#else
+#define	STATIC static
+#endif
+
+#define	DASSERT(x) KASSERT((x), (#x))
+
+/* Device Register Initialization */
+STATIC int mvneta_initreg(struct ifnet *);
+
+/* Descriptor Ring Control for each of queues */
+STATIC int mvneta_ring_alloc_rx_queue(struct mvneta_softc *, int);
+STATIC int mvneta_ring_alloc_tx_queue(struct mvneta_softc *, int);
+STATIC void mvneta_ring_dealloc_rx_queue(struct mvneta_softc *, int);
+STATIC void mvneta_ring_dealloc_tx_queue(struct mvneta_softc *, int);
+STATIC int mvneta_ring_init_rx_queue(struct mvneta_softc *, int);
+STATIC int mvneta_ring_init_tx_queue(struct mvneta_softc *, int);
+STATIC void mvneta_ring_flush_rx_queue(struct mvneta_softc *, int);
+STATIC void mvneta_ring_flush_tx_queue(struct mvneta_softc *, int);
+STATIC void mvneta_dmamap_cb(void *, bus_dma_segment_t *, int, int);
+STATIC int mvneta_dma_create(struct mvneta_softc *);
+
+/* Rx/Tx Queue Control */
+STATIC int mvneta_rx_queue_init(struct ifnet *, int);
+STATIC int mvneta_tx_queue_init(struct ifnet *, int);
+STATIC int mvneta_rx_queue_enable(struct ifnet *, int);
+STATIC int mvneta_tx_queue_enable(struct ifnet *, int);
+STATIC void mvneta_rx_lockq(struct mvneta_softc *, int);
+STATIC void mvneta_rx_unlockq(struct mvneta_softc *, int);
+STATIC void mvneta_tx_lockq(struct mvneta_softc *, int);
+STATIC void mvneta_tx_unlockq(struct mvneta_softc *, int);
+
+/* Interrupt Handlers */
+STATIC void mvneta_disable_intr(struct mvneta_softc *);
+STATIC void mvneta_enable_intr(struct mvneta_softc *);
+STATIC void mvneta_rxtxth_intr(void *);
+STATIC int mvneta_misc_intr(struct mvneta_softc *);
+STATIC void mvneta_tick(void *);
+/* struct ifnet and mii callbacks*/
+STATIC int mvneta_xmitfast_locked(struct mvneta_softc *, int, struct mbuf **);
+STATIC int mvneta_xmit_locked(struct mvneta_softc *, int);
+#ifdef MVNETA_MULTIQUEUE
+STATIC int mvneta_transmit(struct ifnet *, struct mbuf *);
+#else /* !MVNETA_MULTIQUEUE */
+STATIC void mvneta_start(struct ifnet *);
+#endif
+STATIC void mvneta_qflush(struct ifnet *);
+STATIC void mvneta_tx_task(void *, int);
+STATIC int mvneta_ioctl(struct ifnet *, u_long, caddr_t);
+STATIC void mvneta_init(void *);
+STATIC void mvneta_init_locked(void *);
+STATIC void mvneta_stop(struct mvneta_softc *);
+STATIC void mvneta_stop_locked(struct mvneta_softc *);
+STATIC int mvneta_mediachange(struct ifnet *);
+STATIC void mvneta_mediastatus(struct ifnet *, struct ifmediareq *);
+STATIC void mvneta_portup(struct mvneta_softc *);
+STATIC void mvneta_portdown(struct mvneta_softc *);
+
+/* Link State Notify */
+STATIC void mvneta_update_autoneg(struct mvneta_softc *, int);
+STATIC int mvneta_update_media(struct mvneta_softc *, int);
+STATIC void mvneta_adjust_link(struct mvneta_softc *);
+STATIC void mvneta_update_eee(struct mvneta_softc *);
+STATIC void mvneta_update_fc(struct mvneta_softc *);
+STATIC void mvneta_link_isr(struct mvneta_softc *);
+STATIC void mvneta_linkupdate(struct mvneta_softc *, boolean_t);
+STATIC void mvneta_linkup(struct mvneta_softc *);
+STATIC void mvneta_linkdown(struct mvneta_softc *);
+STATIC void mvneta_linkreset(struct mvneta_softc *);
+
+/* Tx Subroutines */
+STATIC int mvneta_tx_queue(struct mvneta_softc *, struct mbuf **, int);
+STATIC void mvneta_tx_set_csumflag(struct ifnet *,
+    struct mvneta_tx_desc *, struct mbuf *);
+STATIC void mvneta_tx_queue_complete(struct mvneta_softc *, int);
+STATIC void mvneta_tx_drain(struct mvneta_softc *);
+
+/* Rx Subroutines */
+STATIC int mvneta_rx(struct mvneta_softc *, int, int);
+STATIC void mvneta_rx_queue(struct mvneta_softc *, int, int);
+STATIC void mvneta_rx_queue_refill(struct mvneta_softc *, int);
+STATIC void mvneta_rx_set_csumflag(struct ifnet *,
+    struct mvneta_rx_desc *, struct mbuf *);
+STATIC void mvneta_rx_buf_free(struct mvneta_softc *, struct mvneta_buf *);
+
+/* MAC address filter */
+STATIC void mvneta_filter_setup(struct mvneta_softc *);
+
+/* sysctl(9) */
+STATIC int sysctl_read_mib(SYSCTL_HANDLER_ARGS);
+STATIC int sysctl_clear_mib(SYSCTL_HANDLER_ARGS);
+STATIC int sysctl_set_queue_rxthtime(SYSCTL_HANDLER_ARGS);
+STATIC void sysctl_mvneta_init(struct mvneta_softc *);
+
+/* MIB */
+STATIC void mvneta_clear_mib(struct mvneta_softc *);
+STATIC void mvneta_update_mib(struct mvneta_softc *);
+
+/* Switch */
+STATIC boolean_t mvneta_has_switch(device_t);
+
+#define	mvneta_sc_lock(sc) mtx_lock(&sc->mtx)
+#define	mvneta_sc_unlock(sc) mtx_unlock(&sc->mtx)
+
+STATIC struct mtx mii_mutex;
+STATIC int mii_init = 0;
+
+/* Device */
+STATIC int mvneta_detach(device_t);
+/* MII */
+STATIC int mvneta_miibus_readreg(device_t, int, int);
+STATIC int mvneta_miibus_writereg(device_t, int, int, int);
+
+static device_method_t mvneta_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_detach,	mvneta_detach),
+	/* MII interface */
+	DEVMETHOD(miibus_readreg,       mvneta_miibus_readreg),
+	DEVMETHOD(miibus_writereg,      mvneta_miibus_writereg),
+	/* MDIO interface */
+	DEVMETHOD(mdio_readreg,		mvneta_miibus_readreg),
+	DEVMETHOD(mdio_writereg,	mvneta_miibus_writereg),
+
+	/* End */
+	DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(mvneta, mvneta_driver, mvneta_methods, sizeof(struct mvneta_softc));
+
+DRIVER_MODULE(miibus, mvneta, miibus_driver, miibus_devclass, 0, 0);
+DRIVER_MODULE(mdio, mvneta, mdio_driver, mdio_devclass, 0, 0);
+MODULE_DEPEND(mvneta, mdio, 1, 1, 1);
+MODULE_DEPEND(mvneta, ether, 1, 1, 1);
+MODULE_DEPEND(mvneta, miibus, 1, 1, 1);
+MODULE_DEPEND(mvneta, mvxpbm, 1, 1, 1);
+
+/*
+ * List of MIB register and names
+ */
+enum mvneta_mib_idx
+{
+	MVNETA_MIB_RX_GOOD_OCT_IDX,
+	MVNETA_MIB_RX_BAD_OCT_IDX,
+	MVNETA_MIB_TX_MAC_TRNS_ERR_IDX,
+	MVNETA_MIB_RX_GOOD_FRAME_IDX,
+	MVNETA_MIB_RX_BAD_FRAME_IDX,
+	MVNETA_MIB_RX_BCAST_FRAME_IDX,
+	MVNETA_MIB_RX_MCAST_FRAME_IDX,
+	MVNETA_MIB_RX_FRAME64_OCT_IDX,
+	MVNETA_MIB_RX_FRAME127_OCT_IDX,
+	MVNETA_MIB_RX_FRAME255_OCT_IDX,
+	MVNETA_MIB_RX_FRAME511_OCT_IDX,
+	MVNETA_MIB_RX_FRAME1023_OCT_IDX,
+	MVNETA_MIB_RX_FRAMEMAX_OCT_IDX,
+	MVNETA_MIB_TX_GOOD_OCT_IDX,
+	MVNETA_MIB_TX_GOOD_FRAME_IDX,
+	MVNETA_MIB_TX_EXCES_COL_IDX,
+	MVNETA_MIB_TX_MCAST_FRAME_IDX,
+	MVNETA_MIB_TX_BCAST_FRAME_IDX,
+	MVNETA_MIB_TX_MAC_CTL_ERR_IDX,
+	MVNETA_MIB_FC_SENT_IDX,
+	MVNETA_MIB_FC_GOOD_IDX,
+	MVNETA_MIB_FC_BAD_IDX,
+	MVNETA_MIB_PKT_UNDERSIZE_IDX,
+	MVNETA_MIB_PKT_FRAGMENT_IDX,
+	MVNETA_MIB_PKT_OVERSIZE_IDX,
+	MVNETA_MIB_PKT_JABBER_IDX,
+	MVNETA_MIB_MAC_RX_ERR_IDX,
+	MVNETA_MIB_MAC_CRC_ERR_IDX,
+	MVNETA_MIB_MAC_COL_IDX,
+	MVNETA_MIB_MAC_LATE_COL_IDX,
+};
+
+STATIC struct mvneta_mib_def {
+	uint32_t regnum;
+	int reg64;
+	const char *sysctl_name;
+	const char *desc;
+} mvneta_mib_list[] = {
+	[MVNETA_MIB_RX_GOOD_OCT_IDX] = {MVNETA_MIB_RX_GOOD_OCT, 1,
+	    "rx_good_oct", "Good Octets Rx"},
+	[MVNETA_MIB_RX_BAD_OCT_IDX] = {MVNETA_MIB_RX_BAD_OCT, 0,
+	    "rx_bad_oct", "Bad  Octets Rx"},
+	[MVNETA_MIB_TX_MAC_TRNS_ERR_IDX] = {MVNETA_MIB_TX_MAC_TRNS_ERR, 0,
+	    "tx_mac_err", "MAC Transmit Error"},
+	[MVNETA_MIB_RX_GOOD_FRAME_IDX] = {MVNETA_MIB_RX_GOOD_FRAME, 0,
+	    "rx_good_frame", "Good Frames Rx"},
+	[MVNETA_MIB_RX_BAD_FRAME_IDX] = {MVNETA_MIB_RX_BAD_FRAME, 0,
+	    "rx_bad_frame", "Bad Frames Rx"},
+	[MVNETA_MIB_RX_BCAST_FRAME_IDX] = {MVNETA_MIB_RX_BCAST_FRAME, 0,
+	    "rx_bcast_frame", "Broadcast Frames Rx"},
+	[MVNETA_MIB_RX_MCAST_FRAME_IDX] = {MVNETA_MIB_RX_MCAST_FRAME, 0,
+	    "rx_mcast_frame", "Multicast Frames Rx"},
+	[MVNETA_MIB_RX_FRAME64_OCT_IDX] = {MVNETA_MIB_RX_FRAME64_OCT, 0,
+	    "rx_frame_1_64", "Frame Size    1 -   64"},
+	[MVNETA_MIB_RX_FRAME127_OCT_IDX] = {MVNETA_MIB_RX_FRAME127_OCT, 0,
+	    "rx_frame_65_127", "Frame Size   65 -  127"},
+	[MVNETA_MIB_RX_FRAME255_OCT_IDX] = {MVNETA_MIB_RX_FRAME255_OCT, 0,
+	    "rx_frame_128_255", "Frame Size  128 -  255"},
+	[MVNETA_MIB_RX_FRAME511_OCT_IDX] = {MVNETA_MIB_RX_FRAME511_OCT, 0,
+	    "rx_frame_256_511", "Frame Size  256 -  511"},
+	[MVNETA_MIB_RX_FRAME1023_OCT_IDX] = {MVNETA_MIB_RX_FRAME1023_OCT, 0,
+	    "rx_frame_512_1023", "Frame Size  512 - 1023"},
+	[MVNETA_MIB_RX_FRAMEMAX_OCT_IDX] = {MVNETA_MIB_RX_FRAMEMAX_OCT, 0,
+	    "rx_fame_1024_max", "Frame Size 1024 -  Max"},
+	[MVNETA_MIB_TX_GOOD_OCT_IDX] = {MVNETA_MIB_TX_GOOD_OCT, 1,
+	    "tx_good_oct", "Good Octets Tx"},
+	[MVNETA_MIB_TX_GOOD_FRAME_IDX] = {MVNETA_MIB_TX_GOOD_FRAME, 0,
+	    "tx_good_frame", "Good Frames Tx"},
+	[MVNETA_MIB_TX_EXCES_COL_IDX] = {MVNETA_MIB_TX_EXCES_COL, 0,
+	    "tx_exces_collision", "Excessive Collision"},
+	[MVNETA_MIB_TX_MCAST_FRAME_IDX] = {MVNETA_MIB_TX_MCAST_FRAME, 0,
+	    "tx_mcast_frame", "Multicast Frames Tx"},
+	[MVNETA_MIB_TX_BCAST_FRAME_IDX] = {MVNETA_MIB_TX_BCAST_FRAME, 0,
+	    "tx_bcast_frame", "Broadcast Frames Tx"},
+	[MVNETA_MIB_TX_MAC_CTL_ERR_IDX] = {MVNETA_MIB_TX_MAC_CTL_ERR, 0,
+	    "tx_mac_ctl_err", "Unknown MAC Control"},
+	[MVNETA_MIB_FC_SENT_IDX] = {MVNETA_MIB_FC_SENT, 0,
+	    "fc_tx", "Flow Control Tx"},
+	[MVNETA_MIB_FC_GOOD_IDX] = {MVNETA_MIB_FC_GOOD, 0,
+	    "fc_rx_good", "Good Flow Control Rx"},
+	[MVNETA_MIB_FC_BAD_IDX] = {MVNETA_MIB_FC_BAD, 0,
+	    "fc_rx_bad", "Bad Flow Control Rx"},
+	[MVNETA_MIB_PKT_UNDERSIZE_IDX] = {MVNETA_MIB_PKT_UNDERSIZE, 0,
+	    "pkt_undersize", "Undersized Packets Rx"},
+	[MVNETA_MIB_PKT_FRAGMENT_IDX] = {MVNETA_MIB_PKT_FRAGMENT, 0,
+	    "pkt_fragment", "Fragmented Packets Rx"},
+	[MVNETA_MIB_PKT_OVERSIZE_IDX] = {MVNETA_MIB_PKT_OVERSIZE, 0,
+	    "pkt_oversize", "Oversized Packets Rx"},
+	[MVNETA_MIB_PKT_JABBER_IDX] = {MVNETA_MIB_PKT_JABBER, 0,
+	    "pkt_jabber", "Jabber Packets Rx"},
+	[MVNETA_MIB_MAC_RX_ERR_IDX] = {MVNETA_MIB_MAC_RX_ERR, 0,
+	    "mac_rx_err", "MAC Rx Errors"},
+	[MVNETA_MIB_MAC_CRC_ERR_IDX] = {MVNETA_MIB_MAC_CRC_ERR, 0,
+	    "mac_crc_err", "MAC CRC Errors"},
+	[MVNETA_MIB_MAC_COL_IDX] = {MVNETA_MIB_MAC_COL, 0,
+	    "mac_collision", "MAC Collision"},
+	[MVNETA_MIB_MAC_LATE_COL_IDX] = {MVNETA_MIB_MAC_LATE_COL, 0,
+	    "mac_late_collision", "MAC Late Collision"},
+};
+
+static struct resource_spec res_spec[] = {
+	{ SYS_RES_MEMORY, 0, RF_ACTIVE },
+	{ SYS_RES_IRQ, 0, RF_ACTIVE },
+	{ -1, 0}
+};
+
+static struct {
+	driver_intr_t *handler;
+	char * description;
+} mvneta_intrs[] = {
+	{ mvneta_rxtxth_intr, "MVNETA aggregated interrupt" },
+};
+
+static int
+mvneta_set_mac_address(struct mvneta_softc *sc, uint8_t *addr)
+{
+	unsigned int mac_h;
+	unsigned int mac_l;
+
+	mac_l = (addr[4] << 8) | (addr[5]);
+	mac_h = (addr[0] << 24) | (addr[1] << 16) |
+	    (addr[2] << 8) | (addr[3] << 0);
+
+	MVNETA_WRITE(sc, MVNETA_MACAL, mac_l);
+	MVNETA_WRITE(sc, MVNETA_MACAH, mac_h);
+	return (0);
+}
+
+static int
+mvneta_get_mac_address(struct mvneta_softc *sc, uint8_t *addr)
+{
+	uint32_t mac_l, mac_h;
+
+#ifdef FDT
+	if (mvneta_fdt_mac_address(sc, addr) == 0)
+		return (0);
+#endif
+	/*
+	 * Fall back -- use the currently programmed address.
+	 */
+	mac_l = MVNETA_READ(sc, MVNETA_MACAL);
+	mac_h = MVNETA_READ(sc, MVNETA_MACAH);
+	if (mac_l == 0 && mac_h == 0) {
+		/*
+		 * Generate pseudo-random MAC.
+		 * Set lower part to random number | unit number.
+		 */
+		mac_l = arc4random() & ~0xff;
+		mac_l |= device_get_unit(sc->dev) & 0xff;
+		mac_h = arc4random();
+		mac_h &= ~(3 << 24);	/* Clear multicast and LAA bits */
+		if (bootverbose) {
+			device_printf(sc->dev,
+			    "Could not acquire MAC address. "
+			    "Using randomized one.\n");
+		}
+	}
+
+	addr[0] = (mac_h & 0xff000000) >> 24;
+	addr[1] = (mac_h & 0x00ff0000) >> 16;
+	addr[2] = (mac_h & 0x0000ff00) >> 8;
+	addr[3] = (mac_h & 0x000000ff);
+	addr[4] = (mac_l & 0x0000ff00) >> 8;
+	addr[5] = (mac_l & 0x000000ff);
+	return (0);
+}
+
+STATIC boolean_t
+mvneta_has_switch(device_t self)
+{
+	phandle_t node, switch_node, switch_eth, switch_eth_handle;
+
+	node = ofw_bus_get_node(self);
+	switch_node =
+	    ofw_bus_find_compatible(OF_finddevice("/"), "marvell,dsa");
+	switch_eth = 0;
+
+	OF_getencprop(switch_node, "dsa,ethernet",
+	    (void*)&switch_eth_handle, sizeof(switch_eth_handle));
+
+	if (switch_eth_handle > 0)
+		switch_eth = OF_node_from_xref(switch_eth_handle);
+
+	/* Return true if dsa,ethernet cell points to us */
+	return (node == switch_eth);
+}
+
+STATIC int
+mvneta_dma_create(struct mvneta_softc *sc)
+{
+	size_t maxsize, maxsegsz;
+	size_t q;
+	int error;
+
+	/*
+	 * Create Tx DMA
+	 */
+	maxsize = maxsegsz = sizeof(struct mvneta_tx_desc) * MVNETA_TX_RING_CNT;
+
+	error = bus_dma_tag_create(
+	    bus_get_dma_tag(sc->dev),		/* parent */
+	    16, 0,                              /* alignment, boundary */
+	    BUS_SPACE_MAXADDR_32BIT,            /* lowaddr */
+	    BUS_SPACE_MAXADDR,                  /* highaddr */
+	    NULL, NULL,                         /* filtfunc, filtfuncarg */
+	    maxsize,				/* maxsize */
+	    1,					/* nsegments */
+	    maxsegsz,				/* maxsegsz */
+	    0,					/* flags */
+	    NULL, NULL,				/* lockfunc, lockfuncarg */
+	    &sc->tx_dtag);			/* dmat */
+	if (error != 0) {
+		device_printf(sc->dev,
+		    "Failed to create DMA tag for Tx descriptors.\n");
+		goto fail;
+	}
+	error = bus_dma_tag_create(
+	    bus_get_dma_tag(sc->dev),		/* parent */
+	    1, 0,				/* alignment, boundary */
+	    BUS_SPACE_MAXADDR_32BIT,		/* lowaddr */
+	    BUS_SPACE_MAXADDR,			/* highaddr */
+	    NULL, NULL,				/* filtfunc, filtfuncarg */
+	    MVNETA_PACKET_SIZE,			/* maxsize */
+	    MVNETA_TX_SEGLIMIT,			/* nsegments */
+	    MVNETA_PACKET_SIZE,			/* maxsegsz */
+	    BUS_DMA_ALLOCNOW,			/* flags */
+	    NULL, NULL,				/* lockfunc, lockfuncarg */
+	    &sc->txmbuf_dtag);
+	if (error != 0) {
+		device_printf(sc->dev,
+		    "Failed to create DMA tag for Tx mbufs.\n");
+		goto fail;
+	}
+
+	for (q = 0; q < MVNETA_TX_QNUM_MAX; q++) {
+		error = mvneta_ring_alloc_tx_queue(sc, q);
+		if (error != 0) {
+			device_printf(sc->dev,
+			    "Failed to allocate DMA safe memory for TxQ: %d\n", q);
+			goto fail;
+		}
+	}
+
+	/*
+	 * Create Rx DMA.
+	 */
+	/* Create tag for Rx descripors */
+	error = bus_dma_tag_create(
+	    bus_get_dma_tag(sc->dev),		/* parent */
+	    32, 0,                              /* alignment, boundary */
+	    BUS_SPACE_MAXADDR_32BIT,            /* lowaddr */
+	    BUS_SPACE_MAXADDR,                  /* highaddr */
+	    NULL, NULL,                         /* filtfunc, filtfuncarg */
+	    sizeof(struct mvneta_rx_desc) * MVNETA_RX_RING_CNT, /* maxsize */
+	    1,					/* nsegments */
+	    sizeof(struct mvneta_rx_desc) * MVNETA_RX_RING_CNT, /* maxsegsz */
+	    0,					/* flags */
+	    NULL, NULL,				/* lockfunc, lockfuncarg */
+	    &sc->rx_dtag);			/* dmat */
+	if (error != 0) {
+		device_printf(sc->dev,
+		    "Failed to create DMA tag for Rx descriptors.\n");
+		goto fail;
+	}
+
+	/* Create tag for Rx buffers */
+	error = bus_dma_tag_create(
+	    bus_get_dma_tag(sc->dev),		/* parent */
+	    32, 0,				/* alignment, boundary */
+	    BUS_SPACE_MAXADDR_32BIT,		/* lowaddr */
+	    BUS_SPACE_MAXADDR,			/* highaddr */
+	    NULL, NULL,				/* filtfunc, filtfuncarg */
+	    MVNETA_PACKET_SIZE, 1,		/* maxsize, nsegments */
+	    MVNETA_PACKET_SIZE,			/* maxsegsz */
+	    0,					/* flags */
+	    NULL, NULL,				/* lockfunc, lockfuncarg */
+	    &sc->rxbuf_dtag);			/* dmat */
+	if (error != 0) {
+		device_printf(sc->dev,
+		    "Failed to create DMA tag for Rx buffers.\n");
+		goto fail;
+	}
+
+	for (q = 0; q < MVNETA_RX_QNUM_MAX; q++) {
+		if (mvneta_ring_alloc_rx_queue(sc, q) != 0) {
+			device_printf(sc->dev,
+			    "Failed to allocate DMA safe memory for RxQ: %d\n", q);
+			goto fail;
+		}
+	}
+
+	return (0);
+fail:
+	mvneta_detach(sc->dev);
+
+	return (error);
+}
+
+/* ARGSUSED */
+int
+mvneta_attach(device_t self)
+{
+	struct mvneta_softc *sc;
+	struct ifnet *ifp;
+	device_t child;
+	int ifm_target;
+	int q, error;
+	uint32_t reg;
+
+	sc = device_get_softc(self);
+	sc->dev = self;
+
+	mtx_init(&sc->mtx, "mvneta_sc", NULL, MTX_DEF);
+
+	error = bus_alloc_resources(self, res_spec, sc->res);
+	if (error) {
+		device_printf(self, "could not allocate resources\n");
+		return (ENXIO);
+	}
+
+	sc->version = MVNETA_READ(sc, MVNETA_PV);
+	device_printf(self, "version is %x\n", sc->version);
+	callout_init(&sc->tick_ch, 0);
+
+	/*
+	 * make sure DMA engines are in reset state
+	 */
+	MVNETA_WRITE(sc, MVNETA_PRXINIT, 0x00000001);
+	MVNETA_WRITE(sc, MVNETA_PTXINIT, 0x00000001);
+
+	/*
+	 * Disable port snoop for buffers and descriptors
+	 * to avoid L2 caching of both without DRAM copy.
+	 * Obtain coherency settings from the first MBUS
+	 * window attribute.
+	 */
+	if ((MVNETA_READ(sc, MV_WIN_NETA_BASE(0)) & IO_WIN_COH_ATTR_MASK) == 0) {
+		reg = MVNETA_READ(sc, MVNETA_PSNPCFG);
+		reg &= ~MVNETA_PSNPCFG_DESCSNP_MASK;
+		reg &= ~MVNETA_PSNPCFG_BUFSNP_MASK;
+		MVNETA_WRITE(sc, MVNETA_PSNPCFG, reg);
+	}
+
+	/*
+	 * MAC address
+	 */
+	if (mvneta_get_mac_address(sc, sc->enaddr)) {
+		device_printf(self, "no mac address.\n");
+		return (ENXIO);
+	}
+	mvneta_set_mac_address(sc, sc->enaddr);
+
+	mvneta_disable_intr(sc);
+
+	/* Allocate network interface */
+	ifp = sc->ifp = if_alloc(IFT_ETHER);
+	if (ifp == NULL) {
+		device_printf(self, "if_alloc() failed\n");
+		mvneta_detach(self);
+		return (ENOMEM);
+	}
+	if_initname(ifp, device_get_name(self), device_get_unit(self));
+
+	/*
+	 * We can support 802.1Q VLAN-sized frames and jumbo
+	 * Ethernet frames.
+	 */
+	ifp->if_capabilities |= IFCAP_VLAN_MTU | IFCAP_JUMBO_MTU;
+
+	ifp->if_softc = sc;
+	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+#ifdef MVNETA_MULTIQUEUE
+	ifp->if_transmit = mvneta_transmit;
+	ifp->if_qflush = mvneta_qflush;
+#else /* !MVNETA_MULTIQUEUE */
+	ifp->if_start = mvneta_start;
+	ifp->if_snd.ifq_drv_maxlen = MVNETA_TX_RING_CNT - 1;
+	IFQ_SET_MAXLEN(&ifp->if_snd, ifp->if_snd.ifq_drv_maxlen);
+	IFQ_SET_READY(&ifp->if_snd);
+#endif
+	ifp->if_init = mvneta_init;
+	ifp->if_ioctl = mvneta_ioctl;
+
+	/*
+	 * We can do IPv4/TCPv4/UDPv4/TCPv6/UDPv6 checksums in hardware.
+	 */
+	ifp->if_capabilities |= IFCAP_HWCSUM;
+
+	/*
+	 * As VLAN hardware tagging is not supported
+	 * but is necessary to perform VLAN hardware checksums,
+	 * it is done in the driver
+	 */
+	ifp->if_capabilities |= IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_HWCSUM;
+
+	/*
+	 * Currently IPv6 HW checksum is broken, so make sure it is disabled.
+	 */
+	ifp->if_capabilities &= ~IFCAP_HWCSUM_IPV6;
+	ifp->if_capenable = ifp->if_capabilities;
+
+	/*
+	 * Disabled option(s):
+	 * - Support for Large Receive Offload
+	 */
+	ifp->if_capabilities |= IFCAP_LRO;
+
+	ifp->if_hwassist = CSUM_IP | CSUM_TCP | CSUM_UDP;
+
+	/*
+	 * Device DMA Buffer allocation.
+	 * Handles resource deallocation in case of failure.
+	 */
+	error = mvneta_dma_create(sc);
+	if (error != 0) {
+		mvneta_detach(self);
+		return (error);
+	}
+
+	/* Initialize queues */
+	for (q = 0; q < MVNETA_TX_QNUM_MAX; q++) {
+		error = mvneta_ring_init_tx_queue(sc, q);
+		if (error != 0) {
+			mvneta_detach(self);
+			return (error);
+		}
+	}
+
+	for (q = 0; q < MVNETA_RX_QNUM_MAX; q++) {
+		error = mvneta_ring_init_rx_queue(sc, q);
+		if (error != 0) {
+			mvneta_detach(self);
+			return (error);
+		}
+	}
+
+	ether_ifattach(ifp, sc->enaddr);
+
+	/*
+	 * Enable DMA engines and Initialize Device Registers.
+	 */
+	MVNETA_WRITE(sc, MVNETA_PRXINIT, 0x00000000);
+	MVNETA_WRITE(sc, MVNETA_PTXINIT, 0x00000000);
+	MVNETA_WRITE(sc, MVNETA_PACC, MVNETA_PACC_ACCELERATIONMODE_EDM);
+	mvneta_sc_lock(sc);
+	mvneta_filter_setup(sc);
+	mvneta_sc_unlock(sc);
+	mvneta_initreg(ifp);
+
+	/*
+	 * Now MAC is working, setup MII.
+	 */
+	if (mii_init == 0) {
+		/*
+		 * MII bus is shared by all MACs and all PHYs in SoC.
+		 * serializing the bus access should be safe.
+		 */
+		mtx_init(&mii_mutex, "mvneta_mii", NULL, MTX_DEF);
+		mii_init = 1;
+	}
+
+	/* Attach PHY(s) */
+	if ((sc->phy_addr != MII_PHY_ANY) && (!sc->use_inband_status)) {
+		error = mii_attach(self, &sc->miibus, ifp, mvneta_mediachange,
+		    mvneta_mediastatus, BMSR_DEFCAPMASK, sc->phy_addr,
+		    MII_OFFSET_ANY, 0);
+		if (error != 0) {
+			if (bootverbose) {
+				device_printf(self,
+				    "MII attach failed, error: %d\n", error);
+			}
+			ether_ifdetach(sc->ifp);
+			mvneta_detach(self);
+			return (error);
+		}
+		sc->mii = device_get_softc(sc->miibus);
+		sc->phy_attached = 1;
+
+		/* Disable auto-negotiation in MAC - rely on PHY layer */
+		mvneta_update_autoneg(sc, FALSE);
+	} else if (sc->use_inband_status == TRUE) {
+		/* In-band link status */
+		ifmedia_init(&sc->mvneta_ifmedia, 0, mvneta_mediachange,
+		    mvneta_mediastatus);
+
+		/* Configure media */
+		ifmedia_add(&sc->mvneta_ifmedia, IFM_ETHER | IFM_1000_T | IFM_FDX,
+		    0, NULL);
+		ifmedia_add(&sc->mvneta_ifmedia, IFM_ETHER | IFM_100_TX, 0, NULL);
+		ifmedia_add(&sc->mvneta_ifmedia, IFM_ETHER | IFM_100_TX | IFM_FDX,
+		    0, NULL);
+		ifmedia_add(&sc->mvneta_ifmedia, IFM_ETHER | IFM_10_T, 0, NULL);
+		ifmedia_add(&sc->mvneta_ifmedia, IFM_ETHER | IFM_10_T | IFM_FDX,
+		    0, NULL);
+		ifmedia_add(&sc->mvneta_ifmedia, IFM_ETHER | IFM_AUTO, 0, NULL);
+		ifmedia_set(&sc->mvneta_ifmedia, IFM_ETHER | IFM_AUTO);
+
+		/* Enable auto-negotiation */
+		mvneta_update_autoneg(sc, TRUE);
+
+		mvneta_sc_lock(sc);
+		if (MVNETA_IS_LINKUP(sc))
+			mvneta_linkup(sc);
+		else
+			mvneta_linkdown(sc);
+		mvneta_sc_unlock(sc);
+
+	} else {
+		/* Fixed-link, use predefined values */
+		ifmedia_init(&sc->mvneta_ifmedia, 0, mvneta_mediachange,
+		    mvneta_mediastatus);
+
+		ifm_target = IFM_ETHER;
+		switch (sc->phy_speed) {
+		case 2500:
+			if (sc->phy_mode != MVNETA_PHY_SGMII &&
+			    sc->phy_mode != MVNETA_PHY_QSGMII) {
+				device_printf(self,
+				    "2.5G speed can work only in (Q)SGMII mode\n");
+				ether_ifdetach(sc->ifp);
+				mvneta_detach(self);
+				return (ENXIO);
+			}
+			ifm_target |= IFM_2500_T;
+			break;
+		case 1000:
+			ifm_target |= IFM_1000_T;
+			break;
+		case 100:
+			ifm_target |= IFM_100_TX;
+			break;
+		case 10:
+			ifm_target |= IFM_10_T;
+			break;
+		default:
+			ether_ifdetach(sc->ifp);
+			mvneta_detach(self);
+			return (ENXIO);
+		}
+
+		if (sc->phy_fdx)
+			ifm_target |= IFM_FDX;
+		else
+			ifm_target |= IFM_HDX;
+
+		ifmedia_add(&sc->mvneta_ifmedia, ifm_target, 0, NULL);
+		ifmedia_set(&sc->mvneta_ifmedia, ifm_target);
+		if_link_state_change(sc->ifp, LINK_STATE_UP);
+
+		if (mvneta_has_switch(self)) {
+			child = device_add_child(sc->dev, "mdio", -1);
+			if (child == NULL) {
+				ether_ifdetach(sc->ifp);
+				mvneta_detach(self);
+				return (ENXIO);
+			}
+			bus_generic_attach(sc->dev);
+			bus_generic_attach(child);
+		}
+
+		/* Configure MAC media */
+		mvneta_update_media(sc, ifm_target);
+	}
+
+	sysctl_mvneta_init(sc);
+
+	callout_reset(&sc->tick_ch, 0, mvneta_tick, sc);
+
+	error = bus_setup_intr(self, sc->res[1],
+	    INTR_TYPE_NET | INTR_MPSAFE, NULL, mvneta_intrs[0].handler, sc,
+	    &sc->ih_cookie[0]);
+	if (error) {
+		device_printf(self, "could not setup %s\n",
+		    mvneta_intrs[0].description);
+		ether_ifdetach(sc->ifp);
+		mvneta_detach(self);
+		return (error);
+	}
+
+	return (0);
+}
+
+STATIC int
+mvneta_detach(device_t dev)
+{
+	struct mvneta_softc *sc;
+	struct ifnet *ifp;
+	int q;
+
+	sc = device_get_softc(dev);
+	ifp = sc->ifp;
+
+	mvneta_stop(sc);
+	/* Detach network interface */
+	if (sc->ifp)
+		if_free(sc->ifp);
+
+	for (q = 0; q < MVNETA_RX_QNUM_MAX; q++)
+		mvneta_ring_dealloc_rx_queue(sc, q);
+	for (q = 0; q < MVNETA_TX_QNUM_MAX; q++)
+		mvneta_ring_dealloc_tx_queue(sc, q);
+
+	if (sc->tx_dtag != NULL)
+		bus_dma_tag_destroy(sc->tx_dtag);
+	if (sc->rx_dtag != NULL)
+		bus_dma_tag_destroy(sc->rx_dtag);
+	if (sc->txmbuf_dtag != NULL)
+		bus_dma_tag_destroy(sc->txmbuf_dtag);
+
+	bus_release_resources(dev, res_spec, sc->res);
+	return (0);
+}
+
+/*
+ * MII
+ */
+STATIC int
+mvneta_miibus_readreg(device_t dev, int phy, int reg)
+{
+	struct mvneta_softc *sc;
+	struct ifnet *ifp;
+	uint32_t smi, val;
+	int i;
+
+	sc = device_get_softc(dev);
+	ifp = sc->ifp;
+
+	mtx_lock(&mii_mutex);
+
+	for (i = 0; i < MVNETA_PHY_TIMEOUT; i++) {
+		if ((MVNETA_READ(sc, MVNETA_SMI) & MVNETA_SMI_BUSY) == 0)
+			break;
+		DELAY(1);
+	}
+	if (i == MVNETA_PHY_TIMEOUT) {
+		if_printf(ifp, "SMI busy timeout\n");
+		mtx_unlock(&mii_mutex);
+		return (-1);
+	}
+
+	smi = MVNETA_SMI_PHYAD(phy) |
+	    MVNETA_SMI_REGAD(reg) | MVNETA_SMI_OPCODE_READ;
+	MVNETA_WRITE(sc, MVNETA_SMI, smi);
+
+	for (i = 0; i < MVNETA_PHY_TIMEOUT; i++) {
+		if ((MVNETA_READ(sc, MVNETA_SMI) & MVNETA_SMI_BUSY) == 0)
+			break;
+		DELAY(1);
+	}
+
+	if (i == MVNETA_PHY_TIMEOUT) {
+		if_printf(ifp, "SMI busy timeout\n");
+		mtx_unlock(&mii_mutex);
+		return (-1);
+	}
+	for (i = 0; i < MVNETA_PHY_TIMEOUT; i++) {
+		smi = MVNETA_READ(sc, MVNETA_SMI);
+		if (smi & MVNETA_SMI_READVALID)
+			break;
+		DELAY(1);
+	}
+
+	if (i == MVNETA_PHY_TIMEOUT) {
+		if_printf(ifp, "SMI busy timeout\n");
+		mtx_unlock(&mii_mutex);
+		return (-1);
+	}
+
+	mtx_unlock(&mii_mutex);
+
+#ifdef MVNETA_KTR
+	CTR3(KTR_SPARE2, "%s i=%d, timeout=%d\n", ifp->if_xname, i,
+	    MVNETA_PHY_TIMEOUT);
+#endif
+
+	val = smi & MVNETA_SMI_DATA_MASK;
+
+#ifdef MVNETA_KTR

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



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201706131846.v5DIkTA7030597>