From 9656974be8b4ecb3a480536ec25b50f19df15109 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Thu, 1 Apr 2004 20:23:30 -0800 Subject: [PATCH] [PATCH] USB: usbcore blinkenlights The per-port LEDs on the most USB 2.0 hubs are programmable. And the USB spec describes some ways to use them, blinking to alert users about hardware (amber) or software (green) problems. This patch is the infrastructure for that blinking. And if you should happen to "modprobe usbcore blinkenlights", the LEDs will cycle through all the ports ... which is not a USB-standard mode, but it can certainly handy be handy as a system heartbeat visible across the room. --- drivers/usb/core/hub.c | 137 ++++++++++++++++++++++++++++++++++++++--- drivers/usb/core/hub.h | 20 ++++++ 2 files changed, 148 insertions(+), 9 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 86adfdd5d4394..289e48450d022 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,12 @@ static DECLARE_WAIT_QUEUE_HEAD(khubd_wait); static pid_t khubd_pid = 0; /* PID of khubd */ static DECLARE_COMPLETION(khubd_exited); +/* cycle leds on hubs that aren't blinking for attention */ +static int blinkenlights = 0; +module_param (blinkenlights, bool, S_IRUGO); +MODULE_PARM_DESC (blinkenlights, "true to cycle leds on hubs"); + + #ifdef DEBUG static inline char *portspeed (int portstatus) { @@ -83,7 +90,6 @@ static int clear_hub_feature(struct usb_device *dev, int feature) /* * USB 2.0 spec Section 11.24.2.2 - * BUG: doesn't handle port indicator selector in high byte of wIndex */ static int clear_port_feature(struct usb_device *dev, int port, int feature) { @@ -93,7 +99,6 @@ static int clear_port_feature(struct usb_device *dev, int port, int feature) /* * USB 2.0 spec Section 11.24.2.13 - * BUG: doesn't handle port indicator selector in high byte of wIndex */ static int set_port_feature(struct usb_device *dev, int port, int feature) { @@ -101,6 +106,104 @@ static int set_port_feature(struct usb_device *dev, int port, int feature) USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port, NULL, 0, HZ); } +/* + * USB 2.0 spec Section 11.24.2.7.1.10 and table 11-7 + * for info about using port indicators + */ +static void set_port_led( + struct usb_device *dev, + struct usb_hub *hub, + int port, + int selector +) +{ + int status = set_port_feature(dev, (selector << 8) | port, + USB_PORT_FEAT_INDICATOR); + if (status < 0) + dev_dbg (&hub->intf->dev, + "port %d indicator %s status %d\n", + port, + ({ char *s; switch (selector) { + case HUB_LED_AMBER: s = "amber"; break; + case HUB_LED_GREEN: s = "green"; break; + case HUB_LED_OFF: s = "off"; break; + case HUB_LED_AUTO: s = "auto"; break; + default: s = "??"; break; + }; s; }), + status); +} + +#define LED_CYCLE_PERIOD ((2*HZ)/3) + +static void led_work (void *__hub) +{ + struct usb_hub *hub = __hub; + struct usb_device *dev = interface_to_usbdev (hub->intf); + unsigned i; + unsigned changed = 0; + int cursor = -1; + + if (dev->state != USB_STATE_CONFIGURED) + return; + + for (i = 0; i < hub->descriptor->bNbrPorts; i++) { + unsigned selector, mode; + + /* 30%-50% duty cycle */ + + switch (hub->indicator[i]) { + /* cycle marker */ + case INDICATOR_CYCLE: + cursor = i; + selector = HUB_LED_AUTO; + mode = INDICATOR_AUTO; + break; + /* blinking green = sw attention */ + case INDICATOR_GREEN_BLINK: + selector = HUB_LED_GREEN; + mode = INDICATOR_GREEN_BLINK_OFF; + break; + case INDICATOR_GREEN_BLINK_OFF: + selector = HUB_LED_OFF; + mode = INDICATOR_GREEN_BLINK; + break; + /* blinking amber = hw attention */ + case INDICATOR_AMBER_BLINK: + selector = HUB_LED_AMBER; + mode = INDICATOR_AMBER_BLINK_OFF; + break; + case INDICATOR_AMBER_BLINK_OFF: + selector = HUB_LED_OFF; + mode = INDICATOR_AMBER_BLINK; + break; + /* blink green/amber = reserved */ + case INDICATOR_ALT_BLINK: + selector = HUB_LED_GREEN; + mode = INDICATOR_ALT_BLINK_OFF; + break; + case INDICATOR_ALT_BLINK_OFF: + selector = HUB_LED_AMBER; + mode = INDICATOR_ALT_BLINK; + break; + default: + continue; + } + if (selector != HUB_LED_AUTO) + changed = 1; + set_port_led(dev, hub, i + 1, selector); + hub->indicator[i] = mode; + } + if (!changed && blinkenlights) { + cursor++; + cursor %= hub->descriptor->bNbrPorts; + set_port_led(dev, hub, cursor + 1, HUB_LED_GREEN); + hub->indicator[cursor] = INDICATOR_CYCLE; + changed++; + } + if (changed) + schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD); +} + /* * USB 2.0 spec Section 11.24.2.6 */ @@ -375,7 +478,7 @@ static int hub_configure(struct usb_hub *hub, break; case 0x02: case 0x03: - dev_dbg(hub_dev, "unknown reserved power switching mode\n"); + dev_dbg(hub_dev, "no power switching (usb 1.0)\n"); break; } @@ -434,9 +537,11 @@ static int hub_configure(struct usb_hub *hub, break; } - dev_dbg(hub_dev, "Port indicators are %s supported\n", - (hub->descriptor->wHubCharacteristics & HUB_CHAR_PORTIND) - ? "" : "not"); + /* probe() zeroes hub->indicator[] */ + if (hub->descriptor->wHubCharacteristics & HUB_CHAR_PORTIND) { + hub->has_indicators = 1; + dev_dbg(hub_dev, "Port indicators are supported\n"); + } dev_dbg(hub_dev, "power on to power good time: %dms\n", hub->descriptor->bPwrOn2PwrGood * 2); @@ -449,12 +554,16 @@ static int hub_configure(struct usb_hub *hub, goto fail; } + /* FIXME implement per-port power budgeting; + * enable it for bus-powered hubs. + */ dev_dbg(hub_dev, "local power source is %s\n", (hubstatus & HUB_STATUS_LOCAL_POWER) ? "lost (inactive)" : "good"); - dev_dbg(hub_dev, "%sover-current condition exists\n", - (hubstatus & HUB_STATUS_OVERCURRENT) ? "" : "no "); + if ((hub->descriptor->wHubCharacteristics & HUB_CHAR_OCPM) == 0) + dev_dbg(hub_dev, "%sover-current condition exists\n", + (hubstatus & HUB_STATUS_OVERCURRENT) ? "" : "no "); /* Start the interrupt endpoint */ pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); @@ -484,6 +593,13 @@ static int hub_configure(struct usb_hub *hub, /* Wake up khubd */ wake_up(&khubd_wait); + /* maybe start cycling the hub leds */ + if (hub->has_indicators && blinkenlights) { + set_port_led(dev, hub, 1, HUB_LED_GREEN); + hub->indicator [0] = INDICATOR_CYCLE; + schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD); + } + hub_power_on(hub); return 0; @@ -518,7 +634,9 @@ static void hub_disconnect(struct usb_interface *intf) up(&hub->khubd_sem); /* assuming we used keventd, it must quiesce too */ - if (hub->tt.hub) + if (hub->has_indicators) + cancel_delayed_work (&hub->leds); + if (hub->has_indicators || hub->tt.hub) flush_scheduled_work (); if (hub->urb) { @@ -603,6 +721,7 @@ descriptor_error: INIT_LIST_HEAD(&hub->event_list); hub->intf = intf; init_MUTEX(&hub->khubd_sem); + INIT_WORK(&hub->leds, led_work, hub); /* Record the new hub's existence */ spin_lock_irqsave(&hub_event_lock, flags); diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 755bc2d89524c..33b21925e5855 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -139,6 +139,22 @@ struct usb_hub_descriptor { __u8 PortPwrCtrlMask[(USB_MAXCHILDREN + 1 + 7) / 8]; } __attribute__ ((packed)); + +/* port indicator status selectors, tables 11-7 and 11-25 */ +#define HUB_LED_AUTO 0 +#define HUB_LED_AMBER 1 +#define HUB_LED_GREEN 2 +#define HUB_LED_OFF 3 + +enum hub_led_mode { + INDICATOR_AUTO = 0, + INDICATOR_CYCLE, + /* software blinks for attention: software, hardware, reserved */ + INDICATOR_GREEN_BLINK, INDICATOR_GREEN_BLINK_OFF, + INDICATOR_AMBER_BLINK, INDICATOR_AMBER_BLINK_OFF, + INDICATOR_ALT_BLINK, INDICATOR_ALT_BLINK_OFF +} __attribute__ ((packed)); + struct usb_device; /* @@ -192,6 +208,10 @@ struct usb_hub { struct usb_hub_descriptor *descriptor; /* class descriptor */ struct semaphore khubd_sem; struct usb_tt tt; /* Transaction Translator */ + + unsigned has_indicators:1; + enum hub_led_mode indicator[USB_MAXCHILDREN]; + struct work_struct leds; }; #endif /* __LINUX_HUB_H */ -- 2.53.0