rt2x00: Improve TX status handling for BlockAckReq frames
authorHelmut Schaa <helmut.schaa@googlemail.com>
Thu, 17 Jan 2013 16:34:32 +0000 (17:34 +0100)
committerJohn W. Linville <linville@tuxdriver.com>
Tue, 22 Jan 2013 21:01:29 +0000 (16:01 -0500)
Since rt2800 hardware isn't capable of reporting the TX status of
BlockAckReq frames implement the TX status handling of BARs in
rt2x00lib. We keep track of all BARs that are send out and try to
match incoming BAs to the appropriate BARs. This allows us to report a
more or less accurate TX status for BAR frames which in turn improves
BA session stability.

This is loosley based on Christian Lamparter's patch for carl9170
"carl9170: fix HT peer BA session corruption".

We have to walk the list of pending BARs for every rx'red BA even
though most BAs don't belong to any of these BARs as they are just
acknowledging an AMPDU. To keep that overhead low use RCU which allows
us to walk the list of pending BARs without the need to acquire a lock.
This however requires us to _copy_ relevant information from the BAR
(RA, TA, control field, start sequence number) into our BAR list entry.

Signed-off-by: Helmut Schaa <helmut.schaa@googlemail.com>
Tested-by: Andreas Hartmann <andihartmann@01019freenet.de>
Acked-by: Gertjan van Wingerde <gwingerde@gmail.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/wireless/rt2x00/rt2800lib.c
drivers/net/wireless/rt2x00/rt2x00.h
drivers/net/wireless/rt2x00/rt2x00dev.c
drivers/net/wireless/rt2x00/rt2x00queue.c

index f139a913c25a385c2608c003daab51ea19c22843..a5c694f23d334ff1308d1797ee1b697baa82caab 100644 (file)
@@ -1296,8 +1296,7 @@ void rt2800_config_filter(struct rt2x00_dev *rt2x00dev,
                           !(filter_flags & FIF_CONTROL));
        rt2x00_set_field32(&reg, RX_FILTER_CFG_DROP_PSPOLL,
                           !(filter_flags & FIF_PSPOLL));
-       rt2x00_set_field32(&reg, RX_FILTER_CFG_DROP_BA,
-                          !(filter_flags & FIF_CONTROL));
+       rt2x00_set_field32(&reg, RX_FILTER_CFG_DROP_BA, 0);
        rt2x00_set_field32(&reg, RX_FILTER_CFG_DROP_BAR,
                           !(filter_flags & FIF_CONTROL));
        rt2x00_set_field32(&reg, RX_FILTER_CFG_DROP_CNTL,
@@ -5146,8 +5145,7 @@ static int rt2800_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
            IEEE80211_HW_SUPPORTS_PS |
            IEEE80211_HW_PS_NULLFUNC_STACK |
            IEEE80211_HW_AMPDU_AGGREGATION |
-           IEEE80211_HW_REPORTS_TX_ACK_STATUS |
-           IEEE80211_HW_TEARDOWN_AGGR_ON_BAR_FAIL;
+           IEEE80211_HW_REPORTS_TX_ACK_STATUS;
 
        /*
         * Don't set IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING for USB devices
index 0751b35ef6dcd536ba51c3554dc4b58e359a9628..b52512b8ac5fd0632ce2c6fcf7aa34dd373e3acc 100644 (file)
@@ -1016,6 +1016,26 @@ struct rt2x00_dev {
         * Protect the interrupt mask register.
         */
        spinlock_t irqmask_lock;
+
+       /*
+        * List of BlockAckReq TX entries that need driver BlockAck processing.
+        */
+       struct list_head bar_list;
+       spinlock_t bar_list_lock;
+};
+
+struct rt2x00_bar_list_entry {
+       struct list_head list;
+       struct rcu_head head;
+
+       struct queue_entry *entry;
+       int block_acked;
+
+       /* Relevant parts of the IEEE80211 BAR header */
+       __u8 ra[6];
+       __u8 ta[6];
+       __le16 control;
+       __le16 start_seq_num;
 };
 
 /*
index 44f8b3f3cbede976cb97ab132f032a1d136549e1..b40a53857498b48576beae6ccb4a83a03ded2360 100644 (file)
@@ -271,6 +271,50 @@ void rt2x00lib_dmadone(struct queue_entry *entry)
 }
 EXPORT_SYMBOL_GPL(rt2x00lib_dmadone);
 
+static inline int rt2x00lib_txdone_bar_status(struct queue_entry *entry)
+{
+       struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev;
+       struct ieee80211_bar *bar = (void *) entry->skb->data;
+       struct rt2x00_bar_list_entry *bar_entry;
+       int ret;
+
+       if (likely(!ieee80211_is_back_req(bar->frame_control)))
+               return 0;
+
+       /*
+        * Unlike all other frames, the status report for BARs does
+        * not directly come from the hardware as it is incapable of
+        * matching a BA to a previously send BAR. The hardware will
+        * report all BARs as if they weren't acked at all.
+        *
+        * Instead the RX-path will scan for incoming BAs and set the
+        * block_acked flag if it sees one that was likely caused by
+        * a BAR from us.
+        *
+        * Remove remaining BARs here and return their status for
+        * TX done processing.
+        */
+       ret = 0;
+       rcu_read_lock();
+       list_for_each_entry_rcu(bar_entry, &rt2x00dev->bar_list, list) {
+               if (bar_entry->entry != entry)
+                       continue;
+
+               spin_lock_bh(&rt2x00dev->bar_list_lock);
+               /* Return whether this BAR was blockacked or not */
+               ret = bar_entry->block_acked;
+               /* Remove the BAR from our checklist */
+               list_del_rcu(&bar_entry->list);
+               spin_unlock_bh(&rt2x00dev->bar_list_lock);
+               kfree_rcu(bar_entry, head);
+
+               break;
+       }
+       rcu_read_unlock();
+
+       return ret;
+}
+
 void rt2x00lib_txdone(struct queue_entry *entry,
                      struct txdone_entry_desc *txdesc)
 {
@@ -324,9 +368,12 @@ void rt2x00lib_txdone(struct queue_entry *entry,
        rt2x00debug_dump_frame(rt2x00dev, DUMP_FRAME_TXDONE, entry->skb);
 
        /*
-        * Determine if the frame has been successfully transmitted.
+        * Determine if the frame has been successfully transmitted and
+        * remove BARs from our check list while checking for their
+        * TX status.
         */
        success =
+           rt2x00lib_txdone_bar_status(entry) ||
            test_bit(TXDONE_SUCCESS, &txdesc->flags) ||
            test_bit(TXDONE_UNKNOWN, &txdesc->flags);
 
@@ -491,6 +538,50 @@ static void rt2x00lib_sleep(struct work_struct *work)
                                 IEEE80211_CONF_CHANGE_PS);
 }
 
+static void rt2x00lib_rxdone_check_ba(struct rt2x00_dev *rt2x00dev,
+                                     struct sk_buff *skb,
+                                     struct rxdone_entry_desc *rxdesc)
+{
+       struct rt2x00_bar_list_entry *entry;
+       struct ieee80211_bar *ba = (void *)skb->data;
+
+       if (likely(!ieee80211_is_back(ba->frame_control)))
+               return;
+
+       if (rxdesc->size < sizeof(*ba) + FCS_LEN)
+               return;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(entry, &rt2x00dev->bar_list, list) {
+
+               if (ba->start_seq_num != entry->start_seq_num)
+                       continue;
+
+#define TID_CHECK(a, b) (                                              \
+       ((a) & cpu_to_le16(IEEE80211_BAR_CTRL_TID_INFO_MASK)) ==        \
+       ((b) & cpu_to_le16(IEEE80211_BAR_CTRL_TID_INFO_MASK)))          \
+
+               if (!TID_CHECK(ba->control, entry->control))
+                       continue;
+
+#undef TID_CHECK
+
+               if (compare_ether_addr(ba->ra, entry->ta))
+                       continue;
+
+               if (compare_ether_addr(ba->ta, entry->ra))
+                       continue;
+
+               /* Mark BAR since we received the according BA */
+               spin_lock_bh(&rt2x00dev->bar_list_lock);
+               entry->block_acked = 1;
+               spin_unlock_bh(&rt2x00dev->bar_list_lock);
+               break;
+       }
+       rcu_read_unlock();
+
+}
+
 static void rt2x00lib_rxdone_check_ps(struct rt2x00_dev *rt2x00dev,
                                      struct sk_buff *skb,
                                      struct rxdone_entry_desc *rxdesc)
@@ -673,6 +764,12 @@ void rt2x00lib_rxdone(struct queue_entry *entry, gfp_t gfp)
         */
        rt2x00lib_rxdone_check_ps(rt2x00dev, entry->skb, &rxdesc);
 
+       /*
+        * Check for incoming BlockAcks to match to the BlockAckReqs
+        * we've send out.
+        */
+       rt2x00lib_rxdone_check_ba(rt2x00dev, entry->skb, &rxdesc);
+
        /*
         * Update extra components
         */
@@ -1183,6 +1280,8 @@ int rt2x00lib_probe_dev(struct rt2x00_dev *rt2x00dev)
 
        spin_lock_init(&rt2x00dev->irqmask_lock);
        mutex_init(&rt2x00dev->csr_mutex);
+       INIT_LIST_HEAD(&rt2x00dev->bar_list);
+       spin_lock_init(&rt2x00dev->bar_list_lock);
 
        set_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags);
 
index e488b944a0340834ed96c02c91df59e9b3f5e142..f35d85a71bbcab0e416f4e6db115e82538098414 100644 (file)
@@ -582,6 +582,48 @@ static void rt2x00queue_kick_tx_queue(struct data_queue *queue,
                queue->rt2x00dev->ops->lib->kick_queue(queue);
 }
 
+static void rt2x00queue_bar_check(struct queue_entry *entry)
+{
+       struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev;
+       struct ieee80211_bar *bar = (void *) (entry->skb->data +
+                                   rt2x00dev->ops->extra_tx_headroom);
+       struct rt2x00_bar_list_entry *bar_entry;
+
+       if (likely(!ieee80211_is_back_req(bar->frame_control)))
+               return;
+
+       bar_entry = kmalloc(sizeof(*bar_entry), GFP_ATOMIC);
+
+       /*
+        * If the alloc fails we still send the BAR out but just don't track
+        * it in our bar list. And as a result we will report it to mac80211
+        * back as failed.
+        */
+       if (!bar_entry)
+               return;
+
+       bar_entry->entry = entry;
+       bar_entry->block_acked = 0;
+
+       /*
+        * Copy the relevant parts of the 802.11 BAR into out check list
+        * such that we can use RCU for less-overhead in the RX path since
+        * sending BARs and processing the according BlockAck should be
+        * the exception.
+        */
+       memcpy(bar_entry->ra, bar->ra, sizeof(bar->ra));
+       memcpy(bar_entry->ta, bar->ta, sizeof(bar->ta));
+       bar_entry->control = bar->control;
+       bar_entry->start_seq_num = bar->start_seq_num;
+
+       /*
+        * Insert BAR into our BAR check list.
+        */
+       spin_lock_bh(&rt2x00dev->bar_list_lock);
+       list_add_tail_rcu(&bar_entry->list, &rt2x00dev->bar_list);
+       spin_unlock_bh(&rt2x00dev->bar_list_lock);
+}
+
 int rt2x00queue_write_tx_frame(struct data_queue *queue, struct sk_buff *skb,
                               bool local)
 {
@@ -680,6 +722,11 @@ int rt2x00queue_write_tx_frame(struct data_queue *queue, struct sk_buff *skb,
                goto out;
        }
 
+       /*
+        * Put BlockAckReqs into our check list for driver BA processing.
+        */
+       rt2x00queue_bar_check(entry);
+
        set_bit(ENTRY_DATA_PENDING, &entry->flags);
 
        rt2x00queue_index_inc(entry, Q_INDEX);