bla
[openwrt/staging/blocktrron.git] / target / linux / generic / files / drivers / leds / trigger / ledtrig-netdev-multi.c
1 // SPDX-License-Identifier: GPL-2.0
2 // Copyright 2023 David Bauer <mail@david-bauer.net>
3 //
4 // LED Kernel Multi-Netdev Trigger
5 //
6 // Toggles the LED to reflect the link state of multiple network devices
7 //
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>
11
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>
24 #include "../leds.h"
25
26 /*
27 * Configurable sysfs attributes:
28 *
29 * device_name - network device name to monitor
30 */
31
32 struct led_netdev_ifdata {
33 char device_name[IFNAMSIZ];
34 struct net_device *net_dev;
35 unsigned int last_activity;
36 };
37
38 struct led_netdev_data {
39 spinlock_t lock;
40
41 struct notifier_block notifier;
42
43 struct led_netdev_ifdata *ifdata;
44 int ifdata_count;
45
46 struct led_classdev *led_cdev;
47 struct net_device *net_dev;
48 };
49
50
51 static void update_link_state(struct led_netdev_data *trigger_data)
52 {
53 int current_brightness;
54 struct led_classdev *led_cdev = trigger_data->led_cdev;
55 bool link = false;
56 int i;
57
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;
63
64 for (i = 0; i < trigger_data->ifdata_count; i++) {
65 if (!trigger_data->ifdata[i].net_dev) {
66 continue;
67 }
68
69 if (netif_carrier_ok(trigger_data->ifdata[i].net_dev)) {
70 link = true;
71 }
72 }
73
74 led_set_brightness(led_cdev, link ? led_cdev->blink_brightness : LED_OFF);
75
76 }
77
78 static ssize_t device_name_show(struct device *dev,
79 struct device_attribute *attr, char *buf)
80 {
81 struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
82 ssize_t len;
83 int i;
84
85 spin_lock_bh(&trigger_data->lock);
86 len = 0;
87 for (i = 0; i < trigger_data->ifdata_count; i++) {
88 len += sprintf(&buf[len], "%s ", trigger_data->ifdata[i].device_name);
89 }
90
91 if (!len) {
92 len = 1;
93 }
94
95 buf[len-1] = '\n';
96 buf[len] = 0;
97
98 spin_unlock_bh(&trigger_data->lock);
99
100 return len;
101 }
102
103 static int device_name_next(const char **dst, const char *buf, const char *end)
104 {
105 size_t s;
106 const char *p = buf;
107 const char *word;
108
109 word = NULL;
110 s = 0;
111 while (p < end) {
112 if (*p == ' ' || *p == '\n') {
113 if (word) {
114 break;
115 }
116 } else {
117 if (!word)
118 word = p;
119 s++;
120 }
121 p++;
122 }
123
124 if (!word) {
125 /* No word --> len 0*/
126 return s;
127 }
128
129 if (s > IFNAMSIZ - 1) {
130 return -EINVAL;
131 }
132
133 *dst = word;
134 return s;
135 }
136
137
138 /* -1: Invalid name ; >=0: Number of devices */
139 static int device_names_count(const char *buf, size_t size)
140 {
141 const char *t, *ptr;
142 int count = 0;
143 int len;
144
145 ptr = buf;
146 while ((len = device_name_next(&t, ptr, buf + size)) > 0) {
147 ptr = t + len;
148 count++;
149 }
150
151 if (len < 0)
152 return len;
153
154 return count;
155 }
156
157 static void netdevice_put_all(struct led_netdev_data *trigger_data)
158 {
159 struct led_netdev_ifdata *ifdata;
160 int i;
161
162 for (i = 0; i < trigger_data->ifdata_count; i++) {
163 ifdata = &trigger_data->ifdata[i];
164
165 dev_put(ifdata->net_dev);
166 ifdata->net_dev = NULL;
167 }
168
169 kfree(trigger_data->ifdata);
170 trigger_data->ifdata = NULL;
171 trigger_data->ifdata_count = 0;
172 }
173
174 static void netdevice_load_all(struct device *dev, const char *buf, size_t size)
175 {
176 struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
177 struct led_netdev_ifdata *ifdata;
178 const char *ifname, *ptr;
179 int ifname_len;
180 int i;
181
182 ptr = buf;
183 i = 0;
184 while ((ifname_len = device_name_next(&ifname, ptr, buf + size)) > 0) {
185 ifdata = &trigger_data->ifdata[i];
186
187 /* Copy ifname */
188 memcpy(ifdata->device_name, ifname, ifname_len);
189 ifdata->device_name[ifname_len] = 0;
190
191 /* Get netdev */
192 ifdata->net_dev = dev_get_by_name(&init_net, ifdata->device_name);
193
194 ptr = ifname + ifname_len;
195 i++;
196 }
197 }
198
199 static ssize_t device_name_store(struct device *dev,
200 struct device_attribute *attr, const char *buf,
201 size_t size)
202 {
203 struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
204 int num_devices = device_names_count(buf, size);
205
206 if (num_devices < 0)
207 return -EINVAL;
208
209 spin_lock_bh(&trigger_data->lock);
210
211 netdevice_put_all(trigger_data);
212 trigger_data->ifdata_count = num_devices;
213
214 if (num_devices == 0)
215 return size;
216
217 /* Allocate list */
218 trigger_data->ifdata = kzalloc(sizeof(struct led_netdev_ifdata) * num_devices, GFP_KERNEL);
219 if (!trigger_data->ifdata)
220 return -ENOMEM;
221
222 netdevice_load_all(dev, buf, size);
223
224 update_link_state(trigger_data);
225 spin_unlock_bh(&trigger_data->lock);
226
227 return size;
228 }
229
230 static DEVICE_ATTR_RW(device_name);
231
232 static struct attribute *netdev_trig_multi_attrs[] = {
233 &dev_attr_device_name.attr,
234 NULL
235 };
236 ATTRIBUTE_GROUPS(netdev_trig_multi);
237
238 static int netdev_trig_multi_notify(struct notifier_block *nb,
239 unsigned long evt, void *dv)
240 {
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;
246 int i;
247
248 if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE
249 && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER
250 && evt != NETDEV_CHANGENAME)
251 return NOTIFY_DONE;
252
253 spin_lock_bh(&trigger_data->lock);
254
255 for (i = 0; i < trigger_data->ifdata_count; i++) {
256 ifdata = &trigger_data->ifdata[i];
257
258 if (!strcmp(ifdata->device_name, dev->name) && (evt == NETDEV_CHANGENAME || evt == NETDEV_REGISTER)) {
259 dev_put(ifdata->net_dev);
260 dev_hold(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;
265 }
266 }
267
268 update_link_state(trigger_data);
269 spin_unlock_bh(&trigger_data->lock);
270
271 return NOTIFY_DONE;
272 }
273
274 static int netdev_trig_multi_activate(struct led_classdev *led_cdev)
275 {
276 struct led_netdev_data *trigger_data;
277 int rc;
278
279 trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL);
280 if (!trigger_data)
281 return -ENOMEM;
282
283 spin_lock_init(&trigger_data->lock);
284
285 trigger_data->notifier.notifier_call = netdev_trig_multi_notify;
286 trigger_data->notifier.priority = 10;
287
288 trigger_data->led_cdev = led_cdev;
289 trigger_data->ifdata = NULL;
290 trigger_data->ifdata_count = 0;
291
292 led_set_trigger_data(led_cdev, trigger_data);
293
294 rc = register_netdevice_notifier(&trigger_data->notifier);
295 if (rc)
296 kfree(trigger_data);
297
298 return rc;
299 }
300
301 static void netdev_trig_multi_deactivate(struct led_classdev *led_cdev)
302 {
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);
306 kfree(trigger_data);
307 }
308
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,
314 };
315
316 static int __init netdev_trig_multi_init(void)
317 {
318 return led_trigger_register(&netdev_led_trigger_multi);
319 }
320
321 static void __exit netdev_trig_multi_exit(void)
322 {
323 led_trigger_unregister(&netdev_led_trigger_multi);
324 }
325
326 module_init(netdev_trig_multi_init);
327 module_exit(netdev_trig_multi_exit);
328
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");