From bdf0ef5086b6f4965e3e7f50f85fc15e5b9a8ede Mon Sep 17 00:00:00 2001
From: Sepherosa Ziehau <sephe@dragonflybsd.org>
Date: Fri, 30 Jan 2015 23:03:03 +0800
Subject: [PATCH] emx: Add fix for the TSO hardware bug

The information of the TSO hardware bug is obtained from FreeBSD,
however, Dragonfly fix is much simpler and more straightforwrard.

This bug was never observed on 82571 and 82574.

While I'm here, update comment about tx_intr_nsegs.

Reported-by: dillon@ on i217
---
 sys/dev/netif/emx/if_emx.c | 60 ++++++++++++++++++++++++++++++++++++++++------
 sys/dev/netif/emx/if_emx.h | 16 +++++++++----
 2 files changed, 64 insertions(+), 12 deletions(-)

diff --git a/sys/dev/netif/emx/if_emx.c b/sys/dev/netif/emx/if_emx.c
index d3e59d2..ce70abe 100644
--- a/sys/dev/netif/emx/if_emx.c
+++ b/sys/dev/netif/emx/if_emx.c
@@ -1545,13 +1545,14 @@ emx_encap(struct emx_txdata *tdata, struct mbuf **m_headp,
 	struct e1000_tx_desc *ctxd = NULL;
 	struct mbuf *m_head = *m_headp;
 	uint32_t txd_upper, txd_lower, cmd = 0;
-	int maxsegs, nsegs, i, j, first, last = 0, error;
+	int maxsegs, nsegs, i, j, first, last = 0, error, tso = 0;
 
 	if (m_head->m_pkthdr.csum_flags & CSUM_TSO) {
 		error = emx_tso_pullup(tdata, m_headp);
 		if (error)
 			return error;
 		m_head = *m_headp;
+		tso = 1;
 	}
 
 	txd_upper = txd_lower = 0;
@@ -1580,11 +1581,50 @@ emx_encap(struct emx_txdata *tdata, struct mbuf **m_headp,
 	}
         bus_dmamap_sync(tdata->txtag, map, BUS_DMASYNC_PREWRITE);
 
+	/*
+	 * TSO hardware bug workaround.
+	 *
+	 * If this packet meeting all of the following conditions:
+	 * - Consuming only one TX descriptor (excluding TX context
+	 *   descriptor).
+	 * - Following a TSO burst.
+	 * Then we need to split this packet into two parts, i.e.
+	 * force using two TX descriptors, to prevent premature TX
+	 * descriptor writeback.
+	 */
+	if (tdata->tx_flags & EMX_TXFLAG_WASTSO) {
+		tdata->tx_flags &= ~EMX_TXFLAG_WASTSO;
+		if (nsegs == 1) {
+			if (segs[0].ds_len < EMX_TSO_WR_MIN) {
+				/* Can't split this packet */
+				bus_dmamap_unload(tdata->txtag, map);
+				m_freem(*m_headp);
+				*m_headp = NULL;
+				tdata->tso_wr_dropped++;
+				return EINVAL;
+			}
+#ifdef EMX_TSO_DEBUG
+			tdata->tso_wr_pkts++;
+#endif
+			++nsegs;
+			KKASSERT(nsegs <= maxsegs);
+			/*
+			 * |  ds_len - SPLIT  |  SPLIT  |
+			 * +------------------+---------+
+			 * |                  |
+			 * ds_addr            ds_addr + ds_len - SPLIT
+			 */
+			segs[0].ds_len -= EMX_TSO_WR_SPLIT;
+			segs[1].ds_addr = segs[0].ds_addr + segs[0].ds_len;
+			segs[1].ds_len = EMX_TSO_WR_SPLIT;
+		}
+	}
+
 	m_head = *m_headp;
 	tdata->tx_nsegs += nsegs;
 	*segs_used += nsegs;
 
-	if (m_head->m_pkthdr.csum_flags & CSUM_TSO) {
+	if (tso) {
 		/* TSO will consume one TX desc */
 		i = emx_tso_setup(tdata, m_head, &txd_upper, &txd_lower);
 		tdata->tx_nsegs += i;
@@ -2834,7 +2874,7 @@ emx_free_tx_ring(struct emx_txdata *tdata)
 		}
 	}
 
-	tdata->tx_flags &= ~EMX_TXFLAG_FORCECTX;
+	tdata->tx_flags &= ~(EMX_TXFLAG_FORCECTX | EMX_TXFLAG_WASTSO);
 
 	tdata->csum_flags = 0;
 	tdata->csum_lhlen = 0;
@@ -3500,6 +3540,8 @@ emx_print_debug_info(struct emx_softc *sc)
 		    sc->tx_data[i].tso_segments);
 		device_printf(dev, "TX %d TSO ctx reused = %lu\n", i,
 		    sc->tx_data[i].tso_ctx_reused);
+		device_printf(dev, "TX %d TSO workaround packets = %lu\n", i,
+		    sc->tx_data[i].tso_wr_pkts);
 	}
 }
 
@@ -3627,10 +3669,8 @@ emx_add_sysctl(struct emx_softc *sc)
 {
 	struct sysctl_ctx_list *ctx;
 	struct sysctl_oid *tree;
-#if defined(EMX_RSS_DEBUG) || defined(EMX_TSS_DEBUG)
 	char pkt_desc[32];
 	int i;
-#endif
 
 	ctx = device_get_sysctl_ctx(sc->dev);
 	tree = device_get_sysctl_tree(sc->dev);
@@ -3692,14 +3732,19 @@ emx_add_sysctl(struct emx_softc *sc)
 		    "RXed packets");
 	}
 #endif
-#ifdef EMX_TSS_DEBUG
+
 	for (i = 0; i < sc->tx_ring_cnt; ++i) {
+#ifdef EMX_TSS_DEBUG
 		ksnprintf(pkt_desc, sizeof(pkt_desc), "tx%d_pkt", i);
 		SYSCTL_ADD_ULONG(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
 		    pkt_desc, CTLFLAG_RW, &sc->tx_data[i].tx_pkts,
 		    "TXed packets");
-	}
 #endif
+		ksnprintf(pkt_desc, sizeof(pkt_desc), "tx%d_tsowr_dropped", i);
+		SYSCTL_ADD_ULONG(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+		    pkt_desc, CTLFLAG_RW, &sc->tx_data[i].tso_wr_dropped,
+		    "packets dropped due to TSO workaround failure");
+	}
 }
 
 static int
@@ -4201,6 +4246,7 @@ emx_tso_setup(struct emx_txdata *tdata, struct mbuf *mp,
 #ifdef EMX_TSO_DEBUG
 	tdata->tso_segments++;
 #endif
+	tdata->tx_flags |= EMX_TXFLAG_WASTSO;
 
 	iphlen = mp->m_pkthdr.csum_iphlen;
 	thoff = mp->m_pkthdr.csum_thlen;
diff --git a/sys/dev/netif/emx/if_emx.h b/sys/dev/netif/emx/if_emx.h
index 05c5e71..f8de5f8 100644
--- a/sys/dev/netif/emx/if_emx.h
+++ b/sys/dev/netif/emx/if_emx.h
@@ -189,6 +189,9 @@
 #define EMX_NTX_RING			2
 #define EMX_NSERIALIZE			5
 
+#define EMX_TSO_WR_SPLIT		4	/* TSO workaround split len */
+#define EMX_TSO_WR_MIN			(EMX_TSO_WR_SPLIT * 2)
+
 typedef union e1000_rx_desc_extended	emx_rxdesc_t;
 
 #define rxd_bufaddr	read.buffer_addr	/* 64bits */
@@ -251,6 +254,7 @@ struct emx_txdata {
 #define EMX_TXFLAG_TSO_PULLEX	0x1
 #define EMX_TXFLAG_ENABLED	0x2
 #define EMX_TXFLAG_FORCECTX	0x4
+#define EMX_TXFLAG_WASTSO	0x8
 
 	/*
 	 * Transmit definitions
@@ -287,17 +291,17 @@ struct emx_txdata {
 
 	/*
 	 * Variables used to reduce TX interrupt rate and
-	 * number of device's TX ring write requests.
+	 * number of device's TX descriptor writeback.
 	 *
 	 * tx_nsegs:
 	 * Number of TX descriptors setup so far.
 	 *
-	 * tx_int_nsegs:
-	 * Once tx_nsegs > tx_int_nsegs, RS bit will be set
+	 * tx_intr_nsegs:
+	 * Once tx_nsegs > tx_intr_nsegs, RS bit will be set
 	 * in the last TX descriptor of the packet, and
 	 * tx_nsegs will be reset to 0.  So TX interrupt and
-	 * TX ring write request should be generated roughly
-	 * every tx_int_nsegs TX descriptors.
+	 * TX descriptor writeback should be generated roughly
+	 * every tx_intr_nsegs TX descriptors.
 	 *
 	 * tx_dd[]:
 	 * Index of the TX descriptors which have RS bit set,
@@ -329,6 +333,8 @@ struct emx_txdata {
 	unsigned long		tx_pkts;
 	unsigned long		tso_segments;
 	unsigned long		tso_ctx_reused;
+	unsigned long		tso_wr_dropped;
+	unsigned long		tso_wr_pkts;
 
 	bus_dma_tag_t		tx_desc_dtag;
 	bus_dmamap_t		tx_desc_dmap;
-- 
2.2.1

