ChangeSet 1.1713.7.4, 2004/04/02 12:23:30-08:00, david-b@pacbell.net [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 -Nru a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c --- a/drivers/usb/core/hub.c Wed Apr 14 14:34:01 2004 +++ b/drivers/usb/core/hub.c Wed Apr 14 14:34:01 2004 @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,12 @@ 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 @@ /* * 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 @@ /* * 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) { @@ -102,6 +107,104 @@ } /* + * 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 */ static int get_hub_status(struct usb_device *dev, @@ -375,7 +478,7 @@ 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 @@ 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 @@ 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 @@ /* 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 @@ 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 @@ 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 -Nru a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h --- a/drivers/usb/core/hub.h Wed Apr 14 14:34:01 2004 +++ b/drivers/usb/core/hub.h Wed Apr 14 14:34:01 2004 @@ -139,6 +139,22 @@ __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_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 */