mac80211: remove tasklet enable/disable

Due to the way the tasklets work in mac80211 there's
no need to ever disable them.

However, we need to clear the pending packets when
taking down the last interface because otherwise
the tx_pending_tasklet might be queued if the
driver mucks with the queues (which it shouldn't).

I've had a situation occasionally with ar9170 in
which ksoftirq was using 100% CPU time because
a disabled tasklet was scheduled, and I think that
was due to ar9170 receiving a packet while the
tasklet was disabled. That's strange and it really
should not do that for other reasons, but there's
no need to waste that much CPU time over it, it
should just warn instead.

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index d231c93..020a94a 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -12,7 +12,11 @@
 
 static inline int drv_start(struct ieee80211_local *local)
 {
-	int ret = local->ops->start(&local->hw);
+	int ret;
+
+	local->started = true;
+	smp_mb();
+	ret = local->ops->start(&local->hw);
 	trace_drv_start(local, ret);
 	return ret;
 }
@@ -21,6 +25,14 @@
 {
 	local->ops->stop(&local->hw);
 	trace_drv_stop(local);
+
+	/* sync away all work on the tasklet before clearing started */
+	tasklet_disable(&local->tasklet);
+	tasklet_enable(&local->tasklet);
+
+	barrier();
+
+	local->started = false;
 }
 
 static inline int drv_add_interface(struct ieee80211_local *local,
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index fa930e0..dbd8411 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -667,6 +667,9 @@
 	 */
 	bool quiescing;
 
+	/* device is started */
+	bool started;
+
 	int tx_headroom; /* required headroom for hardware/radiotap */
 
 	/* Tasklet and skb queue to process calls from IRQ mode. All frames
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 5940e69..d134bd7 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -277,11 +277,6 @@
 		}
 	}
 
-	if (local->open_count == 0) {
-		tasklet_enable(&local->tx_pending_tasklet);
-		tasklet_enable(&local->tasklet);
-	}
-
 	/*
 	 * set_multicast_list will be invoked by the networking core
 	 * which will check whether any increments here were done in
@@ -552,11 +547,9 @@
 	ieee80211_recalc_ps(local, -1);
 
 	if (local->open_count == 0) {
+		ieee80211_clear_tx_pending(local);
 		ieee80211_stop_device(local);
 
-		tasklet_disable(&local->tx_pending_tasklet);
-		tasklet_disable(&local->tasklet);
-
 		/* no reconfiguring after stop! */
 		hw_reconf_flags = 0;
 	}
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index dd3b081..797f539 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -715,12 +715,10 @@
 		skb_queue_head_init(&local->pending[i]);
 	tasklet_init(&local->tx_pending_tasklet, ieee80211_tx_pending,
 		     (unsigned long)local);
-	tasklet_disable(&local->tx_pending_tasklet);
 
 	tasklet_init(&local->tasklet,
 		     ieee80211_tasklet_handler,
 		     (unsigned long) local);
-	tasklet_disable(&local->tasklet);
 
 	skb_queue_head_init(&local->skb_queue);
 	skb_queue_head_init(&local->skb_queue_unreliable);
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index dff2239..b98f1af 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -2471,6 +2471,15 @@
 		return;
 	}
 
+	/*
+	 * The same happens when we're not even started,
+	 * but that's worth a warning.
+	 */
+	if (WARN_ON(!local->started)) {
+		kfree_skb(skb);
+		return;
+	}
+
 	if (status->flag & RX_FLAG_HT) {
 		/* rate_idx is MCS index */
 		if (WARN_ON(status->rate_idx < 0 ||