CFT: gem(4) checksum offload support

Pyun YongHyeon pyunyh at gmail.com
Sat Apr 14 06:08:49 UTC 2007


Hi,

Sorry for cross posting.
I've made a patch to enable hardware checksum offload in gem(4) and
it seems to work correctly on ppc. I saw decreased CPU usage with
checksum offload but I can't sure how much it helped as ppc's vmstat(1)
output shows fluctuating interrupt numbers/CPU usage patterns.
Anyway, before committing to tree I'd like to know any regressions not
noticed by me.
ATM the patch does not enable Tx UDP checksum offload as the hardware
can generate checksum value 0 for UDP packet.(The hardware should flip
the checksum value for UDP checksum 0 such that 0xffff should be
used for UDP packet.) If you still want to use Tx UDP checksum
offload set link0 flag with ifconfig(8).
If there is no objections I'll commit it in a few days.

Thanks.
-- 
Regards,
Pyun YongHyeon
-------------- next part --------------
Index: if_gem.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/gem/if_gem.c,v
retrieving revision 1.40
diff -u -r1.40 if_gem.c
--- if_gem.c	6 Dec 2006 02:04:25 -0000	1.40
+++ if_gem.c	14 Apr 2007 03:30:28 -0000
@@ -65,6 +65,12 @@
 #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 <netinet/udp.h>
+
 #include <machine/bus.h>
 
 #include <dev/mii/mii.h>
@@ -74,6 +80,7 @@
 #include <dev/gem/if_gemvar.h>
 
 #define TRIES	10000
+#define	GEM_CSUM_FEATURES	(CSUM_TCP)
 
 static void	gem_start(struct ifnet *);
 static void	gem_start_locked(struct ifnet *);
@@ -82,6 +89,8 @@
 static void	gem_cddma_callback(void *, bus_dma_segment_t *, int, int);
 static void	gem_txdma_callback(void *, bus_dma_segment_t *, int,
     bus_size_t, int);
+static __inline void gem_txcksum(struct gem_softc *, struct mbuf *, uint64_t *);
+static __inline void gem_rxcksum(struct mbuf *, uint64_t);
 static void	gem_tick(void *);
 static int	gem_watchdog(struct gem_softc *);
 static void	gem_init(void *);
@@ -264,6 +273,7 @@
 	device_printf(sc->sc_dev, "%ukB RX FIFO, %ukB TX FIFO\n",
 	    sc->sc_rxfifosize / 1024, v / 16);
 
+	sc->sc_csum_features = GEM_CSUM_FEATURES;
 	/* Initialize ifnet structure. */
 	ifp->if_softc = sc;
 	if_initname(ifp, device_get_name(sc->sc_dev),
@@ -332,11 +342,12 @@
 #endif
 
 	/*
-	 * Tell the upper layer(s) we support long frames.
+	 * Tell the upper layer(s) we support long frames/checksum offloads.
 	 */
 	ifp->if_data.ifi_hdrlen = sizeof(struct ether_vlan_header);
-	ifp->if_capabilities |= IFCAP_VLAN_MTU;
-	ifp->if_capenable |= IFCAP_VLAN_MTU;
+	ifp->if_capabilities |= IFCAP_VLAN_MTU | IFCAP_HWCSUM;
+	ifp->if_hwassist |= sc->sc_csum_features;
+	ifp->if_capenable |= IFCAP_VLAN_MTU | IFCAP_HWCSUM;
 
 	return (0);
 
@@ -470,7 +481,7 @@
 	struct gem_softc *sc = txd->txd_sc;
 	struct gem_txsoft *txs = txd->txd_txs;
 	bus_size_t len = 0;
-	uint64_t flags = 0;
+	uint64_t cflags, flags;
 	int seg, nexttx;
 
 	if (error != 0)
@@ -488,6 +499,10 @@
 	txs->txs_ndescs = nsegs;
 
 	nexttx = txs->txs_firstdesc;
+
+	flags = cflags = 0;
+	if ((txd->m->m_pkthdr.csum_flags & sc->sc_csum_features) != 0)
+		gem_txcksum(sc, txd->m, &cflags);
 	/*
 	 * Initialize the transmit descriptors.
 	 */
@@ -507,6 +522,7 @@
 		KASSERT(segs[seg].ds_len < GEM_TD_BUFSIZE,
 		    ("gem_txdma_callback: segment size too large!"));
 		flags = segs[seg].ds_len & GEM_TD_BUFSIZE;
+		flags |= cflags;
 		if (len == 0) {
 #ifdef GEM_DEBUG
 			CTR2(KTR_GEM, "txdma_cb: start of packet at seg %d, "
@@ -533,6 +549,112 @@
 	    ("gem_txdma_callback: missed end of packet!"));
 }
 
+static __inline void
+gem_txcksum(struct gem_softc *sc, struct mbuf *m, uint64_t *cflags)
+{
+	struct ip *ip;
+	uint64_t offset, offset2;
+	char *p;
+
+	offset = sizeof(struct ip) + ETHER_HDR_LEN;
+	for(; m && m->m_len == 0; m = m->m_next)
+		;
+	if (m == NULL || m->m_len < ETHER_HDR_LEN) {
+		device_printf(sc->sc_dev, "%s: m_len < ETHER_HDR_LEN\n",
+		    __func__);
+		/* checksum will be corrupted */
+		goto sendit;
+	}
+	if (m->m_len < ETHER_HDR_LEN + sizeof(uint32_t)) {
+		if (m->m_len != ETHER_HDR_LEN) {
+			device_printf(sc->sc_dev,
+			    "%s: m_len != ETHER_HDR_LEN\n", __func__);
+			/* checksum will be corrupted */
+			goto sendit;
+		}
+		for(m = m->m_next; m && m->m_len == 0; m = m->m_next)
+			;
+		if (m == NULL) {
+			/* checksum will be corrupted */
+			goto sendit;
+		}
+		ip = mtod(m, struct ip *);
+	} else {
+		p = mtod(m, uint8_t *);
+		p += ETHER_HDR_LEN;
+		ip = (struct ip *)p;
+	}
+	offset = (ip->ip_hl << 2) + ETHER_HDR_LEN;
+
+sendit:
+	offset2 = m->m_pkthdr.csum_data;
+	*cflags = offset << GEM_TD_CXSUM_SSHIFT;
+	*cflags |= ((offset + offset2) << GEM_TD_CXSUM_OSHIFT);
+	*cflags |= GEM_TD_CXSUM_ENABLE;
+}
+
+static __inline void
+gem_rxcksum(struct mbuf *m, uint64_t flags)
+{
+	struct ether_header *eh;
+	struct ip *ip;
+	struct udphdr *uh;
+	int32_t hlen, len, pktlen;
+	uint16_t cksum, *opts;
+	uint32_t temp32;
+
+	pktlen = m->m_pkthdr.len;
+	if (pktlen < sizeof(struct ether_header) + sizeof(struct ip))
+		return;
+	eh = mtod(m, struct ether_header *);
+	if (eh->ether_type != htons(ETHERTYPE_IP))
+		return;
+	ip = (struct ip *)(eh + 1);
+	if (ip->ip_v != IPVERSION)
+		return;
+
+	hlen = ip->ip_hl << 2;
+	pktlen -= sizeof(struct ether_header);
+	if (hlen < sizeof(struct ip))
+		return;
+	if (ntohs(ip->ip_len) < hlen)
+		return;
+	if (ntohs(ip->ip_len) != pktlen)
+		return;
+	if (ip->ip_off & htons(IP_MF | IP_OFFMASK))
+		return;	/* can't handle fragmented packet */
+
+	switch (ip->ip_p) {
+	case IPPROTO_TCP:
+		if (pktlen < (hlen + sizeof(struct tcphdr)))
+			return;
+		break;
+	case IPPROTO_UDP:
+		if (pktlen < (hlen + sizeof(struct udphdr)))
+			return;
+		uh = (struct udphdr *)((uint8_t *)ip + hlen);
+		if (uh->uh_sum == 0)
+			return; /* no checksum */
+		break;
+	default:
+		return;
+	}
+
+	cksum = ~(flags & GEM_RD_CHECKSUM);
+	/* checksum fixup for IP options */
+	len = hlen - sizeof(struct ip);
+	if (len > 0) {
+		opts = (uint16_t *)(ip + 1);
+		for (; len > 0; len -= sizeof(uint16_t), opts++) {
+			temp32 = cksum - *opts;
+			temp32 = (temp32 >> 16) + (temp32 & 65535);
+			cksum = temp32 & 65535;
+		}
+	}
+	m->m_pkthdr.csum_flags |= CSUM_DATA_VALID;
+	m->m_pkthdr.csum_data = cksum;
+}
+
 static void
 gem_tick(arg)
 	void *arg;
@@ -954,12 +1076,14 @@
 
 	/* Encode Receive Descriptor ring size: four possible values */
 	v = gem_ringsize(GEM_NRXDESC /*XXX*/);
+	/* Rx TCP/UDP checksum offset */
+	v |= ((ETHER_HDR_LEN + sizeof(struct ip)) <<
+	    GEM_RX_CONFIG_CXM_START_SHFT);
 
 	/* Enable DMA */
 	bus_space_write_4(t, h, GEM_RX_CONFIG,
 		v|(GEM_THRSH_1024<<GEM_RX_CONFIG_FIFO_THRS_SHIFT)|
-		(2<<GEM_RX_CONFIG_FBOFF_SHFT)|GEM_RX_CONFIG_RXDMA_EN|
-		(0<<GEM_RX_CONFIG_CXM_START_SHFT));
+		(2<<GEM_RX_CONFIG_FBOFF_SHFT)|GEM_RX_CONFIG_RXDMA_EN);
 	/*
 	 * The following value is for an OFF Threshold of about 3/4 full
 	 * and an ON Threshold of 1/4 full.
@@ -974,7 +1098,7 @@
 
 	/* step 12. RX_MAC Configuration Register */
 	v = bus_space_read_4(t, h, GEM_MAC_RX_CONFIG);
-	v |= GEM_MAC_RX_ENABLE;
+	v |= GEM_MAC_RX_ENABLE | GEM_MAC_RX_STRIP_CRC;
 	bus_space_write_4(t, h, GEM_MAC_RX_CONFIG, v);
 
 	/* step 14. Issue Transmit Pending command */
@@ -1008,6 +1132,7 @@
 	txd.txd_sc = sc;
 	txd.txd_txs = txs;
 	txs->txs_firstdesc = sc->sc_txnext;
+	txd.m = m0;
 	error = bus_dmamap_load_mbuf(sc->sc_tdmatag, txs->txs_dmamap, m0,
 	    gem_txdma_callback, &txd, BUS_DMA_NOWAIT);
 	if (error != 0)
@@ -1454,8 +1579,7 @@
 #endif
 
 		/*
-		 * No errors; receive the packet.  Note the Gem
-		 * includes the CRC with every packet.
+		 * No errors; receive the packet.
 		 */
 		len = GEM_RD_BUFLEN(rxstat);
 
@@ -1473,7 +1597,10 @@
 		m->m_data += 2; /* We're already off by two */
 
 		m->m_pkthdr.rcvif = ifp;
-		m->m_pkthdr.len = m->m_len = len - ETHER_CRC_LEN;
+		m->m_pkthdr.len = m->m_len = len;
+
+		if ((ifp->if_capenable & IFCAP_RXCSUM) != 0)
+			gem_rxcksum(m, rxstat);
 
 		/* Pass it on. */
 		GEM_UNLOCK(sc);
@@ -1876,6 +2003,12 @@
 			if (ifp->if_drv_flags & IFF_DRV_RUNNING)
 				gem_stop(ifp, 0);
 		}
+		if ((ifp->if_flags & IFF_LINK0) != 0)
+			sc->sc_csum_features |= CSUM_UDP;
+		else
+			sc->sc_csum_features &= ~CSUM_UDP;
+		if ((ifp->if_capenable & IFCAP_TXCSUM) != 0)
+			ifp->if_hwassist = sc->sc_csum_features;
 		sc->sc_ifflags = ifp->if_flags;
 		GEM_UNLOCK(sc);
 		break;
@@ -1889,6 +2022,15 @@
 	case SIOCSIFMEDIA:
 		error = ifmedia_ioctl(ifp, ifr, &sc->sc_mii->mii_media, cmd);
 		break;
+	case SIOCSIFCAP:
+		GEM_LOCK(sc);
+		ifp->if_capenable = ifr->ifr_reqcap;
+		if ((ifp->if_capenable & IFCAP_TXCSUM) != 0)
+			ifp->if_hwassist = sc->sc_csum_features;
+		else
+			ifp->if_hwassist = 0;
+		GEM_UNLOCK(sc);
+		break;
 	default:
 		error = ether_ioctl(ifp, cmd, data);
 		break;
Index: if_gemreg.h
===================================================================
RCS file: /home/ncvs/src/sys/dev/gem/if_gemreg.h,v
retrieving revision 1.3
diff -u -r1.3 if_gemreg.h
--- if_gemreg.h	6 Jan 2005 01:42:42 -0000	1.3
+++ if_gemreg.h	14 Apr 2007 03:30:28 -0000
@@ -516,6 +516,10 @@
 #define	GEM_TD_START_OF_PACKET	0x0000000080000000LL
 #define	GEM_TD_INTERRUPT_ME	0x0000000100000000LL	/* Interrupt me now */
 #define	GEM_TD_NO_CRC		0x0000000200000000LL	/* do not insert crc */
+
+#define	GEM_TD_CXSUM_SSHIFT	15
+#define	GEM_TD_CXSUM_OSHIFT	21
+
 /*
  * Only need to set GEM_TD_CXSUM_ENABLE, GEM_TD_CXSUM_STUFF,
  * GEM_TD_CXSUM_START, and GEM_TD_INTERRUPT_ME in 1st descriptor of a group.
Index: if_gemvar.h
===================================================================
RCS file: /home/ncvs/src/sys/dev/gem/if_gemvar.h,v
retrieving revision 1.12
diff -u -r1.12 if_gemvar.h
--- if_gemvar.h	6 Dec 2006 02:04:25 -0000	1.12
+++ if_gemvar.h	14 Apr 2007 03:30:28 -0000
@@ -108,6 +108,7 @@
 struct gem_txdma {
 	struct gem_softc *txd_sc;
 	struct gem_txsoft	*txd_txs;
+	struct mbuf *m;
 };
 
 /*
@@ -189,6 +190,7 @@
 	int		sc_inited;
 	int		sc_debug;
 	int		sc_ifflags;
+	int		sc_csum_features;
 
 	struct mtx	sc_mtx;
 };


More information about the freebsd-sparc64 mailing list