123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- /*
- * USB port LED trigger
- *
- * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- */
- #include <linux/device.h>
- #include <linux/leds.h>
- #include <linux/module.h>
- #include <linux/slab.h>
- #include <linux/usb.h>
- struct usbport_trig_data {
- struct led_classdev *led_cdev;
- struct list_head ports;
- struct notifier_block nb;
- int count; /* Amount of connected matching devices */
- };
- struct usbport_trig_port {
- struct usbport_trig_data *data;
- struct usb_device *hub;
- int portnum;
- char *port_name;
- bool observed;
- struct device_attribute attr;
- struct list_head list;
- };
- /***************************************
- * Helpers
- ***************************************/
- /**
- * usbport_trig_usb_dev_observed - Check if dev is connected to observed port
- */
- static bool usbport_trig_usb_dev_observed(struct usbport_trig_data *usbport_data,
- struct usb_device *usb_dev)
- {
- struct usbport_trig_port *port;
- if (!usb_dev->parent)
- return false;
- list_for_each_entry(port, &usbport_data->ports, list) {
- if (usb_dev->parent == port->hub &&
- usb_dev->portnum == port->portnum)
- return port->observed;
- }
- return false;
- }
- static int usbport_trig_usb_dev_check(struct usb_device *usb_dev, void *data)
- {
- struct usbport_trig_data *usbport_data = data;
- if (usbport_trig_usb_dev_observed(usbport_data, usb_dev))
- usbport_data->count++;
- return 0;
- }
- /**
- * usbport_trig_update_count - Recalculate amount of connected matching devices
- */
- static void usbport_trig_update_count(struct usbport_trig_data *usbport_data)
- {
- struct led_classdev *led_cdev = usbport_data->led_cdev;
- usbport_data->count = 0;
- usb_for_each_dev(usbport_data, usbport_trig_usb_dev_check);
- led_set_brightness(led_cdev, usbport_data->count ? LED_FULL : LED_OFF);
- }
- /***************************************
- * Device attr
- ***************************************/
- static ssize_t usbport_trig_port_show(struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- struct usbport_trig_port *port = container_of(attr,
- struct usbport_trig_port,
- attr);
- return sprintf(buf, "%d\n", port->observed) + 1;
- }
- static ssize_t usbport_trig_port_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t size)
- {
- struct usbport_trig_port *port = container_of(attr,
- struct usbport_trig_port,
- attr);
- if (!strcmp(buf, "0") || !strcmp(buf, "0\n"))
- port->observed = 0;
- else if (!strcmp(buf, "1") || !strcmp(buf, "1\n"))
- port->observed = 1;
- else
- return -EINVAL;
- usbport_trig_update_count(port->data);
- return size;
- }
- static struct attribute *ports_attrs[] = {
- NULL,
- };
- static const struct attribute_group ports_group = {
- .name = "ports",
- .attrs = ports_attrs,
- };
- /***************************************
- * Adding & removing ports
- ***************************************/
- static int usbport_trig_add_port(struct usbport_trig_data *usbport_data,
- struct usb_device *usb_dev,
- const char *hub_name, int portnum)
- {
- struct led_classdev *led_cdev = usbport_data->led_cdev;
- struct usbport_trig_port *port;
- size_t len;
- int err;
- port = kzalloc(sizeof(*port), GFP_KERNEL);
- if (!port) {
- err = -ENOMEM;
- goto err_out;
- }
- port->data = usbport_data;
- port->hub = usb_dev;
- port->portnum = portnum;
- len = strlen(hub_name) + 8;
- port->port_name = kzalloc(len, GFP_KERNEL);
- if (!port->port_name) {
- err = -ENOMEM;
- goto err_free_port;
- }
- snprintf(port->port_name, len, "%s-port%d", hub_name, portnum);
- port->attr.attr.name = port->port_name;
- port->attr.attr.mode = S_IRUSR | S_IWUSR;
- port->attr.show = usbport_trig_port_show;
- port->attr.store = usbport_trig_port_store;
- err = sysfs_add_file_to_group(&led_cdev->dev->kobj, &port->attr.attr,
- ports_group.name);
- if (err)
- goto err_free_port_name;
- list_add_tail(&port->list, &usbport_data->ports);
- return 0;
- err_free_port_name:
- kfree(port->port_name);
- err_free_port:
- kfree(port);
- err_out:
- return err;
- }
- static int usbport_trig_add_usb_dev_ports(struct usb_device *usb_dev,
- void *data)
- {
- struct usbport_trig_data *usbport_data = data;
- int i;
- for (i = 1; i <= usb_dev->maxchild; i++)
- usbport_trig_add_port(usbport_data, usb_dev,
- dev_name(&usb_dev->dev), i);
- return 0;
- }
- static void usbport_trig_remove_port(struct usbport_trig_data *usbport_data,
- struct usbport_trig_port *port)
- {
- struct led_classdev *led_cdev = usbport_data->led_cdev;
- list_del(&port->list);
- sysfs_remove_file_from_group(&led_cdev->dev->kobj, &port->attr.attr,
- ports_group.name);
- kfree(port->port_name);
- kfree(port);
- }
- static void usbport_trig_remove_usb_dev_ports(struct usbport_trig_data *usbport_data,
- struct usb_device *usb_dev)
- {
- struct usbport_trig_port *port, *tmp;
- list_for_each_entry_safe(port, tmp, &usbport_data->ports, list) {
- if (port->hub == usb_dev)
- usbport_trig_remove_port(usbport_data, port);
- }
- }
- /***************************************
- * Init, exit, etc.
- ***************************************/
- static int usbport_trig_notify(struct notifier_block *nb, unsigned long action,
- void *data)
- {
- struct usbport_trig_data *usbport_data =
- container_of(nb, struct usbport_trig_data, nb);
- struct led_classdev *led_cdev = usbport_data->led_cdev;
- struct usb_device *usb_dev = data;
- bool observed;
- observed = usbport_trig_usb_dev_observed(usbport_data, usb_dev);
- switch (action) {
- case USB_DEVICE_ADD:
- usbport_trig_add_usb_dev_ports(usb_dev, usbport_data);
- if (observed && usbport_data->count++ == 0)
- led_set_brightness(led_cdev, LED_FULL);
- return NOTIFY_OK;
- case USB_DEVICE_REMOVE:
- usbport_trig_remove_usb_dev_ports(usbport_data, usb_dev);
- if (observed && --usbport_data->count == 0)
- led_set_brightness(led_cdev, LED_OFF);
- return NOTIFY_OK;
- }
- return NOTIFY_DONE;
- }
- static void usbport_trig_activate(struct led_classdev *led_cdev)
- {
- struct usbport_trig_data *usbport_data;
- int err;
- usbport_data = kzalloc(sizeof(*usbport_data), GFP_KERNEL);
- if (!usbport_data)
- return;
- usbport_data->led_cdev = led_cdev;
- /* List of ports */
- INIT_LIST_HEAD(&usbport_data->ports);
- err = sysfs_create_group(&led_cdev->dev->kobj, &ports_group);
- if (err)
- goto err_free;
- usb_for_each_dev(usbport_data, usbport_trig_add_usb_dev_ports);
- /* Notifications */
- usbport_data->nb.notifier_call = usbport_trig_notify,
- led_cdev->trigger_data = usbport_data;
- usb_register_notify(&usbport_data->nb);
- led_cdev->activated = true;
- return;
- err_free:
- kfree(usbport_data);
- }
- static void usbport_trig_deactivate(struct led_classdev *led_cdev)
- {
- struct usbport_trig_data *usbport_data = led_cdev->trigger_data;
- struct usbport_trig_port *port, *tmp;
- if (!led_cdev->activated)
- return;
- list_for_each_entry_safe(port, tmp, &usbport_data->ports, list) {
- usbport_trig_remove_port(usbport_data, port);
- }
- usb_unregister_notify(&usbport_data->nb);
- sysfs_remove_group(&led_cdev->dev->kobj, &ports_group);
- kfree(usbport_data);
- led_cdev->activated = false;
- }
- static struct led_trigger usbport_led_trigger = {
- .name = "usbport",
- .activate = usbport_trig_activate,
- .deactivate = usbport_trig_deactivate,
- };
- static int __init usbport_trig_init(void)
- {
- return led_trigger_register(&usbport_led_trigger);
- }
- static void __exit usbport_trig_exit(void)
- {
- led_trigger_unregister(&usbport_led_trigger);
- }
- module_init(usbport_trig_init);
- module_exit(usbport_trig_exit);
- MODULE_AUTHOR("Rafał Miłecki <rafal@milecki.pl>");
- MODULE_DESCRIPTION("USB port trigger");
- MODULE_LICENSE("GPL v2");
|