mac80211: selective throughput LED trigger active

The throughput LED trigger was always active when
the radio was enabled. In most cases that's likely
the desired behaviour, but iwlwifi requires it to
be only active when one of the virtual interfaces
is actually "connected" in some way.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 523b90b..3810c72 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -637,9 +637,10 @@
 	const struct ieee80211_tpt_blink *blink_table;
 	unsigned int blink_table_len;
 	struct timer_list timer;
-	bool running;
 	unsigned long prev_traffic;
 	unsigned long tx_bytes, rx_bytes;
+	unsigned int active, want;
+	bool running;
 };
 
 /**
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 989df70..b6db237 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -220,7 +220,8 @@
 		/* we're brought up, everything changes */
 		hw_reconf_flags = ~0;
 		ieee80211_led_radio(local, true);
-		ieee80211_start_tpt_led_trig(local);
+		ieee80211_mod_tpt_led_trig(local,
+					   IEEE80211_TPT_LEDTRIG_FL_RADIO, 0);
 	}
 
 	/*
@@ -1265,6 +1266,7 @@
 	int count = 0;
 	bool working = false, scanning = false;
 	struct ieee80211_work *wk;
+	unsigned int led_trig_start = 0, led_trig_stop = 0;
 
 #ifdef CONFIG_PROVE_LOCKING
 	WARN_ON(debug_locks && !lockdep_rtnl_is_held() &&
@@ -1314,6 +1316,18 @@
 		ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_IDLE);
 	}
 
+	if (working || scanning)
+		led_trig_start |= IEEE80211_TPT_LEDTRIG_FL_WORK;
+	else
+		led_trig_stop |= IEEE80211_TPT_LEDTRIG_FL_WORK;
+
+	if (count)
+		led_trig_start |= IEEE80211_TPT_LEDTRIG_FL_CONNECTED;
+	else
+		led_trig_stop |= IEEE80211_TPT_LEDTRIG_FL_CONNECTED;
+
+	ieee80211_mod_tpt_led_trig(local, led_trig_start, led_trig_stop);
+
 	if (working)
 		return ieee80211_idle_off(local, "working");
 	if (scanning)
diff --git a/net/mac80211/led.c b/net/mac80211/led.c
index 79b1309..4905eb8 100644
--- a/net/mac80211/led.c
+++ b/net/mac80211/led.c
@@ -216,7 +216,7 @@
 }
 
 extern char *__ieee80211_create_tpt_led_trigger(
-				struct ieee80211_hw *hw,
+				struct ieee80211_hw *hw, unsigned int flags,
 				const struct ieee80211_tpt_blink *blink_table,
 				unsigned int blink_table_len)
 {
@@ -237,6 +237,7 @@
 
 	tpt_trig->blink_table = blink_table;
 	tpt_trig->blink_table_len = blink_table_len;
+	tpt_trig->want = flags;
 
 	setup_timer(&tpt_trig->timer, tpt_trig_timer, (unsigned long)local);
 
@@ -246,11 +247,11 @@
 }
 EXPORT_SYMBOL(__ieee80211_create_tpt_led_trigger);
 
-void ieee80211_start_tpt_led_trig(struct ieee80211_local *local)
+static void ieee80211_start_tpt_led_trig(struct ieee80211_local *local)
 {
 	struct tpt_led_trigger *tpt_trig = local->tpt_led_trigger;
 
-	if (!tpt_trig)
+	if (tpt_trig->running)
 		return;
 
 	/* reset traffic */
@@ -261,12 +262,12 @@
 	mod_timer(&tpt_trig->timer, round_jiffies(jiffies + HZ));
 }
 
-void ieee80211_stop_tpt_led_trig(struct ieee80211_local *local)
+static void ieee80211_stop_tpt_led_trig(struct ieee80211_local *local)
 {
 	struct tpt_led_trigger *tpt_trig = local->tpt_led_trigger;
 	struct led_classdev *led_cdev;
 
-	if (!tpt_trig)
+	if (!tpt_trig->running)
 		return;
 
 	tpt_trig->running = false;
@@ -277,3 +278,31 @@
 		led_brightness_set(led_cdev, LED_OFF);
 	read_unlock(&tpt_trig->trig.leddev_list_lock);
 }
+
+void ieee80211_mod_tpt_led_trig(struct ieee80211_local *local,
+				unsigned int types_on, unsigned int types_off)
+{
+	struct tpt_led_trigger *tpt_trig = local->tpt_led_trigger;
+	bool allowed;
+
+	WARN_ON(types_on & types_off);
+
+	if (!tpt_trig)
+		return;
+
+	tpt_trig->active &= ~types_off;
+	tpt_trig->active |= types_on;
+
+	/*
+	 * Regardless of wanted state, we shouldn't blink when
+	 * the radio is disabled -- this can happen due to some
+	 * code ordering issues with __ieee80211_recalc_idle()
+	 * being called before the radio is started.
+	 */
+	allowed = tpt_trig->active & IEEE80211_TPT_LEDTRIG_FL_RADIO;
+
+	if (!allowed || !(tpt_trig->active & tpt_trig->want))
+		ieee80211_stop_tpt_led_trig(local);
+	else
+		ieee80211_start_tpt_led_trig(local);
+}
diff --git a/net/mac80211/led.h b/net/mac80211/led.h
index 6c215dc..e0275d9 100644
--- a/net/mac80211/led.h
+++ b/net/mac80211/led.h
@@ -21,8 +21,8 @@
 void ieee80211_led_names(struct ieee80211_local *local);
 void ieee80211_led_init(struct ieee80211_local *local);
 void ieee80211_led_exit(struct ieee80211_local *local);
-void ieee80211_start_tpt_led_trig(struct ieee80211_local *local);
-void ieee80211_stop_tpt_led_trig(struct ieee80211_local *local);
+void ieee80211_mod_tpt_led_trig(struct ieee80211_local *local,
+				unsigned int types_on, unsigned int types_off);
 #else
 static inline void ieee80211_led_rx(struct ieee80211_local *local)
 {
@@ -47,10 +47,9 @@
 static inline void ieee80211_led_exit(struct ieee80211_local *local)
 {
 }
-static inline void ieee80211_start_tpt_led_trig(struct ieee80211_local *local)
-{
-}
-static inline void ieee80211_stop_tpt_led_trig(struct ieee80211_local *local)
+static inline void ieee80211_mod_tpt_led_trig(struct ieee80211_local *local,
+					      unsigned int types_on,
+					      unsigned int types_off)
 {
 }
 #endif
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 4830641..cf68700 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1116,7 +1116,7 @@
 void ieee80211_stop_device(struct ieee80211_local *local)
 {
 	ieee80211_led_radio(local, false);
-	ieee80211_stop_tpt_led_trig(local);
+	ieee80211_mod_tpt_led_trig(local, 0, IEEE80211_TPT_LEDTRIG_FL_RADIO);
 
 	cancel_work_sync(&local->reconfig_filter);
 
@@ -1151,7 +1151,8 @@
 		}
 
 		ieee80211_led_radio(local, true);
-		ieee80211_start_tpt_led_trig(local);
+		ieee80211_mod_tpt_led_trig(local,
+					   IEEE80211_TPT_LEDTRIG_FL_RADIO, 0);
 	}
 
 	/* add interfaces */