From b0c782be708cb4d9a34041a85c2e091165c48734 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 5 Feb 2016 16:43:38 +0000 Subject: [PATCH] mac80211: add A-MSDU tx support Signed-off-by: Felix Fietkau SVN-Revision: 48637 --- .../322-mac80211-add-A-MSDU-tx-support.patch | 284 ++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 package/kernel/mac80211/patches/322-mac80211-add-A-MSDU-tx-support.patch diff --git a/package/kernel/mac80211/patches/322-mac80211-add-A-MSDU-tx-support.patch b/package/kernel/mac80211/patches/322-mac80211-add-A-MSDU-tx-support.patch new file mode 100644 index 0000000000..43197d7855 --- /dev/null +++ b/package/kernel/mac80211/patches/322-mac80211-add-A-MSDU-tx-support.patch @@ -0,0 +1,284 @@ +From: Felix Fietkau +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 +--- + +--- 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), + }; + + /* +@@ -1961,6 +1963,12 @@ 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 support. ++ * ++ * @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 +2006,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 +2080,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_amsdu_subframes: maximum number of subframes used in software ++ * A-MSDU aggregation ++ * + * @offchannel_tx_hw_queue: HW queue ID to use for offchannel TX + * (if %IEEE80211_HW_QUEUE_CONTROL is set) + * +@@ -2124,6 +2137,7 @@ struct ieee80211_hw { + u8 max_rate_tries; + u8 max_rx_aggregation_subframes; + u8 max_tx_aggregation_subframes; ++ u8 max_tx_amsdu_subframes; + 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,149 @@ void ieee80211_clear_fast_xmit(struct st + kfree_rcu(fast_tx, rcu_head); + } + ++static int ieee80211_amsdu_pad(struct sk_buff *skb, int subframe_len) ++{ ++ int amsdu_len = subframe_len + sizeof(struct ethhdr); ++ int padding = (4 - amsdu_len) & 3; ++ ++ if (padding) ++ memset(skb_put(skb, padding), 0, padding); ++ ++ return padding; ++} ++ ++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->control.flags & IEEE80211_TX_CTRL_AMSDU) ++ return true; ++ ++ if (skb_headroom(skb) < sizeof(amsdu_hdr) || skb_tailroom(skb) < 3) { ++ I802_DEBUG_INC(local->tx_expand_skb_head); ++ ++ if (pskb_expand_head(skb, sizeof(amsdu_hdr), 3, GFP_ATOMIC)) { ++ wiphy_debug(local->hw.wiphy, ++ "failed to reallocate TX buffer\n"); ++ return false; ++ } ++ } ++ ++ subframe_len += ieee80211_amsdu_pad(skb, subframe_len); ++ ++ 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; ++ int max_amsdu_len; ++ __be16 len; ++ void *data; ++ bool ret = false; ++ int n = 1; ++ ++ 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; ++ ++ /* ++ * 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. ++ */ ++ max_amsdu_len = min_t(int, sta->sta.max_amsdu_len, 4095); ++ ++ 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; ++ ++ if (!ieee80211_amsdu_prepare_head(sdata, fast_tx, head)) ++ goto out; ++ ++ frag_tail = &skb_shinfo(head)->frag_list; ++ while (*frag_tail) { ++ frag_tail = &(*frag_tail)->next; ++ n++; ++ } ++ ++ if (local->hw.max_tx_amsdu_subframes && ++ n > local->hw.max_tx_amsdu_subframes) ++ goto out; ++ ++ if (skb_headroom(skb) < 8 || skb_tailroom(skb) < 3) { ++ I802_DEBUG_INC(local->tx_expand_skb_head); ++ ++ if (pskb_expand_head(skb, 8, 3, GFP_ATOMIC)) { ++ wiphy_debug(local->hw.wiphy, ++ "failed to reallocate TX buffer\n"); ++ goto out; ++ } ++ } ++ ++ subframe_len += ieee80211_amsdu_pad(skb, subframe_len); ++ ++ 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, ETH_ALEN); ++ ++ 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 +2958,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' -- 2.30.2