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 <linux/kernel.h>
 #include <linux/errno.h>
 #include <linux/module.h>
+#include <linux/moduleparam.h>
 #include <linux/completion.h>
 #include <linux/sched.h>
 #include <linux/list.h>
@@ -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 */
