CFT: gem(4) checksum offload support
Pyun YongHyeon
pyunyh at gmail.com
Sat Apr 14 06:34:35 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-ppc
mailing list