mac80211: clean up remain-on-channel on interface stop
authorJohannes Berg <johannes.berg@intel.com>
Thu, 31 May 2012 13:09:27 +0000 (15:09 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Mon, 4 Jun 2012 19:25:26 +0000 (15:25 -0400)
When any interface goes down, it could be the one that we
were doing a remain-on-channel with. We therefore need to
cancel the remain-on-channel and flush the related work
structs so they don't run after the interface has been
removed or even destroyed.

It's also possible in this case that an off-channel SKB
was never transmitted, so free it if this is the case.
Note that this can also happen if the driver finishes
the off-channel period without ever starting it.

Cc: stable@kernel.org
Reported-by: Nirav Shah <nirav.j2.shah@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
net/mac80211/iface.c
net/mac80211/offchannel.c

index d4c19a7773db24b12bacf0407330ebccd75ba772..8664111d05663d47678f2088f4169a6ab80fdb96 100644 (file)
@@ -637,6 +637,18 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
                ieee80211_configure_filter(local);
                break;
        default:
+               mutex_lock(&local->mtx);
+               if (local->hw_roc_dev == sdata->dev &&
+                   local->hw_roc_channel) {
+                       /* ignore return value since this is racy */
+                       drv_cancel_remain_on_channel(local);
+                       ieee80211_queue_work(&local->hw, &local->hw_roc_done);
+               }
+               mutex_unlock(&local->mtx);
+
+               flush_work(&local->hw_roc_start);
+               flush_work(&local->hw_roc_done);
+
                flush_work(&sdata->work);
                /*
                 * When we get here, the interface is marked down.
index f054e94901a295443abcae7c3cfddda963f8d7c4..935aa4b6deee0220737ee4c1cebad69472dea343 100644 (file)
@@ -234,6 +234,22 @@ static void ieee80211_hw_roc_done(struct work_struct *work)
                return;
        }
 
+       /* was never transmitted */
+       if (local->hw_roc_skb) {
+               u64 cookie;
+
+               cookie = local->hw_roc_cookie ^ 2;
+
+               cfg80211_mgmt_tx_status(local->hw_roc_dev, cookie,
+                                       local->hw_roc_skb->data,
+                                       local->hw_roc_skb->len, false,
+                                       GFP_KERNEL);
+
+               kfree_skb(local->hw_roc_skb);
+               local->hw_roc_skb = NULL;
+               local->hw_roc_skb_for_status = NULL;
+       }
+
        if (!local->hw_roc_for_tx)
                cfg80211_remain_on_channel_expired(local->hw_roc_dev,
                                                   local->hw_roc_cookie,