Merge master.kernel.org:/pub/scm/linux/kernel/git/davem/net-2.6
authorLinus Torvalds <torvalds@g5.osdl.org>
Fri, 30 Jun 2006 22:40:17 +0000 (15:40 -0700)
committerLinus Torvalds <torvalds@g5.osdl.org>
Fri, 30 Jun 2006 22:40:17 +0000 (15:40 -0700)
* master.kernel.org:/pub/scm/linux/kernel/git/davem/net-2.6:
  [IPV6]: Added GSO support for TCPv6
  [NET]: Generalise TSO-specific bits from skb_setup_caps
  [IPV6]: Added GSO support for TCPv6
  [IPV6]: Remove redundant length check on input
  [NETFILTER]: SCTP conntrack: fix crash triggered by packet without chunks
  [TG3]: Update version and reldate
  [TG3]: Add TSO workaround using GSO
  [TG3]: Turn on hw fix for ASF problems
  [TG3]: Add rx BD workaround
  [TG3]: Add tg3_netif_stop() in vlan functions
  [TCP]: Reset gso_segs if packet is dodgy

1  2 
drivers/net/s2io.c
drivers/net/tg3.c
net/ipv4/ip_output.c
net/ipv4/tcp.c
net/ipv4/tcp_ipv4.c
net/ipv6/af_inet6.c
net/ipv6/inet6_connection_sock.c
net/ipv6/ip6_output.c
net/ipv6/ipv6_sockglue.c
net/ipv6/tcp_ipv6.c

diff --combined drivers/net/s2io.c
@@@ -44,6 -44,7 +44,6 @@@
   *     aggregated as a single large packet
   ************************************************************************/
  
 -#include <linux/config.h>
  #include <linux/module.h>
  #include <linux/types.h>
  #include <linux/errno.h>
@@@ -3959,7 -3960,7 +3959,7 @@@ static int s2io_xmit(struct sk_buff *sk
        txdp->Control_2 = 0;
  #ifdef NETIF_F_TSO
        mss = skb_shinfo(skb)->gso_size;
-       if (skb_shinfo(skb)->gso_type == SKB_GSO_TCPV4) {
+       if (skb_shinfo(skb)->gso_type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6)) {
                txdp->Control_1 |= TXD_TCP_LSO_EN;
                txdp->Control_1 |= TXD_TCP_LSO_MSS(mss);
        }
        }
  
        frg_len = skb->len - skb->data_len;
-       if (skb_shinfo(skb)->gso_type == SKB_GSO_UDPV4) {
+       if (skb_shinfo(skb)->gso_type == SKB_GSO_UDP) {
                int ufo_size;
  
                ufo_size = skb_shinfo(skb)->gso_size;
        txdp->Host_Control = (unsigned long) skb;
        txdp->Control_1 |= TXD_BUFFER0_SIZE(frg_len);
  
-       if (skb_shinfo(skb)->gso_type == SKB_GSO_UDPV4)
+       if (skb_shinfo(skb)->gso_type == SKB_GSO_UDP)
                txdp->Control_1 |= TXD_UFO_EN;
  
        frg_cnt = skb_shinfo(skb)->nr_frags;
                    (sp->pdev, frag->page, frag->page_offset,
                     frag->size, PCI_DMA_TODEVICE);
                txdp->Control_1 = TXD_BUFFER0_SIZE(frag->size);
-               if (skb_shinfo(skb)->gso_type == SKB_GSO_UDPV4)
+               if (skb_shinfo(skb)->gso_type == SKB_GSO_UDP)
                        txdp->Control_1 |= TXD_UFO_EN;
        }
        txdp->Control_1 |= TXD_GATHER_CODE_LAST;
  
-       if (skb_shinfo(skb)->gso_type == SKB_GSO_UDPV4)
+       if (skb_shinfo(skb)->gso_type == SKB_GSO_UDP)
                frg_cnt++; /* as Txd0 was used for inband header */
  
        tx_fifo = mac_control->tx_FIFO_start[queue];
        if (mss)
                val64 |= TX_FIFO_SPECIAL_FUNC;
  #endif
-       if (skb_shinfo(skb)->gso_type == SKB_GSO_UDPV4)
+       if (skb_shinfo(skb)->gso_type == SKB_GSO_UDP)
                val64 |= TX_FIFO_SPECIAL_FUNC;
        writeq(val64, &tx_fifo->List_Control);
  
@@@ -7019,6 -7020,9 +7019,9 @@@ s2io_init_nic(struct pci_dev *pdev, con
                dev->features |= NETIF_F_HIGHDMA;
  #ifdef NETIF_F_TSO
        dev->features |= NETIF_F_TSO;
+ #endif
+ #ifdef NETIF_F_TSO6
+       dev->features |= NETIF_F_TSO6;
  #endif
        if (sp->device_type & XFRAME_II_DEVICE) {
                dev->features |= NETIF_F_UFO;
diff --combined drivers/net/tg3.c
@@@ -15,6 -15,7 +15,6 @@@
   *    notice is accompanying it.
   */
  
 -#include <linux/config.h>
  
  #include <linux/module.h>
  #include <linux/moduleparam.h>
@@@ -68,8 -69,8 +68,8 @@@
  
  #define DRV_MODULE_NAME               "tg3"
  #define PFX DRV_MODULE_NAME   ": "
- #define DRV_MODULE_VERSION    "3.60"
- #define DRV_MODULE_RELDATE    "June 17, 2006"
+ #define DRV_MODULE_VERSION    "3.61"
+ #define DRV_MODULE_RELDATE    "June 29, 2006"
  
  #define TG3_DEF_MAC_MODE      0
  #define TG3_DEF_RX_MODE               0
@@@ -3194,7 -3195,7 +3194,7 @@@ static int tg3_vlan_rx(struct tg3 *tp, 
   */
  static int tg3_rx(struct tg3 *tp, int budget)
  {
-       u32 work_mask;
+       u32 work_mask, rx_std_posted = 0;
        u32 sw_idx = tp->rx_rcb_ptr;
        u16 hw_idx;
        int received;
                                                  mapping);
                        skb = tp->rx_std_buffers[desc_idx].skb;
                        post_ptr = &tp->rx_std_ptr;
+                       rx_std_posted++;
                } else if (opaque_key == RXD_OPAQUE_RING_JUMBO) {
                        dma_addr = pci_unmap_addr(&tp->rx_jumbo_buffers[desc_idx],
                                                  mapping);
  
  next_pkt:
                (*post_ptr)++;
+               if (unlikely(rx_std_posted >= tp->rx_std_max_post)) {
+                       u32 idx = *post_ptr % TG3_RX_RING_SIZE;
+                       tw32_rx_mbox(MAILBOX_RCV_STD_PROD_IDX +
+                                    TG3_64BIT_REG_LOW, idx);
+                       work_mask &= ~RXD_OPAQUE_RING_STD;
+                       rx_std_posted = 0;
+               }
  next_pkt_nopost:
                sw_idx++;
                sw_idx %= TG3_RX_RCB_RING_SIZE(tp);
@@@ -3869,6 -3880,40 +3879,40 @@@ out_unlock
        return NETDEV_TX_OK;
  }
  
+ #if TG3_TSO_SUPPORT != 0
+ static int tg3_start_xmit_dma_bug(struct sk_buff *, struct net_device *);
+ /* Use GSO to workaround a rare TSO bug that may be triggered when the
+  * TSO header is greater than 80 bytes.
+  */
+ static int tg3_tso_bug(struct tg3 *tp, struct sk_buff *skb)
+ {
+       struct sk_buff *segs, *nskb;
+       /* Estimate the number of fragments in the worst case */
+       if (unlikely(TX_BUFFS_AVAIL(tp) <= (skb_shinfo(skb)->gso_segs * 3))) {
+               netif_stop_queue(tp->dev);
+               return NETDEV_TX_BUSY;
+       }
+       segs = skb_gso_segment(skb, tp->dev->features & ~NETIF_F_TSO);
+       if (unlikely(IS_ERR(segs)))
+               goto tg3_tso_bug_end;
+       do {
+               nskb = segs;
+               segs = segs->next;
+               nskb->next = NULL;
+               tg3_start_xmit_dma_bug(nskb, tp->dev);
+       } while (segs);
+ tg3_tso_bug_end:
+       dev_kfree_skb(skb);
+       return NETDEV_TX_OK;
+ }
+ #endif
  /* hard_start_xmit for devices that have the 4G bug and/or 40-bit bug and
   * support TG3_FLG2_HW_TSO_1 or firmware TSO only.
   */
@@@ -3905,7 -3950,7 +3949,7 @@@ static int tg3_start_xmit_dma_bug(struc
        mss = 0;
        if (skb->len > (tp->dev->mtu + ETH_HLEN) &&
            (mss = skb_shinfo(skb)->gso_size) != 0) {
-               int tcp_opt_len, ip_tcp_len;
+               int tcp_opt_len, ip_tcp_len, hdr_len;
  
                if (skb_header_cloned(skb) &&
                    pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) {
                tcp_opt_len = ((skb->h.th->doff - 5) * 4);
                ip_tcp_len = (skb->nh.iph->ihl * 4) + sizeof(struct tcphdr);
  
+               hdr_len = ip_tcp_len + tcp_opt_len;
+               if (unlikely((ETH_HLEN + hdr_len) > 80) &&
+                            (tp->tg3_flags2 & TG3_FLG2_HW_TSO_1_BUG))
+                       return (tg3_tso_bug(tp, skb));
                base_flags |= (TXD_FLAG_CPU_PRE_DMA |
                               TXD_FLAG_CPU_POST_DMA);
  
                skb->nh.iph->check = 0;
-               skb->nh.iph->tot_len = htons(mss + ip_tcp_len + tcp_opt_len);
+               skb->nh.iph->tot_len = htons(mss + hdr_len);
                if (tp->tg3_flags2 & TG3_FLG2_HW_TSO) {
                        skb->h.th->check = 0;
                        base_flags &= ~TXD_FLAG_TCPUDP_CSUM;
@@@ -5980,7 -6030,13 +6029,13 @@@ static int tg3_reset_hw(struct tg3 *tp
        }
  
        /* Setup replenish threshold. */
-       tw32(RCVBDI_STD_THRESH, tp->rx_pending / 8);
+       val = tp->rx_pending / 8;
+       if (val == 0)
+               val = 1;
+       else if (val > tp->rx_std_max_post)
+               val = tp->rx_std_max_post;
+       tw32(RCVBDI_STD_THRESH, val);
  
        /* Initialize TG3_BDINFO's at:
         *  RCVDBDI_STD_BD:     standard eth size rx ring
  #endif
  
        /* Receive/send statistics. */
-       if ((rdmac_mode & RDMAC_MODE_FIFO_SIZE_128) &&
-           (tp->tg3_flags2 & TG3_FLG2_TSO_CAPABLE)) {
+       if (tp->tg3_flags2 & TG3_FLG2_5750_PLUS) {
+               val = tr32(RCVLPC_STATS_ENABLE);
+               val &= ~RCVLPC_STATSENAB_DACK_FIX;
+               tw32(RCVLPC_STATS_ENABLE, val);
+       } else if ((rdmac_mode & RDMAC_MODE_FIFO_SIZE_128) &&
+                  (tp->tg3_flags2 & TG3_FLG2_TSO_CAPABLE)) {
                val = tr32(RCVLPC_STATS_ENABLE);
                val &= ~RCVLPC_STATSENAB_LNGBRST_RFIX;
                tw32(RCVLPC_STATS_ENABLE, val);
@@@ -8737,6 -8797,9 +8796,9 @@@ static void tg3_vlan_rx_register(struc
  {
        struct tg3 *tp = netdev_priv(dev);
  
+       if (netif_running(dev))
+               tg3_netif_stop(tp);
        tg3_full_lock(tp, 0);
  
        tp->vlgrp = grp;
        __tg3_set_rx_mode(dev);
  
        tg3_full_unlock(tp);
+       if (netif_running(dev))
+               tg3_netif_start(tp);
  }
  
  static void tg3_vlan_rx_kill_vid(struct net_device *dev, unsigned short vid)
  {
        struct tg3 *tp = netdev_priv(dev);
  
+       if (netif_running(dev))
+               tg3_netif_stop(tp);
        tg3_full_lock(tp, 0);
        if (tp->vlgrp)
                tp->vlgrp->vlan_devices[vid] = NULL;
        tg3_full_unlock(tp);
+       if (netif_running(dev))
+               tg3_netif_start(tp);
  }
  #endif
  
@@@ -10159,8 -10231,14 +10230,14 @@@ static int __devinit tg3_get_invariants
                    GET_ASIC_REV(tp->pci_chip_rev_id) == ASIC_REV_5787) {
                        tp->tg3_flags2 |= TG3_FLG2_HW_TSO_2;
                        tp->tg3_flags2 |= TG3_FLG2_1SHOT_MSI;
-               } else
-                       tp->tg3_flags2 |= TG3_FLG2_HW_TSO_1;
+               } else {
+                       tp->tg3_flags2 |= TG3_FLG2_HW_TSO_1 |
+                                         TG3_FLG2_HW_TSO_1_BUG;
+                       if (GET_ASIC_REV(tp->pci_chip_rev_id) ==
+                               ASIC_REV_5750 &&
+                           tp->pci_chip_rev_id >= CHIPREV_ID_5750_C2)
+                               tp->tg3_flags2 &= ~TG3_FLG2_HW_TSO_1_BUG;
+               }
        }
  
        if (GET_ASIC_REV(tp->pci_chip_rev_id) != ASIC_REV_5705 &&
            (tp->tg3_flags & TG3_FLAG_PCIX_MODE) != 0)
                tp->rx_offset = 0;
  
+       tp->rx_std_max_post = TG3_RX_RING_SIZE;
+       /* Increment the rx prod index on the rx std ring by at most
+        * 8 for these chips to workaround hw errata.
+        */
+       if (GET_ASIC_REV(tp->pci_chip_rev_id) == ASIC_REV_5750 ||
+           GET_ASIC_REV(tp->pci_chip_rev_id) == ASIC_REV_5752 ||
+           GET_ASIC_REV(tp->pci_chip_rev_id) == ASIC_REV_5755)
+               tp->rx_std_max_post = 8;
        /* By default, disable wake-on-lan.  User can change this
         * using ETHTOOL_SWOL.
         */
diff --combined net/ipv4/ip_output.c
@@@ -53,6 -53,7 +53,6 @@@
  #include <linux/mm.h>
  #include <linux/string.h>
  #include <linux/errno.h>
 -#include <linux/config.h>
  
  #include <linux/socket.h>
  #include <linux/sockios.h>
@@@ -743,7 -744,7 +743,7 @@@ static inline int ip_ufo_append_data(st
        if (!err) {
                /* specify the length of each IP datagram fragment*/
                skb_shinfo(skb)->gso_size = mtu - fragheaderlen;
-               skb_shinfo(skb)->gso_type = SKB_GSO_UDPV4;
+               skb_shinfo(skb)->gso_type = SKB_GSO_UDP;
                __skb_queue_tail(&sk->sk_write_queue, skb);
  
                return 0;
@@@ -1088,7 -1089,7 +1088,7 @@@ ssize_t ip_append_page(struct sock *sk
        if ((sk->sk_protocol == IPPROTO_UDP) &&
            (rt->u.dst.dev->features & NETIF_F_UFO)) {
                skb_shinfo(skb)->gso_size = mtu - fragheaderlen;
-               skb_shinfo(skb)->gso_type = SKB_GSO_UDPV4;
+               skb_shinfo(skb)->gso_type = SKB_GSO_UDP;
        }
  
  
diff --combined net/ipv4/tcp.c
   *    TCP_CLOSE               socket is finished
   */
  
 -#include <linux/config.h>
  #include <linux/module.h>
  #include <linux/types.h>
  #include <linux/fcntl.h>
@@@ -642,7 -643,7 +642,7 @@@ static inline int select_size(struct so
        int tmp = tp->mss_cache;
  
        if (sk->sk_route_caps & NETIF_F_SG) {
-               if (sk->sk_route_caps & NETIF_F_TSO)
+               if (sk_can_gso(sk))
                        tmp = 0;
                else {
                        int pgbreak = SKB_MAX_HEAD(MAX_TCP_HEADER);
@@@ -2165,13 -2166,19 +2165,19 @@@ struct sk_buff *tcp_tso_segment(struct 
        if (!pskb_may_pull(skb, thlen))
                goto out;
  
-       segs = NULL;
-       if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST))
-               goto out;
        oldlen = (u16)~skb->len;
        __skb_pull(skb, thlen);
  
+       if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
+               /* Packet is from an untrusted source, reset gso_segs. */
+               int mss = skb_shinfo(skb)->gso_size;
+               skb_shinfo(skb)->gso_segs = (skb->len + mss - 1) / mss;
+               segs = NULL;
+               goto out;
+       }
        segs = skb_segment(skb, features);
        if (IS_ERR(segs))
                goto out;
  out:
        return segs;
  }
+ EXPORT_SYMBOL(tcp_tso_segment);
  
  extern void __skb_cb_too_small_for_tcp(int, int);
  extern struct tcp_congestion_ops tcp_reno;
diff --combined net/ipv4/tcp_ipv4.c
@@@ -52,6 -52,7 +52,6 @@@
   *                                    a single port at the same time.
   */
  
 -#include <linux/config.h>
  
  #include <linux/types.h>
  #include <linux/fcntl.h>
@@@ -241,6 -242,7 +241,7 @@@ int tcp_v4_connect(struct sock *sk, str
                goto failure;
  
        /* OK, now commit destination to socket.  */
+       sk->sk_gso_type = SKB_GSO_TCPV4;
        sk_setup_caps(sk, &rt->u.dst);
  
        if (!tp->write_seq)
@@@ -883,6 -885,7 +884,7 @@@ struct sock *tcp_v4_syn_recv_sock(struc
        if (!newsk)
                goto exit;
  
+       newsk->sk_gso_type = SKB_GSO_TCPV4;
        sk_setup_caps(newsk, dst);
  
        newtp                 = tcp_sk(newsk);
diff --combined net/ipv6/af_inet6.c
@@@ -23,6 -23,7 +23,6 @@@
  
  #include <linux/module.h>
  #include <linux/capability.h>
 -#include <linux/config.h>
  #include <linux/errno.h>
  #include <linux/types.h>
  #include <linux/socket.h>
@@@ -659,8 -660,6 +659,6 @@@ int inet6_sk_rebuild_header(struct soc
                }
  
                ip6_dst_store(sk, dst, NULL);
-               sk->sk_route_caps = dst->dev->features &
-                       ~(NETIF_F_IP_CSUM | NETIF_F_TSO);
        }
  
        return 0;
@@@ -13,6 -13,7 +13,6 @@@
   *             2 of the License, or(at your option) any later version.
   */
  
 -#include <linux/config.h>
  #include <linux/module.h>
  #include <linux/in6.h>
  #include <linux/ipv6.h>
@@@ -186,8 -187,6 +186,6 @@@ int inet6_csk_xmit(struct sk_buff *skb
                }
  
                ip6_dst_store(sk, dst, NULL);
-               sk->sk_route_caps = dst->dev->features &
-                       ~(NETIF_F_IP_CSUM | NETIF_F_TSO);
        }
  
        skb->dst = dst_clone(dst);
diff --combined net/ipv6/ip6_output.c
@@@ -28,6 -28,7 +28,6 @@@
   *                            for datagram xmit
   */
  
 -#include <linux/config.h>
  #include <linux/errno.h>
  #include <linux/types.h>
  #include <linux/string.h>
@@@ -229,7 -230,7 +229,7 @@@ int ip6_xmit(struct sock *sk, struct sk
        skb->priority = sk->sk_priority;
  
        mtu = dst_mtu(dst);
-       if ((skb->len <= mtu) || ipfragok) {
+       if ((skb->len <= mtu) || ipfragok || skb_shinfo(skb)->gso_size) {
                IP6_INC_STATS(IPSTATS_MIB_OUTREQUESTS);
                return NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev,
                                dst_output);
@@@ -834,7 -835,7 +834,7 @@@ static inline int ip6_ufo_append_data(s
                /* specify the length of each IP datagram fragment*/
                skb_shinfo(skb)->gso_size = mtu - fragheaderlen - 
                                            sizeof(struct frag_hdr);
-               skb_shinfo(skb)->gso_type = SKB_GSO_UDPV4;
+               skb_shinfo(skb)->gso_type = SKB_GSO_UDP;
                ipv6_select_ident(skb, &fhdr);
                skb_shinfo(skb)->ip6_frag_id = fhdr.identification;
                __skb_queue_tail(&sk->sk_write_queue, skb);
diff --combined net/ipv6/ipv6_sockglue.c
@@@ -27,6 -27,7 +27,6 @@@
  
  #include <linux/module.h>
  #include <linux/capability.h>
 -#include <linux/config.h>
  #include <linux/errno.h>
  #include <linux/types.h>
  #include <linux/socket.h>
  
  DEFINE_SNMP_STAT(struct ipstats_mib, ipv6_statistics) __read_mostly;
  
+ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, int features)
+ {
+       struct sk_buff *segs = ERR_PTR(-EINVAL);
+       struct ipv6hdr *ipv6h;
+       struct inet6_protocol *ops;
+       int proto;
+       if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h))))
+               goto out;
+       ipv6h = skb->nh.ipv6h;
+       proto = ipv6h->nexthdr;
+       __skb_pull(skb, sizeof(*ipv6h));
+       rcu_read_lock();
+       for (;;) {
+               struct ipv6_opt_hdr *opth;
+               int len;
+               if (proto != NEXTHDR_HOP) {
+                       ops = rcu_dereference(inet6_protos[proto]);
+                       if (unlikely(!ops))
+                               goto unlock;
+                       if (!(ops->flags & INET6_PROTO_GSO_EXTHDR))
+                               break;
+               }
+               if (unlikely(!pskb_may_pull(skb, 8)))
+                       goto unlock;
+               opth = (void *)skb->data;
+               len = opth->hdrlen * 8 + 8;
+               if (unlikely(!pskb_may_pull(skb, len)))
+                       goto unlock;
+               proto = opth->nexthdr;
+               __skb_pull(skb, len);
+       }
+       skb->h.raw = skb->data;
+       if (likely(ops->gso_segment))
+               segs = ops->gso_segment(skb, features);
+ unlock:
+       rcu_read_unlock();
+       if (unlikely(IS_ERR(segs)))
+               goto out;
+       for (skb = segs; skb; skb = skb->next) {
+               ipv6h = skb->nh.ipv6h;
+               ipv6h->payload_len = htons(skb->len - skb->mac_len);
+       }
+ out:
+       return segs;
+ }
  static struct packet_type ipv6_packet_type = {
        .type = __constant_htons(ETH_P_IPV6), 
        .func = ipv6_rcv,
+       .gso_segment = ipv6_gso_segment,
  };
  
  struct ip6_ra_chain *ip6_ra_chain;
diff --combined net/ipv6/tcp_ipv6.c
@@@ -26,6 -26,7 +26,6 @@@
   */
  
  #include <linux/module.h>
 -#include <linux/config.h>
  #include <linux/errno.h>
  #include <linux/types.h>
  #include <linux/socket.h>
@@@ -269,9 -270,8 +269,8 @@@ static int tcp_v6_connect(struct sock *
        ipv6_addr_copy(&np->saddr, saddr);
        inet->rcv_saddr = LOOPBACK4_IPV6;
  
+       sk->sk_gso_type = SKB_GSO_TCPV6;
        ip6_dst_store(sk, dst, NULL);
-       sk->sk_route_caps = dst->dev->features &
-               ~(NETIF_F_IP_CSUM | NETIF_F_TSO);
  
        icsk->icsk_ext_hdr_len = 0;
        if (np->opt)
@@@ -929,9 -929,8 +928,8 @@@ static struct sock * tcp_v6_syn_recv_so
         * comment in that function for the gory details. -acme
         */
  
+       sk->sk_gso_type = SKB_GSO_TCPV6;
        ip6_dst_store(newsk, dst, NULL);
-       newsk->sk_route_caps = dst->dev->features &
-               ~(NETIF_F_IP_CSUM | NETIF_F_TSO);
  
        newtcp6sk = (struct tcp6_sock *)newsk;
        inet_sk(newsk)->pinet6 = &newtcp6sk->inet6;
@@@ -1605,6 -1604,7 +1603,7 @@@ struct proto tcpv6_prot = 
  static struct inet6_protocol tcpv6_protocol = {
        .handler        =       tcp_v6_rcv,
        .err_handler    =       tcp_v6_err,
+       .gso_segment    =       tcp_tso_segment,
        .flags          =       INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
  };