ledtrig-usbdev.c 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  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. #include <linux/module.h>
  20. #include <linux/jiffies.h>
  21. #include <linux/kernel.h>
  22. #include <linux/init.h>
  23. #include <linux/list.h>
  24. #include <linux/spinlock.h>
  25. #include <linux/device.h>
  26. #include <linux/sysdev.h>
  27. #include <linux/timer.h>
  28. #include <linux/ctype.h>
  29. #include <linux/slab.h>
  30. #include <linux/leds.h>
  31. #include <linux/usb.h>
  32. #include "leds.h"
  33. #define DEV_BUS_ID_SIZE 32
  34. /*
  35. * Configurable sysfs attributes:
  36. *
  37. * device_name - name of the USB device to monitor
  38. * activity_interval - duration of LED blink, in milliseconds
  39. */
  40. struct usbdev_trig_data {
  41. rwlock_t lock;
  42. struct timer_list timer;
  43. struct notifier_block notifier;
  44. struct led_classdev *led_cdev;
  45. struct usb_device *usb_dev;
  46. char device_name[DEV_BUS_ID_SIZE];
  47. unsigned interval;
  48. int last_urbnum;
  49. };
  50. static void usbdev_trig_update_state(struct usbdev_trig_data *td)
  51. {
  52. if (td->usb_dev)
  53. led_set_brightness(td->led_cdev, LED_FULL);
  54. else
  55. led_set_brightness(td->led_cdev, LED_OFF);
  56. if (td->interval && td->usb_dev)
  57. mod_timer(&td->timer, jiffies + td->interval);
  58. else
  59. del_timer(&td->timer);
  60. }
  61. static ssize_t usbdev_trig_name_show(struct device *dev,
  62. struct device_attribute *attr,
  63. char *buf)
  64. {
  65. struct led_classdev *led_cdev = dev_get_drvdata(dev);
  66. struct usbdev_trig_data *td = led_cdev->trigger_data;
  67. read_lock(&td->lock);
  68. sprintf(buf, "%s\n", td->device_name);
  69. read_unlock(&td->lock);
  70. return strlen(buf) + 1;
  71. }
  72. static ssize_t usbdev_trig_name_store(struct device *dev,
  73. struct device_attribute *attr,
  74. const char *buf,
  75. size_t size)
  76. {
  77. struct led_classdev *led_cdev = dev_get_drvdata(dev);
  78. struct usbdev_trig_data *td = led_cdev->trigger_data;
  79. if (size < 0 || size >= DEV_BUS_ID_SIZE)
  80. return -EINVAL;
  81. write_lock(&td->lock);
  82. strcpy(td->device_name, buf);
  83. if (size > 0 && td->device_name[size - 1] == '\n')
  84. td->device_name[size - 1] = 0;
  85. if (td->device_name[0] != 0) {
  86. struct usb_device *usb_dev;
  87. /* check for existing device to update from */
  88. usb_dev = usb_find_device_by_name(td->device_name);
  89. if (usb_dev) {
  90. if (td->usb_dev)
  91. usb_put_dev(td->usb_dev);
  92. td->usb_dev = usb_dev;
  93. td->last_urbnum = atomic_read(&usb_dev->urbnum);
  94. }
  95. /* updates LEDs, may start timers */
  96. usbdev_trig_update_state(td);
  97. }
  98. write_unlock(&td->lock);
  99. return size;
  100. }
  101. static DEVICE_ATTR(device_name, 0644, usbdev_trig_name_show,
  102. usbdev_trig_name_store);
  103. static ssize_t usbdev_trig_interval_show(struct device *dev,
  104. struct device_attribute *attr,
  105. char *buf)
  106. {
  107. struct led_classdev *led_cdev = dev_get_drvdata(dev);
  108. struct usbdev_trig_data *td = led_cdev->trigger_data;
  109. read_lock(&td->lock);
  110. sprintf(buf, "%u\n", jiffies_to_msecs(td->interval));
  111. read_unlock(&td->lock);
  112. return strlen(buf) + 1;
  113. }
  114. static ssize_t usbdev_trig_interval_store(struct device *dev,
  115. struct device_attribute *attr,
  116. const char *buf,
  117. size_t size)
  118. {
  119. struct led_classdev *led_cdev = dev_get_drvdata(dev);
  120. struct usbdev_trig_data *td = led_cdev->trigger_data;
  121. int ret = -EINVAL;
  122. char *after;
  123. unsigned long value = simple_strtoul(buf, &after, 10);
  124. size_t count = after - buf;
  125. if (*after && isspace(*after))
  126. count++;
  127. if (count == size && value <= 10000) {
  128. write_lock(&td->lock);
  129. td->interval = msecs_to_jiffies(value);
  130. usbdev_trig_update_state(td); /* resets timer */
  131. write_unlock(&td->lock);
  132. ret = count;
  133. }
  134. return ret;
  135. }
  136. static DEVICE_ATTR(activity_interval, 0644, usbdev_trig_interval_show,
  137. usbdev_trig_interval_store);
  138. static int usbdev_trig_notify(struct notifier_block *nb,
  139. unsigned long evt,
  140. void *data)
  141. {
  142. struct usb_device *usb_dev;
  143. struct usbdev_trig_data *td;
  144. if (evt != USB_DEVICE_ADD && evt != USB_DEVICE_REMOVE)
  145. return NOTIFY_DONE;
  146. usb_dev = data;
  147. td = container_of(nb, struct usbdev_trig_data, notifier);
  148. write_lock(&td->lock);
  149. if (strcmp(dev_name(&usb_dev->dev), td->device_name))
  150. goto done;
  151. if (evt == USB_DEVICE_ADD) {
  152. usb_get_dev(usb_dev);
  153. if (td->usb_dev != NULL)
  154. usb_put_dev(td->usb_dev);
  155. td->usb_dev = usb_dev;
  156. td->last_urbnum = atomic_read(&usb_dev->urbnum);
  157. } else if (evt == USB_DEVICE_REMOVE) {
  158. if (td->usb_dev != NULL) {
  159. usb_put_dev(td->usb_dev);
  160. td->usb_dev = NULL;
  161. }
  162. }
  163. usbdev_trig_update_state(td);
  164. done:
  165. write_unlock(&td->lock);
  166. return NOTIFY_DONE;
  167. }
  168. /* here's the real work! */
  169. static void usbdev_trig_timer(unsigned long arg)
  170. {
  171. struct usbdev_trig_data *td = (struct usbdev_trig_data *)arg;
  172. int new_urbnum;
  173. write_lock(&td->lock);
  174. if (!td->usb_dev || td->interval == 0) {
  175. /*
  176. * we don't need to do timer work, just reflect device presence
  177. */
  178. if (td->usb_dev)
  179. led_set_brightness(td->led_cdev, LED_FULL);
  180. else
  181. led_set_brightness(td->led_cdev, LED_OFF);
  182. goto no_restart;
  183. }
  184. if (td->interval)
  185. new_urbnum = atomic_read(&td->usb_dev->urbnum);
  186. else
  187. new_urbnum = 0;
  188. if (td->usb_dev) {
  189. /*
  190. * Base state is ON (device is present). If there's no device,
  191. * we don't get this far and the LED is off.
  192. * OFF -> ON always
  193. * ON -> OFF on activity
  194. */
  195. if (td->led_cdev->brightness == LED_OFF)
  196. led_set_brightness(td->led_cdev, LED_FULL);
  197. else if (td->last_urbnum != new_urbnum)
  198. led_set_brightness(td->led_cdev, LED_OFF);
  199. } else {
  200. /*
  201. * base state is OFF
  202. * ON -> OFF always
  203. * OFF -> ON on activity
  204. */
  205. if (td->led_cdev->brightness == LED_FULL)
  206. led_set_brightness(td->led_cdev, LED_OFF);
  207. else if (td->last_urbnum != new_urbnum)
  208. led_set_brightness(td->led_cdev, LED_FULL);
  209. }
  210. td->last_urbnum = new_urbnum;
  211. mod_timer(&td->timer, jiffies + td->interval);
  212. no_restart:
  213. write_unlock(&td->lock);
  214. }
  215. static void usbdev_trig_activate(struct led_classdev *led_cdev)
  216. {
  217. struct usbdev_trig_data *td;
  218. int rc;
  219. td = kzalloc(sizeof(struct usbdev_trig_data), GFP_KERNEL);
  220. if (!td)
  221. return;
  222. rwlock_init(&td->lock);
  223. td->notifier.notifier_call = usbdev_trig_notify;
  224. td->notifier.priority = 10;
  225. setup_timer(&td->timer, usbdev_trig_timer, (unsigned long) td);
  226. td->led_cdev = led_cdev;
  227. td->interval = msecs_to_jiffies(50);
  228. led_cdev->trigger_data = td;
  229. rc = device_create_file(led_cdev->dev, &dev_attr_device_name);
  230. if (rc)
  231. goto err_out;
  232. rc = device_create_file(led_cdev->dev, &dev_attr_activity_interval);
  233. if (rc)
  234. goto err_out_device_name;
  235. usb_register_notify(&td->notifier);
  236. return;
  237. err_out_device_name:
  238. device_remove_file(led_cdev->dev, &dev_attr_device_name);
  239. err_out:
  240. led_cdev->trigger_data = NULL;
  241. kfree(td);
  242. }
  243. static void usbdev_trig_deactivate(struct led_classdev *led_cdev)
  244. {
  245. struct usbdev_trig_data *td = led_cdev->trigger_data;
  246. if (td) {
  247. usb_unregister_notify(&td->notifier);
  248. device_remove_file(led_cdev->dev, &dev_attr_device_name);
  249. device_remove_file(led_cdev->dev, &dev_attr_activity_interval);
  250. write_lock(&td->lock);
  251. if (td->usb_dev) {
  252. usb_put_dev(td->usb_dev);
  253. td->usb_dev = NULL;
  254. }
  255. write_unlock(&td->lock);
  256. del_timer_sync(&td->timer);
  257. kfree(td);
  258. }
  259. }
  260. static struct led_trigger usbdev_led_trigger = {
  261. .name = "usbdev",
  262. .activate = usbdev_trig_activate,
  263. .deactivate = usbdev_trig_deactivate,
  264. };
  265. static int __init usbdev_trig_init(void)
  266. {
  267. return led_trigger_register(&usbdev_led_trigger);
  268. }
  269. static void __exit usbdev_trig_exit(void)
  270. {
  271. led_trigger_unregister(&usbdev_led_trigger);
  272. }
  273. module_init(usbdev_trig_init);
  274. module_exit(usbdev_trig_exit);
  275. MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");
  276. MODULE_DESCRIPTION("USB device LED trigger");
  277. MODULE_LICENSE("GPL v2");