tg3: Prevent page allocation failure during TSO workaround
authorMichael Chan <mchan@broadcom.com>
Mon, 12 May 2014 03:22:54 +0000 (20:22 -0700)
committerDavid S. Miller <davem@davemloft.net>
Tue, 13 May 2014 22:38:51 +0000 (18:38 -0400)
If any TSO fragment hits hardware bug conditions (e.g. 4G boundary), the
driver will workaround by calling skb_copy() to copy to a linear SKB.  Users
have reported page allocation failures as the TSO packet can be up to 64K.
Copying such a large packet is also very inefficient.  We fix this by using
existing tg3_tso_bug() to transmit the packet using GSO.

Signed-off-by: Prashant Sreedharan <prashant@broadcom.com>
Signed-off-by: Michael Chan <mchan@broadcom.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/broadcom/tg3.c

index bdfd08bcfe57cf618f8daa255a4b6f3455265caf..36b2b51facb94ea80a8ff70ba8bfeebb8d2c3d21 100644 (file)
@@ -7882,6 +7882,10 @@ static netdev_tx_t tg3_start_xmit(struct sk_buff *skb, struct net_device *dev)
        struct tg3_napi *tnapi;
        struct netdev_queue *txq;
        unsigned int last;
+       struct iphdr *iph = NULL;
+       struct tcphdr *tcph = NULL;
+       __sum16 tcp_csum = 0, ip_csum = 0;
+       __be16 ip_tot_len = 0;
 
        txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));
        tnapi = &tp->napi[skb_get_queue_mapping(skb)];
@@ -7913,7 +7917,6 @@ static netdev_tx_t tg3_start_xmit(struct sk_buff *skb, struct net_device *dev)
 
        mss = skb_shinfo(skb)->gso_size;
        if (mss) {
-               struct iphdr *iph;
                u32 tcp_opt_len, hdr_len;
 
                if (skb_cow_head(skb, 0))
@@ -7929,6 +7932,8 @@ static netdev_tx_t tg3_start_xmit(struct sk_buff *skb, struct net_device *dev)
                            tg3_flag(tp, TSO_BUG))
                                return tg3_tso_bug(tp, skb);
 
+                       ip_csum = iph->check;
+                       ip_tot_len = iph->tot_len;
                        iph->check = 0;
                        iph->tot_len = htons(mss + hdr_len);
                }
@@ -7936,16 +7941,18 @@ static netdev_tx_t tg3_start_xmit(struct sk_buff *skb, struct net_device *dev)
                base_flags |= (TXD_FLAG_CPU_PRE_DMA |
                               TXD_FLAG_CPU_POST_DMA);
 
+               tcph = tcp_hdr(skb);
+               tcp_csum = tcph->check;
+
                if (tg3_flag(tp, HW_TSO_1) ||
                    tg3_flag(tp, HW_TSO_2) ||
                    tg3_flag(tp, HW_TSO_3)) {
-                       tcp_hdr(skb)->check = 0;
+                       tcph->check = 0;
                        base_flags &= ~TXD_FLAG_TCPUDP_CSUM;
-               } else
-                       tcp_hdr(skb)->check = ~csum_tcpudp_magic(iph->saddr,
-                                                                iph->daddr, 0,
-                                                                IPPROTO_TCP,
-                                                                0);
+               } else {
+                       tcph->check = ~csum_tcpudp_magic(iph->saddr, iph->daddr,
+                                                        0, IPPROTO_TCP, 0);
+               }
 
                if (tg3_flag(tp, HW_TSO_3)) {
                        mss |= (hdr_len & 0xc) << 12;
@@ -8045,6 +8052,18 @@ static netdev_tx_t tg3_start_xmit(struct sk_buff *skb, struct net_device *dev)
        if (would_hit_hwbug) {
                tg3_tx_skb_unmap(tnapi, tnapi->tx_prod, i);
 
+               if (mss) {
+                       /* If it's a TSO packet, do GSO instead of
+                        * allocating and copying to a large linear SKB
+                        */
+                       if (ip_tot_len) {
+                               iph->check = ip_csum;
+                               iph->tot_len = ip_tot_len;
+                       }
+                       tcph->check = tcp_csum;
+                       return tg3_tso_bug(tp, skb);
+               }
+
                /* If the workaround fails due to memory/mapping
                 * failure, silently drop this packet.
                 */