mac80211: add A-MSDU tx support
authorFelix Fietkau <nbd@openwrt.org>
Fri, 5 Feb 2016 16:43:38 +0000 (16:43 +0000)
committerFelix Fietkau <nbd@openwrt.org>
Fri, 5 Feb 2016 16:43:38 +0000 (16:43 +0000)
Signed-off-by: Felix Fietkau <nbd@openwrt.org>
SVN-Revision: 48637

package/kernel/mac80211/patches/322-mac80211-add-A-MSDU-tx-support.patch [new file with mode: 0644]

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 (file)
index 0000000..43197d7
--- /dev/null
@@ -0,0 +1,284 @@
+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),
+ };
+ /*
+@@ -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'