Commit | Line | Data |
---|---|---|
06f502f5 BW |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright 2017 Ben Whitten <ben.whitten@gmail.com> | |
3 | // Copyright 2007 Oliver Jowett <oliver@opencloud.com> | |
4 | // | |
5 | // LED Kernel Netdev Trigger | |
6 | // | |
7 | // Toggles the LED to reflect the link and traffic state of a named net device | |
8 | // | |
9 | // Derived from ledtrig-timer.c which is: | |
10 | // Copyright 2005-2006 Openedhand Ltd. | |
11 | // Author: Richard Purdie <rpurdie@openedhand.com> | |
12 | ||
13 | #include <linux/atomic.h> | |
14 | #include <linux/ctype.h> | |
15 | #include <linux/device.h> | |
d5e01266 | 16 | #include <linux/ethtool.h> |
06f502f5 BW |
17 | #include <linux/init.h> |
18 | #include <linux/jiffies.h> | |
19 | #include <linux/kernel.h> | |
20 | #include <linux/leds.h> | |
06cdca01 | 21 | #include <linux/linkmode.h> |
06f502f5 BW |
22 | #include <linux/list.h> |
23 | #include <linux/module.h> | |
24 | #include <linux/netdevice.h> | |
d1b9e139 | 25 | #include <linux/mutex.h> |
06cdca01 | 26 | #include <linux/phy.h> |
d5e01266 | 27 | #include <linux/rtnetlink.h> |
06f502f5 BW |
28 | #include <linux/timer.h> |
29 | #include "../leds.h" | |
30 | ||
7c145a34 CM |
31 | #define NETDEV_LED_DEFAULT_INTERVAL 50 |
32 | ||
06f502f5 BW |
33 | /* |
34 | * Configurable sysfs attributes: | |
35 | * | |
36 | * device_name - network device name to monitor | |
37 | * interval - duration of LED blink, in milliseconds | |
38 | * link - LED's normal state reflects whether the link is up | |
39 | * (has carrier) or not | |
40 | * tx - LED blinks on transmitted data | |
41 | * rx - LED blinks on receive data | |
42 | * | |
4289e434 HK |
43 | * Note: If the user selects a mode that is not supported by hw, default |
44 | * behavior is to fall back to software control of the LED. However not every | |
45 | * hw supports software control. LED callbacks brightness_set() and | |
46 | * brightness_set_blocking() are NULL in this case. hw_control_is_supported() | |
47 | * should use available means supported by hw to inform the user that selected | |
48 | * mode isn't supported by hw. This could be switching off the LED or any | |
49 | * hw blink mode. If software control fallback isn't possible, we return | |
50 | * -EOPNOTSUPP to the user, but still store the selected mode. This is needed | |
51 | * in case an intermediate unsupported mode is necessary to switch from one | |
52 | * supported mode to another. | |
06f502f5 BW |
53 | */ |
54 | ||
55 | struct led_netdev_data { | |
d1b9e139 | 56 | struct mutex lock; |
06f502f5 BW |
57 | |
58 | struct delayed_work work; | |
59 | struct notifier_block notifier; | |
60 | ||
61 | struct led_classdev *led_cdev; | |
62 | struct net_device *net_dev; | |
63 | ||
64 | char device_name[IFNAMSIZ]; | |
65 | atomic_t interval; | |
66 | unsigned int last_activity; | |
67 | ||
68 | unsigned long mode; | |
d5e01266 | 69 | int link_speed; |
06cdca01 | 70 | __ETHTOOL_DECLARE_LINK_MODE_MASK(supported_link_modes); |
f22f95b9 | 71 | u8 duplex; |
d5e01266 | 72 | |
e2f24cb1 | 73 | bool carrier_link_up; |
4fd1b6d4 | 74 | bool hw_control; |
06f502f5 BW |
75 | }; |
76 | ||
06cdca01 CM |
77 | static const struct attribute_group netdev_trig_link_speed_attrs_group; |
78 | ||
06f502f5 BW |
79 | static void set_baseline_state(struct led_netdev_data *trigger_data) |
80 | { | |
81 | int current_brightness; | |
82 | struct led_classdev *led_cdev = trigger_data->led_cdev; | |
83 | ||
7c145a34 CM |
84 | /* Already validated, hw control is possible with the requested mode */ |
85 | if (trigger_data->hw_control) { | |
86 | led_cdev->hw_control_set(led_cdev, trigger_data->mode); | |
87 | ||
88 | return; | |
89 | } | |
90 | ||
06f502f5 BW |
91 | current_brightness = led_cdev->brightness; |
92 | if (current_brightness) | |
93 | led_cdev->blink_brightness = current_brightness; | |
94 | if (!led_cdev->blink_brightness) | |
95 | led_cdev->blink_brightness = led_cdev->max_brightness; | |
96 | ||
e2f24cb1 | 97 | if (!trigger_data->carrier_link_up) { |
06f502f5 | 98 | led_set_brightness(led_cdev, LED_OFF); |
e2f24cb1 | 99 | } else { |
d5e01266 CM |
100 | bool blink_on = false; |
101 | ||
bdec9cb8 | 102 | if (test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode)) |
d5e01266 CM |
103 | blink_on = true; |
104 | ||
105 | if (test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) && | |
106 | trigger_data->link_speed == SPEED_10) | |
107 | blink_on = true; | |
108 | ||
109 | if (test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) && | |
110 | trigger_data->link_speed == SPEED_100) | |
111 | blink_on = true; | |
112 | ||
113 | if (test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) && | |
114 | trigger_data->link_speed == SPEED_1000) | |
115 | blink_on = true; | |
116 | ||
59b3e31e DG |
117 | if (test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) && |
118 | trigger_data->link_speed == SPEED_2500) | |
119 | blink_on = true; | |
120 | ||
121 | if (test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) && | |
122 | trigger_data->link_speed == SPEED_5000) | |
123 | blink_on = true; | |
124 | ||
125 | if (test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) && | |
126 | trigger_data->link_speed == SPEED_10000) | |
127 | blink_on = true; | |
128 | ||
f22f95b9 CM |
129 | if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) && |
130 | trigger_data->duplex == DUPLEX_HALF) | |
131 | blink_on = true; | |
132 | ||
133 | if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode) && | |
134 | trigger_data->duplex == DUPLEX_FULL) | |
135 | blink_on = true; | |
136 | ||
d5e01266 | 137 | if (blink_on) |
06f502f5 BW |
138 | led_set_brightness(led_cdev, |
139 | led_cdev->blink_brightness); | |
140 | else | |
141 | led_set_brightness(led_cdev, LED_OFF); | |
142 | ||
143 | /* If we are looking for RX/TX start periodically | |
144 | * checking stats | |
145 | */ | |
bdec9cb8 CM |
146 | if (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) || |
147 | test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode)) | |
06f502f5 BW |
148 | schedule_delayed_work(&trigger_data->work, 0); |
149 | } | |
150 | } | |
151 | ||
6352f25f CM |
152 | static bool supports_hw_control(struct led_classdev *led_cdev) |
153 | { | |
154 | if (!led_cdev->hw_control_get || !led_cdev->hw_control_set || | |
155 | !led_cdev->hw_control_is_supported) | |
156 | return false; | |
157 | ||
158 | return !strcmp(led_cdev->hw_control_trigger, led_cdev->trigger->name); | |
159 | } | |
160 | ||
33ec0b53 AL |
161 | /* |
162 | * Validate the configured netdev is the same as the one associated with | |
163 | * the LED driver in hw control. | |
164 | */ | |
165 | static bool validate_net_dev(struct led_classdev *led_cdev, | |
166 | struct net_device *net_dev) | |
167 | { | |
168 | struct device *dev = led_cdev->hw_control_get_device(led_cdev); | |
169 | struct net_device *ndev; | |
170 | ||
171 | if (!dev) | |
172 | return false; | |
173 | ||
174 | ndev = to_net_dev(dev); | |
175 | ||
176 | return ndev == net_dev; | |
177 | } | |
178 | ||
4fd1b6d4 CM |
179 | static bool can_hw_control(struct led_netdev_data *trigger_data) |
180 | { | |
7c145a34 CM |
181 | unsigned long default_interval = msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL); |
182 | unsigned int interval = atomic_read(&trigger_data->interval); | |
6352f25f | 183 | struct led_classdev *led_cdev = trigger_data->led_cdev; |
7c145a34 | 184 | int ret; |
6352f25f CM |
185 | |
186 | if (!supports_hw_control(led_cdev)) | |
187 | return false; | |
188 | ||
7c145a34 CM |
189 | /* |
190 | * Interval must be set to the default | |
191 | * value. Any different value is rejected if in hw | |
192 | * control. | |
193 | */ | |
194 | if (interval != default_interval) | |
195 | return false; | |
196 | ||
197 | /* | |
198 | * net_dev must be set with hw control, otherwise no | |
199 | * blinking can be happening and there is nothing to | |
33ec0b53 AL |
200 | * offloaded. Additionally, for hw control to be |
201 | * valid, the configured netdev must be the same as | |
202 | * netdev associated to the LED. | |
7c145a34 | 203 | */ |
33ec0b53 | 204 | if (!validate_net_dev(led_cdev, trigger_data->net_dev)) |
7c145a34 CM |
205 | return false; |
206 | ||
207 | /* Check if the requested mode is supported */ | |
208 | ret = led_cdev->hw_control_is_supported(led_cdev, trigger_data->mode); | |
209 | /* Fall back to software blinking if not supported */ | |
210 | if (ret == -EOPNOTSUPP) | |
211 | return false; | |
212 | if (ret) { | |
213 | dev_warn(led_cdev->dev, | |
214 | "Current mode check failed with error %d\n", ret); | |
215 | return false; | |
216 | } | |
217 | ||
218 | return true; | |
4fd1b6d4 CM |
219 | } |
220 | ||
d5e01266 CM |
221 | static void get_device_state(struct led_netdev_data *trigger_data) |
222 | { | |
223 | struct ethtool_link_ksettings cmd; | |
224 | ||
225 | trigger_data->carrier_link_up = netif_carrier_ok(trigger_data->net_dev); | |
06cdca01 CM |
226 | |
227 | if (__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd)) | |
d5e01266 CM |
228 | return; |
229 | ||
06cdca01 | 230 | if (trigger_data->carrier_link_up) { |
d5e01266 | 231 | trigger_data->link_speed = cmd.base.speed; |
f22f95b9 CM |
232 | trigger_data->duplex = cmd.base.duplex; |
233 | } | |
06cdca01 CM |
234 | |
235 | /* | |
236 | * Have a local copy of the link speed supported to avoid rtnl lock every time | |
237 | * modes are refreshed on any change event | |
238 | */ | |
239 | linkmode_copy(trigger_data->supported_link_modes, cmd.link_modes.supported); | |
d5e01266 CM |
240 | } |
241 | ||
06f502f5 BW |
242 | static ssize_t device_name_show(struct device *dev, |
243 | struct device_attribute *attr, char *buf) | |
244 | { | |
f8112a1d | 245 | struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); |
06f502f5 BW |
246 | ssize_t len; |
247 | ||
d1b9e139 | 248 | mutex_lock(&trigger_data->lock); |
06f502f5 | 249 | len = sprintf(buf, "%s\n", trigger_data->device_name); |
d1b9e139 | 250 | mutex_unlock(&trigger_data->lock); |
06f502f5 BW |
251 | |
252 | return len; | |
253 | } | |
254 | ||
28a6a2ef AL |
255 | static int set_device_name(struct led_netdev_data *trigger_data, |
256 | const char *name, size_t size) | |
06f502f5 | 257 | { |
259e33cb CM |
258 | if (size >= IFNAMSIZ) |
259 | return -EINVAL; | |
260 | ||
06f502f5 BW |
261 | cancel_delayed_work_sync(&trigger_data->work); |
262 | ||
fe2b1226 HK |
263 | /* |
264 | * Take RTNL lock before trigger_data lock to prevent potential | |
265 | * deadlock with netdev notifier registration. | |
266 | */ | |
267 | rtnl_lock(); | |
d1b9e139 | 268 | mutex_lock(&trigger_data->lock); |
06f502f5 BW |
269 | |
270 | if (trigger_data->net_dev) { | |
271 | dev_put(trigger_data->net_dev); | |
272 | trigger_data->net_dev = NULL; | |
273 | } | |
274 | ||
28a6a2ef | 275 | memcpy(trigger_data->device_name, name, size); |
90934643 | 276 | trigger_data->device_name[size] = 0; |
06f502f5 BW |
277 | if (size > 0 && trigger_data->device_name[size - 1] == '\n') |
278 | trigger_data->device_name[size - 1] = 0; | |
279 | ||
280 | if (trigger_data->device_name[0] != 0) | |
281 | trigger_data->net_dev = | |
282 | dev_get_by_name(&init_net, trigger_data->device_name); | |
283 | ||
e2f24cb1 | 284 | trigger_data->carrier_link_up = false; |
d5e01266 | 285 | trigger_data->link_speed = SPEED_UNKNOWN; |
f22f95b9 | 286 | trigger_data->duplex = DUPLEX_UNKNOWN; |
fe2b1226 | 287 | if (trigger_data->net_dev) |
d5e01266 | 288 | get_device_state(trigger_data); |
06f502f5 BW |
289 | |
290 | trigger_data->last_activity = 0; | |
291 | ||
f574751c HK |
292 | /* Skip if we're called from netdev_trig_activate() and hw_control is true */ |
293 | if (!trigger_data->hw_control || led_get_trigger_data(trigger_data->led_cdev)) | |
294 | set_baseline_state(trigger_data); | |
295 | ||
d1b9e139 | 296 | mutex_unlock(&trigger_data->lock); |
fe2b1226 | 297 | rtnl_unlock(); |
06f502f5 | 298 | |
28a6a2ef AL |
299 | return 0; |
300 | } | |
301 | ||
302 | static ssize_t device_name_store(struct device *dev, | |
303 | struct device_attribute *attr, const char *buf, | |
304 | size_t size) | |
305 | { | |
306 | struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); | |
307 | int ret; | |
308 | ||
28a6a2ef AL |
309 | ret = set_device_name(trigger_data, buf, size); |
310 | ||
311 | if (ret < 0) | |
312 | return ret; | |
06cdca01 CM |
313 | |
314 | /* Refresh link_speed visibility */ | |
315 | sysfs_update_group(&dev->kobj, &netdev_trig_link_speed_attrs_group); | |
316 | ||
06f502f5 BW |
317 | return size; |
318 | } | |
319 | ||
320 | static DEVICE_ATTR_RW(device_name); | |
321 | ||
322 | static ssize_t netdev_led_attr_show(struct device *dev, char *buf, | |
bdec9cb8 | 323 | enum led_trigger_netdev_modes attr) |
06f502f5 | 324 | { |
f8112a1d | 325 | struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); |
06f502f5 BW |
326 | int bit; |
327 | ||
328 | switch (attr) { | |
bdec9cb8 | 329 | case TRIGGER_NETDEV_LINK: |
d5e01266 CM |
330 | case TRIGGER_NETDEV_LINK_10: |
331 | case TRIGGER_NETDEV_LINK_100: | |
332 | case TRIGGER_NETDEV_LINK_1000: | |
59b3e31e DG |
333 | case TRIGGER_NETDEV_LINK_2500: |
334 | case TRIGGER_NETDEV_LINK_5000: | |
335 | case TRIGGER_NETDEV_LINK_10000: | |
f22f95b9 CM |
336 | case TRIGGER_NETDEV_HALF_DUPLEX: |
337 | case TRIGGER_NETDEV_FULL_DUPLEX: | |
bdec9cb8 CM |
338 | case TRIGGER_NETDEV_TX: |
339 | case TRIGGER_NETDEV_RX: | |
340 | bit = attr; | |
06f502f5 BW |
341 | break; |
342 | default: | |
343 | return -EINVAL; | |
344 | } | |
345 | ||
346 | return sprintf(buf, "%u\n", test_bit(bit, &trigger_data->mode)); | |
347 | } | |
348 | ||
349 | static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, | |
bdec9cb8 | 350 | size_t size, enum led_trigger_netdev_modes attr) |
06f502f5 | 351 | { |
f8112a1d | 352 | struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); |
4289e434 | 353 | struct led_classdev *led_cdev = trigger_data->led_cdev; |
d5e01266 | 354 | unsigned long state, mode = trigger_data->mode; |
06f502f5 BW |
355 | int ret; |
356 | int bit; | |
357 | ||
358 | ret = kstrtoul(buf, 0, &state); | |
359 | if (ret) | |
360 | return ret; | |
361 | ||
362 | switch (attr) { | |
bdec9cb8 | 363 | case TRIGGER_NETDEV_LINK: |
d5e01266 CM |
364 | case TRIGGER_NETDEV_LINK_10: |
365 | case TRIGGER_NETDEV_LINK_100: | |
366 | case TRIGGER_NETDEV_LINK_1000: | |
59b3e31e DG |
367 | case TRIGGER_NETDEV_LINK_2500: |
368 | case TRIGGER_NETDEV_LINK_5000: | |
369 | case TRIGGER_NETDEV_LINK_10000: | |
f22f95b9 CM |
370 | case TRIGGER_NETDEV_HALF_DUPLEX: |
371 | case TRIGGER_NETDEV_FULL_DUPLEX: | |
bdec9cb8 CM |
372 | case TRIGGER_NETDEV_TX: |
373 | case TRIGGER_NETDEV_RX: | |
374 | bit = attr; | |
06f502f5 BW |
375 | break; |
376 | default: | |
377 | return -EINVAL; | |
378 | } | |
379 | ||
06f502f5 | 380 | if (state) |
d5e01266 | 381 | set_bit(bit, &mode); |
06f502f5 | 382 | else |
d5e01266 CM |
383 | clear_bit(bit, &mode); |
384 | ||
385 | if (test_bit(TRIGGER_NETDEV_LINK, &mode) && | |
386 | (test_bit(TRIGGER_NETDEV_LINK_10, &mode) || | |
387 | test_bit(TRIGGER_NETDEV_LINK_100, &mode) || | |
59b3e31e DG |
388 | test_bit(TRIGGER_NETDEV_LINK_1000, &mode) || |
389 | test_bit(TRIGGER_NETDEV_LINK_2500, &mode) || | |
390 | test_bit(TRIGGER_NETDEV_LINK_5000, &mode) || | |
391 | test_bit(TRIGGER_NETDEV_LINK_10000, &mode))) | |
d5e01266 CM |
392 | return -EINVAL; |
393 | ||
394 | cancel_delayed_work_sync(&trigger_data->work); | |
06f502f5 | 395 | |
d5e01266 | 396 | trigger_data->mode = mode; |
4fd1b6d4 CM |
397 | trigger_data->hw_control = can_hw_control(trigger_data); |
398 | ||
4289e434 HK |
399 | if (!led_cdev->brightness_set && !led_cdev->brightness_set_blocking && |
400 | !trigger_data->hw_control) | |
401 | return -EOPNOTSUPP; | |
402 | ||
06f502f5 BW |
403 | set_baseline_state(trigger_data); |
404 | ||
405 | return size; | |
406 | } | |
407 | ||
164b67d5 CM |
408 | #define DEFINE_NETDEV_TRIGGER(trigger_name, trigger) \ |
409 | static ssize_t trigger_name##_show(struct device *dev, \ | |
410 | struct device_attribute *attr, char *buf) \ | |
411 | { \ | |
412 | return netdev_led_attr_show(dev, buf, trigger); \ | |
413 | } \ | |
414 | static ssize_t trigger_name##_store(struct device *dev, \ | |
415 | struct device_attribute *attr, const char *buf, size_t size) \ | |
416 | { \ | |
417 | return netdev_led_attr_store(dev, buf, size, trigger); \ | |
418 | } \ | |
419 | static DEVICE_ATTR_RW(trigger_name) | |
420 | ||
421 | DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK); | |
d5e01266 CM |
422 | DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10); |
423 | DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100); | |
424 | DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000); | |
59b3e31e DG |
425 | DEFINE_NETDEV_TRIGGER(link_2500, TRIGGER_NETDEV_LINK_2500); |
426 | DEFINE_NETDEV_TRIGGER(link_5000, TRIGGER_NETDEV_LINK_5000); | |
427 | DEFINE_NETDEV_TRIGGER(link_10000, TRIGGER_NETDEV_LINK_10000); | |
f22f95b9 CM |
428 | DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX); |
429 | DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX); | |
164b67d5 CM |
430 | DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX); |
431 | DEFINE_NETDEV_TRIGGER(rx, TRIGGER_NETDEV_RX); | |
06f502f5 BW |
432 | |
433 | static ssize_t interval_show(struct device *dev, | |
434 | struct device_attribute *attr, char *buf) | |
435 | { | |
f8112a1d | 436 | struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); |
06f502f5 BW |
437 | |
438 | return sprintf(buf, "%u\n", | |
439 | jiffies_to_msecs(atomic_read(&trigger_data->interval))); | |
440 | } | |
441 | ||
442 | static ssize_t interval_store(struct device *dev, | |
443 | struct device_attribute *attr, const char *buf, | |
444 | size_t size) | |
445 | { | |
f8112a1d | 446 | struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); |
06f502f5 BW |
447 | unsigned long value; |
448 | int ret; | |
449 | ||
c84c80c7 CM |
450 | if (trigger_data->hw_control) |
451 | return -EINVAL; | |
452 | ||
06f502f5 BW |
453 | ret = kstrtoul(buf, 0, &value); |
454 | if (ret) | |
455 | return ret; | |
456 | ||
457 | /* impose some basic bounds on the timer interval */ | |
458 | if (value >= 5 && value <= 10000) { | |
459 | cancel_delayed_work_sync(&trigger_data->work); | |
460 | ||
461 | atomic_set(&trigger_data->interval, msecs_to_jiffies(value)); | |
462 | set_baseline_state(trigger_data); /* resets timer */ | |
463 | } | |
464 | ||
465 | return size; | |
466 | } | |
467 | ||
468 | static DEVICE_ATTR_RW(interval); | |
469 | ||
44f0fb8d MB |
470 | static ssize_t offloaded_show(struct device *dev, |
471 | struct device_attribute *attr, char *buf) | |
b655892f CM |
472 | { |
473 | struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); | |
474 | ||
475 | return sprintf(buf, "%d\n", trigger_data->hw_control); | |
476 | } | |
477 | ||
44f0fb8d | 478 | static DEVICE_ATTR_RO(offloaded); |
b655892f | 479 | |
06cdca01 CM |
480 | #define CHECK_LINK_MODE_ATTR(link_speed) \ |
481 | do { \ | |
482 | if (attr == &dev_attr_link_##link_speed.attr && \ | |
483 | link_ksettings.base.speed == SPEED_##link_speed) \ | |
484 | return attr->mode; \ | |
485 | } while (0) | |
486 | ||
487 | static umode_t netdev_trig_link_speed_visible(struct kobject *kobj, | |
488 | struct attribute *attr, int n) | |
489 | { | |
490 | struct device *dev = kobj_to_dev(kobj); | |
491 | struct led_netdev_data *trigger_data; | |
492 | unsigned long *supported_link_modes; | |
493 | u32 mode; | |
494 | ||
495 | trigger_data = led_trigger_get_drvdata(dev); | |
496 | supported_link_modes = trigger_data->supported_link_modes; | |
497 | ||
498 | /* | |
499 | * Search in the supported link mode mask a matching supported mode. | |
500 | * Stop at the first matching entry as we care only to check if a particular | |
501 | * speed is supported and not the kind. | |
502 | */ | |
503 | for_each_set_bit(mode, supported_link_modes, __ETHTOOL_LINK_MODE_MASK_NBITS) { | |
504 | struct ethtool_link_ksettings link_ksettings; | |
505 | ||
506 | ethtool_params_from_link_mode(&link_ksettings, mode); | |
507 | ||
508 | CHECK_LINK_MODE_ATTR(10); | |
509 | CHECK_LINK_MODE_ATTR(100); | |
510 | CHECK_LINK_MODE_ATTR(1000); | |
511 | CHECK_LINK_MODE_ATTR(2500); | |
512 | CHECK_LINK_MODE_ATTR(5000); | |
513 | CHECK_LINK_MODE_ATTR(10000); | |
514 | } | |
515 | ||
516 | return 0; | |
517 | } | |
518 | ||
519 | static struct attribute *netdev_trig_link_speed_attrs[] = { | |
d5e01266 CM |
520 | &dev_attr_link_10.attr, |
521 | &dev_attr_link_100.attr, | |
522 | &dev_attr_link_1000.attr, | |
59b3e31e DG |
523 | &dev_attr_link_2500.attr, |
524 | &dev_attr_link_5000.attr, | |
525 | &dev_attr_link_10000.attr, | |
06cdca01 CM |
526 | NULL |
527 | }; | |
528 | ||
529 | static const struct attribute_group netdev_trig_link_speed_attrs_group = { | |
530 | .attrs = netdev_trig_link_speed_attrs, | |
531 | .is_visible = netdev_trig_link_speed_visible, | |
532 | }; | |
533 | ||
534 | static struct attribute *netdev_trig_attrs[] = { | |
535 | &dev_attr_device_name.attr, | |
536 | &dev_attr_link.attr, | |
f22f95b9 CM |
537 | &dev_attr_full_duplex.attr, |
538 | &dev_attr_half_duplex.attr, | |
f8112a1d UKK |
539 | &dev_attr_rx.attr, |
540 | &dev_attr_tx.attr, | |
541 | &dev_attr_interval.attr, | |
44f0fb8d | 542 | &dev_attr_offloaded.attr, |
f8112a1d UKK |
543 | NULL |
544 | }; | |
06cdca01 CM |
545 | |
546 | static const struct attribute_group netdev_trig_attrs_group = { | |
547 | .attrs = netdev_trig_attrs, | |
548 | }; | |
549 | ||
550 | static const struct attribute_group *netdev_trig_groups[] = { | |
551 | &netdev_trig_attrs_group, | |
552 | &netdev_trig_link_speed_attrs_group, | |
553 | NULL, | |
554 | }; | |
f8112a1d | 555 | |
06f502f5 BW |
556 | static int netdev_trig_notify(struct notifier_block *nb, |
557 | unsigned long evt, void *dv) | |
558 | { | |
559 | struct net_device *dev = | |
560 | netdev_notifier_info_to_dev((struct netdev_notifier_info *)dv); | |
f8112a1d UKK |
561 | struct led_netdev_data *trigger_data = |
562 | container_of(nb, struct led_netdev_data, notifier); | |
06cdca01 | 563 | struct led_classdev *led_cdev = trigger_data->led_cdev; |
06f502f5 BW |
564 | |
565 | if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE | |
5f820ed5 MS |
566 | && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER |
567 | && evt != NETDEV_CHANGENAME) | |
06f502f5 BW |
568 | return NOTIFY_DONE; |
569 | ||
4cb65605 | 570 | if (!(dev == trigger_data->net_dev || |
5f820ed5 | 571 | (evt == NETDEV_CHANGENAME && !strcmp(dev->name, trigger_data->device_name)) || |
4cb65605 | 572 | (evt == NETDEV_REGISTER && !strcmp(dev->name, trigger_data->device_name)))) |
06f502f5 BW |
573 | return NOTIFY_DONE; |
574 | ||
575 | cancel_delayed_work_sync(&trigger_data->work); | |
576 | ||
d1b9e139 | 577 | mutex_lock(&trigger_data->lock); |
06f502f5 | 578 | |
e2f24cb1 | 579 | trigger_data->carrier_link_up = false; |
d5e01266 | 580 | trigger_data->link_speed = SPEED_UNKNOWN; |
f22f95b9 | 581 | trigger_data->duplex = DUPLEX_UNKNOWN; |
06f502f5 | 582 | switch (evt) { |
5f820ed5 | 583 | case NETDEV_CHANGENAME: |
06f502f5 | 584 | case NETDEV_REGISTER: |
af7320ec | 585 | dev_put(trigger_data->net_dev); |
06f502f5 BW |
586 | dev_hold(dev); |
587 | trigger_data->net_dev = dev; | |
415798bc CM |
588 | if (evt == NETDEV_CHANGENAME) |
589 | get_device_state(trigger_data); | |
06f502f5 | 590 | break; |
06f502f5 | 591 | case NETDEV_UNREGISTER: |
4cb65605 RM |
592 | dev_put(trigger_data->net_dev); |
593 | trigger_data->net_dev = NULL; | |
06f502f5 BW |
594 | break; |
595 | case NETDEV_UP: | |
596 | case NETDEV_CHANGE: | |
d5e01266 | 597 | get_device_state(trigger_data); |
06cdca01 CM |
598 | /* Refresh link_speed visibility */ |
599 | if (evt == NETDEV_CHANGE) | |
600 | sysfs_update_group(&led_cdev->dev->kobj, | |
601 | &netdev_trig_link_speed_attrs_group); | |
06f502f5 BW |
602 | break; |
603 | } | |
604 | ||
605 | set_baseline_state(trigger_data); | |
606 | ||
d1b9e139 | 607 | mutex_unlock(&trigger_data->lock); |
06f502f5 BW |
608 | |
609 | return NOTIFY_DONE; | |
610 | } | |
611 | ||
612 | /* here's the real work! */ | |
613 | static void netdev_trig_work(struct work_struct *work) | |
614 | { | |
f8112a1d UKK |
615 | struct led_netdev_data *trigger_data = |
616 | container_of(work, struct led_netdev_data, work.work); | |
06f502f5 BW |
617 | struct rtnl_link_stats64 *dev_stats; |
618 | unsigned int new_activity; | |
619 | struct rtnl_link_stats64 temp; | |
620 | unsigned long interval; | |
621 | int invert; | |
622 | ||
623 | /* If we dont have a device, insure we are off */ | |
624 | if (!trigger_data->net_dev) { | |
625 | led_set_brightness(trigger_data->led_cdev, LED_OFF); | |
626 | return; | |
627 | } | |
628 | ||
629 | /* If we are not looking for RX/TX then return */ | |
bdec9cb8 CM |
630 | if (!test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) && |
631 | !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode)) | |
06f502f5 BW |
632 | return; |
633 | ||
634 | dev_stats = dev_get_stats(trigger_data->net_dev, &temp); | |
635 | new_activity = | |
bdec9cb8 | 636 | (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ? |
06f502f5 | 637 | dev_stats->tx_packets : 0) + |
bdec9cb8 | 638 | (test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ? |
06f502f5 BW |
639 | dev_stats->rx_packets : 0); |
640 | ||
641 | if (trigger_data->last_activity != new_activity) { | |
642 | led_stop_software_blink(trigger_data->led_cdev); | |
643 | ||
d5e01266 CM |
644 | invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode) || |
645 | test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) || | |
646 | test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) || | |
f22f95b9 | 647 | test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) || |
59b3e31e DG |
648 | test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) || |
649 | test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) || | |
650 | test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) || | |
f22f95b9 CM |
651 | test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) || |
652 | test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode); | |
06f502f5 BW |
653 | interval = jiffies_to_msecs( |
654 | atomic_read(&trigger_data->interval)); | |
655 | /* base state is ON (link present) */ | |
656 | led_blink_set_oneshot(trigger_data->led_cdev, | |
657 | &interval, | |
658 | &interval, | |
659 | invert); | |
660 | trigger_data->last_activity = new_activity; | |
661 | } | |
662 | ||
663 | schedule_delayed_work(&trigger_data->work, | |
664 | (atomic_read(&trigger_data->interval)*2)); | |
665 | } | |
666 | ||
2282e125 | 667 | static int netdev_trig_activate(struct led_classdev *led_cdev) |
06f502f5 BW |
668 | { |
669 | struct led_netdev_data *trigger_data; | |
97c5209b | 670 | unsigned long mode = 0; |
0316cc56 | 671 | struct device *dev; |
06f502f5 BW |
672 | int rc; |
673 | ||
674 | trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL); | |
675 | if (!trigger_data) | |
f8112a1d | 676 | return -ENOMEM; |
06f502f5 | 677 | |
d1b9e139 | 678 | mutex_init(&trigger_data->lock); |
06f502f5 BW |
679 | |
680 | trigger_data->notifier.notifier_call = netdev_trig_notify; | |
681 | trigger_data->notifier.priority = 10; | |
682 | ||
683 | INIT_DELAYED_WORK(&trigger_data->work, netdev_trig_work); | |
684 | ||
685 | trigger_data->led_cdev = led_cdev; | |
686 | trigger_data->net_dev = NULL; | |
687 | trigger_data->device_name[0] = 0; | |
688 | ||
689 | trigger_data->mode = 0; | |
7c145a34 | 690 | atomic_set(&trigger_data->interval, msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL)); |
06f502f5 BW |
691 | trigger_data->last_activity = 0; |
692 | ||
0316cc56 CM |
693 | /* Check if hw control is active by default on the LED. |
694 | * Init already enabled mode in hw control. | |
695 | */ | |
7df1f14c | 696 | if (supports_hw_control(led_cdev)) { |
0316cc56 CM |
697 | dev = led_cdev->hw_control_get_device(led_cdev); |
698 | if (dev) { | |
699 | const char *name = dev_name(dev); | |
700 | ||
0316cc56 | 701 | trigger_data->hw_control = true; |
f574751c | 702 | set_device_name(trigger_data, name, strlen(name)); |
7df1f14c AL |
703 | |
704 | rc = led_cdev->hw_control_get(led_cdev, &mode); | |
705 | if (!rc) | |
706 | trigger_data->mode = mode; | |
0316cc56 CM |
707 | } |
708 | } | |
709 | ||
f8112a1d | 710 | led_set_trigger_data(led_cdev, trigger_data); |
06f502f5 | 711 | |
06f502f5 BW |
712 | rc = register_netdevice_notifier(&trigger_data->notifier); |
713 | if (rc) | |
f8112a1d | 714 | kfree(trigger_data); |
2282e125 | 715 | |
f8112a1d | 716 | return rc; |
06f502f5 BW |
717 | } |
718 | ||
719 | static void netdev_trig_deactivate(struct led_classdev *led_cdev) | |
720 | { | |
f8112a1d | 721 | struct led_netdev_data *trigger_data = led_get_trigger_data(led_cdev); |
06f502f5 | 722 | |
f8112a1d | 723 | unregister_netdevice_notifier(&trigger_data->notifier); |
06f502f5 | 724 | |
f8112a1d | 725 | cancel_delayed_work_sync(&trigger_data->work); |
06f502f5 | 726 | |
e8fbcc47 AL |
727 | led_set_brightness(led_cdev, LED_OFF); |
728 | ||
af7320ec | 729 | dev_put(trigger_data->net_dev); |
06f502f5 | 730 | |
f8112a1d | 731 | kfree(trigger_data); |
06f502f5 BW |
732 | } |
733 | ||
734 | static struct led_trigger netdev_led_trigger = { | |
735 | .name = "netdev", | |
736 | .activate = netdev_trig_activate, | |
737 | .deactivate = netdev_trig_deactivate, | |
f8112a1d | 738 | .groups = netdev_trig_groups, |
06f502f5 BW |
739 | }; |
740 | ||
74cd23e8 | 741 | module_led_trigger(netdev_led_trigger); |
06f502f5 BW |
742 | |
743 | MODULE_AUTHOR("Ben Whitten <ben.whitten@gmail.com>"); | |
744 | MODULE_AUTHOR("Oliver Jowett <oliver@opencloud.com>"); | |
745 | MODULE_DESCRIPTION("Netdev LED trigger"); | |
746 | MODULE_LICENSE("GPL v2"); | |
fd14a872 | 747 | MODULE_ALIAS("ledtrig:netdev"); |