ath6kl: Fix race in aggregation reorder logic
authorVasanthakumar Thiagarajan <vthiagar@qca.qualcomm.com>
Wed, 30 May 2012 06:57:11 +0000 (12:27 +0530)
committerKalle Valo <kvalo@qca.qualcomm.com>
Mon, 11 Jun 2012 13:13:41 +0000 (16:13 +0300)
There are many places where tid data are accessed without
the lock (rxtid->lock), this can lead to a race condition
when the timeout handler for aggregatin reorder and the
receive function are getting executed at the same time.
Fix this race, but still there are races which can not
be fixed without rewriting the whole aggregation reorder
logic, for now fix the obvious ones.

Signed-off-by: Vasanthakumar Thiagarajan <vthiagar@qca.qualcomm.com>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
drivers/net/wireless/ath/ath6kl/core.h
drivers/net/wireless/ath/ath6kl/txrx.c

index 17a44fad859bee78b6e92d369224cc1e2b7be4e9..12441f7d90365a93ce5d662cd61edf9f8398c416 100644 (file)
@@ -273,9 +273,15 @@ struct rxtid {
        struct sk_buff_head q;
 
        /*
-        * FIXME: No clue what this should protect. Apparently it should
-        * protect some of the fields above but they are also accessed
-        * without taking the lock.
+        * lock mainly protects seq_next and hold_q. Movement of seq_next
+        * needs to be protected between aggr_timeout() and
+        * aggr_process_recv_frm(). hold_q will be holding the pending
+        * reorder frames and it's access should also be protected.
+        * Some of the other fields like hold_q_sz, win_sz and aggr are
+        * initialized/reset when receiving addba/delba req, also while
+        * deleting aggr state all the pending buffers are flushed before
+        * resetting these fields, so there should not be any race in accessing
+        * these fields.
         */
        spinlock_t lock;
 };
index 67206aedea6cabada2a933a870e81065181c749e..974c51053a71456ff5e37c01daa7496354eb2cdc 100644 (file)
@@ -1036,6 +1036,7 @@ static void aggr_deque_frms(struct aggr_info_conn *agg_conn, u8 tid,
        rxtid = &agg_conn->rx_tid[tid];
        stats = &agg_conn->stat[tid];
 
+       spin_lock_bh(&rxtid->lock);
        idx = AGGR_WIN_IDX(rxtid->seq_next, rxtid->hold_q_sz);
 
        /*
@@ -1054,8 +1055,6 @@ static void aggr_deque_frms(struct aggr_info_conn *agg_conn, u8 tid,
        seq_end = seq_no ? seq_no : rxtid->seq_next;
        idx_end = AGGR_WIN_IDX(seq_end, rxtid->hold_q_sz);
 
-       spin_lock_bh(&rxtid->lock);
-
        do {
                node = &rxtid->hold_q[idx];
                if ((order == 1) && (!node->skb))
@@ -1127,11 +1126,13 @@ static bool aggr_process_recv_frm(struct aggr_info_conn *agg_conn, u8 tid,
                    ((end > extended_end) && (cur > extended_end) &&
                     (cur < end))) {
                        aggr_deque_frms(agg_conn, tid, 0, 0);
+                       spin_lock_bh(&rxtid->lock);
                        if (cur >= rxtid->hold_q_sz - 1)
                                rxtid->seq_next = cur - (rxtid->hold_q_sz - 1);
                        else
                                rxtid->seq_next = ATH6KL_MAX_SEQ_NO -
                                                  (rxtid->hold_q_sz - 2 - cur);
+                       spin_unlock_bh(&rxtid->lock);
                } else {
                        /*
                         * Dequeue only those frames that are outside the
@@ -1186,7 +1187,8 @@ static bool aggr_process_recv_frm(struct aggr_info_conn *agg_conn, u8 tid,
 
        if (agg_conn->timer_scheduled)
                rxtid->progress = true;
-       else
+       else {
+               spin_lock_bh(&rxtid->lock);
                for (idx = 0 ; idx < rxtid->hold_q_sz; idx++) {
                        if (rxtid->hold_q[idx].skb) {
                                /*
@@ -1204,6 +1206,8 @@ static bool aggr_process_recv_frm(struct aggr_info_conn *agg_conn, u8 tid,
                                break;
                        }
                }
+               spin_unlock_bh(&rxtid->lock);
+       }
 
        return is_queued;
 }
@@ -1626,6 +1630,7 @@ static void aggr_timeout(unsigned long arg)
                rxtid = &aggr_conn->rx_tid[i];
 
                if (rxtid->aggr && rxtid->hold_q) {
+                       spin_lock_bh(&rxtid->lock);
                        for (j = 0; j < rxtid->hold_q_sz; j++) {
                                if (rxtid->hold_q[j].skb) {
                                        aggr_conn->timer_scheduled = true;
@@ -1634,6 +1639,7 @@ static void aggr_timeout(unsigned long arg)
                                        break;
                                }
                        }
+                       spin_unlock_bh(&rxtid->lock);
 
                        if (j >= rxtid->hold_q_sz)
                                rxtid->timer_mon = false;