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