#include <asm/vio.h>
 #include <asm/iommu.h>
 #include <asm/firmware.h>
+#include <net/tcp.h>
+#include <net/ip6_checksum.h>
 
 #include "ibmveth.h"
 
 
        ret = h_illan_attributes(adapter->vdev->unit_address, 0, 0, &ret_attr);
 
-       if (ret == H_SUCCESS && !(ret_attr & IBMVETH_ILLAN_ACTIVE_TRUNK) &&
-           !(ret_attr & IBMVETH_ILLAN_TRUNK_PRI_MASK) &&
+       if (ret == H_SUCCESS &&
            (ret_attr & IBMVETH_ILLAN_PADDED_PKT_CSUM)) {
                ret4 = h_illan_attributes(adapter->vdev->unit_address, clr_attr,
                                         set_attr, &ret_attr);
        dma_addr_t dma_addr;
        unsigned long mss = 0;
 
+       /* veth doesn't handle frag_list, so linearize the skb.
+        * When GRO is enabled SKB's can have frag_list.
+        */
+       if (adapter->is_active_trunk &&
+           skb_has_frag_list(skb) && __skb_linearize(skb)) {
+               netdev->stats.tx_dropped++;
+               goto out;
+       }
+
        /*
         * veth handles a maximum of 6 segments including the header, so
         * we have to linearize the skb if there are more than this.
 
        desc_flags = IBMVETH_BUF_VALID;
 
-       if (skb_is_gso(skb) && adapter->fw_large_send_support)
-               desc_flags |= IBMVETH_BUF_LRG_SND;
-
        if (skb->ip_summed == CHECKSUM_PARTIAL) {
                unsigned char *buf = skb_transport_header(skb) +
                                                skb->csum_offset;
                /* Need to zero out the checksum */
                buf[0] = 0;
                buf[1] = 0;
+
+               if (skb_is_gso(skb) && adapter->fw_large_send_support)
+                       desc_flags |= IBMVETH_BUF_LRG_SND;
        }
 
 retry_bounce:
                descs[i+1].fields.address = dma_addr;
        }
 
-       if (skb_is_gso(skb)) {
+       if (skb->ip_summed == CHECKSUM_PARTIAL && skb_is_gso(skb)) {
                if (adapter->fw_large_send_support) {
                        mss = (unsigned long)skb_shinfo(skb)->gso_size;
                        adapter->tx_large_packets++;
        }
 }
 
+static void ibmveth_rx_csum_helper(struct sk_buff *skb,
+                                  struct ibmveth_adapter *adapter)
+{
+       struct iphdr *iph = NULL;
+       struct ipv6hdr *iph6 = NULL;
+       __be16 skb_proto = 0;
+       u16 iphlen = 0;
+       u16 iph_proto = 0;
+       u16 tcphdrlen = 0;
+
+       skb_proto = be16_to_cpu(skb->protocol);
+
+       if (skb_proto == ETH_P_IP) {
+               iph = (struct iphdr *)skb->data;
+
+               /* If the IP checksum is not offloaded and if the packet
+                *  is large send, the checksum must be rebuilt.
+                */
+               if (iph->check == 0xffff) {
+                       iph->check = 0;
+                       iph->check = ip_fast_csum((unsigned char *)iph,
+                                                 iph->ihl);
+               }
+
+               iphlen = iph->ihl * 4;
+               iph_proto = iph->protocol;
+       } else if (skb_proto == ETH_P_IPV6) {
+               iph6 = (struct ipv6hdr *)skb->data;
+               iphlen = sizeof(struct ipv6hdr);
+               iph_proto = iph6->nexthdr;
+       }
+
+       /* In OVS environment, when a flow is not cached, specifically for a
+        * new TCP connection, the first packet information is passed up
+        * the user space for finding a flow. During this process, OVS computes
+        * checksum on the first packet when CHECKSUM_PARTIAL flag is set.
+        *
+        * Given that we zeroed out TCP checksum field in transmit path
+        * (refer ibmveth_start_xmit routine) as we set "no checksum bit",
+        * OVS computed checksum will be incorrect w/o TCP pseudo checksum
+        * in the packet. This leads to OVS dropping the packet and hence
+        * TCP retransmissions are seen.
+        *
+        * So, re-compute TCP pseudo header checksum.
+        */
+       if (iph_proto == IPPROTO_TCP && adapter->is_active_trunk) {
+               struct tcphdr *tcph = (struct tcphdr *)(skb->data + iphlen);
+
+               tcphdrlen = skb->len - iphlen;
+
+               /* Recompute TCP pseudo header checksum */
+               if (skb_proto == ETH_P_IP)
+                       tcph->check = ~csum_tcpudp_magic(iph->saddr,
+                                       iph->daddr, tcphdrlen, iph_proto, 0);
+               else if (skb_proto == ETH_P_IPV6)
+                       tcph->check = ~csum_ipv6_magic(&iph6->saddr,
+                                       &iph6->daddr, tcphdrlen, iph_proto, 0);
+
+               /* Setup SKB fields for checksum offload */
+               skb_partial_csum_set(skb, iphlen,
+                                    offsetof(struct tcphdr, check));
+               skb_reset_network_header(skb);
+       }
+}
+
 static int ibmveth_poll(struct napi_struct *napi, int budget)
 {
        struct ibmveth_adapter *adapter =
        struct net_device *netdev = adapter->netdev;
        int frames_processed = 0;
        unsigned long lpar_rc;
-       struct iphdr *iph;
        u16 mss = 0;
 
 restart_poll:
 
                        if (csum_good) {
                                skb->ip_summed = CHECKSUM_UNNECESSARY;
-                               if (be16_to_cpu(skb->protocol) == ETH_P_IP) {
-                                       iph = (struct iphdr *)skb->data;
-
-                                       /* If the IP checksum is not offloaded and if the packet
-                                        *  is large send, the checksum must be rebuilt.
-                                        */
-                                       if (iph->check == 0xffff) {
-                                               iph->check = 0;
-                                               iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
-                                       }
-                               }
+                               ibmveth_rx_csum_helper(skb, adapter);
                        }
 
                        if (length > netdev->mtu + ETH_HLEN) {
                netdev->hw_features |= NETIF_F_TSO;
        }
 
+       adapter->is_active_trunk = false;
+       if (ret == H_SUCCESS && (ret_attr & IBMVETH_ILLAN_ACTIVE_TRUNK)) {
+               adapter->is_active_trunk = true;
+               netdev->hw_features |= NETIF_F_FRAGLIST;
+               netdev->features |= NETIF_F_FRAGLIST;
+       }
+
        netdev->min_mtu = IBMVETH_MIN_MTU;
        netdev->max_mtu = ETH_MAX_MTU;