Re: hid powersave by autosuspend #2

Oliver Neukum <[email protected]>
Newsgroups gmane.linux.usb.devel
Message-ID <[email protected]>
Am Dienstag, 6. Februar 2007 16:15 schrieb Jiri Kosina:
> On Tue, 6 Feb 2007, Oliver Neukum wrote:
> 
> > this fixes the issue with output in the hid driver. I can now get 
> > keyboards to sleep. It is still not perfect, but an important step. 
> > Comments?
> 
> Hi Oliver,
> 
> I am going to look at it more thoroughly soon, just a quick note - when I 
> plug in the USB keyboard, I get

Yes, sorry, noticed too late.
A fixed patch is included here.

	Regards
		Oliver
----

--- linux-2.6.20/include/linux/hid.h	2007-02-06 14:14:56.000000000 +0100
+++ linux-2.6.20-autosusp/include/linux/hid.h	2007-02-06 10:31:19.000000000 +0100
@@ -389,6 +389,8 @@
 #define HID_RESET_PENDING	4
 #define HID_SUSPENDED		5
 #define HID_CLEAR_HALT		6
+#define HID_IDLE		7
+#define HID_REPORTED_IDLE	8
 
 struct hid_input {
 	struct list_head list;
--- linux-2.6.20/drivers/usb/input/usbhid.h	2007-02-06 14:14:49.000000000 +0100
+++ linux-2.6.20-autosusp/drivers/usb/input/usbhid.h	2007-02-06 10:31:19.000000000 +0100
@@ -72,12 +72,15 @@
 	dma_addr_t outbuf_dma;                                          /* Output buffer dma */
 	spinlock_t outlock;                                             /* Output fifo spinlock */
 
-	unsigned long iofl;                                             /* I/O flags (CTRL_RUNNING, OUT_RUNNING) */
-	struct timer_list io_retry;                                     /* Retry timer */
-	unsigned long stop_retry;                                       /* Time to give up, in jiffies */
-	unsigned int retry_delay;                                       /* Delay length in ms */
-	struct work_struct reset_work;                                  /* Task context for resets */
-
+	unsigned long iofl;					/* I/O flags (CTRL_RUNNING, OUT_RUNNING) */
+	struct timer_list io_retry;				/* Retry timer */
+	struct timer_list idle_timer;				/* timer to determine idleness */
+	unsigned long stop_retry;				/* Time to give up, in jiffies */
+	unsigned int idle_time;					/* Time to determine idleness, in seconds */
+	unsigned int retry_delay;				/* Delay length in ms */
+	struct work_struct reset_work;				/* Task context for resets */
+	struct work_struct idle_work;				/* Task context for reporting idleness */
+	struct work_struct restart_work;			/* waking up for output to be done in task context */
 };
 
 #define	hid_to_usb_dev(hid_dev) \
--- linux-2.6.20/drivers/usb/input/hid-core.c	2007-02-06 14:14:48.000000000 +0100
+++ linux-2.6.20-autosusp/drivers/usb/input/hid-core.c	2007-02-06 15:32:53.000000000 +0100
@@ -5,6 +5,7 @@
  *  Copyright (c) 2000-2005 Vojtech Pavlik <[email protected]>
  *  Copyright (c) 2005 Michael Haboustak <[email protected]> for Concept2, Inc
  *  Copyright (c) 2006 Jiri Kosina
+ *  Copyright (c) 2007 Oliver Neukum
  */
 
 /*
@@ -56,11 +57,45 @@
 module_param_named(mousepoll, hid_mousepoll_interval, uint, 0644);
 MODULE_PARM_DESC(mousepoll, "Polling interval of mice");
 
+static spinlock_t idle_guard = SPIN_LOCK_UNLOCKED;
+static DEFINE_MUTEX(hid_open_mut);
+
+/*
+ * sysfs idle time handling
+ */
+
+static ssize_t show_idle_time(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct usb_interface *intf = to_usb_interface(dev);
+	struct hid_device *hh = usb_get_intfdata(intf);
+	struct usbhid_device *uh = hh->driver_data;
+
+	return sprintf(buf, "%d\n", uh->idle_time);
+}
+
+static ssize_t set_idle_time(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct usb_interface *intf = to_usb_interface(dev);
+	struct hid_device *hh = usb_get_intfdata(intf);
+	struct usbhid_device *uh = hh->driver_data;
+	int temp = simple_strtoul(buf, NULL, 10);
+
+	spin_lock_irq(&idle_guard);
+	uh->idle_time = temp;
+	spin_unlock_irq(&idle_guard);
+
+	return count;
+}
+
+static DEVICE_ATTR(idle_time, S_IWUGO | S_IRUGO, show_idle_time, set_idle_time);
+
 /*
  * Input submission and I/O error handler.
  */
 
 static void hid_io_error(struct hid_device *hid);
+static int hid_submit_out(struct hid_device *hid);
+static int hid_submit_ctrl(struct hid_device *hid);
 
 /* Start up the input URB */
 static int hid_start_in(struct hid_device *hid)
@@ -169,6 +204,44 @@
 	spin_unlock_irqrestore(&usbhid->inlock, flags);
 }
 
+static int usbhid_restart_out_queue(struct usbhid_device *usbhid)
+{
+	struct hid_device *hid = usb_get_intfdata(usbhid->intf);
+	int kicked;
+
+	WARN_ON(hid == NULL);
+	if (!hid)
+		return 0;
+
+	if ((kicked = (usbhid->outhead != usbhid->outtail))) {
+		dbg("Kicking head %d tail %d", usbhid->outhead, usbhid->outtail);
+		if (hid_submit_out(hid)) {
+			clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
+			wake_up(&hid->wait);
+		}
+	}
+	return kicked;
+}
+
+static int usbhid_restart_ctrl_queue(struct usbhid_device *usbhid)
+{
+	struct hid_device *hid = usb_get_intfdata(usbhid->intf);
+	int kicked;
+
+	WARN_ON(hid == NULL);
+	if (!hid)
+		return 0;
+
+	if ((kicked = (usbhid->ctrlhead != usbhid->ctrltail))) {
+		dbg("Kicking head %d tail %d", usbhid->ctrlhead, usbhid->ctrltail);
+		if (hid_submit_ctrl(hid)) {
+			clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
+			wake_up(&hid->wait);
+		}
+	}
+	return kicked;
+}
+
 /*
  * Input interrupt completion handler.
  */
@@ -179,6 +252,8 @@
 	struct usbhid_device 	*usbhid = hid->driver_data;
 	int			status;
 
+	/*data recieved, errors ignored as they are handled, device not idle */
+	clear_bit(HID_IDLE, &usbhid->iofl);
 	switch (urb->status) {
 		case 0:			/* success */
 			usbhid->retry_delay = 0;
@@ -242,9 +317,11 @@
 	struct hid_report *report;
 	struct usbhid_device *usbhid = hid->driver_data;
 
+	WARN_ON(usbhid == NULL);
 	report = usbhid->out[usbhid->outtail];
-
+	WARN_ON(report == NULL);
 	hid_output_report(report, usbhid->outbuf);
+	BUG_ON(usbhid->urbout == NULL);
 	usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0);
 	usbhid->urbout->dev = hid_to_usb_dev(hid);
 
@@ -339,11 +416,7 @@
 	else
 		usbhid->outtail = (usbhid->outtail + 1) & (HID_OUTPUT_FIFO_SIZE - 1);
 
-	if (usbhid->outhead != usbhid->outtail) {
-		if (hid_submit_out(hid)) {
-			clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
-			wake_up(&hid->wait);
-		}
+	if (usbhid_restart_out_queue(usbhid)) {
 		spin_unlock_irqrestore(&usbhid->outlock, flags);
 		return;
 	}
@@ -389,11 +462,7 @@
 	else
 		usbhid->ctrltail = (usbhid->ctrltail + 1) & (HID_CONTROL_FIFO_SIZE - 1);
 
-	if (usbhid->ctrlhead != usbhid->ctrltail) {
-		if (hid_submit_ctrl(hid)) {
-			clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
-			wake_up(&hid->wait);
-		}
+	if (usbhid_restart_ctrl_queue(usbhid)) {
 		spin_unlock_irqrestore(&usbhid->ctrllock, flags);
 		return;
 	}
@@ -403,9 +472,45 @@
 	wake_up(&hid->wait);
 }
 
-void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, unsigned char dir)
+static int usbhid_queue_report_out(struct hid_device *hid, struct hid_report *report)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+	int head;
+
+	if ((head = (usbhid->outhead + 1) & (HID_OUTPUT_FIFO_SIZE - 1)) == usbhid->outtail) {
+		warn("output queue full");
+		return 0;
+	}
+
+	usbhid->out[usbhid->outhead] = report;
+	usbhid->outhead = head;
+
+	return usbhid->outhead < usbhid->outtail ?
+		usbhid->outtail - usbhid->outhead :
+		HID_OUTPUT_FIFO_SIZE - usbhid->outhead + usbhid->outtail;
+}
+
+static int usbhid_queue_report_ctrl(struct hid_device *hid, struct hid_report *report, unsigned char dir)
 {
+	struct usbhid_device *usbhid = hid->driver_data;
 	int head;
+
+	if ((head = (usbhid->ctrlhead + 1) & (HID_CONTROL_FIFO_SIZE - 1)) == usbhid->ctrltail) {
+		warn("control queue full");
+		return 0;
+	}
+
+	usbhid->ctrl[usbhid->ctrlhead].report = report;
+	usbhid->ctrl[usbhid->ctrlhead].dir = dir;
+	usbhid->ctrlhead = head;
+
+	return usbhid->ctrlhead < usbhid->ctrltail ?
+		usbhid->ctrltail - usbhid->ctrlhead :
+		HID_CONTROL_FIFO_SIZE - usbhid->ctrlhead + usbhid->ctrltail;
+}
+
+void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, unsigned char dir)
+{
 	unsigned long flags;
 	struct usbhid_device *usbhid = hid->driver_data;
 
@@ -416,47 +521,46 @@
 
 		spin_lock_irqsave(&usbhid->outlock, flags);
 
-		if ((head = (usbhid->outhead + 1) & (HID_OUTPUT_FIFO_SIZE - 1)) == usbhid->outtail) {
-			spin_unlock_irqrestore(&usbhid->outlock, flags);
-			warn("output queue full");
-			return;
-		}
-
-		usbhid->out[usbhid->outhead] = report;
-		usbhid->outhead = head;
+		usbhid_queue_report_out(hid, report);
 
 		if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl))
 			if (hid_submit_out(hid))
 				clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
 
 		spin_unlock_irqrestore(&usbhid->outlock, flags);
-		return;
-	}
+	} else {
+		spin_lock_irqsave(&usbhid->ctrllock, flags);
 
-	spin_lock_irqsave(&usbhid->ctrllock, flags);
+		usbhid_queue_report_ctrl(hid, report, dir);
+
+		if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl))
+			if (hid_submit_ctrl(hid))
+				clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
 
-	if ((head = (usbhid->ctrlhead + 1) & (HID_CONTROL_FIFO_SIZE - 1)) == usbhid->ctrltail) {
 		spin_unlock_irqrestore(&usbhid->ctrllock, flags);
-		warn("control queue full");
-		return;
 	}
+}
 
-	usbhid->ctrl[usbhid->ctrlhead].report = report;
-	usbhid->ctrl[usbhid->ctrlhead].dir = dir;
-	usbhid->ctrlhead = head;
-
-	if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl))
-		if (hid_submit_ctrl(hid))
-			clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
+static int usbhid_queue_report(struct hid_device *hid, struct hid_report *report, unsigned char dir)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+	int rv;
 
-	spin_unlock_irqrestore(&usbhid->ctrllock, flags);
+	if (usbhid->urbout && dir == USB_DIR_OUT && report->type == HID_OUTPUT_REPORT) {
+		rv = usbhid_queue_report_out(hid, report);
+	} else {
+		rv = usbhid_queue_report_ctrl(hid, report, dir);
+	}
+	return rv;
 }
 
 static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
 {
 	struct hid_device *hid = dev->private;
+	struct usbhid_device *usbhid = hid->driver_data;
 	struct hid_field *field;
-	int offset;
+	unsigned long flags;
+	int offset, used;
 
 	if (type == EV_FF)
 		return input_ff_event(dev, type, code, value);
@@ -469,8 +573,24 @@
 		return -1;
 	}
 
+	spin_lock_irqsave(&usbhid->outlock, flags);
+	spin_lock(&usbhid->ctrllock);
+	clear_bit(HID_IDLE, &usbhid->iofl);
 	hid_set_field(field, offset, value);
-	usbhid_submit_report(hid, field->report, USB_DIR_OUT);
+	if (!test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) {
+		/* the device isn't idle, we do the output now*/
+		spin_unlock(&usbhid->ctrllock);
+		spin_unlock_irqrestore(&usbhid->outlock, flags);
+		usbhid_submit_report(hid, field->report, USB_DIR_OUT);
+	} else {
+		/* the device is idle, queue the request
+		and wake it up if the queue fills up too much*/
+		used = usbhid_queue_report(hid, field->report, USB_DIR_OUT);
+		if ( 3 < HID_OUTPUT_FIFO_SIZE - used)
+			schedule_work(&usbhid->restart_work);
+		spin_unlock(&usbhid->ctrllock);
+		spin_unlock_irqrestore(&usbhid->outlock, flags);
+	}
 
 	return 0;
 }
@@ -514,7 +634,20 @@
 
 int usbhid_open(struct hid_device *hid)
 {
-	++hid->open;
+	struct usbhid_device *usbhid = hid->driver_data;
+	int res;
+
+	clear_bit(HID_IDLE, &usbhid->iofl);
+	mutex_lock(&hid_open_mut);
+	if (!hid->open++) {
+		res = usb_autopm_get_interface(usbhid->intf);
+		if (res < 0) {
+			hid->open--;
+			mutex_unlock(&hid_open_mut);
+			return -EIO;
+		}
+	}
+	mutex_unlock(&hid_open_mut);
 	if (hid_start_in(hid))
 		hid_io_error(hid);
 	return 0;
@@ -524,8 +657,13 @@
 {
 	struct usbhid_device *usbhid = hid->driver_data;
 
-	if (!--hid->open)
+	if (!--hid->open) {
 		usb_kill_urb(usbhid->urbin);
+		del_timer_sync(&usbhid->idle_timer);
+		flush_scheduled_work();
+		if (!test_and_clear_bit(HID_REPORTED_IDLE, &usbhid->iofl))
+			usb_autopm_put_interface(usbhid->intf);
+	}
 }
 
 static int hidinput_open(struct input_dev *dev)
@@ -1005,6 +1143,80 @@
 	return 0;
 }
 
+void hid_add_idle_timer(struct usbhid_device *uhid)
+{
+	uhid->idle_timer.expires = jiffies + uhid->idle_time * HZ;
+	add_timer(&uhid->idle_timer);
+printk(KERN_ERR"Added idle timer for firing in %d seconds\n", uhid->idle_time);
+}
+
+static void hid_mod_idle_timer(struct usbhid_device *uhid)
+{
+	if (usb_get_intfdata(uhid->intf) && uhid->idle_time) {
+		mod_timer(&uhid->idle_timer, jiffies + uhid->idle_time * HZ);
+		dbg("Modded idle timer for firing in %d seconds", uhid->idle_time);
+	}
+}
+
+static void usbhid_restart_queues(struct usbhid_device *usbhid)
+{
+	if (usbhid->urbout)
+		usbhid_restart_out_queue(usbhid);
+	usbhid_restart_ctrl_queue(usbhid);
+}
+
+static void __hid_report_idle(struct work_struct *work)
+{
+	struct usbhid_device *usbhid =
+		container_of(work, struct usbhid_device, idle_work);
+
+	usb_autopm_put_interface(usbhid->intf);
+}
+
+static void __usbhid_restart_queues(struct work_struct *work)
+{
+	struct usbhid_device *usbhid =
+		container_of(work, struct usbhid_device, restart_work);
+
+	usb_autopm_get_interface(usbhid->intf);
+	usbhid_restart_queues(usbhid);
+	usb_autopm_put_interface(usbhid->intf);
+}
+
+static void hid_report_idle(struct usbhid_device *hid)
+{
+	struct usb_interface *intf = hid->intf;
+
+	if (!test_bit(HID_SUSPENDED, &hid->iofl)
+	&& !test_bit(HID_CTRL_RUNNING, &hid->iofl)
+	&& !test_bit(HID_OUT_RUNNING, &hid->iofl)) {
+		set_bit(HID_REPORTED_IDLE, &hid->iofl);
+		if (usb_get_intfdata(intf))
+			schedule_work(&hid->idle_work);
+	}
+
+}
+
+static void hid_check_idle(unsigned long arg)
+{
+	struct usbhid_device *hid = (struct usbhid_device *)arg;
+	unsigned long flags;
+	int state;
+
+	spin_lock_irqsave(&hid->inlock, flags);
+	spin_lock(&hid->outlock);
+	state = test_and_set_bit(HID_IDLE, &hid->iofl);
+
+	if (!state) {
+		hid_mod_idle_timer(hid);
+	} else {
+		hid_report_idle(hid);
+		dbg("Detected idleness");
+	}
+	spin_unlock(&hid->outlock);
+	spin_unlock_irqrestore(&hid->inlock, flags);
+}
+
 static void hid_free_buffers(struct usb_device *dev, struct hid_device *hid)
 {
 	struct usbhid_device *usbhid = hid->driver_data;
@@ -1183,17 +1395,23 @@
 	init_waitqueue_head(&hid->wait);
 
 	INIT_WORK(&usbhid->reset_work, hid_reset);
+	INIT_WORK(&usbhid->idle_work, __hid_report_idle);
+	INIT_WORK(&usbhid->restart_work, __usbhid_restart_queues);
 	setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid);
+	setup_timer(&usbhid->idle_timer, hid_check_idle, (unsigned long) usbhid);
 
 	spin_lock_init(&usbhid->inlock);
 	spin_lock_init(&usbhid->outlock);
 	spin_lock_init(&usbhid->ctrllock);
 
+	set_bit(HID_IDLE, &usbhid->iofl);
+
 	hid->version = le16_to_cpu(hdesc->bcdHID);
 	hid->country = hdesc->bCountryCode;
 	hid->dev = &intf->dev;
 	usbhid->intf = intf;
 	usbhid->ifnum = interface->desc.bInterfaceNumber;
+	usbhid->idle_time = 10; //FIXME
 
 	hid->name[0] = 0;
 
@@ -1263,6 +1481,8 @@
 
 	usbhid = hid->driver_data;
 
+	device_remove_file(&intf->dev, &dev_attr_idle_time);
+
 	spin_lock_irq(&usbhid->inlock);	/* Sync with error handler */
 	usb_set_intfdata(intf, NULL);
 	spin_unlock_irq(&usbhid->inlock);
@@ -1271,6 +1491,7 @@
 	usb_kill_urb(usbhid->urbctrl);
 
 	del_timer_sync(&usbhid->io_retry);
+	del_timer_sync(&usbhid->idle_timer);
 	flush_scheduled_work();
 
 	if (hid->claimed & HID_CLAIMED_INPUT)
@@ -1299,6 +1520,9 @@
 	if (!(hid = usb_hid_configure(intf)))
 		return -ENODEV;
 
+	usb_set_intfdata(intf, hid);
+	intf->needs_remote_wakeup = 1;
+
 	usbhid_init_reports(hid);
 	hid_dump_device(hid);
 
@@ -1307,7 +1531,6 @@
 	if (!hiddev_connect(hid))
 		hid->claimed |= HID_CLAIMED_HIDDEV;
 
-	usb_set_intfdata(intf, hid);
 
 	if (!hid->claimed) {
 		printk ("HID device not claimed by input or hiddev\n");
@@ -1331,6 +1554,9 @@
 	if (hid->claimed & HID_CLAIMED_HIDDEV)
 		printk("hiddev%d", hid->minor);
 
+	if (hid->claimed & HID_CLAIMED_INPUT)
+		hid_add_idle_timer(hid->driver_data);
+
 	c = "Device";
 	for (i = 0; i < hid->maxcollection; i++) {
 		if (hid->collection[i].type == HID_COLLECTION_APPLICATION &&
@@ -1342,6 +1568,7 @@
 	}
 
 	usb_make_path(interface_to_usbdev(intf), path, 63);
+	device_create_file(&intf->dev, &dev_attr_idle_time);
 
 	printk(": USB HID v%x.%02x %s [%s] on %s\n",
 		hid->version >> 8, hid->version & 0xff, c, hid->name, path);
@@ -1359,6 +1586,7 @@
 	spin_unlock_irq(&usbhid->inlock);
 	del_timer(&usbhid->io_retry);
 	usb_kill_urb(usbhid->urbin);
+	flush_scheduled_work();
 	dev_dbg(&intf->dev, "suspend\n");
 	return 0;
 }
@@ -1370,8 +1598,13 @@
 	int status;
 
 	clear_bit(HID_SUSPENDED, &usbhid->iofl);
+	clear_bit(HID_IDLE, &usbhid->iofl);
 	usbhid->retry_delay = 0;
 	status = hid_start_in(hid);
+	if (test_and_clear_bit(HID_REPORTED_IDLE, &usbhid->iofl))
+		intf->pm_usage_cnt++; /* get'd deadlock */
+	usbhid_restart_queues(usbhid);
+	hid_add_idle_timer(usbhid);
 	dev_dbg(&intf->dev, "resume status %d\n", status);
 	return status;
 }
@@ -1410,6 +1643,7 @@
 	.pre_reset =	hid_pre_reset,
 	.post_reset =	hid_post_reset,
 	.id_table =	hid_usb_ids,
+	.supports_autosuspend = 1,
 };
 
 static int __init hid_init(void)
--- linux-2.6.20/drivers/usb/input/hiddev.c	2007-02-06 14:14:49.000000000 +0100
+++ linux-2.6.20-autosusp/drivers/usb/input/hiddev.c	2007-02-06 10:31:19.000000000 +0100
@@ -496,8 +496,11 @@
 		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
 			return -EINVAL;
 
+		if (usb_autopm_get_interface(usbhid->intf) < 0)
+			return -EIO;
 		usbhid_submit_report(hid, report, USB_DIR_IN);
 		usbhid_wait_io(hid);
+		usb_autopm_put_interface(usbhid->intf);
 
 		return 0;
 
@@ -511,8 +514,11 @@
 		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
 			return -EINVAL;
 
+		if (usb_autopm_get_interface(usbhid->intf) < 0)
+			return -EIO;
 		usbhid_submit_report(hid, report, USB_DIR_OUT);
 		usbhid_wait_io(hid);
+		usb_autopm_put_interface(usbhid->intf);
 
 		return 0;
 

-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier.
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
[email protected]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.