--- wireless-testing.orig/drivers/net/wireless/ath/ath9k/hw.h	2011-01-10 13:03:16.000000000 +0200
+++ wireless-testing/drivers/net/wireless/ath/ath9k/hw.h	2011-01-10 13:15:39.000000000 +0200
@@ -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)
--- wireless-testing.orig/drivers/net/wireless/ath/ath9k/main.c	2011-01-10 13:14:26.000000000 +0200
+++ wireless-testing/drivers/net/wireless/ath/ath9k/main.c	2011-01-10 13:15:05.000000000 +0200
@@ -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);
--- wireless-testing.orig/drivers/net/wireless/ath/ath9k/recv.c	2011-01-10 12:24:08.000000000 +0200
+++ wireless-testing/drivers/net/wireless/ath/ath9k/recv.c	2011-01-10 14:29:29.000000000 +0200
@@ -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.
