summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorBrad Smith <brad@cvs.openbsd.org>2012-12-01 10:04:59 +0000
committerBrad Smith <brad@cvs.openbsd.org>2012-12-01 10:04:59 +0000
commitd1bd04b624d62eb0bf00a94af56764241fdcdcef (patch)
tree13aef4c96b2e03ea07e08f7bf8600ff1da1d195a /sys
parent42fdbab24c29f93fd5f74334afbf1e60f0431b44 (diff)
Add RX TCP/UDP checksum offload support.
ok naddy@ sthen@
Diffstat (limited to 'sys')
-rw-r--r--sys/dev/ic/gem.c102
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);