diff options
author | Brad Smith <brad@cvs.openbsd.org> | 2012-12-01 10:04:59 +0000 |
---|---|---|
committer | Brad Smith <brad@cvs.openbsd.org> | 2012-12-01 10:04:59 +0000 |
commit | d1bd04b624d62eb0bf00a94af56764241fdcdcef (patch) | |
tree | 13aef4c96b2e03ea07e08f7bf8600ff1da1d195a /sys | |
parent | 42fdbab24c29f93fd5f74334afbf1e60f0431b44 (diff) |
Add RX TCP/UDP checksum offload support.
ok naddy@ sthen@
Diffstat (limited to 'sys')
-rw-r--r-- | sys/dev/ic/gem.c | 102 |
1 files changed, 100 insertions, 2 deletions
diff --git a/sys/dev/ic/gem.c b/sys/dev/ic/gem.c index f805cf78866..e365f105987 100644 --- a/sys/dev/ic/gem.c +++ b/sys/dev/ic/gem.c @@ -1,4 +1,4 @@ -/* $OpenBSD: gem.c,v 1.97 2012/10/22 05:05:57 brad Exp $ */ +/* $OpenBSD: gem.c,v 1.98 2012/12/01 10:04:58 brad Exp $ */ /* $NetBSD: gem.c,v 1.1 2001/09/16 00:11:43 eeh Exp $ */ /* @@ -56,7 +56,11 @@ #ifdef INET #include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> #include <netinet/if_ether.h> +#include <netinet/tcp.h> +#include <netinet/udp.h> #endif #if NBPFILTER > 0 @@ -116,6 +120,7 @@ int gem_eint(struct gem_softc *, u_int); int gem_rint(struct gem_softc *); int gem_tint(struct gem_softc *, u_int32_t); int gem_pint(struct gem_softc *); +void gem_rxcksum(struct mbuf *, u_int64_t); #ifdef GEM_DEBUG #define DPRINTF(sc, x) if ((sc)->sc_arpcom.ac_if.if_flags & IFF_DEBUG) \ @@ -803,7 +808,9 @@ gem_init(struct ifnet *ifp) /* 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)| @@ -938,6 +945,95 @@ gem_init_regs(struct gem_softc *sc) } /* + * RX TCP/UDP checksum + */ +void +gem_rxcksum(struct mbuf *m, u_int64_t rxstat) +{ + struct ether_header *eh; + struct ip *ip; + struct udphdr *uh; + int32_t hlen, len, pktlen; + u_int16_t cksum, *opts; + u_int32_t temp32; + union pseudoh { + struct hdr { + u_int16_t len; + u_int8_t ttl; + u_int8_t proto; + u_int32_t src; + u_int32_t dst; + } h; + u_int16_t w[6]; + } ph; + + pktlen = m->m_pkthdr.len; + if (pktlen < sizeof(struct ether_header)) + 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 *)((caddr_t)ip + hlen); + if (uh->uh_sum == 0) + return; /* no checksum */ + break; + default: + return; + } + + cksum = htons(~(rxstat & GEM_RD_CHECKSUM)); + /* cksum fixup for IP options */ + len = hlen - sizeof(struct ip); + if (len > 0) { + opts = (u_int16_t *)(ip + 1); + for (; len > 0; len -= sizeof(u_int16_t), opts++) { + temp32 = cksum - *opts; + temp32 = (temp32 >> 16) + (temp32 & 65535); + cksum = temp32 & 65535; + } + } + + ph.h.len = htons(ntohs(ip->ip_len) - hlen); + ph.h.ttl = 0; + ph.h.proto = ip->ip_p; + ph.h.src = ip->ip_src.s_addr; + ph.h.dst = ip->ip_dst.s_addr; + temp32 = cksum; + opts = &ph.w[0]; + temp32 += opts[0] + opts[1] + opts[2] + opts[3] + opts[4] + opts[5]; + temp32 = (temp32 >> 16) + (temp32 & 65535); + temp32 += (temp32 >> 16); + cksum = ~temp32; + if (cksum == 0) { + m->m_pkthdr.csum_flags |= + M_TCP_CSUM_IN_OK | M_UDP_CSUM_IN_OK; + } +} + +/* * Receive interrupt. */ int @@ -1002,6 +1098,8 @@ gem_rint(struct gem_softc *sc) m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = m->m_len = len; + gem_rxcksum(m, rxstat); + #if NBPFILTER > 0 if (ifp->if_bpf) bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_IN); |