@@ -852,6 +852,8 @@ struct ath_hw {
/* Enterprise mode cap */
u32 ent_mode;
+
+ struct sk_buff *rx_frag;
};
static inline struct ath_common *ath9k_hw_common(struct ath_hw *ah)
@@ -1320,6 +1320,11 @@ static void ath9k_stop(struct ieee80211_
} else
sc->rx.rxlink = NULL;
+ if (ah->rx_frag) {
+ dev_kfree_skb_any(ah->rx_frag);
+ ah->rx_frag = NULL;
+ }
+
/* disable HAL and put h/w to sleep */
ath9k_hw_disable(ah);
ath9k_hw_configpcipowersave(ah, 1, 1);
@@ -324,8 +324,19 @@ int ath_rx_init(struct ath_softc *sc, in
if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) {
return ath_rx_edma_init(sc, nbufs);
} else {
- common->rx_bufsize = roundup(IEEE80211_MAX_MPDU_LEN,
+ /*
+ * Use a buffer that allows a full frame to be received in at
+ * most two buffers with scattering DMA. This is needed for
+ * A-MSDU; other cases will fit in a single buffer. This will
+ * allow the skbs to fit in a single page to avoid issues with
+ * higher order allocation.
+ */
+ common->rx_bufsize = roundup(IEEE80211_MAX_MPDU_LEN / 2,
min(common->cachelsz, (u16)64));
+#if 0 /* TESTING - force two fragments even without A-MSDU */
+ common->rx_bufsize = roundup(2800 / 2,
+ min(common->cachelsz, (u16)64));
+#endif
ath_dbg(common, ATH_DBG_CONFIG, "cachelsz %u rxbufsize %u\n",
common->cachelsz, common->rx_bufsize);
@@ -863,16 +874,6 @@ static bool ath9k_rx_accept(struct ath_c
return false;
/*
- * rs_more indicates chained descriptors which can be used
- * to link buffers together for a sort of scatter-gather
- * operation.
- * reject the frame, we don't support scatter-gather yet and
- * the frame is probably corrupt anyway
- */
- if (rx_stats->rs_more)
- return false;
-
- /*
* The rx_stats->rs_status will not be set until the end of the
* chained descriptors so it can be ignored if rs_more is set. The
* rs_more will be false at the last element of the chained
@@ -1022,6 +1023,9 @@ static int ath9k_rx_skb_preprocess(struc
if (!ath9k_rx_accept(common, hdr, rx_status, rx_stats, decrypt_error))
return -EINVAL;
+ if (rx_stats->rs_more)
+ return 0; /* Only use data from the last fragment */
+
ath9k_process_rssi(common, hw, hdr, rx_stats);
if (ath9k_process_rate(common, hw, rx_stats, rx_status))
@@ -1674,7 +1678,16 @@ int ath_rx_tasklet(struct ath_softc *sc,
if (!skb)
continue;
- hdr = (struct ieee80211_hdr *) (skb->data + rx_status_len);
+ /*
+ * Take frame header from the first fragment and RX status from
+ * the last one.
+ */
+ if (ah->rx_frag)
+ hdr = (struct ieee80211_hdr *)
+ (ah->rx_frag->data + rx_status_len);
+ else
+ hdr = (struct ieee80211_hdr *)
+ (skb->data + rx_status_len);
rxs = IEEE80211_SKB_RXCB(skb);
hw = ath_get_virt_hw(sc, hdr);
@@ -1718,12 +1731,14 @@ int ath_rx_tasklet(struct ath_softc *sc,
common->rx_bufsize,
dma_type);
+ /* FIX: for fragmented frames, only one rx_status_len */
skb_put(skb, rs.rs_datalen + ah->caps.rx_status_len);
if (ah->caps.rx_status_len)
skb_pull(skb, ah->caps.rx_status_len);
- ath9k_rx_skb_postprocess(common, skb, &rs,
- rxs, decrypt_error);
+ if (!ah->rx_frag && !rs.rs_more)
+ ath9k_rx_skb_postprocess(common, skb, &rs,
+ rxs, decrypt_error);
/* We will now give hardware our shiny new allocated skb */
bf->bf_mpdu = requeue_skb;
@@ -1740,6 +1755,65 @@ int ath_rx_tasklet(struct ath_softc *sc,
break;
}
+ if (rs.rs_more) {
+ /*
+ * rs_more indicates chained descriptors which can be
+ * used to link buffers together for a sort of
+ * scatter-gather operation.
+ */
+ if (ah->rx_frag) {
+ printk(KERN_DEBUG "%s:dropped prev rx_frag\n",
+ __func__);
+ dev_kfree_skb_any(ah->rx_frag);
+ }
+ ah->rx_frag = skb;
+ goto requeue;
+ }
+ if (ah->rx_frag) {
+#if 1
+ struct sk_buff *nskb;
+ /*
+ * This is the final fragment of the frame - merge with
+ * previously received data.
+ */
+ nskb = skb_copy_expand(ah->rx_frag, 0, skb->len,
+ GFP_ATOMIC);
+ dev_kfree_skb_any(ah->rx_frag);
+ ah->rx_frag = NULL;
+ if (nskb == NULL) {
+ dev_kfree_skb_any(skb);
+ goto requeue;
+ }
+ skb_copy_from_linear_data(skb, skb_put(nskb, skb->len),
+ skb->len);
+ /* Take RX status for the last fragment */
+ memcpy(nskb->cb, skb->cb, sizeof(skb->cb));
+ dev_kfree_skb_any(skb);
+ skb = nskb;
+#else
+ /*
+ * TODO: This should really be optimized by using
+ * skb frag_list etc. to avoid new allocation and
+ * copying of data here. The fragmentation case will
+ * only happen when receiving A-MSDU aggregates and
+ * mac80211 will end up re-allocating and splitting
+ * those anyway. Once mac80211 is able to do this based
+ * on multiple fragments, alloc+copy code above can
+ * be replaced with something like following.
+ */
+ /* Take RX status for the last fragment */
+ memcpy(ah->rx_frag->cb, skb->cb, sizeof(skb->cb));
+ /* Link the fragments together using frag_list */
+ skb_frag_list_init(ah->rx_frag);
+ skb_frag_add_head(ah->rx_frag, skb);
+ skb = ah->rx_frag;
+ ah->rx_frag = NULL;
+#endif
+ rxs = IEEE80211_SKB_RXCB(skb);
+ ath9k_rx_skb_postprocess(common, skb, &rs,
+ rxs, decrypt_error);
+ }
+
/*
* change the default rx antenna if rx diversity chooses the
* other antenna 3 times in a row.