Subject: tcp/udp vector h/w checksum
To: None <tech-net@netbsd.org>
From: john heasley <heas@shrubbery.net>
List: tech-net
Date: 02/05/2005 10:23:06
I've asked for comment on this before, but that may have been obscured
by information about the gem driver.

Basically, there are at least two chips, hme and gem, which offer only a
linear h/w checksum.  That is, given a start offset and a stuff offset,
they checksum from the start to the end of the packet and place the result
at the stuff offset.  They have no provision to compute the pseudo-header
portion of the checksum.
 
The pseudo-header checksum can be computed in the driver, but that means
code duplication and more time spent in the driver.
 
The attached diff adds the pseudo header to the packet if M_CSUM_NO_PSEUDOHDR
is set on if_csum_flags_tx and overloads m_pkthdr.csum_data, placing the IP
header length in the high 16 bits and thus the driver does not need to look
at the packet at all.

I've been using this with hme for more than a month now and have not noticed
any problems.  I'd like commit this today and get the hme changes in shortly
thereafter.  TIA.

Index: sys/mbuf.h
===================================================================
RCS file: /cvsroot/src/sys/sys/mbuf.h,v
retrieving revision 1.99
diff -u -r1.99 mbuf.h
--- sys/mbuf.h	21 Sep 2004 21:57:30 -0000	1.99
+++ sys/mbuf.h	5 Feb 2005 18:14:51 -0000
@@ -172,10 +172,14 @@
 #define	M_CSUM_IPv4_BAD		0x00000080	/* IPv4 header checksum bad */
 
 /* Checksum-assist quirks: keep separate from jump-table bits. */
-#define	M_CSUM_NO_PSEUDOHDR	0x80000000	/* Rx M_CSUM_DATA does not include
-						 * the UDP/TCP pseudo-hdr, and
-						 * is not yet 1s-complemented.
-						 */
+/* Rx: M_CSUM_DATA does not include the UDP/TCP pseudo-hdr, and is not yet
+ *     1s-complemented.
+ * Tx: The controller only does linear checksums (ie: from some offset to end
+ *     of the packet), the IP module should stuff the TCP/UDP pseudo-hdr
+ *     checksum in the TCP/UDP header.  The high 16 bits of M_CSUM_DATA is the
+ *     start offset (ie: end of the IP header).
+ */
+#define	M_CSUM_NO_PSEUDOHDR	0x80000000
 
 /*
  * Max # of pages we can attach to m_ext.  This is carefully chosen
Index: netinet/ip_output.c
===================================================================
RCS file: /cvsroot/src/sys/netinet/ip_output.c,v
retrieving revision 1.138
diff -u -r1.138 ip_output.c
--- netinet/ip_output.c	15 Dec 2004 04:25:19 -0000	1.138
+++ netinet/ip_output.c	5 Feb 2005 18:14:51 -0000
@@ -128,6 +128,8 @@
 #include <netinet/in_pcb.h>
 #include <netinet/in_var.h>
 #include <netinet/ip_var.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
 
 #ifdef MROUTING
 #include <netinet/ip_mroute.h>
@@ -780,6 +782,34 @@
 			m->m_pkthdr.csum_flags &= ~(M_CSUM_TCPv4|M_CSUM_UDPv4);
 		}
 
+		else if (ifp->if_csum_flags_tx & M_CSUM_NO_PSEUDOHDR &&
+			 m->m_pkthdr.csum_flags & (M_CSUM_UDPv4|M_CSUM_TCPv4)) {
+			/* XXX add pseudo sum */
+			struct udphdr *uh;
+			struct tcphdr *th;
+
+			ip = mtod(m, struct ip *);
+			hlen = ip->ip_hl << 2;
+			m->m_pkthdr.csum_data = (hlen << 16) |
+					(m->m_pkthdr.csum_data & 0xffff);
+
+			if (ip->ip_p == IPPROTO_TCP) {
+				th = (struct tcphdr *) (mtod(m, caddr_t) +
+				     hlen);
+				th->th_sum = in_cksum_phdr(ip->ip_src.s_addr,
+					     ip->ip_dst.s_addr,
+					     htons(ntohs(ip->ip_len) - hlen +
+					     IPPROTO_TCP));
+			} else if (ip->ip_p == IPPROTO_UDP) {
+				uh = (struct udphdr *) (mtod(m, caddr_t) +
+				     hlen);
+				uh->uh_sum = in_cksum_phdr(ip->ip_src.s_addr,
+					     ip->ip_dst.s_addr,
+					     htons(ntohs(ip->ip_len) - hlen +
+					     IPPROTO_UDP));
+			}
+		}
+
 #ifdef IPSEC
 		/* clean ipsec history once it goes out of the node */
 		ipsec_delaux(m);