xref: /freebsd/sys/netinet/tcp_lro.c (revision 27f190a3ca1ba77e3793b14e5aecaee829090169)
1*27f190a3SBjoern A. Zeeb /*-
2*27f190a3SBjoern A. Zeeb  * Copyright (c) 2007, Myricom Inc.
3*27f190a3SBjoern A. Zeeb  * Copyright (c) 2008, Intel Corporation.
4*27f190a3SBjoern A. Zeeb  * All rights reserved.
5*27f190a3SBjoern A. Zeeb  *
6*27f190a3SBjoern A. Zeeb  * Redistribution and use in source and binary forms, with or without
7*27f190a3SBjoern A. Zeeb  * modification, are permitted provided that the following conditions
8*27f190a3SBjoern A. Zeeb  * are met:
9*27f190a3SBjoern A. Zeeb  * 1. Redistributions of source code must retain the above copyright
10*27f190a3SBjoern A. Zeeb  *    notice, this list of conditions and the following disclaimer.
11*27f190a3SBjoern A. Zeeb  * 2. Redistributions in binary form must reproduce the above copyright
12*27f190a3SBjoern A. Zeeb  *    notice, this list of conditions and the following disclaimer in the
13*27f190a3SBjoern A. Zeeb  *    documentation and/or other materials provided with the distribution.
14*27f190a3SBjoern A. Zeeb  *
15*27f190a3SBjoern A. Zeeb  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16*27f190a3SBjoern A. Zeeb  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17*27f190a3SBjoern A. Zeeb  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18*27f190a3SBjoern A. Zeeb  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19*27f190a3SBjoern A. Zeeb  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20*27f190a3SBjoern A. Zeeb  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21*27f190a3SBjoern A. Zeeb  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22*27f190a3SBjoern A. Zeeb  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23*27f190a3SBjoern A. Zeeb  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24*27f190a3SBjoern A. Zeeb  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25*27f190a3SBjoern A. Zeeb  * SUCH DAMAGE.
26*27f190a3SBjoern A. Zeeb  *
27*27f190a3SBjoern A. Zeeb  * $FreeBSD$
28*27f190a3SBjoern A. Zeeb  */
296c5087a8SJack F Vogel 
306c5087a8SJack F Vogel #include <sys/param.h>
316c5087a8SJack F Vogel #include <sys/systm.h>
326c5087a8SJack F Vogel #include <sys/endian.h>
336c5087a8SJack F Vogel #include <sys/mbuf.h>
346c5087a8SJack F Vogel #include <sys/kernel.h>
356c5087a8SJack F Vogel #include <sys/socket.h>
366c5087a8SJack F Vogel 
376c5087a8SJack F Vogel #include <net/if.h>
386c5087a8SJack F Vogel #include <net/ethernet.h>
396c5087a8SJack F Vogel #include <net/if_media.h>
406c5087a8SJack F Vogel 
416c5087a8SJack F Vogel #include <netinet/in_systm.h>
426c5087a8SJack F Vogel #include <netinet/in.h>
436c5087a8SJack F Vogel #include <netinet/ip.h>
446c5087a8SJack F Vogel #include <netinet/tcp.h>
456c5087a8SJack F Vogel #include <netinet/tcp_lro.h>
466c5087a8SJack F Vogel 
476c5087a8SJack F Vogel #include <machine/bus.h>
486c5087a8SJack F Vogel #include <machine/in_cksum.h>
496c5087a8SJack F Vogel 
506c5087a8SJack F Vogel 
516c5087a8SJack F Vogel static uint16_t do_csum_data(uint16_t *raw, int len)
526c5087a8SJack F Vogel {
536c5087a8SJack F Vogel 	uint32_t csum;
546c5087a8SJack F Vogel 	csum = 0;
556c5087a8SJack F Vogel 	while (len > 0) {
566c5087a8SJack F Vogel 		csum += *raw;
576c5087a8SJack F Vogel 		raw++;
586c5087a8SJack F Vogel 		csum += *raw;
596c5087a8SJack F Vogel 		raw++;
606c5087a8SJack F Vogel 		len -= 4;
616c5087a8SJack F Vogel 	}
626c5087a8SJack F Vogel 	csum = (csum >> 16) + (csum & 0xffff);
636c5087a8SJack F Vogel 	csum = (csum >> 16) + (csum & 0xffff);
646c5087a8SJack F Vogel 	return (uint16_t)csum;
656c5087a8SJack F Vogel }
666c5087a8SJack F Vogel 
676c5087a8SJack F Vogel /*
686c5087a8SJack F Vogel  * Allocate and init the LRO data structures
696c5087a8SJack F Vogel  */
706c5087a8SJack F Vogel int
716c5087a8SJack F Vogel tcp_lro_init(struct lro_ctrl *cntl)
726c5087a8SJack F Vogel {
736c5087a8SJack F Vogel 	struct lro_entry *lro;
746c5087a8SJack F Vogel 	int i, error = 0;
756c5087a8SJack F Vogel 
766c5087a8SJack F Vogel 	SLIST_INIT(&cntl->lro_free);
776c5087a8SJack F Vogel 	SLIST_INIT(&cntl->lro_active);
786c5087a8SJack F Vogel 
796c5087a8SJack F Vogel 	cntl->lro_bad_csum = 0;
806c5087a8SJack F Vogel 	cntl->lro_queued = 0;
816c5087a8SJack F Vogel 	cntl->lro_flushed = 0;
826c5087a8SJack F Vogel 
836c5087a8SJack F Vogel 	for (i = 0; i < LRO_ENTRIES; i++) {
846c5087a8SJack F Vogel                 lro = (struct lro_entry *) malloc(sizeof (struct lro_entry),
856c5087a8SJack F Vogel 		    M_DEVBUF, M_NOWAIT | M_ZERO);
866c5087a8SJack F Vogel                 if (lro == NULL) {
876c5087a8SJack F Vogel 			if (i == 0)
886c5087a8SJack F Vogel 				error = ENOMEM;
896c5087a8SJack F Vogel                         break;
906c5087a8SJack F Vogel                 }
916c5087a8SJack F Vogel 		cntl->lro_cnt = i;
926c5087a8SJack F Vogel                 SLIST_INSERT_HEAD(&cntl->lro_free, lro, next);
936c5087a8SJack F Vogel         }
946c5087a8SJack F Vogel 
956c5087a8SJack F Vogel 	return (error);
966c5087a8SJack F Vogel }
976c5087a8SJack F Vogel 
986c5087a8SJack F Vogel void
996c5087a8SJack F Vogel tcp_lro_free(struct lro_ctrl *cntl)
1006c5087a8SJack F Vogel {
1016c5087a8SJack F Vogel 	struct lro_entry *entry;
1026c5087a8SJack F Vogel 
1036c5087a8SJack F Vogel 	while (!SLIST_EMPTY(&cntl->lro_free)) {
1046c5087a8SJack F Vogel 		entry = SLIST_FIRST(&cntl->lro_free);
1056c5087a8SJack F Vogel 		SLIST_REMOVE_HEAD(&cntl->lro_free, next);
1066c5087a8SJack F Vogel 		free(entry, M_DEVBUF);
1076c5087a8SJack F Vogel 	}
1086c5087a8SJack F Vogel }
1096c5087a8SJack F Vogel 
1106c5087a8SJack F Vogel void
1116c5087a8SJack F Vogel tcp_lro_flush(struct lro_ctrl *cntl, struct lro_entry *lro)
1126c5087a8SJack F Vogel {
1136c5087a8SJack F Vogel 	struct ifnet *ifp;
1146c5087a8SJack F Vogel 	struct ip *ip;
1156c5087a8SJack F Vogel 	struct tcphdr *tcp;
1166c5087a8SJack F Vogel 	uint32_t *ts_ptr;
1176c5087a8SJack F Vogel 	uint32_t tcplen, tcp_csum;
1186c5087a8SJack F Vogel 
1196c5087a8SJack F Vogel 
1206c5087a8SJack F Vogel 	if (lro->append_cnt) {
1216c5087a8SJack F Vogel 		/* incorporate the new len into the ip header and
1226c5087a8SJack F Vogel 		 * re-calculate the checksum */
1236c5087a8SJack F Vogel 		ip = lro->ip;
1246c5087a8SJack F Vogel 		ip->ip_len = htons(lro->len - ETHER_HDR_LEN);
1256c5087a8SJack F Vogel 		ip->ip_sum = 0;
1266c5087a8SJack F Vogel 		ip->ip_sum = 0xffff ^
1276c5087a8SJack F Vogel 			do_csum_data((uint16_t*)ip,
1286c5087a8SJack F Vogel 					      sizeof (*ip));
1296c5087a8SJack F Vogel 
1306c5087a8SJack F Vogel 		lro->m_head->m_pkthdr.csum_flags = CSUM_IP_CHECKED |
1316c5087a8SJack F Vogel 			CSUM_IP_VALID | CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
1326c5087a8SJack F Vogel 		lro->m_head->m_pkthdr.csum_data = 0xffff;
1336c5087a8SJack F Vogel 		lro->m_head->m_pkthdr.len = lro->len;
1346c5087a8SJack F Vogel 
1356c5087a8SJack F Vogel 		/* incorporate the latest ack into the tcp header */
1366c5087a8SJack F Vogel 		tcp = (struct tcphdr *) (ip + 1);
1376c5087a8SJack F Vogel 		tcp->th_ack = lro->ack_seq;
1386c5087a8SJack F Vogel 		tcp->th_win = lro->window;
1396c5087a8SJack F Vogel 		/* incorporate latest timestamp into the tcp header */
1406c5087a8SJack F Vogel 		if (lro->timestamp) {
1416c5087a8SJack F Vogel 			ts_ptr = (uint32_t *)(tcp + 1);
1426c5087a8SJack F Vogel 			ts_ptr[1] = htonl(lro->tsval);
1436c5087a8SJack F Vogel 			ts_ptr[2] = lro->tsecr;
1446c5087a8SJack F Vogel 		}
1456c5087a8SJack F Vogel 		/*
1466c5087a8SJack F Vogel 		 * update checksum in tcp header by re-calculating the
1476c5087a8SJack F Vogel 		 * tcp pseudoheader checksum, and adding it to the checksum
1486c5087a8SJack F Vogel 		 * of the tcp payload data
1496c5087a8SJack F Vogel 		 */
1506c5087a8SJack F Vogel 		tcp->th_sum = 0;
1516c5087a8SJack F Vogel 		tcplen = lro->len - sizeof(*ip) - ETHER_HDR_LEN;
1526c5087a8SJack F Vogel 		tcp_csum = lro->data_csum;
1536c5087a8SJack F Vogel 		tcp_csum += in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr,
1546c5087a8SJack F Vogel 				      htons(tcplen + IPPROTO_TCP));
1556c5087a8SJack F Vogel 		tcp_csum += do_csum_data((uint16_t*)tcp,
1566c5087a8SJack F Vogel 						  tcp->th_off << 2);
1576c5087a8SJack F Vogel 		tcp_csum = (tcp_csum & 0xffff) + (tcp_csum >> 16);
1586c5087a8SJack F Vogel 		tcp_csum = (tcp_csum & 0xffff) + (tcp_csum >> 16);
1596c5087a8SJack F Vogel 		tcp->th_sum = 0xffff ^ tcp_csum;
1606c5087a8SJack F Vogel 	}
1616c5087a8SJack F Vogel 	ifp = cntl->ifp;
1626c5087a8SJack F Vogel 	(*ifp->if_input)(cntl->ifp, lro->m_head);
1636c5087a8SJack F Vogel 	cntl->lro_queued += lro->append_cnt + 1;
1646c5087a8SJack F Vogel 	cntl->lro_flushed++;
1656c5087a8SJack F Vogel 	lro->m_head = NULL;
1666c5087a8SJack F Vogel 	lro->timestamp = 0;
1676c5087a8SJack F Vogel 	lro->append_cnt = 0;
1686c5087a8SJack F Vogel 	SLIST_INSERT_HEAD(&cntl->lro_free, lro, next);
1696c5087a8SJack F Vogel }
1706c5087a8SJack F Vogel 
1716c5087a8SJack F Vogel int
1726c5087a8SJack F Vogel tcp_lro_rx(struct lro_ctrl *cntl, struct mbuf *m_head, uint32_t csum)
1736c5087a8SJack F Vogel {
1746c5087a8SJack F Vogel 	struct ether_header *eh;
1756c5087a8SJack F Vogel 	struct ip *ip;
1766c5087a8SJack F Vogel 	struct tcphdr *tcp;
1776c5087a8SJack F Vogel 	uint32_t *ts_ptr;
1786c5087a8SJack F Vogel 	struct mbuf *m_nxt, *m_tail;
1796c5087a8SJack F Vogel 	struct lro_entry *lro;
1806c5087a8SJack F Vogel 	int hlen, ip_len, tcp_hdr_len, tcp_data_len, tot_len;
18145709593SKip Macy 	int opt_bytes, trim, csum_flags;
1826c5087a8SJack F Vogel 	uint32_t seq, tmp_csum, device_mtu;
1836c5087a8SJack F Vogel 
1846c5087a8SJack F Vogel 
1856c5087a8SJack F Vogel 	eh = mtod(m_head, struct ether_header *);
1866c5087a8SJack F Vogel 	if (eh->ether_type != htons(ETHERTYPE_IP))
1876c5087a8SJack F Vogel 		return 1;
1886c5087a8SJack F Vogel 	ip = (struct ip *) (eh + 1);
1896c5087a8SJack F Vogel 	if (ip->ip_p != IPPROTO_TCP)
1906c5087a8SJack F Vogel 		return 1;
1916c5087a8SJack F Vogel 
1926c5087a8SJack F Vogel 	/* ensure there are no options */
1936c5087a8SJack F Vogel 	if ((ip->ip_hl << 2) != sizeof (*ip))
1946c5087a8SJack F Vogel 		return -1;
1956c5087a8SJack F Vogel 
1966c5087a8SJack F Vogel 	/* .. and the packet is not fragmented */
1976c5087a8SJack F Vogel 	if (ip->ip_off & htons(IP_MF|IP_OFFMASK))
1986c5087a8SJack F Vogel 		return -1;
1996c5087a8SJack F Vogel 
2006c5087a8SJack F Vogel 	/* verify that the IP header checksum is correct */
20145709593SKip Macy 	csum_flags = m_head->m_pkthdr.csum_flags;
20245709593SKip Macy 	if (csum_flags & CSUM_IP_CHECKED) {
20345709593SKip Macy 		if (__predict_false((csum_flags & CSUM_IP_VALID) == 0)) {
20445709593SKip Macy 			cntl->lro_bad_csum++;
20545709593SKip Macy 			return -1;
20645709593SKip Macy 		}
20745709593SKip Macy 	} else {
2086c5087a8SJack F Vogel 		tmp_csum = do_csum_data((uint16_t *)ip, sizeof (*ip));
2096c5087a8SJack F Vogel 		if (__predict_false((tmp_csum ^ 0xffff) != 0)) {
2106c5087a8SJack F Vogel 			cntl->lro_bad_csum++;
2116c5087a8SJack F Vogel 			return -1;
2126c5087a8SJack F Vogel 		}
21345709593SKip Macy 	}
2146c5087a8SJack F Vogel 
2156c5087a8SJack F Vogel 	/* find the TCP header */
2166c5087a8SJack F Vogel 	tcp = (struct tcphdr *) (ip + 1);
2176c5087a8SJack F Vogel 
2186c5087a8SJack F Vogel 	/* Get the TCP checksum if we dont have it */
2196c5087a8SJack F Vogel 	if (!csum)
2206c5087a8SJack F Vogel 		csum = tcp->th_sum;
2216c5087a8SJack F Vogel 
2226c5087a8SJack F Vogel 	/* ensure no bits set besides ack or psh */
2236c5087a8SJack F Vogel 	if ((tcp->th_flags & ~(TH_ACK | TH_PUSH)) != 0)
2246c5087a8SJack F Vogel 		return -1;
2256c5087a8SJack F Vogel 
2266c5087a8SJack F Vogel 	/* check for timestamps. Since the only option we handle are
2276c5087a8SJack F Vogel 	   timestamps, we only have to handle the simple case of
2286c5087a8SJack F Vogel 	   aligned timestamps */
2296c5087a8SJack F Vogel 
2306c5087a8SJack F Vogel 	opt_bytes = (tcp->th_off << 2) - sizeof (*tcp);
2316c5087a8SJack F Vogel 	tcp_hdr_len =  sizeof (*tcp) + opt_bytes;
2326c5087a8SJack F Vogel 	ts_ptr = (uint32_t *)(tcp + 1);
2336c5087a8SJack F Vogel 	if (opt_bytes != 0) {
2346c5087a8SJack F Vogel 		if (__predict_false(opt_bytes != TCPOLEN_TSTAMP_APPA) ||
2356c5087a8SJack F Vogel 		    (*ts_ptr !=  ntohl(TCPOPT_NOP<<24|TCPOPT_NOP<<16|
2366c5087a8SJack F Vogel 		    TCPOPT_TIMESTAMP<<8|TCPOLEN_TIMESTAMP)))
2376c5087a8SJack F Vogel 			return -1;
2386c5087a8SJack F Vogel 	}
2396c5087a8SJack F Vogel 
2406c5087a8SJack F Vogel 	ip_len = ntohs(ip->ip_len);
2416c5087a8SJack F Vogel 	tcp_data_len = ip_len - (tcp->th_off << 2) - sizeof (*ip);
2426c5087a8SJack F Vogel 
2436c5087a8SJack F Vogel 
2446c5087a8SJack F Vogel 	/*
2456c5087a8SJack F Vogel 	 * If frame is padded beyond the end of the IP packet,
2466c5087a8SJack F Vogel 	 * then we must trim the extra bytes off the end.
2476c5087a8SJack F Vogel 	 */
2486c5087a8SJack F Vogel 	tot_len = m_head->m_pkthdr.len;
2496c5087a8SJack F Vogel 	trim = tot_len - (ip_len + ETHER_HDR_LEN);
2506c5087a8SJack F Vogel 	if (trim != 0) {
2516c5087a8SJack F Vogel 		if (trim < 0) {
2526c5087a8SJack F Vogel 			/* truncated packet */
2536c5087a8SJack F Vogel 			return -1;
2546c5087a8SJack F Vogel 		}
2556c5087a8SJack F Vogel 		m_adj(m_head, -trim);
2566c5087a8SJack F Vogel 		tot_len = m_head->m_pkthdr.len;
2576c5087a8SJack F Vogel 	}
2586c5087a8SJack F Vogel 
2596c5087a8SJack F Vogel 	m_nxt = m_head;
2606c5087a8SJack F Vogel 	m_tail = NULL; /* -Wuninitialized */
2616c5087a8SJack F Vogel 	while (m_nxt != NULL) {
2626c5087a8SJack F Vogel 		m_tail = m_nxt;
2636c5087a8SJack F Vogel 		m_nxt = m_tail->m_next;
2646c5087a8SJack F Vogel 	}
2656c5087a8SJack F Vogel 
2666c5087a8SJack F Vogel 	hlen = ip_len + ETHER_HDR_LEN - tcp_data_len;
2676c5087a8SJack F Vogel 	seq = ntohl(tcp->th_seq);
2686c5087a8SJack F Vogel 
2696c5087a8SJack F Vogel 	SLIST_FOREACH(lro, &cntl->lro_active, next) {
2706c5087a8SJack F Vogel 		if (lro->source_port == tcp->th_sport &&
2716c5087a8SJack F Vogel 		    lro->dest_port == tcp->th_dport &&
2726c5087a8SJack F Vogel 		    lro->source_ip == ip->ip_src.s_addr &&
2736c5087a8SJack F Vogel 		    lro->dest_ip == ip->ip_dst.s_addr) {
274ca712262SColin Percival 			/* Flush now if appending will result in overflow. */
275ca712262SColin Percival 			if (lro->len > (65535 - tcp_data_len)) {
276ca712262SColin Percival 				SLIST_REMOVE(&cntl->lro_active, lro,
277ca712262SColin Percival 					     lro_entry, next);
278ca712262SColin Percival 				tcp_lro_flush(cntl, lro);
279ca712262SColin Percival 				break;
280ca712262SColin Percival 			}
281ca712262SColin Percival 
2826c5087a8SJack F Vogel 			/* Try to append it */
2836c5087a8SJack F Vogel 
284c31aa19cSJack F Vogel 			if (__predict_false(seq != lro->next_seq ||
285c31aa19cSJack F Vogel 				    (tcp_data_len == 0 &&
286c31aa19cSJack F Vogel 				    lro->ack_seq == tcp->th_ack))) {
287c31aa19cSJack F Vogel 				/* out of order packet or dup ack */
2886c5087a8SJack F Vogel 				SLIST_REMOVE(&cntl->lro_active, lro,
2896c5087a8SJack F Vogel 					     lro_entry, next);
2906c5087a8SJack F Vogel 				tcp_lro_flush(cntl, lro);
2916c5087a8SJack F Vogel 				return -1;
2926c5087a8SJack F Vogel 			}
2936c5087a8SJack F Vogel 
2946c5087a8SJack F Vogel 			if (opt_bytes) {
2956c5087a8SJack F Vogel 				uint32_t tsval = ntohl(*(ts_ptr + 1));
2966c5087a8SJack F Vogel 				/* make sure timestamp values are increasing */
2976c5087a8SJack F Vogel 				if (__predict_false(lro->tsval > tsval ||
2986c5087a8SJack F Vogel 					     *(ts_ptr + 2) == 0)) {
2996c5087a8SJack F Vogel 					return -1;
3006c5087a8SJack F Vogel 				}
3016c5087a8SJack F Vogel 				lro->tsval = tsval;
3026c5087a8SJack F Vogel 				lro->tsecr = *(ts_ptr + 2);
3036c5087a8SJack F Vogel 			}
3046c5087a8SJack F Vogel 
3056c5087a8SJack F Vogel 			lro->next_seq += tcp_data_len;
3066c5087a8SJack F Vogel 			lro->ack_seq = tcp->th_ack;
3076c5087a8SJack F Vogel 			lro->window = tcp->th_win;
3086c5087a8SJack F Vogel 			lro->append_cnt++;
3096c5087a8SJack F Vogel 			if (tcp_data_len == 0) {
3106c5087a8SJack F Vogel 				m_freem(m_head);
3116c5087a8SJack F Vogel 				return 0;
3126c5087a8SJack F Vogel 			}
3136c5087a8SJack F Vogel 			/* subtract off the checksum of the tcp header
3146c5087a8SJack F Vogel                          * from the hardware checksum, and add it to the
3156c5087a8SJack F Vogel                          * stored tcp data checksum.  Byteswap the checksum
3166c5087a8SJack F Vogel 			 * if the total length so far is odd
3176c5087a8SJack F Vogel                          */
3186c5087a8SJack F Vogel 			tmp_csum = do_csum_data((uint16_t*)tcp,
3196c5087a8SJack F Vogel 							 tcp_hdr_len);
3206c5087a8SJack F Vogel 			csum = csum + (tmp_csum ^ 0xffff);
3216c5087a8SJack F Vogel 			csum = (csum & 0xffff) + (csum >> 16);
3226c5087a8SJack F Vogel 			csum = (csum & 0xffff) + (csum >> 16);
3236c5087a8SJack F Vogel 			if (lro->len & 0x1) {
3246c5087a8SJack F Vogel 				/* Odd number of bytes so far, flip bytes */
3256c5087a8SJack F Vogel 				csum = ((csum << 8) | (csum >> 8)) & 0xffff;
3266c5087a8SJack F Vogel 			}
3276c5087a8SJack F Vogel 			csum = csum + lro->data_csum;
3286c5087a8SJack F Vogel 			csum = (csum & 0xffff) + (csum >> 16);
3296c5087a8SJack F Vogel 			csum = (csum & 0xffff) + (csum >> 16);
3306c5087a8SJack F Vogel 			lro->data_csum = csum;
3316c5087a8SJack F Vogel 
3326c5087a8SJack F Vogel 			lro->len += tcp_data_len;
3336c5087a8SJack F Vogel 
3346c5087a8SJack F Vogel 			/* adjust mbuf so that m->m_data points to
3356c5087a8SJack F Vogel 			   the first byte of the payload */
3366c5087a8SJack F Vogel 			m_adj(m_head, hlen);
3376c5087a8SJack F Vogel 			/* append mbuf chain */
3386c5087a8SJack F Vogel 			lro->m_tail->m_next = m_head;
3396c5087a8SJack F Vogel 			/* advance the last pointer */
3406c5087a8SJack F Vogel 			lro->m_tail = m_tail;
3416c5087a8SJack F Vogel 			/* flush packet if required */
3426c5087a8SJack F Vogel 			device_mtu = cntl->ifp->if_mtu;
3436c5087a8SJack F Vogel 			if (lro->len > (65535 - device_mtu)) {
3446c5087a8SJack F Vogel 				SLIST_REMOVE(&cntl->lro_active, lro,
3456c5087a8SJack F Vogel 					     lro_entry, next);
3466c5087a8SJack F Vogel 				tcp_lro_flush(cntl, lro);
3476c5087a8SJack F Vogel 			}
3486c5087a8SJack F Vogel 			return 0;
3496c5087a8SJack F Vogel 		}
3506c5087a8SJack F Vogel 	}
3516c5087a8SJack F Vogel 
3526c5087a8SJack F Vogel 	if (SLIST_EMPTY(&cntl->lro_free))
3536c5087a8SJack F Vogel 	    return -1;
3546c5087a8SJack F Vogel 
3556c5087a8SJack F Vogel 	/* start a new chain */
3566c5087a8SJack F Vogel 	lro = SLIST_FIRST(&cntl->lro_free);
3576c5087a8SJack F Vogel 	SLIST_REMOVE_HEAD(&cntl->lro_free, next);
3586c5087a8SJack F Vogel 	SLIST_INSERT_HEAD(&cntl->lro_active, lro, next);
3596c5087a8SJack F Vogel 	lro->source_port = tcp->th_sport;
3606c5087a8SJack F Vogel 	lro->dest_port = tcp->th_dport;
3616c5087a8SJack F Vogel 	lro->source_ip = ip->ip_src.s_addr;
3626c5087a8SJack F Vogel 	lro->dest_ip = ip->ip_dst.s_addr;
3636c5087a8SJack F Vogel 	lro->next_seq = seq + tcp_data_len;
3646c5087a8SJack F Vogel 	lro->mss = tcp_data_len;
3656c5087a8SJack F Vogel 	lro->ack_seq = tcp->th_ack;
3666c5087a8SJack F Vogel 	lro->window = tcp->th_win;
3676c5087a8SJack F Vogel 
3686c5087a8SJack F Vogel 	/* save the checksum of just the TCP payload by
3696c5087a8SJack F Vogel 	 * subtracting off the checksum of the TCP header from
3706c5087a8SJack F Vogel 	 * the entire hardware checksum
3716c5087a8SJack F Vogel 	 * Since IP header checksum is correct, checksum over
3726c5087a8SJack F Vogel 	 * the IP header is -0.  Substracting -0 is unnecessary.
3736c5087a8SJack F Vogel 	 */
3746c5087a8SJack F Vogel 	tmp_csum = do_csum_data((uint16_t*)tcp, tcp_hdr_len);
3756c5087a8SJack F Vogel 	csum = csum + (tmp_csum ^ 0xffff);
3766c5087a8SJack F Vogel 	csum = (csum & 0xffff) + (csum >> 16);
3776c5087a8SJack F Vogel 	csum = (csum & 0xffff) + (csum >> 16);
3786c5087a8SJack F Vogel 	lro->data_csum = csum;
3796c5087a8SJack F Vogel 
3806c5087a8SJack F Vogel 	lro->ip = ip;
3816c5087a8SJack F Vogel 	/* record timestamp if it is present */
3826c5087a8SJack F Vogel 	if (opt_bytes) {
3836c5087a8SJack F Vogel 		lro->timestamp = 1;
3846c5087a8SJack F Vogel 		lro->tsval = ntohl(*(ts_ptr + 1));
3856c5087a8SJack F Vogel 		lro->tsecr = *(ts_ptr + 2);
3866c5087a8SJack F Vogel 	}
3876c5087a8SJack F Vogel 	lro->len = tot_len;
3886c5087a8SJack F Vogel 	lro->m_head = m_head;
3896c5087a8SJack F Vogel 	lro->m_tail = m_tail;
3906c5087a8SJack F Vogel 	return 0;
3916c5087a8SJack F Vogel }
392