rtlwifi: Fix kernel panic resulting from RX buffer allocation failure

To handle amsdu_8k capability, the PCI routine of this driver must
allocate receive buffers of order 2. Under heavy load, this causes
fragmentation of memory. The present code releases the current buffer
before checking to see if a new one is availble. Recovery from
allocation failures is not possible, which results in kernel panics.

The fix is to reorder the code to check that a new buffer can be
allocated before the old one is released. If not possible, the
received frame is dropped and the old one is reused. Without this
change, it is impossible to transfer a 2 GB file without a kernel panic.

Signed-off-by: Larry Finger <Larry.Finger@lwfinger.net>
Cc: Stable <stable@vger.kernel.org>              [2.6.{37,38,39}]
Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
Larry Finger 2011-05-19 10:17:04 -05:00 committed by John W. Linville
parent 208c72f4fe
commit a9e1286975

View File

@ -669,11 +669,6 @@ static void _rtl_pci_rx_interrupt(struct ieee80211_hw *hw)
&rx_status, &rx_status,
(u8 *) pdesc, skb); (u8 *) pdesc, skb);
pci_unmap_single(rtlpci->pdev,
*((dma_addr_t *) skb->cb),
rtlpci->rxbuffersize,
PCI_DMA_FROMDEVICE);
skb_put(skb, rtlpriv->cfg->ops->get_desc((u8 *) pdesc, skb_put(skb, rtlpriv->cfg->ops->get_desc((u8 *) pdesc,
false, false,
HW_DESC_RXPKT_LEN)); HW_DESC_RXPKT_LEN));
@ -690,6 +685,21 @@ static void _rtl_pci_rx_interrupt(struct ieee80211_hw *hw)
hdr = rtl_get_hdr(skb); hdr = rtl_get_hdr(skb);
fc = rtl_get_fc(skb); fc = rtl_get_fc(skb);
/* try for new buffer - if allocation fails, drop
* frame and reuse old buffer
*/
new_skb = dev_alloc_skb(rtlpci->rxbuffersize);
if (unlikely(!new_skb)) {
RT_TRACE(rtlpriv, (COMP_INTR | COMP_RECV),
DBG_DMESG,
("can't alloc skb for rx\n"));
goto done;
}
pci_unmap_single(rtlpci->pdev,
*((dma_addr_t *) skb->cb),
rtlpci->rxbuffersize,
PCI_DMA_FROMDEVICE);
if (!stats.crc || !stats.hwerror) { if (!stats.crc || !stats.hwerror) {
memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, memcpy(IEEE80211_SKB_RXCB(skb), &rx_status,
sizeof(rx_status)); sizeof(rx_status));
@ -758,15 +768,7 @@ static void _rtl_pci_rx_interrupt(struct ieee80211_hw *hw)
rtl_lps_leave(hw); rtl_lps_leave(hw);
} }
new_skb = dev_alloc_skb(rtlpci->rxbuffersize);
if (unlikely(!new_skb)) {
RT_TRACE(rtlpriv, (COMP_INTR | COMP_RECV),
DBG_DMESG,
("can't alloc skb for rx\n"));
goto done;
}
skb = new_skb; skb = new_skb;
/*skb->dev = dev; */
rtlpci->rx_ring[rx_queue_idx].rx_buf[rtlpci-> rtlpci->rx_ring[rx_queue_idx].rx_buf[rtlpci->
rx_ring rx_ring