svn commit: r199671 - head/sys/dev/bge

Pyun YongHyeon yongari at FreeBSD.org
Sun Nov 22 21:16:31 UTC 2009


Author: yongari
Date: Sun Nov 22 21:16:30 2009
New Revision: 199671
URL: http://svn.freebsd.org/changeset/base/199671

Log:
  Implement TSO for BCM5755 or newer controllers. Some controllers
  seem to require a special firmware to use TSO. But the firmware is
  not available to FreeBSD and Linux claims that the TSO performed by
  the firmware is slower than hardware based TSO. Moreover the
  firmware based TSO has one known bug which can't handle TSO if
  ethernet header + IP/TCP header is greater than 80 bytes. The
  workaround for the TSO bug exist but it seems it's too expensive
  than not using TSO at all. Some hardwares also have the TSO bug so
  limit the TSO to the controllers that are not affected TSO issues
  (e.g. 5755 or higher).
  While I'm here set VLAN tag bit to all descriptors that belengs to
  a frame instead of the first descriptor of a frame. The datasheet
  is not clear how to handle VLAN tag bit but it worked either way in
  my testing. This makes it simplify TSO configuration a little bit.
  
  Big thanks to davidch@ who sent me detailed TSO information.
  Without this I was not able to implement it.
  
  Tested by:	current

Modified:
  head/sys/dev/bge/if_bge.c
  head/sys/dev/bge/if_bgereg.h

Modified: head/sys/dev/bge/if_bge.c
==============================================================================
--- head/sys/dev/bge/if_bge.c	Sun Nov 22 20:50:27 2009	(r199670)
+++ head/sys/dev/bge/if_bge.c	Sun Nov 22 21:16:30 2009	(r199671)
@@ -96,6 +96,7 @@ __FBSDID("$FreeBSD$");
 #include <netinet/in_systm.h>
 #include <netinet/in.h>
 #include <netinet/ip.h>
+#include <netinet/tcp.h>
 
 #include <machine/bus.h>
 #include <machine/resource.h>
@@ -1811,6 +1812,8 @@ bge_blockinit(struct bge_softc *sc)
 		    BGE_RDMAMODE_MBUF_SBD_CRPT_ATTN;
 	if (sc->bge_flags & BGE_FLAG_PCIE)
 		val |= BGE_RDMAMODE_FIFO_LONG_BURST;
+	if (sc->bge_flags & BGE_FLAG_TSO)
+		val |= BGE_RDMAMODE_TSO4_ENABLE;
 	CSR_WRITE_4(sc, BGE_RDMA_MODE, val);
 	DELAY(40);
 
@@ -1837,7 +1840,10 @@ bge_blockinit(struct bge_softc *sc)
 	CSR_WRITE_4(sc, BGE_SDC_MODE, val);
 
 	/* Turn on send data initiator state machine */
-	CSR_WRITE_4(sc, BGE_SDI_MODE, BGE_SDIMODE_ENABLE);
+	if (sc->bge_flags & BGE_FLAG_TSO)
+		CSR_WRITE_4(sc, BGE_SDI_MODE, BGE_SDIMODE_ENABLE | 0x08);
+	else
+		CSR_WRITE_4(sc, BGE_SDI_MODE, BGE_SDIMODE_ENABLE);
 
 	/* Turn on send BD initiator state machine */
 	CSR_WRITE_4(sc, BGE_SBDI_MODE, BGE_SBDIMODE_ENABLE);
@@ -2105,6 +2111,7 @@ bge_dma_alloc(device_t dev)
 	struct bge_dmamap_arg ctx;
 	struct bge_softc *sc;
 	bus_addr_t lowaddr;
+	bus_size_t txsegsz, txmaxsegsz;
 	int i, error;
 
 	sc = device_get_softc(dev);
@@ -2131,10 +2138,17 @@ bge_dma_alloc(device_t dev)
 	/*
 	 * Create tag for Tx mbufs.
 	 */
+	if (sc->bge_flags & BGE_FLAG_TSO) {
+		txsegsz = BGE_TSOSEG_SZ;
+		txmaxsegsz = 65535 + sizeof(struct ether_vlan_header);
+	} else {
+		txsegsz = MCLBYTES;
+		txmaxsegsz = MCLBYTES * BGE_NSEG_NEW;
+	}
 	error = bus_dma_tag_create(sc->bge_cdata.bge_parent_tag, 1,
-	    0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL,
-	    NULL, MCLBYTES * BGE_NSEG_NEW, BGE_NSEG_NEW, MCLBYTES,
-	    BUS_DMA_ALLOCNOW, NULL, NULL, &sc->bge_cdata.bge_tx_mtag);
+	    0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL,
+	    txmaxsegsz, BGE_NSEG_NEW, txsegsz, 0, NULL, NULL,
+	    &sc->bge_cdata.bge_tx_mtag);
 
 	if (error) {
 		device_printf(sc->bge_dev, "could not allocate TX dma tag\n");
@@ -2146,7 +2160,7 @@ bge_dma_alloc(device_t dev)
 	 */
 	error = bus_dma_tag_create(sc->bge_cdata.bge_parent_tag, 1, 0,
 	    BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES, 1,
-	    MCLBYTES, BUS_DMA_ALLOCNOW, NULL, NULL, &sc->bge_cdata.bge_rx_mtag);
+	    MCLBYTES, 0, NULL, NULL, &sc->bge_cdata.bge_rx_mtag);
 
 	if (error) {
 		device_printf(sc->bge_dev, "could not allocate RX dma tag\n");
@@ -2592,6 +2606,21 @@ bge_attach(device_t dev)
 	    misccfg == BGE_MISCCFG_BOARD_ID_5788M)
 		sc->bge_flags |= BGE_FLAG_5788;
 
+	/*
+	 * Some controllers seem to require a special firmware to use
+	 * TSO. But the firmware is not available to FreeBSD and Linux
+	 * claims that the TSO performed by the firmware is slower than
+	 * hardware based TSO. Moreover the firmware based TSO has one
+	 * known bug which can't handle TSO if ethernet header + IP/TCP
+	 * header is greater than 80 bytes. The workaround for the TSO
+	 * bug exist but it seems it's too expensive than not using
+	 * TSO at all. Some hardwares also have the TSO bug so limit
+	 * the TSO to the controllers that are not affected TSO issues
+	 * (e.g. 5755 or higher).
+	 */
+	if (BGE_IS_5755_PLUS(sc))
+		sc->bge_flags |= BGE_FLAG_TSO;
+
   	/*
 	 * Check if this is a PCI-X or PCI Express device.
   	 */
@@ -2738,6 +2767,10 @@ bge_attach(device_t dev)
 	ifp->if_hwassist = BGE_CSUM_FEATURES;
 	ifp->if_capabilities = IFCAP_HWCSUM | IFCAP_VLAN_HWTAGGING |
 	    IFCAP_VLAN_MTU;
+	if ((sc->bge_flags & BGE_FLAG_TSO) != 0) {
+		ifp->if_hwassist |= CSUM_TSO;
+		ifp->if_capabilities |= IFCAP_TSO4;
+	}
 #ifdef IFCAP_VLAN_HWCSUM
 	ifp->if_capabilities |= IFCAP_VLAN_HWCSUM;
 #endif
@@ -3752,6 +3785,72 @@ bge_cksum_pad(struct mbuf *m)
 	return (0);
 }
 
+static struct mbuf *
+bge_setup_tso(struct bge_softc *sc, struct mbuf *m, uint16_t *mss)
+{
+	struct ether_header *eh;
+	struct ip *ip;
+	struct tcphdr *tcp;
+	struct mbuf *n;
+	uint16_t hlen;
+	uint32_t ip_off, poff;
+
+	if (M_WRITABLE(m) == 0) {
+		/* Get a writable copy. */
+		n = m_dup(m, M_DONTWAIT);
+		m_freem(m);
+		if (n == NULL)
+			return (NULL);
+		m = n;
+	}
+	ip_off = sizeof(struct ether_header);
+	m = m_pullup(m, ip_off);
+	if (m == NULL)
+		return (NULL);
+	eh = mtod(m, struct ether_header *);
+	/* Check the existence of VLAN tag. */
+	if (eh->ether_type == htons(ETHERTYPE_VLAN)) {
+		ip_off = sizeof(struct ether_vlan_header);
+		m = m_pullup(m, ip_off);
+		if (m == NULL)
+			return (NULL);
+	}
+	m = m_pullup(m, ip_off + sizeof(struct ip));
+	if (m == NULL)
+		return (NULL);
+	ip = (struct ip *)(mtod(m, char *) + ip_off);
+	poff = ip_off + (ip->ip_hl << 2);
+	m = m_pullup(m, poff + sizeof(struct tcphdr));
+	if (m == NULL)
+		return (NULL);
+	tcp = (struct tcphdr *)(mtod(m, char *) + poff);
+	m = m_pullup(m, poff + sizeof(struct tcphdr) + tcp->th_off);
+	if (m == NULL)
+		return (NULL);
+	/*
+	 * It seems controller doesn't modify IP length and TCP pseudo
+	 * checksum. These checksum computed by upper stack should be 0.
+	 */
+	*mss = m->m_pkthdr.tso_segsz;
+	ip->ip_sum = 0;
+	ip->ip_len = htons(*mss + (ip->ip_hl << 2) + (tcp->th_off << 2));
+	/* Clear pseudo checksum computed by TCP stack. */
+	tcp->th_sum = 0;
+	/*
+	 * Broadcom controllers uses different descriptor format for
+	 * TSO depending on ASIC revision. Due to TSO-capable firmware
+	 * license issue and lower performance of firmware based TSO
+	 * we only support hardware based TSO which is applicable for
+	 * BCM5755 or newer controllers. Hardware based TSO uses 11
+	 * bits to store MSS and upper 5 bits are used to store IP/TCP
+	 * header length(including IP/TCP options). The header length
+	 * is expressed as 32 bits unit.
+	 */
+	hlen = ((ip->ip_hl << 2) + (tcp->th_off << 2)) >> 2;
+	*mss |= (hlen << 11);
+	return (m);
+}
+
 /*
  * Encapsulate an mbuf chain in the tx ring  by coupling the mbuf data
  * pointers to descriptors.
@@ -3764,11 +3863,19 @@ bge_encap(struct bge_softc *sc, struct m
 	struct bge_tx_bd	*d;
 	struct mbuf		*m = *m_head;
 	uint32_t		idx = *txidx;
-	uint16_t		csum_flags;
+	uint16_t		csum_flags, mss, vlan_tag;
 	int			nsegs, i, error;
 
 	csum_flags = 0;
-	if (m->m_pkthdr.csum_flags) {
+	mss = 0;
+	vlan_tag = 0;
+	if ((m->m_pkthdr.csum_flags & CSUM_TSO) != 0) {
+		*m_head = m = bge_setup_tso(sc, m, &mss);
+		if (*m_head == NULL)
+			return (ENOBUFS);
+		csum_flags |= BGE_TXBDFLAG_CPU_PRE_DMA |
+		    BGE_TXBDFLAG_CPU_POST_DMA;
+	} else if ((m->m_pkthdr.csum_flags & BGE_CSUM_FEATURES) != 0) {
 		if (m->m_pkthdr.csum_flags & CSUM_IP)
 			csum_flags |= BGE_TXBDFLAG_IP_CSUM;
 		if (m->m_pkthdr.csum_flags & (CSUM_TCP | CSUM_UDP)) {
@@ -3815,12 +3922,29 @@ bge_encap(struct bge_softc *sc, struct m
 
 	bus_dmamap_sync(sc->bge_cdata.bge_tx_mtag, map, BUS_DMASYNC_PREWRITE);
 
+#if __FreeBSD_version > 700022
+	if (m->m_flags & M_VLANTAG) {
+		csum_flags |= BGE_TXBDFLAG_VLAN_TAG;
+		vlan_tag = m->m_pkthdr.ether_vtag;
+	}
+#else
+	{
+		struct m_tag		*mtag;
+
+		if ((mtag = VLAN_OUTPUT_TAG(sc->bge_ifp, m)) != NULL) {
+			csum_flags |= BGE_TXBDFLAG_VLAN_TAG;
+			vlan_tag = VLAN_TAG_VALUE(mtag);
+		}
+	}
+#endif
 	for (i = 0; ; i++) {
 		d = &sc->bge_ldata.bge_tx_ring[idx];
 		d->bge_addr.bge_addr_lo = BGE_ADDR_LO(segs[i].ds_addr);
 		d->bge_addr.bge_addr_hi = BGE_ADDR_HI(segs[i].ds_addr);
 		d->bge_len = segs[i].ds_len;
 		d->bge_flags = csum_flags;
+		d->bge_vlan_tag = vlan_tag;
+		d->bge_mss = mss;
 		if (i == nsegs - 1)
 			break;
 		BGE_INC(idx, BGE_TX_RING_CNT);
@@ -3829,26 +3953,6 @@ bge_encap(struct bge_softc *sc, struct m
 	/* Mark the last segment as end of packet... */
 	d->bge_flags |= BGE_TXBDFLAG_END;
 
-	/* ... and put VLAN tag into first segment.  */
-	d = &sc->bge_ldata.bge_tx_ring[*txidx];
-#if __FreeBSD_version > 700022
-	if (m->m_flags & M_VLANTAG) {
-		d->bge_flags |= BGE_TXBDFLAG_VLAN_TAG;
-		d->bge_vlan_tag = m->m_pkthdr.ether_vtag;
-	} else
-		d->bge_vlan_tag = 0;
-#else
-	{
-		struct m_tag		*mtag;
-
-		if ((mtag = VLAN_OUTPUT_TAG(sc->bge_ifp, m)) != NULL) {
-			d->bge_flags |= BGE_TXBDFLAG_VLAN_TAG;
-			d->bge_vlan_tag = VLAN_TAG_VALUE(mtag);
-		} else
-			d->bge_vlan_tag = 0;
-	}
-#endif
-
 	/*
 	 * Insure that the map for this transmission
 	 * is placed at the array index of the last descriptor
@@ -4355,14 +4459,23 @@ bge_ioctl(struct ifnet *ifp, u_long comm
 			ifp->if_capenable ^= IFCAP_HWCSUM;
 			if (IFCAP_HWCSUM & ifp->if_capenable &&
 			    IFCAP_HWCSUM & ifp->if_capabilities)
-				ifp->if_hwassist = BGE_CSUM_FEATURES;
+				ifp->if_hwassist |= BGE_CSUM_FEATURES;
 			else
-				ifp->if_hwassist = 0;
+				ifp->if_hwassist &= ~BGE_CSUM_FEATURES;
 #ifdef VLAN_CAPABILITIES
 			VLAN_CAPABILITIES(ifp);
 #endif
 		}
 
+		if ((mask & IFCAP_TSO4) != 0 &&
+		    (ifp->if_capabilities & IFCAP_TSO4) != 0) {
+			ifp->if_capenable ^= IFCAP_TSO4;
+			if ((ifp->if_capenable & IFCAP_TSO4) != 0)
+				ifp->if_hwassist |= CSUM_TSO;
+			else
+				ifp->if_hwassist &= ~CSUM_TSO;
+		}
+
 		if (mask & IFCAP_VLAN_MTU) {
 			ifp->if_capenable ^= IFCAP_VLAN_MTU;
 			ifp->if_drv_flags &= ~IFF_DRV_RUNNING;

Modified: head/sys/dev/bge/if_bgereg.h
==============================================================================
--- head/sys/dev/bge/if_bgereg.h	Sun Nov 22 20:50:27 2009	(r199670)
+++ head/sys/dev/bge/if_bgereg.h	Sun Nov 22 21:16:30 2009	(r199671)
@@ -1402,6 +1402,8 @@
 #define	BGE_RDMAMODE_MBUF_SBD_CRPT_ATTN	0x00002000
 #define	BGE_RDMAMODE_FIFO_SIZE_128	0x00020000
 #define	BGE_RDMAMODE_FIFO_LONG_BURST	0x00030000
+#define	BGE_RDMAMODE_TSO4_ENABLE	0x08000000
+#define	BGE_RDMAMODE_TSO6_ENABLE	0x10000000
 
 /* Read DMA status register */
 #define	BGE_RDMASTAT_PCI_TGT_ABRT_ATTN	0x00000004
@@ -1949,11 +1951,11 @@ struct bge_tx_bd {
 	uint16_t		bge_flags;
 	uint16_t		bge_len;
 	uint16_t		bge_vlan_tag;
-	uint16_t		bge_rsvd;
+	uint16_t		bge_mss;
 #else
 	uint16_t		bge_len;
 	uint16_t		bge_flags;
-	uint16_t		bge_rsvd;
+	uint16_t		bge_mss;
 	uint16_t		bge_vlan_tag;
 #endif
 };
@@ -2482,7 +2484,8 @@ struct bge_gib {
 #define	BGE_JSLOTS	384
 
 #define	BGE_NSEG_JUMBO	4
-#define	BGE_NSEG_NEW 32
+#define	BGE_NSEG_NEW	32
+#define	BGE_TSOSEG_SZ	4096
 
 /* Maximum DMA address for controllers that have 40bit DMA address bug. */
 #if (BUS_SPACE_MAXADDR < 0xFFFFFFFFFF)
@@ -2602,6 +2605,7 @@ struct bge_softc {
 #define	BGE_FLAG_MSI		0x00000100
 #define	BGE_FLAG_PCIX		0x00000200
 #define	BGE_FLAG_PCIE		0x00000400
+#define	BGE_FLAG_TSO		0x00000800
 #define	BGE_FLAG_5700_FAMILY	0x00001000
 #define	BGE_FLAG_5705_PLUS	0x00002000
 #define	BGE_FLAG_5714_FAMILY	0x00004000


More information about the svn-src-all mailing list