diff options
| -rw-r--r-- | Documentation/ABI/testing/sysfs-class-led-trigger-netdev | 45 | ||||
| -rw-r--r-- | drivers/leds/trigger/Kconfig | 7 | ||||
| -rw-r--r-- | drivers/leds/trigger/Makefile | 1 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-netdev.c | 496 |
4 files changed, 549 insertions, 0 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-led-trigger-netdev b/Documentation/ABI/testing/sysfs-class-led-trigger-netdev new file mode 100644 index 000000000000..451af6d6768c --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-led-trigger-netdev | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | What: /sys/class/leds/<led>/device_name | ||
| 2 | Date: Dec 2017 | ||
| 3 | KernelVersion: 4.16 | ||
| 4 | Contact: linux-leds@vger.kernel.org | ||
| 5 | Description: | ||
| 6 | Specifies the network device name to monitor. | ||
| 7 | |||
| 8 | What: /sys/class/leds/<led>/interval | ||
| 9 | Date: Dec 2017 | ||
| 10 | KernelVersion: 4.16 | ||
| 11 | Contact: linux-leds@vger.kernel.org | ||
| 12 | Description: | ||
| 13 | Specifies the duration of the LED blink in milliseconds. | ||
| 14 | Defaults to 50 ms. | ||
| 15 | |||
| 16 | What: /sys/class/leds/<led>/link | ||
| 17 | Date: Dec 2017 | ||
| 18 | KernelVersion: 4.16 | ||
| 19 | Contact: linux-leds@vger.kernel.org | ||
| 20 | Description: | ||
| 21 | Signal the link state of the named network device. | ||
| 22 | If set to 0 (default), the LED's normal state is off. | ||
| 23 | If set to 1, the LED's normal state reflects the link state | ||
| 24 | of the named network device. | ||
| 25 | Setting this value also immediately changes the LED state. | ||
| 26 | |||
| 27 | What: /sys/class/leds/<led>/tx | ||
| 28 | Date: Dec 2017 | ||
| 29 | KernelVersion: 4.16 | ||
| 30 | Contact: linux-leds@vger.kernel.org | ||
| 31 | Description: | ||
| 32 | Signal transmission of data on the named network device. | ||
| 33 | If set to 0 (default), the LED will not blink on transmission. | ||
| 34 | If set to 1, the LED will blink for the milliseconds specified | ||
| 35 | in interval to signal transmission. | ||
| 36 | |||
| 37 | What: /sys/class/leds/<led>/rx | ||
| 38 | Date: Dec 2017 | ||
| 39 | KernelVersion: 4.16 | ||
| 40 | Contact: linux-leds@vger.kernel.org | ||
| 41 | Description: | ||
| 42 | Signal reception of data on the named network device. | ||
| 43 | If set to 0 (default), the LED will not blink on reception. | ||
| 44 | If set to 1, the LED will blink for the milliseconds specified | ||
| 45 | in interval to signal reception. | ||
diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig index 6dac48c457d1..a2559b4fdfff 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig | |||
| @@ -135,4 +135,11 @@ config LEDS_TRIGGER_PANIC | |||
| 135 | a different trigger. | 135 | a different trigger. |
| 136 | If unsure, say Y. | 136 | If unsure, say Y. |
| 137 | 137 | ||
| 138 | config LEDS_TRIGGER_NETDEV | ||
| 139 | tristate "LED Netdev Trigger" | ||
| 140 | depends on NET && LEDS_TRIGGERS | ||
| 141 | help | ||
| 142 | This allows LEDs to be controlled by network device activity. | ||
| 143 | If unsure, say Y. | ||
| 144 | |||
| 138 | endif # LEDS_TRIGGERS | 145 | endif # LEDS_TRIGGERS |
diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile index 4a8b6cff7761..f3cfe1950538 100644 --- a/drivers/leds/trigger/Makefile +++ b/drivers/leds/trigger/Makefile | |||
| @@ -12,3 +12,4 @@ obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o | |||
| 12 | obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o | 12 | obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o |
| 13 | obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o | 13 | obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o |
| 14 | obj-$(CONFIG_LEDS_TRIGGER_PANIC) += ledtrig-panic.o | 14 | obj-$(CONFIG_LEDS_TRIGGER_PANIC) += ledtrig-panic.o |
| 15 | obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += ledtrig-netdev.o | ||
diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c new file mode 100644 index 000000000000..6df4781a6308 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-netdev.c | |||
| @@ -0,0 +1,496 @@ | |||
| 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> | ||
| 16 | #include <linux/init.h> | ||
| 17 | #include <linux/jiffies.h> | ||
| 18 | #include <linux/kernel.h> | ||
| 19 | #include <linux/leds.h> | ||
| 20 | #include <linux/list.h> | ||
| 21 | #include <linux/module.h> | ||
| 22 | #include <linux/netdevice.h> | ||
| 23 | #include <linux/spinlock.h> | ||
| 24 | #include <linux/timer.h> | ||
| 25 | #include "../leds.h" | ||
| 26 | |||
| 27 | /* | ||
| 28 | * Configurable sysfs attributes: | ||
| 29 | * | ||
| 30 | * device_name - network device name to monitor | ||
| 31 | * interval - duration of LED blink, in milliseconds | ||
| 32 | * link - LED's normal state reflects whether the link is up | ||
| 33 | * (has carrier) or not | ||
| 34 | * tx - LED blinks on transmitted data | ||
| 35 | * rx - LED blinks on receive data | ||
| 36 | * | ||
| 37 | */ | ||
| 38 | |||
| 39 | struct led_netdev_data { | ||
| 40 | spinlock_t lock; | ||
| 41 | |||
| 42 | struct delayed_work work; | ||
| 43 | struct notifier_block notifier; | ||
| 44 | |||
| 45 | struct led_classdev *led_cdev; | ||
| 46 | struct net_device *net_dev; | ||
| 47 | |||
| 48 | char device_name[IFNAMSIZ]; | ||
| 49 | atomic_t interval; | ||
| 50 | unsigned int last_activity; | ||
| 51 | |||
| 52 | unsigned long mode; | ||
| 53 | #define NETDEV_LED_LINK 0 | ||
| 54 | #define NETDEV_LED_TX 1 | ||
| 55 | #define NETDEV_LED_RX 2 | ||
| 56 | #define NETDEV_LED_MODE_LINKUP 3 | ||
| 57 | }; | ||
| 58 | |||
| 59 | enum netdev_led_attr { | ||
| 60 | NETDEV_ATTR_LINK, | ||
| 61 | NETDEV_ATTR_TX, | ||
| 62 | NETDEV_ATTR_RX | ||
| 63 | }; | ||
| 64 | |||
| 65 | static void set_baseline_state(struct led_netdev_data *trigger_data) | ||
| 66 | { | ||
| 67 | int current_brightness; | ||
| 68 | struct led_classdev *led_cdev = trigger_data->led_cdev; | ||
| 69 | |||
| 70 | current_brightness = led_cdev->brightness; | ||
| 71 | if (current_brightness) | ||
| 72 | led_cdev->blink_brightness = current_brightness; | ||
| 73 | if (!led_cdev->blink_brightness) | ||
| 74 | led_cdev->blink_brightness = led_cdev->max_brightness; | ||
| 75 | |||
| 76 | if (!test_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode)) | ||
| 77 | led_set_brightness(led_cdev, LED_OFF); | ||
| 78 | else { | ||
| 79 | if (test_bit(NETDEV_LED_LINK, &trigger_data->mode)) | ||
| 80 | led_set_brightness(led_cdev, | ||
| 81 | led_cdev->blink_brightness); | ||
| 82 | else | ||
| 83 | led_set_brightness(led_cdev, LED_OFF); | ||
| 84 | |||
| 85 | /* If we are looking for RX/TX start periodically | ||
| 86 | * checking stats | ||
| 87 | */ | ||
| 88 | if (test_bit(NETDEV_LED_TX, &trigger_data->mode) || | ||
| 89 | test_bit(NETDEV_LED_RX, &trigger_data->mode)) | ||
| 90 | schedule_delayed_work(&trigger_data->work, 0); | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | static ssize_t device_name_show(struct device *dev, | ||
| 95 | struct device_attribute *attr, char *buf) | ||
| 96 | { | ||
| 97 | struct led_classdev *led_cdev = dev_get_drvdata(dev); | ||
| 98 | struct led_netdev_data *trigger_data = led_cdev->trigger_data; | ||
| 99 | ssize_t len; | ||
| 100 | |||
| 101 | spin_lock_bh(&trigger_data->lock); | ||
| 102 | len = sprintf(buf, "%s\n", trigger_data->device_name); | ||
| 103 | spin_unlock_bh(&trigger_data->lock); | ||
| 104 | |||
| 105 | return len; | ||
| 106 | } | ||
| 107 | |||
| 108 | static ssize_t device_name_store(struct device *dev, | ||
| 109 | struct device_attribute *attr, const char *buf, | ||
| 110 | size_t size) | ||
| 111 | { | ||
| 112 | struct led_classdev *led_cdev = dev_get_drvdata(dev); | ||
| 113 | struct led_netdev_data *trigger_data = led_cdev->trigger_data; | ||
| 114 | |||
| 115 | if (size >= IFNAMSIZ) | ||
| 116 | return -EINVAL; | ||
| 117 | |||
| 118 | cancel_delayed_work_sync(&trigger_data->work); | ||
| 119 | |||
| 120 | spin_lock_bh(&trigger_data->lock); | ||
| 121 | |||
| 122 | if (trigger_data->net_dev) { | ||
| 123 | dev_put(trigger_data->net_dev); | ||
| 124 | trigger_data->net_dev = NULL; | ||
| 125 | } | ||
| 126 | |||
| 127 | strncpy(trigger_data->device_name, buf, size); | ||
| 128 | if (size > 0 && trigger_data->device_name[size - 1] == '\n') | ||
| 129 | trigger_data->device_name[size - 1] = 0; | ||
| 130 | |||
| 131 | if (trigger_data->device_name[0] != 0) | ||
| 132 | trigger_data->net_dev = | ||
| 133 | dev_get_by_name(&init_net, trigger_data->device_name); | ||
| 134 | |||
| 135 | clear_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode); | ||
| 136 | if (trigger_data->net_dev != NULL) | ||
| 137 | if (netif_carrier_ok(trigger_data->net_dev)) | ||
| 138 | set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode); | ||
| 139 | |||
| 140 | trigger_data->last_activity = 0; | ||
| 141 | |||
| 142 | set_baseline_state(trigger_data); | ||
| 143 | spin_unlock_bh(&trigger_data->lock); | ||
| 144 | |||
| 145 | return size; | ||
| 146 | } | ||
| 147 | |||
| 148 | static DEVICE_ATTR_RW(device_name); | ||
| 149 | |||
| 150 | static ssize_t netdev_led_attr_show(struct device *dev, char *buf, | ||
| 151 | enum netdev_led_attr attr) | ||
| 152 | { | ||
| 153 | struct led_classdev *led_cdev = dev_get_drvdata(dev); | ||
| 154 | struct led_netdev_data *trigger_data = led_cdev->trigger_data; | ||
| 155 | int bit; | ||
| 156 | |||
| 157 | switch (attr) { | ||
| 158 | case NETDEV_ATTR_LINK: | ||
| 159 | bit = NETDEV_LED_LINK; | ||
| 160 | break; | ||
| 161 | case NETDEV_ATTR_TX: | ||
| 162 | bit = NETDEV_LED_TX; | ||
| 163 | break; | ||
| 164 | case NETDEV_ATTR_RX: | ||
| 165 | bit = NETDEV_LED_RX; | ||
| 166 | break; | ||
| 167 | default: | ||
| 168 | return -EINVAL; | ||
| 169 | } | ||
| 170 | |||
| 171 | return sprintf(buf, "%u\n", test_bit(bit, &trigger_data->mode)); | ||
| 172 | } | ||
| 173 | |||
| 174 | static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, | ||
| 175 | size_t size, enum netdev_led_attr attr) | ||
| 176 | { | ||
| 177 | struct led_classdev *led_cdev = dev_get_drvdata(dev); | ||
| 178 | struct led_netdev_data *trigger_data = led_cdev->trigger_data; | ||
| 179 | unsigned long state; | ||
| 180 | int ret; | ||
| 181 | int bit; | ||
| 182 | |||
| 183 | ret = kstrtoul(buf, 0, &state); | ||
| 184 | if (ret) | ||
| 185 | return ret; | ||
| 186 | |||
| 187 | switch (attr) { | ||
| 188 | case NETDEV_ATTR_LINK: | ||
| 189 | bit = NETDEV_LED_LINK; | ||
| 190 | break; | ||
| 191 | case NETDEV_ATTR_TX: | ||
| 192 | bit = NETDEV_LED_TX; | ||
| 193 | break; | ||
| 194 | case NETDEV_ATTR_RX: | ||
| 195 | bit = NETDEV_LED_RX; | ||
| 196 | break; | ||
| 197 | default: | ||
| 198 | return -EINVAL; | ||
| 199 | } | ||
| 200 | |||
| 201 | cancel_delayed_work_sync(&trigger_data->work); | ||
| 202 | |||
| 203 | if (state) | ||
| 204 | set_bit(bit, &trigger_data->mode); | ||
| 205 | else | ||
| 206 | clear_bit(bit, &trigger_data->mode); | ||
| 207 | |||
| 208 | set_baseline_state(trigger_data); | ||
| 209 | |||
| 210 | return size; | ||
| 211 | } | ||
| 212 | |||
| 213 | static ssize_t link_show(struct device *dev, | ||
| 214 | struct device_attribute *attr, char *buf) | ||
| 215 | { | ||
| 216 | return netdev_led_attr_show(dev, buf, NETDEV_ATTR_LINK); | ||
| 217 | } | ||
| 218 | |||
| 219 | static ssize_t link_store(struct device *dev, | ||
| 220 | struct device_attribute *attr, const char *buf, size_t size) | ||
| 221 | { | ||
| 222 | return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_LINK); | ||
| 223 | } | ||
| 224 | |||
| 225 | static DEVICE_ATTR_RW(link); | ||
| 226 | |||
| 227 | static ssize_t tx_show(struct device *dev, | ||
| 228 | struct device_attribute *attr, char *buf) | ||
| 229 | { | ||
| 230 | return netdev_led_attr_show(dev, buf, NETDEV_ATTR_TX); | ||
| 231 | } | ||
| 232 | |||
| 233 | static ssize_t tx_store(struct device *dev, | ||
| 234 | struct device_attribute *attr, const char *buf, size_t size) | ||
| 235 | { | ||
| 236 | return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_TX); | ||
| 237 | } | ||
| 238 | |||
| 239 | static DEVICE_ATTR_RW(tx); | ||
| 240 | |||
| 241 | static ssize_t rx_show(struct device *dev, | ||
| 242 | struct device_attribute *attr, char *buf) | ||
| 243 | { | ||
| 244 | return netdev_led_attr_show(dev, buf, NETDEV_ATTR_RX); | ||
| 245 | } | ||
| 246 | |||
| 247 | static ssize_t rx_store(struct device *dev, | ||
| 248 | struct device_attribute *attr, const char *buf, size_t size) | ||
| 249 | { | ||
| 250 | return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_RX); | ||
| 251 | } | ||
| 252 | |||
| 253 | static DEVICE_ATTR_RW(rx); | ||
| 254 | |||
| 255 | static ssize_t interval_show(struct device *dev, | ||
| 256 | struct device_attribute *attr, char *buf) | ||
| 257 | { | ||
| 258 | struct led_classdev *led_cdev = dev_get_drvdata(dev); | ||
| 259 | struct led_netdev_data *trigger_data = led_cdev->trigger_data; | ||
| 260 | |||
| 261 | return sprintf(buf, "%u\n", | ||
| 262 | jiffies_to_msecs(atomic_read(&trigger_data->interval))); | ||
| 263 | } | ||
| 264 | |||
| 265 | static ssize_t interval_store(struct device *dev, | ||
| 266 | struct device_attribute *attr, const char *buf, | ||
| 267 | size_t size) | ||
| 268 | { | ||
| 269 | struct led_classdev *led_cdev = dev_get_drvdata(dev); | ||
| 270 | struct led_netdev_data *trigger_data = led_cdev->trigger_data; | ||
| 271 | unsigned long value; | ||
| 272 | int ret; | ||
| 273 | |||
| 274 | ret = kstrtoul(buf, 0, &value); | ||
| 275 | if (ret) | ||
| 276 | return ret; | ||
| 277 | |||
| 278 | /* impose some basic bounds on the timer interval */ | ||
| 279 | if (value >= 5 && value <= 10000) { | ||
| 280 | cancel_delayed_work_sync(&trigger_data->work); | ||
| 281 | |||
| 282 | atomic_set(&trigger_data->interval, msecs_to_jiffies(value)); | ||
| 283 | set_baseline_state(trigger_data); /* resets timer */ | ||
| 284 | } | ||
| 285 | |||
| 286 | return size; | ||
| 287 | } | ||
| 288 | |||
| 289 | static DEVICE_ATTR_RW(interval); | ||
| 290 | |||
| 291 | static int netdev_trig_notify(struct notifier_block *nb, | ||
| 292 | unsigned long evt, void *dv) | ||
| 293 | { | ||
| 294 | struct net_device *dev = | ||
| 295 | netdev_notifier_info_to_dev((struct netdev_notifier_info *)dv); | ||
| 296 | struct led_netdev_data *trigger_data = container_of(nb, | ||
| 297 | struct | ||
| 298 | led_netdev_data, | ||
| 299 | notifier); | ||
| 300 | |||
| 301 | if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE | ||
| 302 | && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER | ||
| 303 | && evt != NETDEV_CHANGENAME) | ||
| 304 | return NOTIFY_DONE; | ||
| 305 | |||
| 306 | if (strcmp(dev->name, trigger_data->device_name)) | ||
| 307 | return NOTIFY_DONE; | ||
| 308 | |||
| 309 | cancel_delayed_work_sync(&trigger_data->work); | ||
| 310 | |||
| 311 | spin_lock_bh(&trigger_data->lock); | ||
| 312 | |||
| 313 | clear_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode); | ||
| 314 | switch (evt) { | ||
| 315 | case NETDEV_REGISTER: | ||
| 316 | if (trigger_data->net_dev) | ||
| 317 | dev_put(trigger_data->net_dev); | ||
| 318 | dev_hold(dev); | ||
| 319 | trigger_data->net_dev = dev; | ||
| 320 | break; | ||
| 321 | case NETDEV_CHANGENAME: | ||
| 322 | case NETDEV_UNREGISTER: | ||
| 323 | if (trigger_data->net_dev) { | ||
| 324 | dev_put(trigger_data->net_dev); | ||
| 325 | trigger_data->net_dev = NULL; | ||
| 326 | } | ||
| 327 | break; | ||
| 328 | case NETDEV_UP: | ||
| 329 | case NETDEV_CHANGE: | ||
| 330 | if (netif_carrier_ok(dev)) | ||
| 331 | set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode); | ||
| 332 | break; | ||
| 333 | } | ||
| 334 | |||
| 335 | set_baseline_state(trigger_data); | ||
| 336 | |||
| 337 | spin_unlock_bh(&trigger_data->lock); | ||
| 338 | |||
| 339 | return NOTIFY_DONE; | ||
| 340 | } | ||
| 341 | |||
| 342 | /* here's the real work! */ | ||
| 343 | static void netdev_trig_work(struct work_struct *work) | ||
| 344 | { | ||
| 345 | struct led_netdev_data *trigger_data = container_of(work, | ||
| 346 | struct | ||
| 347 | led_netdev_data, | ||
| 348 | work.work); | ||
| 349 | struct rtnl_link_stats64 *dev_stats; | ||
| 350 | unsigned int new_activity; | ||
| 351 | struct rtnl_link_stats64 temp; | ||
| 352 | unsigned long interval; | ||
| 353 | int invert; | ||
| 354 | |||
| 355 | /* If we dont have a device, insure we are off */ | ||
| 356 | if (!trigger_data->net_dev) { | ||
| 357 | led_set_brightness(trigger_data->led_cdev, LED_OFF); | ||
| 358 | return; | ||
| 359 | } | ||
| 360 | |||
| 361 | /* If we are not looking for RX/TX then return */ | ||
| 362 | if (!test_bit(NETDEV_LED_TX, &trigger_data->mode) && | ||
| 363 | !test_bit(NETDEV_LED_RX, &trigger_data->mode)) | ||
| 364 | return; | ||
| 365 | |||
| 366 | dev_stats = dev_get_stats(trigger_data->net_dev, &temp); | ||
| 367 | new_activity = | ||
| 368 | (test_bit(NETDEV_LED_TX, &trigger_data->mode) ? | ||
| 369 | dev_stats->tx_packets : 0) + | ||
| 370 | (test_bit(NETDEV_LED_RX, &trigger_data->mode) ? | ||
| 371 | dev_stats->rx_packets : 0); | ||
| 372 | |||
| 373 | if (trigger_data->last_activity != new_activity) { | ||
| 374 | led_stop_software_blink(trigger_data->led_cdev); | ||
| 375 | |||
| 376 | invert = test_bit(NETDEV_LED_LINK, &trigger_data->mode); | ||
| 377 | interval = jiffies_to_msecs( | ||
| 378 | atomic_read(&trigger_data->interval)); | ||
| 379 | /* base state is ON (link present) */ | ||
| 380 | led_blink_set_oneshot(trigger_data->led_cdev, | ||
| 381 | &interval, | ||
| 382 | &interval, | ||
| 383 | invert); | ||
| 384 | trigger_data->last_activity = new_activity; | ||
| 385 | } | ||
| 386 | |||
| 387 | schedule_delayed_work(&trigger_data->work, | ||
| 388 | (atomic_read(&trigger_data->interval)*2)); | ||
| 389 | } | ||
| 390 | |||
| 391 | static void netdev_trig_activate(struct led_classdev *led_cdev) | ||
| 392 | { | ||
| 393 | struct led_netdev_data *trigger_data; | ||
| 394 | int rc; | ||
| 395 | |||
| 396 | trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL); | ||
| 397 | if (!trigger_data) | ||
| 398 | return; | ||
| 399 | |||
| 400 | spin_lock_init(&trigger_data->lock); | ||
| 401 | |||
| 402 | trigger_data->notifier.notifier_call = netdev_trig_notify; | ||
| 403 | trigger_data->notifier.priority = 10; | ||
| 404 | |||
| 405 | INIT_DELAYED_WORK(&trigger_data->work, netdev_trig_work); | ||
| 406 | |||
| 407 | trigger_data->led_cdev = led_cdev; | ||
| 408 | trigger_data->net_dev = NULL; | ||
| 409 | trigger_data->device_name[0] = 0; | ||
| 410 | |||
| 411 | trigger_data->mode = 0; | ||
| 412 | atomic_set(&trigger_data->interval, msecs_to_jiffies(50)); | ||
| 413 | trigger_data->last_activity = 0; | ||
| 414 | |||
| 415 | led_cdev->trigger_data = trigger_data; | ||
| 416 | |||
| 417 | rc = device_create_file(led_cdev->dev, &dev_attr_device_name); | ||
| 418 | if (rc) | ||
| 419 | goto err_out; | ||
| 420 | rc = device_create_file(led_cdev->dev, &dev_attr_link); | ||
| 421 | if (rc) | ||
| 422 | goto err_out_device_name; | ||
| 423 | rc = device_create_file(led_cdev->dev, &dev_attr_rx); | ||
| 424 | if (rc) | ||
| 425 | goto err_out_link; | ||
| 426 | rc = device_create_file(led_cdev->dev, &dev_attr_tx); | ||
| 427 | if (rc) | ||
| 428 | goto err_out_rx; | ||
| 429 | rc = device_create_file(led_cdev->dev, &dev_attr_interval); | ||
| 430 | if (rc) | ||
| 431 | goto err_out_tx; | ||
| 432 | rc = register_netdevice_notifier(&trigger_data->notifier); | ||
| 433 | if (rc) | ||
| 434 | goto err_out_interval; | ||
| 435 | return; | ||
| 436 | |||
| 437 | err_out_interval: | ||
| 438 | device_remove_file(led_cdev->dev, &dev_attr_interval); | ||
| 439 | err_out_tx: | ||
| 440 | device_remove_file(led_cdev->dev, &dev_attr_tx); | ||
| 441 | err_out_rx: | ||
| 442 | device_remove_file(led_cdev->dev, &dev_attr_rx); | ||
| 443 | err_out_link: | ||
| 444 | device_remove_file(led_cdev->dev, &dev_attr_link); | ||
| 445 | err_out_device_name: | ||
| 446 | device_remove_file(led_cdev->dev, &dev_attr_device_name); | ||
| 447 | err_out: | ||
| 448 | led_cdev->trigger_data = NULL; | ||
| 449 | kfree(trigger_data); | ||
| 450 | } | ||
| 451 | |||
| 452 | static void netdev_trig_deactivate(struct led_classdev *led_cdev) | ||
| 453 | { | ||
| 454 | struct led_netdev_data *trigger_data = led_cdev->trigger_data; | ||
| 455 | |||
| 456 | if (trigger_data) { | ||
| 457 | unregister_netdevice_notifier(&trigger_data->notifier); | ||
| 458 | |||
| 459 | device_remove_file(led_cdev->dev, &dev_attr_device_name); | ||
| 460 | device_remove_file(led_cdev->dev, &dev_attr_link); | ||
| 461 | device_remove_file(led_cdev->dev, &dev_attr_rx); | ||
| 462 | device_remove_file(led_cdev->dev, &dev_attr_tx); | ||
| 463 | device_remove_file(led_cdev->dev, &dev_attr_interval); | ||
| 464 | |||
| 465 | cancel_delayed_work_sync(&trigger_data->work); | ||
| 466 | |||
| 467 | if (trigger_data->net_dev) | ||
| 468 | dev_put(trigger_data->net_dev); | ||
| 469 | |||
| 470 | kfree(trigger_data); | ||
| 471 | } | ||
| 472 | } | ||
| 473 | |||
| 474 | static struct led_trigger netdev_led_trigger = { | ||
| 475 | .name = "netdev", | ||
| 476 | .activate = netdev_trig_activate, | ||
| 477 | .deactivate = netdev_trig_deactivate, | ||
| 478 | }; | ||
| 479 | |||
| 480 | static int __init netdev_trig_init(void) | ||
| 481 | { | ||
| 482 | return led_trigger_register(&netdev_led_trigger); | ||
| 483 | } | ||
| 484 | |||
| 485 | static void __exit netdev_trig_exit(void) | ||
| 486 | { | ||
| 487 | led_trigger_unregister(&netdev_led_trigger); | ||
| 488 | } | ||
| 489 | |||
| 490 | module_init(netdev_trig_init); | ||
| 491 | module_exit(netdev_trig_exit); | ||
| 492 | |||
| 493 | MODULE_AUTHOR("Ben Whitten <ben.whitten@gmail.com>"); | ||
| 494 | MODULE_AUTHOR("Oliver Jowett <oliver@opencloud.com>"); | ||
| 495 | MODULE_DESCRIPTION("Netdev LED trigger"); | ||
| 496 | MODULE_LICENSE("GPL v2"); | ||
