Multiple devices/ports status - single LED

Currently I am testing two additional modules for these situations (ledtrig-netdev-lan and ledtrig-netdev-phy)

Patch:

diff -ruN linux-6.6.93.old/drivers/leds/trigger/Kconfig linux-6.6.93/drivers/leds/trigger/Kconfig
--- linux-6.6.93.old/drivers/leds/trigger/Kconfig	2025-08-23 17:21:35.472517700 +0200
+++ linux-6.6.93/drivers/leds/trigger/Kconfig	2025-08-23 17:17:57.228517700 +0200
@@ -132,6 +132,20 @@
 	  This allows LEDs to be controlled by network device activity.
 	  If unsure, say Y.
 
+config LEDS_TRIGGER_NETDEV_LAN
+	tristate "LED Netdev LAN Trigger"
+	depends on NET
+	help
+	  This allows LEDs to be controlled by network LAN device activity.
+	  If unsure, say Y.
+
+config LEDS_TRIGGER_NETDEV_PHY
+	tristate "LED Netdev PHY Trigger"
+	depends on NET
+	help
+	  This allows LEDs to be controlled by network PHY device activity.
+	  If unsure, say Y.
+
 config LEDS_TRIGGER_PATTERN
 	tristate "LED Pattern Trigger"
 	help
diff -ruN linux-6.6.93.old/drivers/leds/trigger/ledtrig-netdev-lan.c linux-6.6.93/drivers/leds/trigger/ledtrig-netdev-lan.c
--- linux-6.6.93.old/drivers/leds/trigger/ledtrig-netdev-lan.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-6.6.93/drivers/leds/trigger/ledtrig-netdev-lan.c	2025-08-23 17:10:30.000000000 +0200
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0
+// LED trigger monitoring up to 16 lan* interfaces using RX/TX summation and OR logic
+
+#include <linux/atomic.h>
+#include <linux/jiffies.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/spinlock.h>
+#include <linux/rtnetlink.h>
+#include <linux/workqueue.h>
+#include "../leds.h"
+
+#define MAX_IFACES 16       /* Support up to 16 LAN interfaces */
+#define DEFAULT_INTERVAL 50 /* Blink interval in ms */
+
+struct led_netdev_lan {
+    spinlock_t lock;
+    struct delayed_work work;
+    struct notifier_block notifier;
+    struct led_classdev *led_cdev;
+    struct net_device *netdevs[MAX_IFACES];
+    int iface_count;
+    atomic_t interval;   /* in jiffies */
+    unsigned long last_rx_sum;
+    unsigned long last_tx_sum;
+};
+
+/* Periodic work function */
+static void lan_work_fn(struct work_struct *work)
+{
+    struct led_netdev_lan *d =
+        container_of(work, struct led_netdev_lan, work.work);
+    unsigned long rx_sum = 0, tx_sum = 0;
+    unsigned long interval;
+    bool any_up = false;
+    struct rtnl_link_stats64 *dev_stats;
+    struct rtnl_link_stats64 temp;
+
+    spin_lock_bh(&d->lock);
+    for (int i = 0; i < d->iface_count; i++) {
+        struct net_device *dev = d->netdevs[i];
+        if (!dev)
+            continue;
+        /* Check if the interface is online */
+        if (netif_running(dev) && netif_carrier_ok(dev))
+            any_up = true;
+        dev_stats = dev_get_stats(dev, &temp);
+        rx_sum += dev_stats->rx_packets;
+        tx_sum += dev_stats->tx_packets;
+    }
+
+    if (!any_up) {
+        /* No active link on any interface — turn LED off */
+        led_set_brightness(d->led_cdev, LED_OFF);
+    } else if ((rx_sum + tx_sum) != (d->last_rx_sum + d->last_tx_sum)) {
+        interval = jiffies_to_msecs(atomic_read(&d->interval));
+        /* Detected RX/TX activity — blink LED */
+        led_stop_software_blink(d->led_cdev);
+        led_blink_set_oneshot(d->led_cdev,
+                              &interval,
+                              &interval,
+                              true);
+    } else {
+        /* Link up but no traffic — LED stays on */
+        led_set_brightness(d->led_cdev, LED_FULL);
+    }
+
+    d->last_rx_sum = rx_sum;
+    d->last_tx_sum = tx_sum;
+    spin_unlock_bh(&d->lock);
+
+    schedule_delayed_work(&d->work, atomic_read(&d->interval) * 2);
+}
+
+/* Notifier for link or registration changes */
+static int lan_notify(struct notifier_block *nb,
+                      unsigned long event, void *ptr)
+{
+    struct netdev_notifier_info *info = ptr;
+    struct net_device *dev = info->dev;
+    struct led_netdev_lan *d =
+        container_of(nb, struct led_netdev_lan, notifier);
+
+    spin_lock_bh(&d->lock);
+    for (int i = 0; i < d->iface_count; i++) {
+//        if (d->netdevs[i] == dev) {
+        if (!strcmp(dev->name, d->netdevs[i]->name)) {
+            /* Reset counters and reschedule work */
+            d->last_rx_sum = 0;
+            d->last_tx_sum = 0;
+            cancel_delayed_work_sync(&d->work);
+            schedule_delayed_work(&d->work, 0);
+            break;
+        }
+    }
+    spin_unlock_bh(&d->lock);
+    return NOTIFY_DONE;
+}
+
+/* Detect all interfaces with names starting with "lan" */
+static void detect_lan_interfaces(struct led_netdev_lan *d)
+{
+    struct net_device *dev;
+
+    d->iface_count = 0;
+    rtnl_lock();
+    for_each_netdev(&init_net, dev) {
+        if (d->iface_count >= MAX_IFACES)
+            break;
+        if (strncmp(dev->name, "lan", 3) == 0) {
+            dev_hold(dev);
+            d->netdevs[d->iface_count++] = dev;
+        }
+    }
+    rtnl_unlock();
+}
+
+static int lan_activate(struct led_classdev *led_cdev)
+{
+    struct led_netdev_lan *d;
+
+    d = kzalloc(sizeof(*d), GFP_KERNEL);
+    if (!d)
+        return -ENOMEM;
+
+    spin_lock_init(&d->lock);
+    INIT_DELAYED_WORK(&d->work, lan_work_fn);
+    d->notifier.notifier_call = lan_notify;
+    d->notifier.priority = 0;
+    d->led_cdev = led_cdev;
+    atomic_set(&d->interval, msecs_to_jiffies(DEFAULT_INTERVAL));
+    d->last_rx_sum = 0;
+    d->last_tx_sum = 0;
+
+    detect_lan_interfaces(d);
+
+    if (d->iface_count == 0) {
+        pr_err("netdev_lan: no LAN interfaces found\n");
+        kfree(d);
+        return -ENODEV;
+    }
+
+    register_netdevice_notifier(&d->notifier);
+    schedule_delayed_work(&d->work, 0);
+    led_cdev->trigger_data = d;
+    return 0;
+}
+
+static void lan_deactivate(struct led_classdev *led_cdev)
+{
+    struct led_netdev_lan *d = led_cdev->trigger_data;
+    int i;
+
+    unregister_netdevice_notifier(&d->notifier);
+    cancel_delayed_work_sync(&d->work);
+    for (i = 0; i < d->iface_count; i++)
+        dev_put(d->netdevs[i]);
+
+    led_set_brightness(led_cdev, LED_OFF);
+    kfree(d);
+}
+
+static struct led_trigger netdev_lan_trigger = {
+    .name = "netdev_lan",
+    .activate = lan_activate,
+    .deactivate = lan_deactivate,
+};
+
+module_led_trigger(netdev_lan_trigger);
+
+MODULE_AUTHOR("Mieczyslaw Nalewaj namiltd@yahoo.com");
+MODULE_DESCRIPTION("Netdev LED trigger for up to 16 lan* interfaces with RX/TX sumation and OR logic");
+MODULE_LICENSE("GPL v2");
diff -ruN linux-6.6.93.old/drivers/leds/trigger/ledtrig-netdev-phy.c linux-6.6.93/drivers/leds/trigger/ledtrig-netdev-phy.c
--- linux-6.6.93.old/drivers/leds/trigger/ledtrig-netdev-phy.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-6.6.93/drivers/leds/trigger/ledtrig-netdev-phy.c	2025-08-23 17:10:30.000000000 +0200
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+// LED trigger: emulate mac80211 tpt logic with blink at all traffic levels, but solid ON only when no traffic.
+
+#include <linux/atomic.h>
+#include <linux/jiffies.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include "../leds.h"
+
+#define MAX_IFACES 16
+#define FIXED_INTERVAL_MS 100
+
+/* Blink timing table (kbps thresholds, on/off ms) */
+static const struct {
+    u32 throughput;
+    long unsigned int on_ms;
+    long unsigned int off_ms;
+} tpt_table[] = {
+    {  64,   200, 800 },
+    { 512,   200, 300 },
+    {2048,   200, 150 },
+    {10000,  200,  50 },
+    {54000,  100,  50 }, /* now blink instead of solid ON */
+};
+
+struct led_netdev_phy {
+    spinlock_t lock;
+    struct delayed_work work;
+    struct notifier_block notifier;
+    struct led_classdev *led_cdev;
+    struct net_device *netdevs[MAX_IFACES];
+    int iface_count;
+
+    u64 last_rx_bytes_sum;
+    u64 last_tx_bytes_sum;
+};
+
+static void phy_work_fn(struct work_struct *work)
+{
+    struct led_netdev_phy *d = container_of(work, struct led_netdev_phy, work.work);
+    u64 rx_bytes_sum = 0, tx_bytes_sum = 0, bytes_delta, bits_per_sec, kbps;
+    bool any_up = false;
+    struct rtnl_link_stats64 temp, *stats;
+    unsigned int interval_ms = FIXED_INTERVAL_MS;
+    int idx = -1, i;
+    long unsigned int on_ms, off_ms;
+
+    spin_lock_bh(&d->lock);
+
+    for (i = 0; i < d->iface_count; i++) {
+        struct net_device *dev = d->netdevs[i];
+        if (!dev)
+            continue;
+        if (netif_running(dev) && netif_carrier_ok(dev))
+            any_up = true;
+        stats = dev_get_stats(dev, &temp);
+        rx_bytes_sum += stats->rx_bytes;
+        tx_bytes_sum += stats->tx_bytes;
+    }
+
+    if (!any_up) {
+        led_stop_software_blink(d->led_cdev);
+        led_set_brightness(d->led_cdev, LED_OFF);
+        goto resched;
+    }
+
+    bytes_delta = (rx_bytes_sum - d->last_rx_bytes_sum) +
+                  (tx_bytes_sum - d->last_tx_bytes_sum);
+    bits_per_sec = div64_u64(bytes_delta * 8ULL * 1000ULL, interval_ms);
+    kbps = div64_u64(bits_per_sec, 1000ULL);
+
+    if (kbps == 0) {
+        /* Solid ON when no traffic */
+        led_stop_software_blink(d->led_cdev);
+        led_set_brightness(d->led_cdev, LED_FULL);
+    } else {
+        /* Find the highest matching threshold */
+        for (i = 0; i < ARRAY_SIZE(tpt_table); i++) {
+            if (kbps >= tpt_table[i].throughput)
+                idx = i;
+            else
+                break;
+        }
+        if (idx < 0)
+            idx = 0;
+
+        on_ms = tpt_table[idx].on_ms;
+        off_ms = tpt_table[idx].off_ms;
+        led_blink_set(d->led_cdev, &on_ms, &off_ms);
+    }
+
+    d->last_rx_bytes_sum = rx_bytes_sum;
+    d->last_tx_bytes_sum = tx_bytes_sum;
+
+resched:
+    spin_unlock_bh(&d->lock);
+    schedule_delayed_work(&d->work, msecs_to_jiffies(FIXED_INTERVAL_MS));
+}
+
+static int phy_notify(struct notifier_block *nb, unsigned long event, void *ptr)
+{
+    struct netdev_notifier_info *info = ptr;
+    struct net_device *dev = info->dev;
+    struct led_netdev_phy *d = container_of(nb, struct led_netdev_phy, notifier);
+    int i;
+
+    spin_lock_bh(&d->lock);
+    for (i = 0; i < d->iface_count; i++) {
+        if (d->netdevs[i] && !strcmp(dev->name, d->netdevs[i]->name)) {
+            d->last_rx_bytes_sum = 0;
+            d->last_tx_bytes_sum = 0;
+            cancel_delayed_work_sync(&d->work);
+            schedule_delayed_work(&d->work, 0);
+            break;
+        }
+    }
+    spin_unlock_bh(&d->lock);
+    return NOTIFY_DONE;
+}
+
+static void detect_phy_interfaces(struct led_netdev_phy *d)
+{
+    struct net_device *dev;
+    d->iface_count = 0;
+    rtnl_lock();
+    for_each_netdev(&init_net, dev) {
+        if (d->iface_count >= MAX_IFACES)
+            break;
+        if (!strncmp(dev->name, "phy", 3)) {
+            dev_hold(dev);
+            d->netdevs[d->iface_count++] = dev;
+        }
+    }
+    rtnl_unlock();
+}
+
+static int phy_activate(struct led_classdev *led_cdev)
+{
+    struct led_netdev_phy *d = kzalloc(sizeof(*d), GFP_KERNEL);
+    if (!d)
+        return -ENOMEM;
+
+    spin_lock_init(&d->lock);
+    INIT_DELAYED_WORK(&d->work, phy_work_fn);
+    d->notifier.notifier_call = phy_notify;
+    d->led_cdev = led_cdev;
+
+    detect_phy_interfaces(d);
+    if (!d->iface_count) {
+        pr_err("netdev_phy: no PHY interfaces found\n");
+        kfree(d);
+        return -ENODEV;
+    }
+
+    register_netdevice_notifier(&d->notifier);
+    schedule_delayed_work(&d->work, 0);
+    led_cdev->trigger_data = d;
+    return 0;
+}
+
+static void phy_deactivate(struct led_classdev *led_cdev)
+{
+    struct led_netdev_phy *d = led_cdev->trigger_data;
+    int i;
+
+    unregister_netdevice_notifier(&d->notifier);
+    cancel_delayed_work_sync(&d->work);
+    for (i = 0; i < d->iface_count; i++)
+        if (d->netdevs[i])
+            dev_put(d->netdevs[i]);
+
+    led_set_brightness(led_cdev, LED_OFF);
+    kfree(d);
+}
+
+static struct led_trigger netdev_phy_trigger = {
+    .name = "netdev_phy",
+    .activate = phy_activate,
+    .deactivate = phy_deactivate,
+};
+
+module_led_trigger(netdev_phy_trigger);
+
+MODULE_AUTHOR("Mieczyslaw Nalewaj <namiltd@yahoo.com>");
+MODULE_DESCRIPTION("Netdev LED trigger for up to 16 phy* interfaces with consistent blink and solid idle state");
+MODULE_LICENSE("GPL v2");
diff -ruN linux-6.6.93.old/drivers/leds/trigger/Makefile linux-6.6.93/drivers/leds/trigger/Makefile
--- linux-6.6.93.old/drivers/leds/trigger/Makefile	2025-08-23 17:21:10.396517700 +0200
+++ linux-6.6.93/drivers/leds/trigger/Makefile	2025-08-23 17:16:37.400517700 +0200
@@ -16,3 +16,5 @@
 obj-$(CONFIG_LEDS_TRIGGER_PATTERN)	+= ledtrig-pattern.o
 obj-$(CONFIG_LEDS_TRIGGER_AUDIO)	+= ledtrig-audio.o
 obj-$(CONFIG_LEDS_TRIGGER_TTY)		+= ledtrig-tty.o
+obj-$(CONFIG_LEDS_TRIGGER_NETDEV_LAN)	+= ledtrig-netdev-lan.o
+obj-$(CONFIG_LEDS_TRIGGER_NETDEV_PHY)	+= ledtrig-netdev-phy.o

2 Likes