gianfar: reallocate skb when headroom is not enough for fcb
Li Yang [Tue, 24 Mar 2009 23:15:33 +0000 (23:15 +0000)]
Gianfar uses a hardware header FCB for offloading.  However when used
with bridging or IP forwarding, TX skb might not have enough headroom
for the FCB.  Reallocate skb for such cases.

Signed-off-by: Li Yang <leoli@freescale.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

drivers/net/gianfar.c

index 8a51df0..9d81e7a 100644 (file)
@@ -1239,10 +1239,19 @@ static int gfar_enet_open(struct net_device *dev)
        return err;
 }
 
-static inline struct txfcb *gfar_add_fcb(struct sk_buff *skb)
+static inline struct txfcb *gfar_add_fcb(struct sk_buff **skbp)
 {
-       struct txfcb *fcb = (struct txfcb *)skb_push (skb, GMAC_FCB_LEN);
-
+       struct txfcb *fcb;
+       struct sk_buff *skb = *skbp;
+
+       if (unlikely(skb_headroom(skb) < GMAC_FCB_LEN)) {
+               struct sk_buff *old_skb = skb;
+               skb = skb_realloc_headroom(old_skb, GMAC_FCB_LEN);
+               if (!skb)
+                       return NULL;
+               dev_kfree_skb_any(old_skb);
+       }
+       fcb = (struct txfcb *)skb_push(skb, GMAC_FCB_LEN);
        cacheable_memzero(fcb, GMAC_FCB_LEN);
 
        return fcb;
@@ -1363,18 +1372,20 @@ static int gfar_start_xmit(struct sk_buff *skb, struct net_device *dev)
 
        /* Set up checksumming */
        if (CHECKSUM_PARTIAL == skb->ip_summed) {
-               fcb = gfar_add_fcb(skb);
-               lstatus |= BD_LFLAG(TXBD_TOE);
-               gfar_tx_checksum(skb, fcb);
+               fcb = gfar_add_fcb(&skb);
+               if (likely(fcb != NULL)) {
+                       lstatus |= BD_LFLAG(TXBD_TOE);
+                       gfar_tx_checksum(skb, fcb);
+               }
        }
 
        if (priv->vlgrp && vlan_tx_tag_present(skb)) {
-               if (unlikely(NULL == fcb)) {
-                       fcb = gfar_add_fcb(skb);
+               if (unlikely(NULL == fcb))
+                       fcb = gfar_add_fcb(&skb);
+               if (likely(fcb != NULL)) {
                        lstatus |= BD_LFLAG(TXBD_TOE);
+                       gfar_tx_vlan(skb, fcb);
                }
-
-               gfar_tx_vlan(skb, fcb);
        }
 
        /* setup the TxBD length and buffer pointer for the first BD */