lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Message-ID: <2110132361.480390.1763933383779@mail.yahoo.com>
Date: Sun, 23 Nov 2025 21:29:43 +0000 (UTC)
From: Mieczyslaw Nalewaj <namiltd@...oo.com>
To: "linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>
Cc: "linux-leds@...r.kernel.org" <linux-leds@...r.kernel.org>, 
	"pavel@....cz" <pavel@....cz>, 
	"jacek.anaszewski@...il.com" <jacek.anaszewski@...il.com>
Subject: [PATCH 2/2] leds: add "network" LED trigger (lan/wan/wlan)

LED trigger for network interfaces.

- Aggregated per-family (lan/wan/wlan).
- Family and flags (link/tx/rx) are taken from led_cdev->params if present
   (format "network:<family>[ link][ tx][ rx]" or "<family>-online").
   If params is NULL/empty they fall back to the LED device name:
     <colour>:<family>[-online]   e.g.  "green:wlan", "red:wan-online"
   or, if no colon exists, to the whole name.

Behaviour:
- wlan (normal): blink/solid driven by throughput table
- lan/wan (normal): one-shot blink on TX/RX packet change
- *-online variants: steady ON while any interface of the family has carrier

Interfaces are auto-tracked by name match (lan0, wan1, wlan2, phy0, wl1, ath0, ra0…).
Up to MAX_IFACES (16) interfaces per family.

This trigger is intended for board/device authors and drivers to provide simple
network-activity LED behaviour without per-interface wiring in userspace.

Signed-off-by: Mieczyslaw Nalewaj <namiltd@...oo.com>

--- a/drivers/leds/trigger/Kconfig
+++ b/drivers/leds/trigger/Kconfig
@@ -129,6 +129,15 @@ config LEDS_TRIGGER_NETDEV
       This allows LEDs to be controlled by network device activity.
       If unsure, say Y.
 
+config LEDS_TRIGGER_NETWORK
+    tristate "LED trigger for network interfaces"
+    depends on NET
+    help
+      Family (lan/wan/wlan) and flags are read from led_cdev->params
+      ("network:wlan tx rx" or "network:lan-online") and, if missing,
+      parsed from the LED name itself.
+      If unsure, say Y.
+
 config LEDS_TRIGGER_PATTERN
     tristate "LED Pattern Trigger"
     help
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-network.c
@@ -0,0 +1,718 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * LED trigger for network interfaces.
+ *
+ * - Aggregated per-family (lan/wan/wlan).
+ * - Family and flags (link/tx/rx) are taken from led_cdev->params if present
+ *   (format "network:<family>[ link][ tx][ rx]" or "<family>-online").
+ *   If params is NULL/empty they fall back to the LED device name:
+ *     <colour>:<family>[-online]   e.g.  "green:wlan", "red:wan-online"
+ *   or, if no colon exists, to the whole name.
+ *
+ * Behaviour:
+ * - wlan (normal): blink/solid driven by throughput table
+ * - lan/wan (normal): one-shot blink on TX/RX packet change
+ * - *-online variants: steady ON while any interface of the family has carrier
+ *
+ * Interfaces are auto-tracked by name match (lan0, wan1, wlan2, phy0, wl1, ath0, ra0…).
+ * Up to MAX_IFACES (16) interfaces per family.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/leds.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/workqueue.h>
+#include <linux/of.h>
+#include <linux/list.h>
+#include <linux/atomic.h>
+#include <linux/compiler.h>
+#include <linux/string.h>
+#include "../leds.h"
+
+#define MAX_IFACES 16
+#define DEFAULT_INTERVAL_MS 50
+
+enum net_trig_type {
+    NET_TRIG_LAN = 0,
+    NET_TRIG_WAN,
+    NET_TRIG_WLAN,
+    NET_TRIG_TYPE_MAX,
+};
+
+static const char * const type_names[] = {
+    [NET_TRIG_LAN] = "lan",
+    [NET_TRIG_WAN] = "wan",
+    [NET_TRIG_WLAN] = "wlan",
+};
+
+/* labels indexed by bitmask: (link<<2)|(tx<<1)|(rx<<0) */
+static const char *const labels[] = {
+    "",              /* 0 */
+    "(tx)",         /* 1 */
+    "(rx)",         /* 2 */
+    "(tx rx)",      /* 3 */
+    "(link)",       /* 4 */
+    "(link rx)",    /* 5 */
+    "(link tx)",    /* 6 */
+    "(link tx rx)", /* 7 */
+};
+
+/* wlan throughput table */
+static const struct {
+    u32 throughput;
+    unsigned long on_ms;
+    unsigned long off_ms;
+} wlan_tpt_table[] = {
+    {   64, 200, 800 },
+    {  512, 200, 300 },
+    { 2048, 200, 150 },
+    {10000, 200,  50 },
+    {54000, 100,  50 },
+};
+
+struct net_mgr {
+    enum net_trig_type type; /* family: lan/wan/wlan */
+
+    struct mutex lock;
+    struct notifier_block notifier;
+    struct delayed_work work;
+
+    struct net_device *devs[MAX_IFACES];
+    int dev_count;
+
+    u64 agg_rx_packets;
+    u64 agg_tx_packets;
+    u64 agg_rx_bytes;
+    u64 agg_tx_bytes;
+
+    struct list_head leds;
+    atomic_t refcnt;
+};
+
+struct net_led {
+    struct list_head node;
+    struct led_classdev *led_cdev;
+    struct net_mgr *mgr;
+
+    u64 last_rx_packets;
+    u64 last_tx_packets;
+    u64 last_rx_bytes;
+    u64 last_tx_bytes;
+
+    bool link;
+    bool rx;
+    bool tx;
+
+};
+
+static DEFINE_MUTEX(managers_lock);
+static struct net_mgr *managers[NET_TRIG_TYPE_MAX];
+
+/* helpers */
+static inline void led_set_off_full(struct led_classdev *led, bool on)
+{
+    led_stop_software_blink(led);
+    led_set_brightness(led, on ? LED_FULL : LED_OFF);
+}
+
+static inline void led_set_oneshot_ms(struct led_classdev *led, unsigned long ms, bool invert)
+{
+    led_stop_software_blink(led);
+    led_blink_set_oneshot(led, &ms, &ms, invert);
+}
+
+/* helper: check that next char after prefix is digit, '-' or NUL */
+static inline bool next_char_ok(const char *s, size_t pos)
+{
+    char c = s[pos];
+    return c == '\0' || c == '-' || (c >= '0' && c <= '9');
+}
+
+/* name matching: lan/wan; wlan matched by various wifi prefixes with restriction
+ * additionally accept ath (Atheros) and ra/rai (Ralink/MediaTek) prefixes.
+ * For lan and wan require next char to be digit/'-' or end to avoid accidental matches.
+ */
+static bool name_matches_type(const char *name, enum net_trig_type type)
+{
+    if (!name)
+        return false;
+    switch (type) {
+
+    case NET_TRIG_LAN:
+        /* accept "lan", "lanX", "lan-X" */
+        if (!strncmp(name, "lan", 3))
+            return next_char_ok(name, 3);
+        return false;
+    case NET_TRIG_WAN:
+        /* accept "wan", "wanX", "wan-X" */
+        if (!strncmp(name, "wan", 3))
+            return next_char_ok(name, 3);
+        return false;
+    case NET_TRIG_WLAN:
+        /* accept "phyX", "wlX"/"wl-..." or "wlan" and common driver prefixes */
+        if (!strncmp(name, "phy", 3))
+            return next_char_ok(name, 3);
+        if (!strncmp(name, "wlan", 4))
+            return next_char_ok(name, 4);
+        if (!strncmp(name, "wl", 2))
+            return next_char_ok(name, 2);
+        /* Atheros (athX) */
+        if (!strncmp(name, "ath", 3))
+            return next_char_ok(name, 3);
+        /* Ralink/MediaTek: check "rai" (e.g. rai0) first, then "ra" (ra0) */
+        if (!strncmp(name, "rai", 3))
+            return next_char_ok(name, 3);
+        if (!strncmp(name, "ra", 2))
+            return next_char_ok(name, 2);
+        return false;
+    default:
+        return false;
+    }
+}
+
+/* Parse comma-separated tokens (exact match, no trimming).
+ * Recognized tokens:
+ *   - "lan"/"wan"/"wlan" => return NET_TRIG_*
+ *   - "link", "tx", "rx"  => set boolean flags via pointers
+ * Special-case: "lan-online", "wan-online", "wlan-online" => return NET_TRIG_* and set link.
+ * If none of link/tx/rx found, set all three to true.
+ */
+static int parse_function_string(const char *fn, bool *link, bool *tx, bool *rx)
+{
+    size_t len;
+    const char *buf = NULL; /* pointer into fn (use const) */
+    size_t buflen = 0;
+    size_t i;
+    int ret = -1;
+
+    if (!fn || !link || !tx || !rx)
+        return -1;
+
+    *link = false;
+    *tx = false;
+    *rx = false;
+
+    /* whole-string "-online" special case */
+    len = strlen(fn);
+    if (len > 7 && !strcmp(fn + len - 7, "-online")) {
+        *link = true;
+        if (len == 7 + 3 && !strncmp(fn, "lan", 3))
+            return NET_TRIG_LAN;
+        if (len == 7 + 3 && !strncmp(fn, "wan", 3))
+            return NET_TRIG_WAN;
+        if (len == 7 + 4 && !strncmp(fn, "wlan", 4))
+            return NET_TRIG_WLAN;
+
+        /* Suffix "-online" present but base is unrecognized -> failure */
+        return -1;
+    }
+
+    /* parse fields by pointing into fn and comparing via memcmp (no copying) */
+    for (i = 0; i < len; i++) {
+        if ((fn[i] == ' ') || (fn[i] == '\t')) {
+            buf = NULL;
+            buflen = 0;
+            continue;
+        }
+
+        if (!buf) {
+            buf = fn + i;
+            buflen = 0;
+        }
+        buflen++;
+
+        /* token end: next is end or comma (short-circuit prevents OOB) */
+        if ((i + 1 == len) || (fn[i + 1] == ' ') || (fn[i + 1] == '\t')) {
+            /* Note: if multiple family tokens are present, the last matching family wins. */
+            if (buflen == 3 && !memcmp(buf, "lan", 3))
+                ret = NET_TRIG_LAN;
+            else if (buflen == 3 && !memcmp(buf, "wan", 3))
+                ret = NET_TRIG_WAN;
+            else if (buflen == 4 && !memcmp(buf, "wlan", 4))
+                ret = NET_TRIG_WLAN;
+            else if (buflen == 4 && !memcmp(buf, "link", 4))
+                *link = true;
+            else if (buflen == 2 && !memcmp(buf, "tx", 2))
+                *tx = true;
+            else if (buflen == 2 && !memcmp(buf, "rx", 2))
+                *rx = true;
+
+            buf = NULL;
+            buflen = 0;
+        }
+    }
+
+    /* default: if no flags found, set all true */
+    if (!*link && !*tx && !*rx) {
+        *link = true;
+        *tx = true;
+        *rx = true;
+    }
+
+    return ret;
+}
+
+/* safe stats read wrapper */
+static void get_dev_stats_safe(struct net_device *dev, struct rtnl_link_stats64 *st)
+{
+    memset(st, 0, sizeof(*st));
+    dev_get_stats(dev, st);
+}
+
+
+/* Update single LED according to manager aggregates and per-LED flags.
+ * any_online indicates whether any tracked interface currently has carrier.
+ * If LED is online-only (link && !tx && !rx) it is driven directly by any_online.
+ */
+static void update_led(struct net_led *e, struct net_mgr *m, bool any_online)
+{
+    long unsigned int on_ms, off_ms;
+    struct led_classdev *led = e->led_cdev;
+
+    /* defensive: avoid deref if led unexpectedly NULL */
+    if (!led)
+        return;
+
+    /* If LED requested online-only, reflect any_online */
+    if (e->link && !e->tx && !e->rx) {
+        led_set_off_full(led, any_online);
+        return;
+    }
+
+    /* For non-online-only LEDs: if no tracked interface has carrier,
+     * keep the LED off and reset history baseline to avoid spurious deltas
+     * when carrier later returns.
+     */
+    if (!any_online) {
+        led_set_off_full(led, false);
+
+        e->last_tx_packets = m->agg_tx_packets;
+        e->last_rx_packets = m->agg_rx_packets;
+        e->last_tx_bytes   = m->agg_tx_bytes;
+        e->last_rx_bytes   = m->agg_rx_bytes;
+
+        return;
+    }
+
+    /* non-online-only behaviour depends on family */
+    if (m->type == NET_TRIG_WLAN) {
+        /* throughput-driven */
+        u64 bytes_delta = 0;
+        const u64 mul = 8ULL * 1000ULL;
+        u64 bits_per_sec = 0;
+
+        if (e->tx)
+            bytes_delta += m->agg_tx_bytes - e->last_tx_bytes;
+        if (e->rx)
+            bytes_delta += m->agg_rx_bytes - e->last_rx_bytes;
+        
+        /* Avoid overflow when multiplying bytes_delta; cap to max u64. */
+        if (bytes_delta) {
+            if (bytes_delta > ((u64)-1) / mul)
+                bits_per_sec = (u64)-1;
+            else
+                bits_per_sec = div64_u64(bytes_delta * mul, 2 * DEFAULT_INTERVAL_MS);
+        }
+        u64 kbps = div64_u64(bits_per_sec, 1000ULL);
+
+        if (kbps == 0)
+            led_set_off_full(led, e->link);
+        else {
+            int idx = 0, t;
+            for (t = 0; t < ARRAY_SIZE(wlan_tpt_table); t++) {
+                if (kbps >= wlan_tpt_table[t].throughput)
+                    idx = t;
+                else
+                    break;
+            }
+
+            on_ms = wlan_tpt_table[idx].on_ms;
+            off_ms = wlan_tpt_table[idx].off_ms;
+            if (e->link)
+                led_blink_set(led, &on_ms, &off_ms);
+            else
+                led_blink_set(led, &off_ms, &on_ms);
+        }
+
+        e->last_tx_bytes = m->agg_tx_bytes;
+        e->last_rx_bytes = m->agg_rx_bytes;
+    } else {
+        /* LAN/WAN: oneshot on packet-count change */
+        u64 tx_sum = m->agg_tx_packets;
+        u64 rx_sum = m->agg_rx_packets;
+
+        if ((e->tx && tx_sum != e->last_tx_packets) || (e->rx && rx_sum != e->last_rx_packets)) {
+            unsigned long ms = DEFAULT_INTERVAL_MS;
+            led_set_oneshot_ms(led, ms, e->link);
+        } else {
+            led_set_off_full(led, e->link);
+        }
+        e->last_tx_packets = tx_sum;
+        e->last_rx_packets = rx_sum;
+    }
+}
+
+/* core work: aggregate + update LEDs */
+static void net_mgr_work(struct work_struct *work)
+{
+    struct net_mgr *m = container_of(work, struct net_mgr, work.work);
+    bool any_online = false;
+    int i;
+
+    mutex_lock(&m->lock);
+
+    if (m->dev_count == 0) {
+        struct net_led *e;
+        m->agg_tx_packets = m->agg_rx_packets = 0;
+        m->agg_tx_bytes = m->agg_rx_bytes = 0;
+        list_for_each_entry(e, &m->leds, node) {
+            led_set_off_full(e->led_cdev, false);
+            e->last_tx_packets = e->last_rx_packets = 0;
+            e->last_tx_bytes = e->last_rx_bytes = 0;
+        }
+        mutex_unlock(&m->lock);
+        schedule_delayed_work(&m->work, msecs_to_jiffies(2 * DEFAULT_INTERVAL_MS));
+        return;
+    }
+
+    /* aggregate */
+    m->agg_tx_packets = m->agg_rx_packets = 0;
+    m->agg_tx_bytes = m->agg_rx_bytes = 0;
+
+    /* detect any tracked-interface online (carrier) and sum stats */
+    for (i = 0; i < m->dev_count; i++) {
+        struct net_device *dev = m->devs[i];
+        struct rtnl_link_stats64 st;
+
+        if (!dev)
+            continue;
+
+        get_dev_stats_safe(dev, &st);
+        m->agg_tx_packets += st.tx_packets;
+        m->agg_rx_packets += st.rx_packets;
+        m->agg_tx_bytes += st.tx_bytes;
+        m->agg_rx_bytes += st.rx_bytes;
+
+        if (netif_running(dev) && netif_carrier_ok(dev))
+            any_online = true;
+    }
+
+    /* update each subscribed LED according to its flags and family */
+    {
+        struct net_led *e;
+        list_for_each_entry(e, &m->leds, node)
+            update_led(e, m, any_online);
+    }
+
+    mutex_unlock(&m->lock);
+    schedule_delayed_work(&m->work, msecs_to_jiffies(2 * DEFAULT_INTERVAL_MS));
+}
+
+/* notifier: manage tracked devices */
+static int net_mgr_notify(struct notifier_block *nb, unsigned long event, void *ptr)
+{
+    struct netdev_notifier_info *info = ptr;
+    struct net_device *tmp, *dev = NULL;
+    struct net_mgr *m = container_of(nb, struct net_mgr, notifier);
+    int i, id = -1, newid;
+
+    if (event != NETDEV_REGISTER && event != NETDEV_UNREGISTER &&
+        event != NETDEV_CHANGENAME)
+        return NOTIFY_DONE;
+
+    if (!info)
+        return NOTIFY_DONE;
+
+    dev = info->dev;
+
+    if (!dev)
+        return NOTIFY_DONE;
+
+    mutex_lock(&m->lock);
+    for (i = 0; i < m->dev_count; i++) {
+        if (m->devs[i] && m->devs[i] == dev) {
+            id = i;
+            break;
+        }
+    }
+
+    switch (event) {
+    case NETDEV_UNREGISTER:
+        if (id >= 0) {
+            tmp = m->devs[id];
+            m->devs[id] = NULL;
+            pr_info("%s - interface %s unregistered\n", type_names[m->type], dev->name);
+            if (m->dev_count == id + 1)
+                m->dev_count--;
+            mutex_unlock(&m->lock);
+            dev_put(tmp);
+            mutex_lock(&m->lock);
+        }
+        break;
+    case NETDEV_CHANGENAME:
+        if (id >= 0 && !name_matches_type(dev->name, m->type)) {
+            tmp = m->devs[id];
+            m->devs[id] = NULL;
+            pr_info("%s - interface %s unregistered (name changed)\n",
+                type_names[m->type], dev->name);
+            if (m->dev_count == id + 1)
+                m->dev_count--;
+            mutex_unlock(&m->lock);
+            dev_put(tmp);
+            mutex_lock(&m->lock);
+            break;
+        }
+        fallthrough;
+    case NETDEV_REGISTER:
+        if (id < 0 && name_matches_type(dev->name, m->type)) {
+            newid = -1;
+            for (i = 0; i < m->dev_count; i++) {
+                if (!m->devs[i]) {
+                    newid = i;
+                    break;
+                }
+            }
+            if (newid < 0 && m->dev_count < MAX_IFACES)
+                newid = m->dev_count++;
+            if (newid >= 0) {
+                dev_hold(dev);
+                m->devs[newid] = dev;
+                pr_info("%s - interface %s registered\n", type_names[m->type], dev->name);
+            }
+        }
+        break;
+    }
+    mutex_unlock(&m->lock);
+    return NOTIFY_DONE;
+}
+
+/* find/create manager for base family */
+static struct net_mgr *net_mgr_get(enum net_trig_type type)
+{
+    struct net_mgr *m, *existing = NULL;
+
+    if (type >= NET_TRIG_TYPE_MAX)
+        return NULL;
+
+    /* fast-path: if already exists, bump ref and return */
+    mutex_lock(&managers_lock);
+    m = managers[type];
+    if (m) {
+        atomic_inc(&m->refcnt);
+        mutex_unlock(&managers_lock);
+        return m;
+    }
+    mutex_unlock(&managers_lock);
+
+    /* allocate and init (not yet published) */
+    m = kzalloc(sizeof(*m), GFP_KERNEL);
+    if (!m)
+        return NULL;
+
+    m->type = type;
+    mutex_init(&m->lock);
+    INIT_LIST_HEAD(&m->leds);
+    atomic_set(&m->refcnt, 1);
+    INIT_DELAYED_WORK(&m->work, net_mgr_work);
+
+    m->notifier.notifier_call = net_mgr_notify;
+    m->notifier.priority = 0;
+
+    /* try to register notifier; on failure clean up and return */
+    if (register_netdevice_notifier(&m->notifier)) {
+        kfree(m);
+        return NULL;
+    }
+
+    /* publish manager, but handle rare race where another thread created it */
+    mutex_lock(&managers_lock);
+    existing = managers[type];
+    if (existing) {
+        /* use existing one: increase refcount, drop our resources */
+        atomic_inc(&existing->refcnt);
+        mutex_unlock(&managers_lock);
+
+        unregister_netdevice_notifier(&m->notifier);
+        kfree(m);
+
+        return existing;
+    }
+
+    /* no existing manager -> publish ours */
+    managers[type] = m;
+    mutex_unlock(&managers_lock);
+
+    /* start background work */
+    schedule_delayed_work(&m->work, 0);
+
+    return m;
+}
+
+static void net_mgr_put(struct net_mgr *m)
+{
+    int i;
+    if (!m)
+        return;
+
+    if (atomic_dec_and_test(&m->refcnt)) {
+        mutex_lock(&managers_lock);
+        if (managers[m->type] == m)
+            managers[m->type] = NULL;
+        mutex_unlock(&managers_lock);
+
+        cancel_delayed_work_sync(&m->work);
+        unregister_netdevice_notifier(&m->notifier);
+
+        mutex_lock(&m->lock);
+        for (i = 0; i < m->dev_count; i++)
+            if (m->devs[i])
+                dev_put(m->devs[i]);
+        mutex_unlock(&m->lock);
+
+        kfree(m);
+    }
+}
+
+/* deactivate: detach led from manager */
+static void net_deactivate(struct led_classdev *led_cdev)
+{
+    struct net_led *entry = led_cdev->trigger_data;
+    struct net_mgr *m;
+
+    if (!entry)
+        return;
+
+    m = entry->mgr;
+    if (!m) {
+        /* defensive: shouldn't happen, but avoid crash */
+        led_cdev->trigger_data = NULL;
+        kfree(entry);
+        return;
+    }
+    mutex_lock(&m->lock);
+    list_del(&entry->node);
+    mutex_unlock(&m->lock);
+
+    led_set_off_full(led_cdev, false);
+    led_cdev->trigger_data = NULL;
+
+    pr_info("LED %s - trigger %s%s detached\n",
+        dev_name(led_cdev->dev),
+        type_names[m->type],
+        labels[(entry->link << 2) | (entry->tx << 1) | (entry->rx << 0)]);
+
+    kfree(entry);
+    net_mgr_put(m);
+}
+
+/* activate/deactivate: attach led to manager and remember flags */
+static int net_activate(struct led_classdev *led_cdev)
+{
+    const char *fn = NULL;
+    int parsed;
+    bool link = false;
+    bool tx = false;
+    bool rx = false;
+    struct net_mgr *m;
+    struct net_led *entry;
+    const char *name;
+    const char *sep;
+
+    if (!led_cdev) {
+        pr_err("network: net_activate called with NULL led_cdev\n");
+        return -EINVAL;
+    }
+    if (!led_cdev->dev) {
+        pr_err("network: LED device is NULL, aborting activate\n");
+        return -EINVAL;
+    }
+    name = dev_name(led_cdev->dev);
+    if (!name || !*name) {
+        pr_err("network: LED has no name, aborting activate\n");
+        return -EINVAL;
+    }
+    if (led_cdev->trigger_data) {
+        pr_warn("network: LED %s already has trigger_data set, refusing attach\n",
+            name);
+        return -EBUSY;
+    }
+
+    /* 
+     * Prefer core-supplied transient params if present (format: "network:params").
+     * Do NOT consume or free led_cdev->params here - core will free it
+     * after activate returns if the trigger doesn't take ownership.
+     */
+    if (led_cdev->params && led_cdev->params[0]) {
+        fn = led_cdev->params;
+    } else {
+        /* fallback: parse function from LED name "<color>:<function>" */
+        sep = strrchr(name, ':');
+        if (sep && sep[1] != '\0') {
+            fn = sep + 1;
+        } else {
+            /* fallback: try the whole name */
+            fn = name;
+        }
+    }
+
+    /* If function not recognised, return -EINVAL to signal invalid configuration to userspace */
+    parsed = parse_function_string(fn, &link, &tx, &rx);
+    if (parsed < 0) {
+        pr_info("network: unknown function '%s' for LED %s\n", fn ?: "<NULL>", name);
+        return -EINVAL;
+    }
+
+    m = net_mgr_get(parsed);
+    if (!m)
+        return -ENOMEM;
+
+    entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+    if (!entry) {
+        net_mgr_put(m);
+        return -ENOMEM;
+    }
+
+    entry->led_cdev = led_cdev;
+    entry->mgr = m;
+    entry->last_tx_packets = entry->last_rx_packets = 0;
+    entry->last_tx_bytes = entry->last_rx_bytes = 0;
+    entry->link = link;
+    entry->tx = tx;
+    entry->rx = rx;
+
+    /* attach under manager lock and initialize history to current aggregates */
+    mutex_lock(&m->lock);
+    list_add_tail(&entry->node, &m->leds);
+
+    /* initialize history so future reads won't see a spurious delta */
+    entry->last_tx_packets = m->agg_tx_packets;
+    entry->last_rx_packets = m->agg_rx_packets;
+    entry->last_tx_bytes   = m->agg_tx_bytes;
+    entry->last_rx_bytes   = m->agg_rx_bytes;
+    mutex_unlock(&m->lock);
+
+    led_cdev->trigger_data = entry;
+    pr_info("LED %s - trigger %s%s attached\n",
+        name,
+        type_names[m->type],
+        labels[(entry->link << 2) | (entry->tx << 1) | (entry->rx << 0)]);
+    return 0;
+}
+
+static struct led_trigger network_trigger = {
+    .name = "network",
+    .activate = net_activate,
+    .deactivate = net_deactivate,
+};
+
+module_led_trigger(network_trigger);
+
+MODULE_AUTHOR("Mieczyslaw Nalewaj <namiltd@...oo.com>");
+MODULE_DESCRIPTION("LED trigger for network interfaces — aggregated by family; supports link/tx/rx and -online");
+MODULE_LICENSE("GPL v2");
--- a/drivers/leds/trigger/Makefile
+++ b/drivers/leds/trigger/Makefile
@@ -16,3 +16,4 @@ obj-$(CONFIG_LEDS_TRIGGER_NETDEV)    += led
 obj-$(CONFIG_LEDS_TRIGGER_PATTERN)    += ledtrig-pattern.o
 obj-$(CONFIG_LEDS_TRIGGER_TTY)        += ledtrig-tty.o
 obj-$(CONFIG_LEDS_TRIGGER_INPUT_EVENTS)    += ledtrig-input-events.o
+obj-$(CONFIG_LEDS_TRIGGER_NETWORK)    += ledtrig-network.o

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ