mac80211: add throughput based LED blink trigger
iwlwifi and other drivers like to blink their LED
based on throughput. Implement this generically in
mac80211, based on a throughput table the driver
specifies. That way, drivers can set the blink
frequencies depending on their desired behaviour
and max throughput.
All the drivers need to do is provide an LED class
device, best with blink hardware offload.
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 eadaa24..523b90b 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -23,6 +23,7 @@
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/etherdevice.h>
+#include <linux/leds.h>
#include <net/ieee80211_radiotap.h>
#include <net/cfg80211.h>
#include <net/mac80211.h>
@@ -630,6 +631,17 @@
IEEE80211_QUEUE_STOP_REASON_SKB_ADD,
};
+struct tpt_led_trigger {
+ struct led_trigger trig;
+ char name[32];
+ 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;
+};
+
/**
* mac80211 scan flags - currently active scan mode
*
@@ -838,6 +850,7 @@
#ifdef CONFIG_MAC80211_LEDS
int tx_led_counter, rx_led_counter;
struct led_trigger *tx_led, *rx_led, *assoc_led, *radio_led;
+ struct tpt_led_trigger *tpt_led_trigger;
char tx_led_name[32], rx_led_name[32],
assoc_led_name[32], radio_led_name[32];
#endif
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index f0f11bb..989df70 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -220,6 +220,7 @@
/* we're brought up, everything changes */
hw_reconf_flags = ~0;
ieee80211_led_radio(local, true);
+ ieee80211_start_tpt_led_trig(local);
}
/*
diff --git a/net/mac80211/led.c b/net/mac80211/led.c
index 740a1d4..79b1309 100644
--- a/net/mac80211/led.c
+++ b/net/mac80211/led.c
@@ -103,6 +103,13 @@
local->radio_led = NULL;
}
}
+
+ if (local->tpt_led_trigger) {
+ if (led_trigger_register(&local->tpt_led_trigger->trig)) {
+ kfree(local->tpt_led_trigger);
+ local->tpt_led_trigger = NULL;
+ }
+ }
}
void ieee80211_led_exit(struct ieee80211_local *local)
@@ -123,6 +130,11 @@
led_trigger_unregister(local->rx_led);
kfree(local->rx_led);
}
+
+ if (local->tpt_led_trigger) {
+ led_trigger_unregister(&local->tpt_led_trigger->trig);
+ kfree(local->tpt_led_trigger);
+ }
}
char *__ieee80211_get_radio_led_name(struct ieee80211_hw *hw)
@@ -156,3 +168,112 @@
return local->rx_led_name;
}
EXPORT_SYMBOL(__ieee80211_get_rx_led_name);
+
+static unsigned long tpt_trig_traffic(struct ieee80211_local *local,
+ struct tpt_led_trigger *tpt_trig)
+{
+ unsigned long traffic, delta;
+
+ traffic = tpt_trig->tx_bytes + tpt_trig->rx_bytes;
+
+ delta = traffic - tpt_trig->prev_traffic;
+ tpt_trig->prev_traffic = traffic;
+ return DIV_ROUND_UP(delta, 1024 / 8);
+}
+
+static void tpt_trig_timer(unsigned long data)
+{
+ struct ieee80211_local *local = (void *)data;
+ struct tpt_led_trigger *tpt_trig = local->tpt_led_trigger;
+ struct led_classdev *led_cdev;
+ unsigned long on, off, tpt;
+ int i;
+
+ if (!tpt_trig->running)
+ return;
+
+ mod_timer(&tpt_trig->timer, round_jiffies(jiffies + HZ));
+
+ tpt = tpt_trig_traffic(local, tpt_trig);
+
+ /* default to just solid on */
+ on = 1;
+ off = 0;
+
+ for (i = tpt_trig->blink_table_len - 1; i >= 0; i--) {
+ if (tpt_trig->blink_table[i].throughput < 0 ||
+ tpt > tpt_trig->blink_table[i].throughput) {
+ off = tpt_trig->blink_table[i].blink_time / 2;
+ on = tpt_trig->blink_table[i].blink_time - off;
+ break;
+ }
+ }
+
+ read_lock(&tpt_trig->trig.leddev_list_lock);
+ list_for_each_entry(led_cdev, &tpt_trig->trig.led_cdevs, trig_list)
+ led_blink_set(led_cdev, &on, &off);
+ read_unlock(&tpt_trig->trig.leddev_list_lock);
+}
+
+extern char *__ieee80211_create_tpt_led_trigger(
+ struct ieee80211_hw *hw,
+ const struct ieee80211_tpt_blink *blink_table,
+ unsigned int blink_table_len)
+{
+ struct ieee80211_local *local = hw_to_local(hw);
+ struct tpt_led_trigger *tpt_trig;
+
+ if (WARN_ON(local->tpt_led_trigger))
+ return NULL;
+
+ tpt_trig = kzalloc(sizeof(struct tpt_led_trigger), GFP_KERNEL);
+ if (!tpt_trig)
+ return NULL;
+
+ snprintf(tpt_trig->name, sizeof(tpt_trig->name),
+ "%stpt", wiphy_name(local->hw.wiphy));
+
+ tpt_trig->trig.name = tpt_trig->name;
+
+ tpt_trig->blink_table = blink_table;
+ tpt_trig->blink_table_len = blink_table_len;
+
+ setup_timer(&tpt_trig->timer, tpt_trig_timer, (unsigned long)local);
+
+ local->tpt_led_trigger = tpt_trig;
+
+ return tpt_trig->name;
+}
+EXPORT_SYMBOL(__ieee80211_create_tpt_led_trigger);
+
+void ieee80211_start_tpt_led_trig(struct ieee80211_local *local)
+{
+ struct tpt_led_trigger *tpt_trig = local->tpt_led_trigger;
+
+ if (!tpt_trig)
+ return;
+
+ /* reset traffic */
+ tpt_trig_traffic(local, tpt_trig);
+ tpt_trig->running = true;
+
+ tpt_trig_timer((unsigned long)local);
+ mod_timer(&tpt_trig->timer, round_jiffies(jiffies + HZ));
+}
+
+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)
+ return;
+
+ tpt_trig->running = false;
+ del_timer_sync(&tpt_trig->timer);
+
+ read_lock(&tpt_trig->trig.leddev_list_lock);
+ list_for_each_entry(led_cdev, &tpt_trig->trig.led_cdevs, trig_list)
+ led_brightness_set(led_cdev, LED_OFF);
+ read_unlock(&tpt_trig->trig.leddev_list_lock);
+}
diff --git a/net/mac80211/led.h b/net/mac80211/led.h
index 8320cba..6c215dc 100644
--- a/net/mac80211/led.h
+++ b/net/mac80211/led.h
@@ -12,15 +12,17 @@
#include "ieee80211_i.h"
#ifdef CONFIG_MAC80211_LEDS
-extern void ieee80211_led_rx(struct ieee80211_local *local);
-extern void ieee80211_led_tx(struct ieee80211_local *local, int q);
-extern void ieee80211_led_assoc(struct ieee80211_local *local,
- bool associated);
-extern void ieee80211_led_radio(struct ieee80211_local *local,
- bool enabled);
-extern void ieee80211_led_names(struct ieee80211_local *local);
-extern void ieee80211_led_init(struct ieee80211_local *local);
-extern void ieee80211_led_exit(struct ieee80211_local *local);
+void ieee80211_led_rx(struct ieee80211_local *local);
+void ieee80211_led_tx(struct ieee80211_local *local, int q);
+void ieee80211_led_assoc(struct ieee80211_local *local,
+ bool associated);
+void ieee80211_led_radio(struct ieee80211_local *local,
+ bool enabled);
+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);
#else
static inline void ieee80211_led_rx(struct ieee80211_local *local)
{
@@ -45,4 +47,28 @@
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)
+{
+}
#endif
+
+static inline void
+ieee80211_tpt_led_trig_tx(struct ieee80211_local *local, __le16 fc, int bytes)
+{
+#ifdef CONFIG_MAC80211_LEDS
+ if (local->tpt_led_trigger && ieee80211_is_data(fc))
+ local->tpt_led_trigger->tx_bytes += bytes;
+#endif
+}
+
+static inline void
+ieee80211_tpt_led_trig_rx(struct ieee80211_local *local, __le16 fc, int bytes)
+{
+#ifdef CONFIG_MAC80211_LEDS
+ if (local->tpt_led_trigger && ieee80211_is_data(fc))
+ local->tpt_led_trigger->rx_bytes += bytes;
+#endif
+}
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 7c5d1b2..01a3f26 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -2928,6 +2928,9 @@
return;
}
+ ieee80211_tpt_led_trig_rx(local,
+ ((struct ieee80211_hdr *)skb->data)->frame_control,
+ skb->len);
__ieee80211_rx_handle_packet(hw, skb);
rcu_read_unlock();
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index d2b4b67..68c2fbd 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -1297,6 +1297,7 @@
while (skb) {
int q = skb_get_queue_mapping(skb);
+ __le16 fc;
spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
ret = IEEE80211_TX_OK;
@@ -1339,6 +1340,7 @@
else
info->control.sta = NULL;
+ fc = ((struct ieee80211_hdr *)skb->data)->frame_control;
ret = drv_tx(local, skb);
if (WARN_ON(ret != NETDEV_TX_OK && skb->len != len)) {
dev_kfree_skb(skb);
@@ -1349,6 +1351,7 @@
return IEEE80211_TX_AGAIN;
}
+ ieee80211_tpt_led_trig_tx(local, fc, len);
*skbp = skb = next;
ieee80211_led_tx(local, 1);
fragm = true;
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index e497476..4830641 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1116,6 +1116,7 @@
void ieee80211_stop_device(struct ieee80211_local *local)
{
ieee80211_led_radio(local, false);
+ ieee80211_stop_tpt_led_trig(local);
cancel_work_sync(&local->reconfig_filter);
@@ -1150,6 +1151,7 @@
}
ieee80211_led_radio(local, true);
+ ieee80211_start_tpt_led_trig(local);
}
/* add interfaces */