mac80211: fix offchannel queue stop
authorJohannes Berg <johannes.berg@intel.com>
Fri, 27 Aug 2010 11:45:28 +0000 (13:45 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Fri, 27 Aug 2010 17:53:31 +0000 (13:53 -0400)
Somebody noticed this problem, and I outlined
to them how to fix it, but haven't heard back
from them. So while I was adding the state
field I figured I could use it to fix it.

The problem, as I understand it, is that when
we go offchannel while the driver has a queue
stopped, the driver will likely start draining
the queue and then enable it while offchannel.
This in turn will enable the interface queue,
and that leads to transmitting data frames on
the wrong channel.

Fix this by keeping track of offchannel status
per interface, and not enabling the interface
queues on interfaces that are offchannel when
the driver enables a queue.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
net/mac80211/ieee80211_i.h
net/mac80211/offchannel.c
net/mac80211/util.c

index d529bd5eab47911fbe506cef6800191dcad42cc8..9af50fbcd48b1f9bdf9276585adf9cbb43198ab1 100644 (file)
@@ -477,9 +477,12 @@ enum ieee80211_sub_if_data_flags {
  * @SDATA_STATE_RUNNING: virtual interface is up & running; this
  *     mirrors netif_running() but is separate for interface type
  *     change handling while the interface is up
+ * @SDATA_STATE_OFFCHANNEL: This interface is currently in offchannel
+ *     mode, so queues are stopped
  */
 enum ieee80211_sdata_state_bits {
        SDATA_STATE_RUNNING,
+       SDATA_STATE_OFFCHANNEL,
 };
 
 struct ieee80211_sub_if_data {
index c36b1911987afec08262139ccaaf5e7887a17735..eeacaa59380a4c4cc91fe7f48d8ca622a13396b0 100644 (file)
@@ -112,8 +112,10 @@ void ieee80211_offchannel_stop_beaconing(struct ieee80211_local *local)
                 * used from user space controlled off-channel operations.
                 */
                if (sdata->vif.type != NL80211_IFTYPE_STATION &&
-                   sdata->vif.type != NL80211_IFTYPE_MONITOR)
+                   sdata->vif.type != NL80211_IFTYPE_MONITOR) {
+                       set_bit(SDATA_STATE_OFFCHANNEL, &sdata->state);
                        netif_tx_stop_all_queues(sdata->dev);
+               }
        }
        mutex_unlock(&local->iflist_mtx);
 }
@@ -131,6 +133,7 @@ void ieee80211_offchannel_stop_station(struct ieee80211_local *local)
                        continue;
 
                if (sdata->vif.type == NL80211_IFTYPE_STATION) {
+                       set_bit(SDATA_STATE_OFFCHANNEL, &sdata->state);
                        netif_tx_stop_all_queues(sdata->dev);
                        if (sdata->u.mgd.associated)
                                ieee80211_offchannel_ps_enable(sdata);
@@ -155,8 +158,20 @@ void ieee80211_offchannel_return(struct ieee80211_local *local,
                                ieee80211_offchannel_ps_disable(sdata);
                }
 
-               if (sdata->vif.type != NL80211_IFTYPE_MONITOR)
+               if (sdata->vif.type != NL80211_IFTYPE_MONITOR) {
+                       clear_bit(SDATA_STATE_OFFCHANNEL, &sdata->state);
+                       /*
+                        * This may wake up queues even though the driver
+                        * currently has them stopped. This is not very
+                        * likely, since the driver won't have gotten any
+                        * (or hardly any) new packets while we weren't
+                        * on the right channel, and even if it happens
+                        * it will at most lead to queueing up one more
+                        * packet per queue in mac80211 rather than on
+                        * the interface qdisc.
+                        */
                        netif_tx_wake_all_queues(sdata->dev);
+               }
 
                /* re-enable beaconing */
                if (enable_beaconing &&
index d38b3767e8cc35caf8e4bfee9d0cb5ee7472eb6f..bd40b11d5ab9e514d7e75f887baebf65352e0370 100644 (file)
@@ -283,8 +283,11 @@ static void __ieee80211_wake_queue(struct ieee80211_hw *hw, int queue,
 
        if (skb_queue_empty(&local->pending[queue])) {
                rcu_read_lock();
-               list_for_each_entry_rcu(sdata, &local->interfaces, list)
+               list_for_each_entry_rcu(sdata, &local->interfaces, list) {
+                       if (test_bit(SDATA_STATE_OFFCHANNEL, &sdata->state))
+                               continue;
                        netif_wake_subqueue(sdata->dev, queue);
+               }
                rcu_read_unlock();
        } else
                tasklet_schedule(&local->tx_pending_tasklet);