318 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			318 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
From: Felix Fietkau <nbd@openwrt.org>
 | 
						|
Date: Fri, 5 Feb 2016 01:38:51 +0100
 | 
						|
Subject: [PATCH] mac80211: add A-MSDU tx support
 | 
						|
 | 
						|
Requires software tx queueing support. frag_list support (for zero-copy)
 | 
						|
is optional.
 | 
						|
 | 
						|
Signed-off-by: Felix Fietkau <nbd@openwrt.org>
 | 
						|
---
 | 
						|
 | 
						|
--- a/include/net/mac80211.h
 | 
						|
+++ b/include/net/mac80211.h
 | 
						|
@@ -709,6 +709,7 @@ enum mac80211_tx_info_flags {
 | 
						|
  * @IEEE80211_TX_CTRL_PS_RESPONSE: This frame is a response to a poll
 | 
						|
  *	frame (PS-Poll or uAPSD).
 | 
						|
  * @IEEE80211_TX_CTRL_RATE_INJECT: This frame is injected with rate information
 | 
						|
+ * @IEEE80211_TX_CTRL_AMSDU: This frame is an A-MSDU frame
 | 
						|
  *
 | 
						|
  * These flags are used in tx_info->control.flags.
 | 
						|
  */
 | 
						|
@@ -716,6 +717,7 @@ enum mac80211_tx_control_flags {
 | 
						|
 	IEEE80211_TX_CTRL_PORT_CTRL_PROTO	= BIT(0),
 | 
						|
 	IEEE80211_TX_CTRL_PS_RESPONSE		= BIT(1),
 | 
						|
 	IEEE80211_TX_CTRL_RATE_INJECT		= BIT(2),
 | 
						|
+	IEEE80211_TX_CTRL_AMSDU			= BIT(3),
 | 
						|
 };
 | 
						|
 
 | 
						|
 /*
 | 
						|
@@ -1728,6 +1730,7 @@ struct ieee80211_sta_rates {
 | 
						|
  *		  size is min(max_amsdu_len, 7935) bytes.
 | 
						|
  *	Both additional HT limits must be enforced by the low level driver.
 | 
						|
  *	This is defined by the spec (IEEE 802.11-2012 section 8.3.2.2 NOTE 2).
 | 
						|
+ * @max_rc_amsdu_len: Maximum A-MSDU size in bytes recommended by rate control.
 | 
						|
  * @txq: per-TID data TX queues (if driver uses the TXQ abstraction)
 | 
						|
  */
 | 
						|
 struct ieee80211_sta {
 | 
						|
@@ -1748,6 +1751,7 @@ struct ieee80211_sta {
 | 
						|
 	bool mfp;
 | 
						|
 	u8 max_amsdu_subframes;
 | 
						|
 	u16 max_amsdu_len;
 | 
						|
+	u16 max_rc_amsdu_len;
 | 
						|
 
 | 
						|
 	struct ieee80211_txq *txq[IEEE80211_NUM_TIDS];
 | 
						|
 
 | 
						|
@@ -1961,6 +1965,15 @@ struct ieee80211_txq {
 | 
						|
  *	order and does not need to manage its own reorder buffer or BA session
 | 
						|
  *	timeout.
 | 
						|
  *
 | 
						|
+ * @IEEE80211_HW_TX_AMSDU: Hardware (or driver) supports software aggregated
 | 
						|
+ *	A-MSDU frames. Requires software tx queueing and fast-xmit support.
 | 
						|
+ *	When not using minstrel/minstrel_ht rate control, the driver should
 | 
						|
+ *	limit the maximum A-MSDU size based on the current tx rate by setting
 | 
						|
+ *	max_rc_amsdu_len in struct ieee80211_sta.
 | 
						|
+ *
 | 
						|
+ * @IEEE80211_HW_TX_FRAG_LIST: Hardware (or driver) supports sending frag_list
 | 
						|
+ *	skbs, needed for zero-copy software A-MSDU.
 | 
						|
+ *
 | 
						|
  * @NUM_IEEE80211_HW_FLAGS: number of hardware flags, used for sizing arrays
 | 
						|
  */
 | 
						|
 enum ieee80211_hw_flags {
 | 
						|
@@ -1998,6 +2011,8 @@ enum ieee80211_hw_flags {
 | 
						|
 	IEEE80211_HW_BEACON_TX_STATUS,
 | 
						|
 	IEEE80211_HW_NEEDS_UNIQUE_STA_ADDR,
 | 
						|
 	IEEE80211_HW_SUPPORTS_REORDERING_BUFFER,
 | 
						|
+	IEEE80211_HW_TX_AMSDU,
 | 
						|
+	IEEE80211_HW_TX_FRAG_LIST,
 | 
						|
 
 | 
						|
 	/* keep last, obviously */
 | 
						|
 	NUM_IEEE80211_HW_FLAGS
 | 
						|
@@ -2070,6 +2085,9 @@ enum ieee80211_hw_flags {
 | 
						|
  *	size is smaller (an example is LinkSys WRT120N with FW v1.0.07
 | 
						|
  *	build 002 Jun 18 2012).
 | 
						|
  *
 | 
						|
+ * @max_tx_fragments: maximum number of tx buffers per (A)-MSDU, sum
 | 
						|
+ *	of 1 + skb_shinfo(skb)->nr_frags for each skb in the frag_list.
 | 
						|
+ *
 | 
						|
  * @offchannel_tx_hw_queue: HW queue ID to use for offchannel TX
 | 
						|
  *	(if %IEEE80211_HW_QUEUE_CONTROL is set)
 | 
						|
  *
 | 
						|
@@ -2124,6 +2142,7 @@ struct ieee80211_hw {
 | 
						|
 	u8 max_rate_tries;
 | 
						|
 	u8 max_rx_aggregation_subframes;
 | 
						|
 	u8 max_tx_aggregation_subframes;
 | 
						|
+	u8 max_tx_fragments;
 | 
						|
 	u8 offchannel_tx_hw_queue;
 | 
						|
 	u8 radiotap_mcs_details;
 | 
						|
 	u16 radiotap_vht_details;
 | 
						|
--- a/net/mac80211/agg-tx.c
 | 
						|
+++ b/net/mac80211/agg-tx.c
 | 
						|
@@ -935,6 +935,7 @@ void ieee80211_process_addba_resp(struct
 | 
						|
 				  size_t len)
 | 
						|
 {
 | 
						|
 	struct tid_ampdu_tx *tid_tx;
 | 
						|
+	struct ieee80211_txq *txq;
 | 
						|
 	u16 capab, tid;
 | 
						|
 	u8 buf_size;
 | 
						|
 	bool amsdu;
 | 
						|
@@ -945,6 +946,10 @@ void ieee80211_process_addba_resp(struct
 | 
						|
 	buf_size = (capab & IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK) >> 6;
 | 
						|
 	buf_size = min(buf_size, local->hw.max_tx_aggregation_subframes);
 | 
						|
 
 | 
						|
+	txq = sta->sta.txq[tid];
 | 
						|
+	if (!amsdu && txq)
 | 
						|
+		set_bit(IEEE80211_TXQ_NO_AMSDU, &to_txq_info(txq)->flags);
 | 
						|
+
 | 
						|
 	mutex_lock(&sta->ampdu_mlme.mtx);
 | 
						|
 
 | 
						|
 	tid_tx = rcu_dereference_protected_tid_tx(sta, tid);
 | 
						|
--- a/net/mac80211/debugfs.c
 | 
						|
+++ b/net/mac80211/debugfs.c
 | 
						|
@@ -127,6 +127,8 @@ static const char *hw_flag_names[NUM_IEE
 | 
						|
 	FLAG(BEACON_TX_STATUS),
 | 
						|
 	FLAG(NEEDS_UNIQUE_STA_ADDR),
 | 
						|
 	FLAG(SUPPORTS_REORDERING_BUFFER),
 | 
						|
+	FLAG(TX_AMSDU),
 | 
						|
+	FLAG(TX_FRAG_LIST),
 | 
						|
 
 | 
						|
 	/* keep last for the build bug below */
 | 
						|
 	(void *)0x1
 | 
						|
--- a/net/mac80211/ieee80211_i.h
 | 
						|
+++ b/net/mac80211/ieee80211_i.h
 | 
						|
@@ -799,6 +799,7 @@ struct mac80211_qos_map {
 | 
						|
 enum txq_info_flags {
 | 
						|
 	IEEE80211_TXQ_STOP,
 | 
						|
 	IEEE80211_TXQ_AMPDU,
 | 
						|
+	IEEE80211_TXQ_NO_AMSDU,
 | 
						|
 };
 | 
						|
 
 | 
						|
 struct txq_info {
 | 
						|
--- a/net/mac80211/tx.c
 | 
						|
+++ b/net/mac80211/tx.c
 | 
						|
@@ -1318,6 +1318,10 @@ struct sk_buff *ieee80211_tx_dequeue(str
 | 
						|
 out:
 | 
						|
 	spin_unlock_bh(&txqi->queue.lock);
 | 
						|
 
 | 
						|
+	if (skb && skb_has_frag_list(skb) &&
 | 
						|
+	    !ieee80211_hw_check(&local->hw, TX_FRAG_LIST))
 | 
						|
+		skb_linearize(skb);
 | 
						|
+
 | 
						|
 	return skb;
 | 
						|
 }
 | 
						|
 EXPORT_SYMBOL(ieee80211_tx_dequeue);
 | 
						|
@@ -2757,6 +2761,163 @@ void ieee80211_clear_fast_xmit(struct st
 | 
						|
 		kfree_rcu(fast_tx, rcu_head);
 | 
						|
 }
 | 
						|
 
 | 
						|
+static bool ieee80211_amsdu_realloc_pad(struct ieee80211_local *local,
 | 
						|
+					struct sk_buff *skb, int headroom,
 | 
						|
+					int *subframe_len)
 | 
						|
+{
 | 
						|
+	int amsdu_len = *subframe_len + sizeof(struct ethhdr);
 | 
						|
+	int padding = (4 - amsdu_len) & 3;
 | 
						|
+
 | 
						|
+	if (skb_headroom(skb) < headroom || skb_tailroom(skb) < padding) {
 | 
						|
+		I802_DEBUG_INC(local->tx_expand_skb_head);
 | 
						|
+
 | 
						|
+		if (pskb_expand_head(skb, headroom, padding, GFP_ATOMIC)) {
 | 
						|
+			wiphy_debug(local->hw.wiphy,
 | 
						|
+				    "failed to reallocate TX buffer\n");
 | 
						|
+			return false;
 | 
						|
+		}
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (padding) {
 | 
						|
+		*subframe_len += padding;
 | 
						|
+		memset(skb_put(skb, padding), 0, padding);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return true;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static bool ieee80211_amsdu_prepare_head(struct ieee80211_sub_if_data *sdata,
 | 
						|
+					 struct ieee80211_fast_tx *fast_tx,
 | 
						|
+					 struct sk_buff *skb)
 | 
						|
+{
 | 
						|
+	struct ieee80211_local *local = sdata->local;
 | 
						|
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
 | 
						|
+	struct ieee80211_hdr *hdr;
 | 
						|
+	struct ethhdr amsdu_hdr;
 | 
						|
+	int hdr_len = fast_tx->hdr_len - sizeof(rfc1042_header);
 | 
						|
+	int subframe_len = skb->len - hdr_len;
 | 
						|
+	void *data;
 | 
						|
+	u8 *qc;
 | 
						|
+
 | 
						|
+	if (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE)
 | 
						|
+		return false;
 | 
						|
+
 | 
						|
+	if (info->control.flags & IEEE80211_TX_CTRL_AMSDU)
 | 
						|
+		return true;
 | 
						|
+
 | 
						|
+	if (!ieee80211_amsdu_realloc_pad(local, skb, sizeof(amsdu_hdr),
 | 
						|
+					 &subframe_len))
 | 
						|
+		return false;
 | 
						|
+
 | 
						|
+	amsdu_hdr.h_proto = cpu_to_be16(subframe_len);
 | 
						|
+	memcpy(amsdu_hdr.h_source, skb->data + fast_tx->sa_offs, ETH_ALEN);
 | 
						|
+	memcpy(amsdu_hdr.h_dest, skb->data + fast_tx->da_offs, ETH_ALEN);
 | 
						|
+
 | 
						|
+	data = skb_push(skb, sizeof(amsdu_hdr));
 | 
						|
+	memmove(data, data + sizeof(amsdu_hdr), hdr_len);
 | 
						|
+	memcpy(data + hdr_len, &amsdu_hdr, sizeof(amsdu_hdr));
 | 
						|
+
 | 
						|
+	hdr = data;
 | 
						|
+	qc = ieee80211_get_qos_ctl(hdr);
 | 
						|
+	*qc |= IEEE80211_QOS_CTL_A_MSDU_PRESENT;
 | 
						|
+
 | 
						|
+	info->control.flags |= IEEE80211_TX_CTRL_AMSDU;
 | 
						|
+
 | 
						|
+	return true;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static bool ieee80211_amsdu_aggregate(struct ieee80211_sub_if_data *sdata,
 | 
						|
+				      struct sta_info *sta,
 | 
						|
+				      struct ieee80211_fast_tx *fast_tx,
 | 
						|
+				      struct sk_buff *skb)
 | 
						|
+{
 | 
						|
+	struct ieee80211_local *local = sdata->local;
 | 
						|
+	u8 tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
 | 
						|
+	struct ieee80211_txq *txq = sta->sta.txq[tid];
 | 
						|
+	struct txq_info *txqi;
 | 
						|
+	struct sk_buff **frag_tail, *head;
 | 
						|
+	int subframe_len = skb->len - ETH_ALEN;
 | 
						|
+	u8 max_subframes = sta->sta.max_amsdu_subframes;
 | 
						|
+	int max_frags = local->hw.max_tx_fragments;
 | 
						|
+	int max_amsdu_len = sta->sta.max_amsdu_len;
 | 
						|
+	__be16 len;
 | 
						|
+	void *data;
 | 
						|
+	bool ret = false;
 | 
						|
+	int n = 1, nfrags;
 | 
						|
+
 | 
						|
+	if (!ieee80211_hw_check(&local->hw, TX_AMSDU))
 | 
						|
+		return false;
 | 
						|
+
 | 
						|
+	if (!txq)
 | 
						|
+		return false;
 | 
						|
+
 | 
						|
+	txqi = to_txq_info(txq);
 | 
						|
+	if (test_bit(IEEE80211_TXQ_NO_AMSDU, &txqi->flags))
 | 
						|
+		return false;
 | 
						|
+
 | 
						|
+	if (sta->sta.max_rc_amsdu_len)
 | 
						|
+		max_amsdu_len = min_t(int, max_amsdu_len,
 | 
						|
+				      sta->sta.max_rc_amsdu_len);
 | 
						|
+
 | 
						|
+	spin_lock_bh(&txqi->queue.lock);
 | 
						|
+
 | 
						|
+	head = skb_peek_tail(&txqi->queue);
 | 
						|
+	if (!head)
 | 
						|
+		goto out;
 | 
						|
+
 | 
						|
+	if (skb->len + head->len > max_amsdu_len)
 | 
						|
+		goto out;
 | 
						|
+
 | 
						|
+	/*
 | 
						|
+	 * HT A-MPDU limits maximum MPDU size to 4095 bytes. Since aggregation
 | 
						|
+	 * sessions are started/stopped without txq flush, use the limit here
 | 
						|
+	 * to avoid having to de-aggregate later.
 | 
						|
+	 */
 | 
						|
+	if (skb->len + head->len > 4095 &&
 | 
						|
+	    !sta->sta.vht_cap.vht_supported)
 | 
						|
+		goto out;
 | 
						|
+
 | 
						|
+	if (!ieee80211_amsdu_prepare_head(sdata, fast_tx, head))
 | 
						|
+		goto out;
 | 
						|
+
 | 
						|
+	nfrags = 1 + skb_shinfo(skb)->nr_frags;
 | 
						|
+	nfrags += 1 + skb_shinfo(head)->nr_frags;
 | 
						|
+	frag_tail = &skb_shinfo(head)->frag_list;
 | 
						|
+	while (*frag_tail) {
 | 
						|
+		nfrags += 1 + skb_shinfo(*frag_tail)->nr_frags;
 | 
						|
+		frag_tail = &(*frag_tail)->next;
 | 
						|
+		n++;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if (max_subframes && n > max_subframes)
 | 
						|
+		goto out;
 | 
						|
+
 | 
						|
+	if (max_frags && nfrags > max_frags)
 | 
						|
+		goto out;
 | 
						|
+
 | 
						|
+	if (!ieee80211_amsdu_realloc_pad(local, skb, sizeof(rfc1042_header) + 2,
 | 
						|
+					 &subframe_len))
 | 
						|
+		return false;
 | 
						|
+
 | 
						|
+	ret = true;
 | 
						|
+	data = skb_push(skb, ETH_ALEN + 2);
 | 
						|
+	memmove(data, data + ETH_ALEN + 2, 2 * ETH_ALEN);
 | 
						|
+
 | 
						|
+	data += 2 * ETH_ALEN;
 | 
						|
+	len = cpu_to_be16(subframe_len);
 | 
						|
+	memcpy(data, &len, 2);
 | 
						|
+	memcpy(data + 2, rfc1042_header, sizeof(rfc1042_header));
 | 
						|
+
 | 
						|
+	head->len += skb->len;
 | 
						|
+	head->data_len += skb->len;
 | 
						|
+	*frag_tail = skb;
 | 
						|
+
 | 
						|
+out:
 | 
						|
+	spin_unlock_bh(&txqi->queue.lock);
 | 
						|
+
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
 static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata,
 | 
						|
 				struct net_device *dev, struct sta_info *sta,
 | 
						|
 				struct ieee80211_fast_tx *fast_tx,
 | 
						|
@@ -2811,6 +2972,10 @@ static bool ieee80211_xmit_fast(struct i
 | 
						|
 
 | 
						|
 	ieee80211_tx_stats(dev, skb->len + extra_head);
 | 
						|
 
 | 
						|
+	if ((hdr->frame_control & cpu_to_le16(IEEE80211_STYPE_QOS_DATA)) &&
 | 
						|
+	    ieee80211_amsdu_aggregate(sdata, sta, fast_tx, skb))
 | 
						|
+		return true;
 | 
						|
+
 | 
						|
 	/* will not be crypto-handled beyond what we do here, so use false
 | 
						|
 	 * as the may-encrypt argument for the resize to not account for
 | 
						|
 	 * more room than we already have in 'extra_head'
 |