Module Name:    src
Committed By:   bouyer
Date:           Wed Aug 29 20:39:24 UTC 2012

Modified Files:
        src/sys/dev/pci: if_wm.c if_wmreg.h

Log Message:
Make vlan and all ip/ip6 checksum offload work for the I350.

On newer devices, when using the legacy TX descriptors, vlan-related flags
that were set on the last descriptor of a packet have to be set on the
first one.
For tso/checksum offloads, a new "advanced" descriptor format has to be
used.

Change wcd_txdescs to a union defining all types of descriptors (they
are all 16-bytes wide).
Define a new tx function wm_nq_start(), which handle newer devices.
There is some code duplication with wm_start(), but adding support to
the existing wm_start() would make it a if () {} else {} maze. This also
allows to get rid of some workaround for older chips that are not needed
here.
Use wm_nq_start() instead of wm_start() for the I350 (this should probably
be for all WM_F_NEWQUEUE devices, but I have no hardware but the I350 to
test). Call ifp->if_start() instead of wm_start() where is matters.

Tested on a I350, and a i80003 (which use the old format), both with and
without vlans, with and without checksum offloads.


To generate a diff of this commit:
cvs rdiff -u -r1.231 -r1.232 src/sys/dev/pci/if_wm.c
cvs rdiff -u -r1.47 -r1.48 src/sys/dev/pci/if_wmreg.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/pci/if_wm.c
diff -u src/sys/dev/pci/if_wm.c:1.231 src/sys/dev/pci/if_wm.c:1.232
--- src/sys/dev/pci/if_wm.c:1.231	Thu Aug  9 07:48:39 2012
+++ src/sys/dev/pci/if_wm.c	Wed Aug 29 20:39:24 2012
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_wm.c,v 1.231 2012/08/09 07:48:39 msaitoh Exp $	*/
+/*	$NetBSD: if_wm.c,v 1.232 2012/08/29 20:39:24 bouyer Exp $	*/
 
 /*
  * Copyright (c) 2001, 2002, 2003, 2004 Wasabi Systems, Inc.
@@ -76,7 +76,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: if_wm.c,v 1.231 2012/08/09 07:48:39 msaitoh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: if_wm.c,v 1.232 2012/08/29 20:39:24 bouyer Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -194,7 +194,10 @@ struct wm_control_data_82544 {
 	 * The transmit descriptors.  Put these at the end, because
 	 * we might use a smaller number of them.
 	 */
-	wiseman_txdesc_t wcd_txdescs[WM_NTXDESC_82544];
+	union {
+		wiseman_txdesc_t wcdu_txdescs[WM_NTXDESC_82544];
+		nq_txdesc_t      wcdu_nq_txdescs[WM_NTXDESC_82544];
+	} wdc_u;
 };
 
 struct wm_control_data_82542 {
@@ -203,7 +206,7 @@ struct wm_control_data_82542 {
 };
 
 #define	WM_CDOFF(x)	offsetof(struct wm_control_data_82544, x)
-#define	WM_CDTXOFF(x)	WM_CDOFF(wcd_txdescs[(x)])
+#define	WM_CDTXOFF(x)	WM_CDOFF(wdc_u.wcdu_txdescs[(x)])
 #define	WM_CDRXOFF(x)	WM_CDOFF(wcd_rxdescs[(x)])
 
 /*
@@ -294,7 +297,8 @@ struct wm_softc {
 	int sc_cd_rseg;			/* real number of control segment */
 	size_t sc_cd_size;		/* control data size */
 #define	sc_cddma	sc_cddmamap->dm_segs[0].ds_addr
-#define	sc_txdescs	sc_control_data->wcd_txdescs
+#define	sc_txdescs	sc_control_data->wdc_u.wcdu_txdescs
+#define	sc_nq_txdescs	sc_control_data->wdc_u.wcdu_nq_txdescs
 #define	sc_rxdescs	sc_control_data->wcd_rxdescs
 
 #ifdef WM_EVENT_COUNTERS
@@ -490,6 +494,7 @@ do {									\
 } while (/*CONSTCOND*/0)
 
 static void	wm_start(struct ifnet *);
+static void	wm_nq_start(struct ifnet *);
 static void	wm_watchdog(struct ifnet *);
 static int	wm_ifflags_cb(struct ethercom *);
 static int	wm_ioctl(struct ifnet *, u_long, void *);
@@ -1877,7 +1882,10 @@ wm_attach(device_t parent, device_t self
 	ifp->if_softc = sc;
 	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
 	ifp->if_ioctl = wm_ioctl;
-	ifp->if_start = wm_start;
+	if (sc->sc_type == WM_T_I350)
+		ifp->if_start = wm_nq_start;
+	else
+		ifp->if_start = wm_start;
 	ifp->if_watchdog = wm_watchdog;
 	ifp->if_init = wm_init;
 	ifp->if_stop = wm_stop;
@@ -2761,6 +2769,480 @@ wm_start(struct ifnet *ifp)
 }
 
 /*
+ * wm_nq_tx_offload:
+ *
+ *	Set up TCP/IP checksumming parameters for the
+ *	specified packet, for NEWQUEUE devices
+ */
+static int
+wm_nq_tx_offload(struct wm_softc *sc, struct wm_txsoft *txs,
+    uint32_t *cmdlenp, uint32_t *fieldsp, bool *do_csum)
+{
+	struct mbuf *m0 = txs->txs_mbuf;
+	struct m_tag *mtag;
+	uint32_t vl_len, mssidx, cmdc;
+	struct ether_header *eh;
+	int offset, iphl;
+
+	/*
+	 * XXX It would be nice if the mbuf pkthdr had offset
+	 * fields for the protocol headers.
+	 */
+
+	eh = mtod(m0, struct ether_header *);
+	switch (htons(eh->ether_type)) {
+	case ETHERTYPE_IP:
+	case ETHERTYPE_IPV6:
+		offset = ETHER_HDR_LEN;
+		break;
+
+	case ETHERTYPE_VLAN:
+		offset = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN;
+		break;
+
+	default:
+		/*
+		 * Don't support this protocol or encapsulation.
+		 */
+		*do_csum = false;
+		return 0;
+	}
+	*do_csum = true;
+	*cmdlenp = NQTX_DTYP_D | NQTX_CMD_DEXT | NQTX_CMD_IFCS;
+	cmdc = NQTX_DTYP_C | NQTX_CMD_DEXT;
+
+	vl_len = (offset << NQTXC_VLLEN_MACLEN_SHIFT);
+	KASSERT((offset & ~NQTXC_VLLEN_MACLEN_MASK) == 0);
+
+	if ((m0->m_pkthdr.csum_flags &
+	    (M_CSUM_TSOv4|M_CSUM_UDPv4|M_CSUM_TCPv4|M_CSUM_IPv4)) != 0) {
+		iphl = M_CSUM_DATA_IPv4_IPHL(m0->m_pkthdr.csum_data);
+	} else {
+		iphl = M_CSUM_DATA_IPv6_HL(m0->m_pkthdr.csum_data);
+	}
+	vl_len |= (iphl << NQTXC_VLLEN_IPLEN_SHIFT);
+	KASSERT((iphl & ~NQTXC_VLLEN_IPLEN_MASK) == 0);
+
+	if ((mtag = VLAN_OUTPUT_TAG(&sc->sc_ethercom, m0)) != NULL) {
+		vl_len |= ((VLAN_TAG_VALUE(mtag) & NQTXC_VLLEN_VLAN_MASK)
+		     << NQTXC_VLLEN_VLAN_SHIFT);
+		*cmdlenp |= NQTX_CMD_VLE;
+	}
+
+	*fieldsp = 0;
+	mssidx = 0;
+
+	if ((m0->m_pkthdr.csum_flags & (M_CSUM_TSOv4 | M_CSUM_TSOv6)) != 0) {
+		int hlen = offset + iphl;
+		int tcp_hlen;
+		bool v4 = (m0->m_pkthdr.csum_flags & M_CSUM_TSOv4) != 0;
+
+		if (__predict_false(m0->m_len <
+				    (hlen + sizeof(struct tcphdr)))) {
+			/*
+			 * TCP/IP headers are not in the first mbuf; we need
+			 * to do this the slow and painful way.  Let's just
+			 * hope this doesn't happen very often.
+			 */
+			struct tcphdr th;
+
+			WM_EVCNT_INCR(&sc->sc_ev_txtsopain);
+
+			m_copydata(m0, hlen, sizeof(th), &th);
+			if (v4) {
+				struct ip ip;
+
+				m_copydata(m0, offset, sizeof(ip), &ip);
+				ip.ip_len = 0;
+				m_copyback(m0,
+				    offset + offsetof(struct ip, ip_len),
+				    sizeof(ip.ip_len), &ip.ip_len);
+				th.th_sum = in_cksum_phdr(ip.ip_src.s_addr,
+				    ip.ip_dst.s_addr, htons(IPPROTO_TCP));
+			} else {
+				struct ip6_hdr ip6;
+
+				m_copydata(m0, offset, sizeof(ip6), &ip6);
+				ip6.ip6_plen = 0;
+				m_copyback(m0,
+				    offset + offsetof(struct ip6_hdr, ip6_plen),
+				    sizeof(ip6.ip6_plen), &ip6.ip6_plen);
+				th.th_sum = in6_cksum_phdr(&ip6.ip6_src,
+				    &ip6.ip6_dst, 0, htonl(IPPROTO_TCP));
+			}
+			m_copyback(m0, hlen + offsetof(struct tcphdr, th_sum),
+			    sizeof(th.th_sum), &th.th_sum);
+
+			tcp_hlen = th.th_off << 2;
+		} else {
+			/*
+			 * TCP/IP headers are in the first mbuf; we can do
+			 * this the easy way.
+			 */
+			struct tcphdr *th;
+
+			if (v4) {
+				struct ip *ip =
+				    (void *)(mtod(m0, char *) + offset);
+				th = (void *)(mtod(m0, char *) + hlen);
+
+				ip->ip_len = 0;
+				th->th_sum = in_cksum_phdr(ip->ip_src.s_addr,
+				    ip->ip_dst.s_addr, htons(IPPROTO_TCP));
+			} else {
+				struct ip6_hdr *ip6 =
+				    (void *)(mtod(m0, char *) + offset);
+				th = (void *)(mtod(m0, char *) + hlen);
+
+				ip6->ip6_plen = 0;
+				th->th_sum = in6_cksum_phdr(&ip6->ip6_src,
+				    &ip6->ip6_dst, 0, htonl(IPPROTO_TCP));
+			}
+			tcp_hlen = th->th_off << 2;
+		}
+		hlen += tcp_hlen;
+		*cmdlenp |= NQTX_CMD_TSE;
+
+		if (v4) {
+			WM_EVCNT_INCR(&sc->sc_ev_txtso);
+			*fieldsp |= NQTXD_FIELDS_IXSM | NQTXD_FIELDS_TUXSM;
+		} else {
+			WM_EVCNT_INCR(&sc->sc_ev_txtso6);
+			*fieldsp |= NQTXD_FIELDS_TUXSM;
+		}
+		*fieldsp |= ((m0->m_pkthdr.len - hlen) << NQTXD_FIELDS_PAYLEN_SHIFT);
+		KASSERT(((m0->m_pkthdr.len - hlen) & ~NQTXD_FIELDS_PAYLEN_MASK) == 0);
+		mssidx |= (m0->m_pkthdr.segsz << NQTXC_MSSIDX_MSS_SHIFT);
+		KASSERT((m0->m_pkthdr.segsz & ~NQTXC_MSSIDX_MSS_MASK) == 0);
+		mssidx |= (tcp_hlen << NQTXC_MSSIDX_L4LEN_SHIFT);
+		KASSERT((tcp_hlen & ~NQTXC_MSSIDX_L4LEN_MASK) == 0);
+	} else {
+		*fieldsp |= (m0->m_pkthdr.len << NQTXD_FIELDS_PAYLEN_SHIFT);
+		KASSERT((m0->m_pkthdr.len & ~NQTXD_FIELDS_PAYLEN_MASK) == 0);
+	}
+
+	if (m0->m_pkthdr.csum_flags & M_CSUM_IPv4) {
+		*fieldsp |= NQTXD_FIELDS_IXSM;
+		cmdc |= NQTXC_CMD_IP4;
+	}
+
+	if (m0->m_pkthdr.csum_flags &
+	    (M_CSUM_UDPv4 | M_CSUM_TCPv4 | M_CSUM_TSOv4)) {
+		WM_EVCNT_INCR(&sc->sc_ev_txtusum);
+		if (m0->m_pkthdr.csum_flags & (M_CSUM_TCPv4 | M_CSUM_TSOv4)) {
+			cmdc |= NQTXC_CMD_TCP;
+		} else {
+			cmdc |= NQTXC_CMD_UDP;
+		}
+		cmdc |= NQTXC_CMD_IP4;
+		*fieldsp |= NQTXD_FIELDS_TUXSM;
+	}
+	if (m0->m_pkthdr.csum_flags &
+	    (M_CSUM_UDPv6 | M_CSUM_TCPv6 | M_CSUM_TSOv6)) {
+		WM_EVCNT_INCR(&sc->sc_ev_txtusum6);
+		if (m0->m_pkthdr.csum_flags & (M_CSUM_TCPv6 | M_CSUM_TSOv6)) {
+			cmdc |= NQTXC_CMD_TCP;
+		} else {
+			cmdc |= NQTXC_CMD_UDP;
+		}
+		cmdc |= NQTXC_CMD_IP6;
+		*fieldsp |= NQTXD_FIELDS_TUXSM;
+	}
+
+	/* Fill in the context descriptor. */
+	sc->sc_nq_txdescs[sc->sc_txnext].nqrx_ctx.nqtxc_vl_len =
+	    htole32(vl_len);
+	sc->sc_nq_txdescs[sc->sc_txnext].nqrx_ctx.nqtxc_sn = 0;
+	sc->sc_nq_txdescs[sc->sc_txnext].nqrx_ctx.nqtxc_cmd = 
+	    htole32(cmdc);
+	sc->sc_nq_txdescs[sc->sc_txnext].nqrx_ctx.nqtxc_mssidx = 
+	    htole32(mssidx);
+	WM_CDTXSYNC(sc, sc->sc_txnext, 1, BUS_DMASYNC_PREWRITE);
+	DPRINTF(WM_DEBUG_TX,
+	    ("%s: TX: context desc %d 0x%08x%08x\n", device_xname(sc->sc_dev),
+	    sc->sc_txnext, 0, vl_len));
+	DPRINTF(WM_DEBUG_TX, ("\t0x%08x%08x\n", mssidx, cmdc));
+	sc->sc_txnext = WM_NEXTTX(sc, sc->sc_txnext);
+	txs->txs_ndesc++;
+	return 0;
+}
+
+/*
+ * wm_nq_start:		[ifnet interface function]
+ *
+ *	Start packet transmission on the interface for NEWQUEUE devices
+ */
+static void
+wm_nq_start(struct ifnet *ifp)
+{
+	struct wm_softc *sc = ifp->if_softc;
+	struct mbuf *m0;
+	struct m_tag *mtag;
+	struct wm_txsoft *txs;
+	bus_dmamap_t dmamap;
+	int error, nexttx, lasttx = -1, seg, segs_needed;
+	bool do_csum, sent;
+	uint32_t cmdlen, fields, dcmdlen;
+
+	if ((ifp->if_flags & (IFF_RUNNING|IFF_OACTIVE)) != IFF_RUNNING)
+		return;
+
+	sent = false;
+
+	/*
+	 * Loop through the send queue, setting up transmit descriptors
+	 * until we drain the queue, or use up all available transmit
+	 * descriptors.
+	 */
+	for (;;) {
+		/* Grab a packet off the queue. */
+		IFQ_POLL(&ifp->if_snd, m0);
+		if (m0 == NULL)
+			break;
+
+		DPRINTF(WM_DEBUG_TX,
+		    ("%s: TX: have packet to transmit: %p\n",
+		    device_xname(sc->sc_dev), m0));
+
+		/* Get a work queue entry. */
+		if (sc->sc_txsfree < WM_TXQUEUE_GC(sc)) {
+			wm_txintr(sc);
+			if (sc->sc_txsfree == 0) {
+				DPRINTF(WM_DEBUG_TX,
+				    ("%s: TX: no free job descriptors\n",
+					device_xname(sc->sc_dev)));
+				WM_EVCNT_INCR(&sc->sc_ev_txsstall);
+				break;
+			}
+		}
+
+		txs = &sc->sc_txsoft[sc->sc_txsnext];
+		dmamap = txs->txs_dmamap;
+
+		/*
+		 * Load the DMA map.  If this fails, the packet either
+		 * didn't fit in the allotted number of segments, or we
+		 * were short on resources.  For the too-many-segments
+		 * case, we simply report an error and drop the packet,
+		 * since we can't sanely copy a jumbo packet to a single
+		 * buffer.
+		 */
+		error = bus_dmamap_load_mbuf(sc->sc_dmat, dmamap, m0,
+		    BUS_DMA_WRITE|BUS_DMA_NOWAIT);
+		if (error) {
+			if (error == EFBIG) {
+				WM_EVCNT_INCR(&sc->sc_ev_txdrop);
+				log(LOG_ERR, "%s: Tx packet consumes too many "
+				    "DMA segments, dropping...\n",
+				    device_xname(sc->sc_dev));
+				IFQ_DEQUEUE(&ifp->if_snd, m0);
+				wm_dump_mbuf_chain(sc, m0);
+				m_freem(m0);
+				continue;
+			}
+			/*
+			 * Short on resources, just stop for now.
+			 */
+			DPRINTF(WM_DEBUG_TX,
+			    ("%s: TX: dmamap load failed: %d\n",
+			    device_xname(sc->sc_dev), error));
+			break;
+		}
+
+		segs_needed = dmamap->dm_nsegs;
+
+		/*
+		 * Ensure we have enough descriptors free to describe
+		 * the packet.  Note, we always reserve one descriptor
+		 * at the end of the ring due to the semantics of the
+		 * TDT register, plus one more in the event we need
+		 * to load offload context.
+		 */
+		if (segs_needed > sc->sc_txfree - 2) {
+			/*
+			 * Not enough free descriptors to transmit this
+			 * packet.  We haven't committed anything yet,
+			 * so just unload the DMA map, put the packet
+			 * pack on the queue, and punt.  Notify the upper
+			 * layer that there are no more slots left.
+			 */
+			DPRINTF(WM_DEBUG_TX,
+			    ("%s: TX: need %d (%d) descriptors, have %d\n",
+			    device_xname(sc->sc_dev), dmamap->dm_nsegs,
+			    segs_needed, sc->sc_txfree - 1));
+			ifp->if_flags |= IFF_OACTIVE;
+			bus_dmamap_unload(sc->sc_dmat, dmamap);
+			WM_EVCNT_INCR(&sc->sc_ev_txdstall);
+			break;
+		}
+
+		IFQ_DEQUEUE(&ifp->if_snd, m0);
+
+		/*
+		 * WE ARE NOW COMMITTED TO TRANSMITTING THE PACKET.
+		 */
+
+		DPRINTF(WM_DEBUG_TX,
+		    ("%s: TX: packet has %d (%d) DMA segments\n",
+		    device_xname(sc->sc_dev), dmamap->dm_nsegs, segs_needed));
+
+		WM_EVCNT_INCR(&sc->sc_ev_txseg[dmamap->dm_nsegs - 1]);
+
+		/*
+		 * Store a pointer to the packet so that we can free it
+		 * later.
+		 *
+		 * Initially, we consider the number of descriptors the
+		 * packet uses the number of DMA segments.  This may be
+		 * incremented by 1 if we do checksum offload (a descriptor
+		 * is used to set the checksum context).
+		 */
+		txs->txs_mbuf = m0;
+		txs->txs_firstdesc = sc->sc_txnext;
+		txs->txs_ndesc = segs_needed;
+
+		/* Set up offload parameters for this packet. */
+		if (m0->m_pkthdr.csum_flags &
+		    (M_CSUM_TSOv4|M_CSUM_TSOv6|
+		    M_CSUM_IPv4|M_CSUM_TCPv4|M_CSUM_UDPv4|
+		    M_CSUM_TCPv6|M_CSUM_UDPv6)) {
+			if (wm_nq_tx_offload(sc, txs, &cmdlen, &fields,
+			    &do_csum) != 0) {
+				/* Error message already displayed. */
+				bus_dmamap_unload(sc->sc_dmat, dmamap);
+				continue;
+			}
+		} else {
+			do_csum = false;
+		}
+
+		/* Sync the DMA map. */
+		bus_dmamap_sync(sc->sc_dmat, dmamap, 0, dmamap->dm_mapsize,
+		    BUS_DMASYNC_PREWRITE);
+
+		/*
+		 * Initialize the first transmit descriptor.
+		 */
+		nexttx = sc->sc_txnext;
+		if (!do_csum) {
+			/* setup a legacy descriptor */
+			wm_set_dma_addr(
+			    &sc->sc_txdescs[nexttx].wtx_addr,
+			    dmamap->dm_segs[0].ds_addr);
+			sc->sc_txdescs[nexttx].wtx_cmdlen =
+			    htole32(WTX_CMD_IFCS | dmamap->dm_segs[0].ds_len);
+			sc->sc_txdescs[nexttx].wtx_fields.wtxu_status = 0;
+			sc->sc_txdescs[nexttx].wtx_fields.wtxu_options = 0;
+			if ((mtag = VLAN_OUTPUT_TAG(&sc->sc_ethercom, m0)) !=
+			    NULL) {
+				sc->sc_txdescs[nexttx].wtx_cmdlen |=
+				    htole32(WTX_CMD_VLE);
+				sc->sc_txdescs[nexttx].wtx_fields.wtxu_vlan =
+				    htole16(VLAN_TAG_VALUE(mtag) & 0xffff);
+			} else {
+				sc->sc_txdescs[nexttx].wtx_fields.wtxu_vlan = 0;
+			}
+			dcmdlen = 0;
+		} else {
+			/* setup an advanced data descriptor */
+			sc->sc_nq_txdescs[nexttx].nqtx_data.nqtxd_addr =
+			    htole64(dmamap->dm_segs[0].ds_addr);
+			KASSERT((dmamap->dm_segs[0].ds_len & cmdlen) == 0);
+			sc->sc_nq_txdescs[nexttx].nqtx_data.nqtxd_cmdlen =
+			    htole32(dmamap->dm_segs[0].ds_len | cmdlen );
+			sc->sc_nq_txdescs[nexttx].nqtx_data.nqtxd_fields =
+			    htole32(fields);
+			DPRINTF(WM_DEBUG_TX,
+			    ("%s: TX: adv data desc %d 0x%" PRIx64 "\n",
+			    device_xname(sc->sc_dev), nexttx, 
+			    dmamap->dm_segs[0].ds_addr));
+			DPRINTF(WM_DEBUG_TX,
+			    ("\t 0x%08x%08x\n", fields,
+			    (uint32_t)dmamap->dm_segs[0].ds_len | cmdlen));
+			dcmdlen = NQTX_DTYP_D | NQTX_CMD_DEXT;
+		}
+
+		lasttx = nexttx;
+		nexttx = WM_NEXTTX(sc, nexttx);
+		/*
+		 * fill in the next descriptors. legacy or adcanced format
+		 * is the same here
+		 */
+		for (seg = 1; seg < dmamap->dm_nsegs;
+		    seg++, nexttx = WM_NEXTTX(sc, nexttx)) {
+			sc->sc_nq_txdescs[nexttx].nqtx_data.nqtxd_addr =
+			    htole64(dmamap->dm_segs[seg].ds_addr);
+			sc->sc_nq_txdescs[nexttx].nqtx_data.nqtxd_cmdlen =
+			    htole32(dcmdlen | dmamap->dm_segs[seg].ds_len);
+			KASSERT((dcmdlen & dmamap->dm_segs[seg].ds_len) == 0);
+			sc->sc_nq_txdescs[nexttx].nqtx_data.nqtxd_fields = 0;
+			lasttx = nexttx;
+
+			DPRINTF(WM_DEBUG_TX,
+			    ("%s: TX: desc %d: %#" PRIxPADDR ", "
+			     "len %#04zx\n",
+			    device_xname(sc->sc_dev), nexttx,
+			    dmamap->dm_segs[seg].ds_addr,
+			    dmamap->dm_segs[seg].ds_len));
+		}
+
+		KASSERT(lasttx != -1);
+
+		/*
+		 * Set up the command byte on the last descriptor of
+		 * the packet.  If we're in the interrupt delay window,
+		 * delay the interrupt.
+		 */
+		KASSERT((WTX_CMD_EOP | WTX_CMD_RS) ==
+		    (NQTX_CMD_EOP | NQTX_CMD_RS));
+		sc->sc_txdescs[lasttx].wtx_cmdlen |=
+		    htole32(WTX_CMD_EOP | WTX_CMD_RS);
+
+		txs->txs_lastdesc = lasttx;
+
+		DPRINTF(WM_DEBUG_TX,
+		    ("%s: TX: desc %d: cmdlen 0x%08x\n",
+		    device_xname(sc->sc_dev),
+		    lasttx, le32toh(sc->sc_txdescs[lasttx].wtx_cmdlen)));
+
+		/* Sync the descriptors we're using. */
+		WM_CDTXSYNC(sc, sc->sc_txnext, txs->txs_ndesc,
+		    BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
+
+		/* Give the packet to the chip. */
+		CSR_WRITE(sc, sc->sc_tdt_reg, nexttx);
+		sent = true;
+
+		DPRINTF(WM_DEBUG_TX,
+		    ("%s: TX: TDT -> %d\n", device_xname(sc->sc_dev), nexttx));
+
+		DPRINTF(WM_DEBUG_TX,
+		    ("%s: TX: finished transmitting packet, job %d\n",
+		    device_xname(sc->sc_dev), sc->sc_txsnext));
+
+		/* Advance the tx pointer. */
+		sc->sc_txfree -= txs->txs_ndesc;
+		sc->sc_txnext = nexttx;
+
+		sc->sc_txsfree--;
+		sc->sc_txsnext = WM_NEXTTXS(sc, sc->sc_txsnext);
+
+		/* Pass the packet to any BPF listeners. */
+		bpf_mtap(ifp, m0);
+	}
+
+	if (sc->sc_txsfree == 0 || sc->sc_txfree <= 2) {
+		/* No more slots; notify upper layer. */
+		ifp->if_flags |= IFF_OACTIVE;
+	}
+
+	if (sent) {
+		/* Set a watchdog timer in case the chip flakes out. */
+		ifp->if_timer = 5;
+	}
+}
+
+/*
  * wm_watchdog:		[ifnet interface function]
  *
  *	Watchdog timer handler.
@@ -2777,18 +3259,39 @@ wm_watchdog(struct ifnet *ifp)
 	wm_txintr(sc);
 
 	if (sc->sc_txfree != WM_NTXDESC(sc)) {
+#ifdef WM_DEBUG
+		int i, j;
+		struct wm_txsoft *txs;
+#endif
 		log(LOG_ERR,
 		    "%s: device timeout (txfree %d txsfree %d txnext %d)\n",
 		    device_xname(sc->sc_dev), sc->sc_txfree, sc->sc_txsfree,
 		    sc->sc_txnext);
 		ifp->if_oerrors++;
-
+#ifdef WM_DEBUG
+		for (i = sc->sc_txsdirty; i != sc->sc_txsnext ;
+		    i = WM_NEXTTXS(sc, i)) {
+		    txs = &sc->sc_txsoft[i];
+		    printf("txs %d tx %d -> %d\n",
+			i, txs->txs_firstdesc, txs->txs_lastdesc);
+		    for (j = txs->txs_firstdesc; ;
+			j = WM_NEXTTX(sc, j)) {
+			printf("\tdesc %d: 0x%" PRIx64 "\n", j,
+			    sc->sc_nq_txdescs[j].nqtx_data.nqtxd_addr);
+			printf("\t %#08x%08x\n",
+			    sc->sc_nq_txdescs[j].nqtx_data.nqtxd_fields,
+			    sc->sc_nq_txdescs[j].nqtx_data.nqtxd_cmdlen);
+			if (j == txs->txs_lastdesc)
+				break;
+			}
+		}
+#endif
 		/* Reset the interface. */
 		(void) wm_init(ifp);
 	}
 
 	/* Try to get more packets going. */
-	wm_start(ifp);
+	ifp->if_start(ifp);
 }
 
 static int
@@ -2877,7 +3380,7 @@ wm_ioctl(struct ifnet *ifp, u_long cmd, 
 	}
 
 	/* Try to get more packets going. */
-	wm_start(ifp);
+	ifp->if_start(ifp);
 
 	splx(s);
 	return error;
@@ -2940,7 +3443,7 @@ wm_intr(void *arg)
 
 	if (handled) {
 		/* Try to get more packets going. */
-		wm_start(ifp);
+		ifp->if_start(ifp);
 	}
 
 	return handled;
@@ -5438,7 +5941,7 @@ wm_tbi_check_link(struct wm_softc *sc)
 			DPRINTF(WM_DEBUG_LINK, ("RXCFG storm! (%d)\n",
 				sc->sc_tbi_nrxcfg - sc->sc_tbi_lastnrxcfg));
 			wm_init(ifp);
-			wm_start(ifp);
+			ifp->if_start(ifp);
 		} else if (IFM_SUBTYPE(ife->ifm_media) == IFM_AUTO) {
 			/* If the timer expired, retry autonegotiation */
 			if (++sc->sc_tbi_ticks >= sc->sc_tbi_anegticks) {

Index: src/sys/dev/pci/if_wmreg.h
diff -u src/sys/dev/pci/if_wmreg.h:1.47 src/sys/dev/pci/if_wmreg.h:1.48
--- src/sys/dev/pci/if_wmreg.h:1.47	Fri May 25 23:37:38 2012
+++ src/sys/dev/pci/if_wmreg.h	Wed Aug 29 20:39:24 2012
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_wmreg.h,v 1.47 2012/05/25 23:37:38 msaitoh Exp $	*/
+/*	$NetBSD: if_wmreg.h,v 1.48 2012/08/29 20:39:24 bouyer Exp $	*/
 
 /*
  * Copyright (c) 2001 Wasabi Systems, Inc.
@@ -963,3 +963,60 @@ struct livengood_tcpip_ctxdesc {
 
 /* for PCI express Capability registers */
 #define	WM_PCI_PCIE_DCSR2_16MS	0x00000005
+
+/* advanced TX descriptor for 82575 and newer */
+typedef union nq_txdesc {
+	struct {
+		uint64_t nqtxd_addr;
+		uint32_t nqtxd_cmdlen;
+		uint32_t nqtxd_fields;
+	} nqtx_data;
+	struct {
+		uint32_t nqtxc_vl_len;
+		uint32_t nqtxc_sn;
+		uint32_t nqtxc_cmd;
+		uint32_t nqtxc_mssidx;
+	} nqrx_ctx;
+} __packed nq_txdesc_t;
+
+
+/* Commands for nqtxd_cmdlen and nqtxc_cmd */
+#define	NQTX_CMD_EOP	(1U << 24)	/* end of packet */
+#define	NQTX_CMD_IFCS	(1U << 25)	/* insert FCS */
+#define	NQTX_CMD_RS	(1U << 27)	/* report status */
+#define	NQTX_CMD_DEXT	(1U << 29)	/* descriptor extension */
+#define	NQTX_CMD_VLE	(1U << 30)	/* VLAN enable */
+#define	NQTX_CMD_TSE	(1U << 31)	/* TCP segmentation enable */
+
+/* Descriptor types (if DEXT is set) */
+#define	NQTX_DTYP_C	(2U << 20)	/* context */
+#define	NQTX_DTYP_D	(3U << 20)	/* data */
+
+#define NQTXD_FIELDS_IDX_SHIFT		4	/* context index shift */
+#define NQTXD_FIELDS_IDX_MASK		0xf
+#define NQTXD_FIELDS_PAYLEN_SHIFT	14	/* payload len shift */
+#define NQTXD_FIELDS_PAYLEN_MASK	0x3ffff
+
+#define NQTXD_FIELDS_IXSM		(1U << 8) /* do IP checksum */
+#define NQTXD_FIELDS_TUXSM		(1U << 9) /* do TCP/UDP checksum */
+
+#define NQTXC_VLLEN_IPLEN_SHIFT		0	/* IP header len */
+#define NQTXC_VLLEN_IPLEN_MASK		0x1ff
+#define NQTXC_VLLEN_MACLEN_SHIFT	9	/* MAC header len */
+#define NQTXC_VLLEN_MACLEN_MASK		0x7f
+#define NQTXC_VLLEN_VLAN_SHIFT		16	/* vlan number */
+#define NQTXC_VLLEN_VLAN_MASK		0xffff
+
+#define NQTXC_CMD_MKRLOC_SHIFT		0	/* IP checksum offset */
+#define NQTXC_CMD_MKRLOC_MASK		0x1ff
+#define NQTXC_CMD_SNAP			(1U << 9)
+#define NQTXC_CMD_IP4			(1U << 10)
+#define NQTXC_CMD_IP6			(0U << 10)
+#define NQTXC_CMD_TCP			(1U << 11)
+#define NQTXC_CMD_UDP			(0U << 11)
+#define NQTXC_MSSIDX_IDX_SHIFT		4	/* context index shift */
+#define NQTXC_MSSIDX_IDX_MASK		0xf
+#define NQTXC_MSSIDX_L4LEN_SHIFT	8	/* L4 header len shift */
+#define NQTXC_MSSIDX_L4LEN_MASK		0xff
+#define NQTXC_MSSIDX_MSS_SHIFT		16	/* MSS */
+#define NQTXC_MSSIDX_MSS_MASK		0xffff

Reply via email to