1 // SPDX-License-Identifier: GPL-2.0
2 // Copyright 2023 David Bauer <mail@david-bauer.net>
4 // LED Kernel Multi-Netdev Trigger
6 // Toggles the LED to reflect the link state of multiple network devices
8 // Derived from ledtrig-netdev.c which is:
9 // Copyright 2017 Ben Whitten <ben.whitten@gmail.com>
10 // Copyright 2007 Oliver Jowett <oliver@opencloud.com>
12 #include <linux/atomic.h>
13 #include <linux/ctype.h>
14 #include <linux/device.h>
15 #include <linux/init.h>
16 #include <linux/jiffies.h>
17 #include <linux/kernel.h>
18 #include <linux/leds.h>
19 #include <linux/list.h>
20 #include <linux/module.h>
21 #include <linux/netdevice.h>
22 #include <linux/spinlock.h>
23 #include <linux/timer.h>
27 * Configurable sysfs attributes:
29 * device_name - network device name to monitor
32 struct led_netdev_ifdata
{
33 char device_name
[IFNAMSIZ
];
34 struct net_device
*net_dev
;
35 unsigned int last_activity
;
38 struct led_netdev_data
{
41 struct notifier_block notifier
;
43 struct led_netdev_ifdata
*ifdata
;
46 struct led_classdev
*led_cdev
;
47 struct net_device
*net_dev
;
51 static void update_link_state(struct led_netdev_data
*trigger_data
)
53 int current_brightness
;
54 struct led_classdev
*led_cdev
= trigger_data
->led_cdev
;
58 current_brightness
= led_cdev
->brightness
;
59 if (current_brightness
)
60 led_cdev
->blink_brightness
= current_brightness
;
61 if (!led_cdev
->blink_brightness
)
62 led_cdev
->blink_brightness
= led_cdev
->max_brightness
;
64 for (i
= 0; i
< trigger_data
->ifdata_count
; i
++) {
65 if (!trigger_data
->ifdata
[i
].net_dev
) {
69 if (netif_carrier_ok(trigger_data
->ifdata
[i
].net_dev
)) {
74 led_set_brightness(led_cdev
, link
? led_cdev
->blink_brightness
: LED_OFF
);
78 static ssize_t
device_name_show(struct device
*dev
,
79 struct device_attribute
*attr
, char *buf
)
81 struct led_netdev_data
*trigger_data
= led_trigger_get_drvdata(dev
);
85 spin_lock_bh(&trigger_data
->lock
);
87 for (i
= 0; i
< trigger_data
->ifdata_count
; i
++) {
88 len
+= sprintf(&buf
[len
], "%s ", trigger_data
->ifdata
[i
].device_name
);
98 spin_unlock_bh(&trigger_data
->lock
);
103 static int device_name_next(const char **dst
, const char *buf
, const char *end
)
112 if (*p
== ' ' || *p
== '\n') {
125 /* No word --> len 0*/
129 if (s
> IFNAMSIZ
- 1) {
138 /* -1: Invalid name ; >=0: Number of devices */
139 static int device_names_count(const char *buf
, size_t size
)
146 while ((len
= device_name_next(&t
, ptr
, buf
+ size
)) > 0) {
157 static void netdevice_put_all(struct led_netdev_data
*trigger_data
)
159 struct led_netdev_ifdata
*ifdata
;
162 for (i
= 0; i
< trigger_data
->ifdata_count
; i
++) {
163 ifdata
= &trigger_data
->ifdata
[i
];
165 dev_put(ifdata
->net_dev
);
166 ifdata
->net_dev
= NULL
;
169 kfree(trigger_data
->ifdata
);
170 trigger_data
->ifdata
= NULL
;
171 trigger_data
->ifdata_count
= 0;
174 static void netdevice_load_all(struct device
*dev
, const char *buf
, size_t size
)
176 struct led_netdev_data
*trigger_data
= led_trigger_get_drvdata(dev
);
177 struct led_netdev_ifdata
*ifdata
;
178 const char *ifname
, *ptr
;
184 while ((ifname_len
= device_name_next(&ifname
, ptr
, buf
+ size
)) > 0) {
185 ifdata
= &trigger_data
->ifdata
[i
];
188 memcpy(ifdata
->device_name
, ifname
, ifname_len
);
189 ifdata
->device_name
[ifname_len
] = 0;
192 ifdata
->net_dev
= dev_get_by_name(&init_net
, ifdata
->device_name
);
194 ptr
= ifname
+ ifname_len
;
199 static ssize_t
device_name_store(struct device
*dev
,
200 struct device_attribute
*attr
, const char *buf
,
203 struct led_netdev_data
*trigger_data
= led_trigger_get_drvdata(dev
);
204 int num_devices
= device_names_count(buf
, size
);
209 spin_lock_bh(&trigger_data
->lock
);
211 netdevice_put_all(trigger_data
);
212 trigger_data
->ifdata_count
= num_devices
;
214 if (num_devices
== 0)
218 trigger_data
->ifdata
= kzalloc(sizeof(struct led_netdev_ifdata
) * num_devices
, GFP_KERNEL
);
219 if (!trigger_data
->ifdata
)
222 netdevice_load_all(dev
, buf
, size
);
224 update_link_state(trigger_data
);
225 spin_unlock_bh(&trigger_data
->lock
);
230 static DEVICE_ATTR_RW(device_name
);
232 static struct attribute
*netdev_trig_multi_attrs
[] = {
233 &dev_attr_device_name
.attr
,
236 ATTRIBUTE_GROUPS(netdev_trig_multi
);
238 static int netdev_trig_multi_notify(struct notifier_block
*nb
,
239 unsigned long evt
, void *dv
)
241 struct net_device
*dev
=
242 netdev_notifier_info_to_dev((struct netdev_notifier_info
*)dv
);
243 struct led_netdev_data
*trigger_data
=
244 container_of(nb
, struct led_netdev_data
, notifier
);
245 struct led_netdev_ifdata
*ifdata
;
248 if (evt
!= NETDEV_UP
&& evt
!= NETDEV_DOWN
&& evt
!= NETDEV_CHANGE
249 && evt
!= NETDEV_REGISTER
&& evt
!= NETDEV_UNREGISTER
250 && evt
!= NETDEV_CHANGENAME
)
253 spin_lock_bh(&trigger_data
->lock
);
255 for (i
= 0; i
< trigger_data
->ifdata_count
; i
++) {
256 ifdata
= &trigger_data
->ifdata
[i
];
258 if (!strcmp(ifdata
->device_name
, dev
->name
) && (evt
== NETDEV_CHANGENAME
|| evt
== NETDEV_REGISTER
)) {
259 dev_put(ifdata
->net_dev
);
261 ifdata
->net_dev
= dev
;
262 } else if (evt
== NETDEV_UNREGISTER
&& dev
== ifdata
->net_dev
) {
263 dev_put(ifdata
->net_dev
);
264 ifdata
->net_dev
= NULL
;
268 update_link_state(trigger_data
);
269 spin_unlock_bh(&trigger_data
->lock
);
274 static int netdev_trig_multi_activate(struct led_classdev
*led_cdev
)
276 struct led_netdev_data
*trigger_data
;
279 trigger_data
= kzalloc(sizeof(struct led_netdev_data
), GFP_KERNEL
);
283 spin_lock_init(&trigger_data
->lock
);
285 trigger_data
->notifier
.notifier_call
= netdev_trig_multi_notify
;
286 trigger_data
->notifier
.priority
= 10;
288 trigger_data
->led_cdev
= led_cdev
;
289 trigger_data
->ifdata
= NULL
;
290 trigger_data
->ifdata_count
= 0;
292 led_set_trigger_data(led_cdev
, trigger_data
);
294 rc
= register_netdevice_notifier(&trigger_data
->notifier
);
301 static void netdev_trig_multi_deactivate(struct led_classdev
*led_cdev
)
303 struct led_netdev_data
*trigger_data
= led_get_trigger_data(led_cdev
);
304 unregister_netdevice_notifier(&trigger_data
->notifier
);
305 netdevice_put_all(trigger_data
);
309 static struct led_trigger netdev_led_trigger_multi
= {
310 .name
= "netdev-multi",
311 .activate
= netdev_trig_multi_activate
,
312 .deactivate
= netdev_trig_multi_deactivate
,
313 .groups
= netdev_trig_multi_groups
,
316 static int __init
netdev_trig_multi_init(void)
318 return led_trigger_register(&netdev_led_trigger_multi
);
321 static void __exit
netdev_trig_multi_exit(void)
323 led_trigger_unregister(&netdev_led_trigger_multi
);
326 module_init(netdev_trig_multi_init
);
327 module_exit(netdev_trig_multi_exit
);
329 MODULE_AUTHOR("Ben Whitten <ben.whitten@gmail.com>");
330 MODULE_AUTHOR("Oliver Jowett <oliver@opencloud.com>");
331 MODULE_AUTHOR("David Bauer <mail@david-bauer.net>");
332 MODULE_DESCRIPTION("Multi-Netdev LED trigger");
333 MODULE_LICENSE("GPL v2");