iwlwifi: fix skb truesize underestimation
Eric Dumazet [Sat, 24 Mar 2012 04:29:46 +0000 (00:29 -0400)]
By default, iwlwifi uses order-1 pages (8 KB) to store incoming frames,
but doesnt say so in skb->truesize.

This makes very possible to exhaust kernel memory since these skb evade
normal socket memory accounting.

As struct ieee80211_hdr is going to be pulled before calling IP stack,
there is no need to use dev_alloc_skb() to reserve NET_SKB_PAD bytes.
alloc_skb() is ok in this driver, allowing more tailroom.

Pull beginning of frame in skb header, in the hope we can reuse order-1
pages in the driver immediately for small frames and reduce their
truesize to the minimum (linear skbs)

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Cc: Wey-Yi Guy <wey-yi.w.guy@intel.com>
Cc: "John W. Linville" <linville@tuxdriver.com>
Cc: Neal Cardwell <ncardwell@google.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>

drivers/net/wireless/iwlwifi/iwl-agn-rx.c
drivers/net/wireless/iwlwifi/iwl-trans-pcie-rx.c
drivers/net/wireless/iwlwifi/iwl-trans.h

index f4b84d1..2247460 100644 (file)
@@ -773,8 +773,7 @@ static void iwlagn_pass_packet_to_mac80211(struct iwl_priv *priv,
        struct sk_buff *skb;
        __le16 fc = hdr->frame_control;
        struct iwl_rxon_context *ctx;
-       struct page *p;
-       int offset;
+       unsigned int hdrlen, fraglen;
 
        /* We only process data packets if the interface is open */
        if (unlikely(!priv->is_open)) {
@@ -788,16 +787,24 @@ static void iwlagn_pass_packet_to_mac80211(struct iwl_priv *priv,
            iwlagn_set_decrypted_flag(priv, hdr, ampdu_status, stats))
                return;
 
-       skb = dev_alloc_skb(128);
+       /* Dont use dev_alloc_skb(), we'll have enough headroom once
+        * ieee80211_hdr pulled.
+        */
+       skb = alloc_skb(128, GFP_ATOMIC);
        if (!skb) {
-               IWL_ERR(priv, "dev_alloc_skb failed\n");
+               IWL_ERR(priv, "alloc_skb failed\n");
                return;
        }
+       hdrlen = min_t(unsigned int, len, skb_tailroom(skb));
+       memcpy(skb_put(skb, hdrlen), hdr, hdrlen);
+       fraglen = len - hdrlen;
 
-       offset = (void *)hdr - rxb_addr(rxb);
-       p = rxb_steal_page(rxb);
-       skb_add_rx_frag(skb, 0, p, offset, len, len);
+       if (fraglen) {
+               int offset = (void *)hdr + hdrlen - rxb_addr(rxb);
 
+               skb_add_rx_frag(skb, 0, rxb_steal_page(rxb), offset,
+                               fraglen, rxb->truesize);
+       }
        iwl_update_stats(priv, false, fc, len);
 
        /*
index 8b1a798..aa7aea1 100644 (file)
@@ -374,8 +374,9 @@ static void iwl_rx_handle_rxbuf(struct iwl_trans *trans,
        if (WARN_ON(!rxb))
                return;
 
+       rxcb.truesize = PAGE_SIZE << hw_params(trans).rx_page_order;
        dma_unmap_page(trans->dev, rxb->page_dma,
-                      PAGE_SIZE << hw_params(trans).rx_page_order,
+                      rxcb.truesize,
                       DMA_FROM_DEVICE);
 
        rxcb._page = rxb->page;
index 0c81cba..fdf9788 100644 (file)
@@ -260,6 +260,7 @@ static inline void iwl_free_resp(struct iwl_host_cmd *cmd)
 
 struct iwl_rx_cmd_buffer {
        struct page *_page;
+       unsigned int truesize;
 };
 
 static inline void *rxb_addr(struct iwl_rx_cmd_buffer *r)