| 1 | /* |
|---|
| 2 | * LED USB device Trigger |
|---|
| 3 | * |
|---|
| 4 | * Toggles the LED to reflect the presence and activity of an USB device |
|---|
| 5 | * Copyright (C) Gabor Juhos <juhosg@openwrt.org> |
|---|
| 6 | * |
|---|
| 7 | * derived from ledtrig-netdev.c: |
|---|
| 8 | * Copyright 2007 Oliver Jowett <oliver@opencloud.com> |
|---|
| 9 | * |
|---|
| 10 | * ledtrig-netdev.c derived from ledtrig-timer.c: |
|---|
| 11 | * Copyright 2005-2006 Openedhand Ltd. |
|---|
| 12 | * Author: Richard Purdie <rpurdie@openedhand.com> |
|---|
| 13 | * |
|---|
| 14 | * This program is free software; you can redistribute it and/or modify |
|---|
| 15 | * it under the terms of the GNU General Public License version 2 as |
|---|
| 16 | * published by the Free Software Foundation. |
|---|
| 17 | * |
|---|
| 18 | */ |
|---|
| 19 | |
|---|
| 20 | #include <linux/module.h> |
|---|
| 21 | #include <linux/jiffies.h> |
|---|
| 22 | #include <linux/kernel.h> |
|---|
| 23 | #include <linux/init.h> |
|---|
| 24 | #include <linux/list.h> |
|---|
| 25 | #include <linux/spinlock.h> |
|---|
| 26 | #include <linux/device.h> |
|---|
| 27 | #include <linux/sysdev.h> |
|---|
| 28 | #include <linux/timer.h> |
|---|
| 29 | #include <linux/ctype.h> |
|---|
| 30 | #include <linux/slab.h> |
|---|
| 31 | #include <linux/leds.h> |
|---|
| 32 | #include <linux/usb.h> |
|---|
| 33 | |
|---|
| 34 | #include "leds.h" |
|---|
| 35 | |
|---|
| 36 | #define DEV_BUS_ID_SIZE 32 |
|---|
| 37 | |
|---|
| 38 | /* |
|---|
| 39 | * Configurable sysfs attributes: |
|---|
| 40 | * |
|---|
| 41 | * device_name - name of the USB device to monitor |
|---|
| 42 | * activity_interval - duration of LED blink, in milliseconds |
|---|
| 43 | */ |
|---|
| 44 | |
|---|
| 45 | struct usbdev_trig_data { |
|---|
| 46 | rwlock_t lock; |
|---|
| 47 | |
|---|
| 48 | struct timer_list timer; |
|---|
| 49 | struct notifier_block notifier; |
|---|
| 50 | |
|---|
| 51 | struct led_classdev *led_cdev; |
|---|
| 52 | struct usb_device *usb_dev; |
|---|
| 53 | |
|---|
| 54 | char device_name[DEV_BUS_ID_SIZE]; |
|---|
| 55 | unsigned interval; |
|---|
| 56 | int last_urbnum; |
|---|
| 57 | }; |
|---|
| 58 | |
|---|
| 59 | static void usbdev_trig_update_state(struct usbdev_trig_data *td) |
|---|
| 60 | { |
|---|
| 61 | if (td->usb_dev) |
|---|
| 62 | led_set_brightness(td->led_cdev, LED_FULL); |
|---|
| 63 | else |
|---|
| 64 | led_set_brightness(td->led_cdev, LED_OFF); |
|---|
| 65 | |
|---|
| 66 | if (td->interval && td->usb_dev) |
|---|
| 67 | mod_timer(&td->timer, jiffies + td->interval); |
|---|
| 68 | else |
|---|
| 69 | del_timer(&td->timer); |
|---|
| 70 | } |
|---|
| 71 | |
|---|
| 72 | static ssize_t usbdev_trig_name_show(struct device *dev, |
|---|
| 73 | struct device_attribute *attr, |
|---|
| 74 | char *buf) |
|---|
| 75 | { |
|---|
| 76 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
|---|
| 77 | struct usbdev_trig_data *td = led_cdev->trigger_data; |
|---|
| 78 | |
|---|
| 79 | read_lock(&td->lock); |
|---|
| 80 | sprintf(buf, "%s\n", td->device_name); |
|---|
| 81 | read_unlock(&td->lock); |
|---|
| 82 | |
|---|
| 83 | return strlen(buf) + 1; |
|---|
| 84 | } |
|---|
| 85 | |
|---|
| 86 | static ssize_t usbdev_trig_name_store(struct device *dev, |
|---|
| 87 | struct device_attribute *attr, |
|---|
| 88 | const char *buf, |
|---|
| 89 | size_t size) |
|---|
| 90 | { |
|---|
| 91 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
|---|
| 92 | struct usbdev_trig_data *td = led_cdev->trigger_data; |
|---|
| 93 | |
|---|
| 94 | if (size < 0 || size >= DEV_BUS_ID_SIZE) |
|---|
| 95 | return -EINVAL; |
|---|
| 96 | |
|---|
| 97 | write_lock(&td->lock); |
|---|
| 98 | |
|---|
| 99 | strcpy(td->device_name, buf); |
|---|
| 100 | if (size > 0 && td->device_name[size - 1] == '\n') |
|---|
| 101 | td->device_name[size - 1] = 0; |
|---|
| 102 | |
|---|
| 103 | if (td->device_name[0] != 0) { |
|---|
| 104 | struct usb_device *usb_dev; |
|---|
| 105 | |
|---|
| 106 | /* check for existing device to update from */ |
|---|
| 107 | usb_dev = usb_find_device_by_name(td->device_name); |
|---|
| 108 | if (usb_dev) { |
|---|
| 109 | if (td->usb_dev) |
|---|
| 110 | usb_put_dev(td->usb_dev); |
|---|
| 111 | |
|---|
| 112 | td->usb_dev = usb_dev; |
|---|
| 113 | td->last_urbnum = atomic_read(&usb_dev->urbnum); |
|---|
| 114 | } |
|---|
| 115 | |
|---|
| 116 | /* updates LEDs, may start timers */ |
|---|
| 117 | usbdev_trig_update_state(td); |
|---|
| 118 | } |
|---|
| 119 | |
|---|
| 120 | write_unlock(&td->lock); |
|---|
| 121 | return size; |
|---|
| 122 | } |
|---|
| 123 | |
|---|
| 124 | static DEVICE_ATTR(device_name, 0644, usbdev_trig_name_show, |
|---|
| 125 | usbdev_trig_name_store); |
|---|
| 126 | |
|---|
| 127 | static ssize_t usbdev_trig_interval_show(struct device *dev, |
|---|
| 128 | struct device_attribute *attr, |
|---|
| 129 | char *buf) |
|---|
| 130 | { |
|---|
| 131 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
|---|
| 132 | struct usbdev_trig_data *td = led_cdev->trigger_data; |
|---|
| 133 | |
|---|
| 134 | read_lock(&td->lock); |
|---|
| 135 | sprintf(buf, "%u\n", jiffies_to_msecs(td->interval)); |
|---|
| 136 | read_unlock(&td->lock); |
|---|
| 137 | |
|---|
| 138 | return strlen(buf) + 1; |
|---|
| 139 | } |
|---|
| 140 | |
|---|
| 141 | static ssize_t usbdev_trig_interval_store(struct device *dev, |
|---|
| 142 | struct device_attribute *attr, |
|---|
| 143 | const char *buf, |
|---|
| 144 | size_t size) |
|---|
| 145 | { |
|---|
| 146 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
|---|
| 147 | struct usbdev_trig_data *td = led_cdev->trigger_data; |
|---|
| 148 | int ret = -EINVAL; |
|---|
| 149 | char *after; |
|---|
| 150 | unsigned long value = simple_strtoul(buf, &after, 10); |
|---|
| 151 | size_t count = after - buf; |
|---|
| 152 | |
|---|
| 153 | if (*after && isspace(*after)) |
|---|
| 154 | count++; |
|---|
| 155 | |
|---|
| 156 | if (count == size && value <= 10000) { |
|---|
| 157 | write_lock(&td->lock); |
|---|
| 158 | td->interval = msecs_to_jiffies(value); |
|---|
| 159 | usbdev_trig_update_state(td); /* resets timer */ |
|---|
| 160 | write_unlock(&td->lock); |
|---|
| 161 | ret = count; |
|---|
| 162 | } |
|---|
| 163 | |
|---|
| 164 | return ret; |
|---|
| 165 | } |
|---|
| 166 | |
|---|
| 167 | static DEVICE_ATTR(activity_interval, 0644, usbdev_trig_interval_show, |
|---|
| 168 | usbdev_trig_interval_store); |
|---|
| 169 | |
|---|
| 170 | static int usbdev_trig_notify(struct notifier_block *nb, |
|---|
| 171 | unsigned long evt, |
|---|
| 172 | void *data) |
|---|
| 173 | { |
|---|
| 174 | struct usb_device *usb_dev; |
|---|
| 175 | struct usbdev_trig_data *td; |
|---|
| 176 | |
|---|
| 177 | if (evt != USB_DEVICE_ADD && evt != USB_DEVICE_REMOVE) |
|---|
| 178 | return NOTIFY_DONE; |
|---|
| 179 | |
|---|
| 180 | usb_dev = data; |
|---|
| 181 | td = container_of(nb, struct usbdev_trig_data, notifier); |
|---|
| 182 | |
|---|
| 183 | write_lock(&td->lock); |
|---|
| 184 | |
|---|
| 185 | if (strcmp(dev_name(&usb_dev->dev), td->device_name)) |
|---|
| 186 | goto done; |
|---|
| 187 | |
|---|
| 188 | if (evt == USB_DEVICE_ADD) { |
|---|
| 189 | usb_get_dev(usb_dev); |
|---|
| 190 | if (td->usb_dev != NULL) |
|---|
| 191 | usb_put_dev(td->usb_dev); |
|---|
| 192 | td->usb_dev = usb_dev; |
|---|
| 193 | td->last_urbnum = atomic_read(&usb_dev->urbnum); |
|---|
| 194 | } else if (evt == USB_DEVICE_REMOVE) { |
|---|
| 195 | if (td->usb_dev != NULL) { |
|---|
| 196 | usb_put_dev(td->usb_dev); |
|---|
| 197 | td->usb_dev = NULL; |
|---|
| 198 | } |
|---|
| 199 | } |
|---|
| 200 | |
|---|
| 201 | usbdev_trig_update_state(td); |
|---|
| 202 | |
|---|
| 203 | done: |
|---|
| 204 | write_unlock(&td->lock); |
|---|
| 205 | return NOTIFY_DONE; |
|---|
| 206 | } |
|---|
| 207 | |
|---|
| 208 | /* here's the real work! */ |
|---|
| 209 | static void usbdev_trig_timer(unsigned long arg) |
|---|
| 210 | { |
|---|
| 211 | struct usbdev_trig_data *td = (struct usbdev_trig_data *)arg; |
|---|
| 212 | int new_urbnum; |
|---|
| 213 | |
|---|
| 214 | write_lock(&td->lock); |
|---|
| 215 | |
|---|
| 216 | if (!td->usb_dev || td->interval == 0) { |
|---|
| 217 | /* |
|---|
| 218 | * we don't need to do timer work, just reflect device presence |
|---|
| 219 | */ |
|---|
| 220 | if (td->usb_dev) |
|---|
| 221 | led_set_brightness(td->led_cdev, LED_FULL); |
|---|
| 222 | else |
|---|
| 223 | led_set_brightness(td->led_cdev, LED_OFF); |
|---|
| 224 | |
|---|
| 225 | goto no_restart; |
|---|
| 226 | } |
|---|
| 227 | |
|---|
| 228 | if (td->interval) |
|---|
| 229 | new_urbnum = atomic_read(&td->usb_dev->urbnum); |
|---|
| 230 | else |
|---|
| 231 | new_urbnum = 0; |
|---|
| 232 | |
|---|
| 233 | if (td->usb_dev) { |
|---|
| 234 | /* |
|---|
| 235 | * Base state is ON (device is present). If there's no device, |
|---|
| 236 | * we don't get this far and the LED is off. |
|---|
| 237 | * OFF -> ON always |
|---|
| 238 | * ON -> OFF on activity |
|---|
| 239 | */ |
|---|
| 240 | if (td->led_cdev->brightness == LED_OFF) |
|---|
| 241 | led_set_brightness(td->led_cdev, LED_FULL); |
|---|
| 242 | else if (td->last_urbnum != new_urbnum) |
|---|
| 243 | led_set_brightness(td->led_cdev, LED_OFF); |
|---|
| 244 | } else { |
|---|
| 245 | /* |
|---|
| 246 | * base state is OFF |
|---|
| 247 | * ON -> OFF always |
|---|
| 248 | * OFF -> ON on activity |
|---|
| 249 | */ |
|---|
| 250 | if (td->led_cdev->brightness == LED_FULL) |
|---|
| 251 | led_set_brightness(td->led_cdev, LED_OFF); |
|---|
| 252 | else if (td->last_urbnum != new_urbnum) |
|---|
| 253 | led_set_brightness(td->led_cdev, LED_FULL); |
|---|
| 254 | } |
|---|
| 255 | |
|---|
| 256 | td->last_urbnum = new_urbnum; |
|---|
| 257 | mod_timer(&td->timer, jiffies + td->interval); |
|---|
| 258 | |
|---|
| 259 | no_restart: |
|---|
| 260 | write_unlock(&td->lock); |
|---|
| 261 | } |
|---|
| 262 | |
|---|
| 263 | static void usbdev_trig_activate(struct led_classdev *led_cdev) |
|---|
| 264 | { |
|---|
| 265 | struct usbdev_trig_data *td; |
|---|
| 266 | int rc; |
|---|
| 267 | |
|---|
| 268 | td = kzalloc(sizeof(struct usbdev_trig_data), GFP_KERNEL); |
|---|
| 269 | if (!td) |
|---|
| 270 | return; |
|---|
| 271 | |
|---|
| 272 | rwlock_init(&td->lock); |
|---|
| 273 | |
|---|
| 274 | td->notifier.notifier_call = usbdev_trig_notify; |
|---|
| 275 | td->notifier.priority = 10; |
|---|
| 276 | |
|---|
| 277 | setup_timer(&td->timer, usbdev_trig_timer, (unsigned long) td); |
|---|
| 278 | |
|---|
| 279 | td->led_cdev = led_cdev; |
|---|
| 280 | td->interval = msecs_to_jiffies(50); |
|---|
| 281 | |
|---|
| 282 | led_cdev->trigger_data = td; |
|---|
| 283 | |
|---|
| 284 | rc = device_create_file(led_cdev->dev, &dev_attr_device_name); |
|---|
| 285 | if (rc) |
|---|
| 286 | goto err_out; |
|---|
| 287 | |
|---|
| 288 | rc = device_create_file(led_cdev->dev, &dev_attr_activity_interval); |
|---|
| 289 | if (rc) |
|---|
| 290 | goto err_out_device_name; |
|---|
| 291 | |
|---|
| 292 | usb_register_notify(&td->notifier); |
|---|
| 293 | return; |
|---|
| 294 | |
|---|
| 295 | err_out_device_name: |
|---|
| 296 | device_remove_file(led_cdev->dev, &dev_attr_device_name); |
|---|
| 297 | err_out: |
|---|
| 298 | led_cdev->trigger_data = NULL; |
|---|
| 299 | kfree(td); |
|---|
| 300 | } |
|---|
| 301 | |
|---|
| 302 | static void usbdev_trig_deactivate(struct led_classdev *led_cdev) |
|---|
| 303 | { |
|---|
| 304 | struct usbdev_trig_data *td = led_cdev->trigger_data; |
|---|
| 305 | |
|---|
| 306 | if (td) { |
|---|
| 307 | usb_unregister_notify(&td->notifier); |
|---|
| 308 | |
|---|
| 309 | device_remove_file(led_cdev->dev, &dev_attr_device_name); |
|---|
| 310 | device_remove_file(led_cdev->dev, &dev_attr_activity_interval); |
|---|
| 311 | |
|---|
| 312 | write_lock(&td->lock); |
|---|
| 313 | |
|---|
| 314 | if (td->usb_dev) { |
|---|
| 315 | usb_put_dev(td->usb_dev); |
|---|
| 316 | td->usb_dev = NULL; |
|---|
| 317 | } |
|---|
| 318 | |
|---|
| 319 | write_unlock(&td->lock); |
|---|
| 320 | |
|---|
| 321 | del_timer_sync(&td->timer); |
|---|
| 322 | |
|---|
| 323 | kfree(td); |
|---|
| 324 | } |
|---|
| 325 | } |
|---|
| 326 | |
|---|
| 327 | static struct led_trigger usbdev_led_trigger = { |
|---|
| 328 | .name = "usbdev", |
|---|
| 329 | .activate = usbdev_trig_activate, |
|---|
| 330 | .deactivate = usbdev_trig_deactivate, |
|---|
| 331 | }; |
|---|
| 332 | |
|---|
| 333 | static int __init usbdev_trig_init(void) |
|---|
| 334 | { |
|---|
| 335 | return led_trigger_register(&usbdev_led_trigger); |
|---|
| 336 | } |
|---|
| 337 | |
|---|
| 338 | static void __exit usbdev_trig_exit(void) |
|---|
| 339 | { |
|---|
| 340 | led_trigger_unregister(&usbdev_led_trigger); |
|---|
| 341 | } |
|---|
| 342 | |
|---|
| 343 | module_init(usbdev_trig_init); |
|---|
| 344 | module_exit(usbdev_trig_exit); |
|---|
| 345 | |
|---|
| 346 | MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); |
|---|
| 347 | MODULE_DESCRIPTION("USB device LED trigger"); |
|---|
| 348 | MODULE_LICENSE("GPL v2"); |
|---|